Files
medical-mall/pages/mall/merchant/product-edit.uvue

1025 lines
34 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 机构端 - 服务/商品编辑页面 -->
<template>
<view class="product-edit-page">
<!-- #ifdef MP-WEIXIN -->
<view style="padding-top: var(--status-bar-height); background-color: #ffffff; display: flex; flex-direction: row; align-items: flex-end; border-bottom: 1rpx solid #eeeeee; box-sizing: border-box; height: calc(88rpx + var(--status-bar-height));">
<view style="display: flex; flex-direction: row; align-items: center; padding: 0 30rpx; height: 88rpx;" @click="uni.navigateBack()">
<text style="font-size: 44rpx; color: #333333; line-height: 1; margin-right: 6rpx;"></text>
<text style="font-size: 28rpx; color: #333333;">返回</text>
</view>
</view>
<!-- #endif -->
<!-- 服务基本信息 -->
<view class="section">
<view class="section-title">基本信息</view>
<view class="form-item">
<text class="label">服务名称 *</text>
<input
class="input"
v-model="product.name"
placeholder="请输入服务名称"
maxlength="100"
/>
</view>
<view class="form-item">
<text class="label">服务副标题</text>
<input
class="input"
v-model="product.subtitle"
placeholder="请输入服务副标题"
maxlength="200"
/>
</view>
<view class="form-item">
<text class="label">商品分类 *</text>
<picker
class="picker"
:range="categories"
range-key="name"
:value="categoryIndex"
@change="onCategoryChange"
>
<view class="picker-value">
{{ selectedCategory?.name || '请选择分类' }}
</view>
</picker>
</view>
<view class="form-item">
<text class="label">商品品牌</text>
<picker
class="picker"
:range="brands"
range-key="name"
:value="brandIndex"
@change="onBrandChange"
>
<view class="picker-value">
{{ selectedBrand?.name || '请选择品牌' }}
</view>
</picker>
</view>
</view>
<!-- 商品图片 -->
<view class="section">
<view class="section-title">展示图片</view>
<view class="image-section">
<text class="label">主图 *</text>
<view class="image-grid">
<view class="image-item main-image" @click="chooseMainImage">
<image v-if="product.main_image_url" :src="product.main_image_url" mode="aspectFill" class="preview-image"/>
<view v-else class="add-image">+</view>
</view>
</view>
</view>
<view class="image-section">
<text class="label">轮播图</text>
<view class="image-grid">
<view
v-for="(img, index) in product.imageList"
:key="index"
class="image-item"
>
<image :src="img" mode="aspectFill" class="preview-image"/>
<view class="delete-btn" @click="removeImage(index)">×</view>
</view>
<view class="image-item add-image" @click="chooseImages" v-if="product.imageList.length < 5">
+
</view>
</view>
</view>
</view>
<!-- 价格库存 -->
<view class="section">
<view class="section-title">价格库存</view>
<view class="form-item">
<text class="label">参考价 *</text>
<view class="price-input">
<text class="unit">¥</text>
<input
class="input"
type="digit"
v-model="product.base_price"
placeholder="0.00"
/>
</view>
</view>
<view class="form-item">
<text class="label">门市价</text>
<view class="price-input">
<text class="unit">¥</text>
<input
class="input"
type="digit"
v-model="product.market_price"
placeholder="0.00"
/>
</view>
</view>
<view class="form-item">
<text class="label">结算成本</text>
<view class="price-input">
<text class="unit">¥</text>
<input
class="input"
type="digit"
v-model="product.cost_price"
placeholder="0.00"
/>
</view>
</view>
<view class="form-item">
<text class="label">长者关怀价/专属补贴</text>
<switch :checked="product.is_vip_discount" @change="e => { product.is_vip_discount = e.detail.value as boolean }" />
</view>
<view class="form-item" v-if="product.is_vip_discount">
<text class="label">关怀价折扣率</text>
<input
class="input"
type="digit"
v-model="product.vip_discount_rate"
placeholder="如 0.85 代表 85 折(空代表采用全局)"
/>
</view>
<view class="form-item">
<text class="label">库存数量/可预约名额 *</text>
<input
class="input"
type="number"
v-model="product.total_stock"
placeholder="0"
/>
</view>
<view class="form-item">
<text class="label">器械库存预警</text>
<input
class="input"
type="number"
v-model="product.warning_stock"
placeholder="库存低于此值提醒"
/>
</view>
</view>
<!-- 会员阶梯价 -->
<view class="section">
<view class="section-title">长者关怀价/专属补贴价 (选填)</view>
<view class="section-desc">若不填写则按照服务参考价或默认折扣计算</view>
<view v-for="(level, index) in memberLevels" :key="index" class="form-item">
<text class="label">{{ level.name }}价格</text>
<view class="price-input">
<text class="unit">¥</text>
<input
class="input"
v-model="level.price"
type="digit"
placeholder="长者关怀价"
/>
</view>
</view>
</view>
<!-- 服务属性 -->
<view class="section">
<view class="section-title">服务属性</view>
<view class="form-item">
<text class="label">商品单位</text>
<input
class="input"
v-model="product.unit"
placeholder="如: 件, 盒, 箱"
/>
</view>
<view class="switch-item">
<text class="label">热门服务</text>
<switch
:checked="product.is_hot"
@change="product.is_hot = !product.is_hot"
color="rgb(66, 121, 240)"
/>
</view>
<view class="switch-item">
<text class="label">新增服务</text>
<switch
:checked="product.is_new"
@change="product.is_new = !product.is_new"
color="rgb(66, 121, 240)"
/>
</view>
<view class="switch-item">
<text class="label">推荐服务</text>
<switch
:checked="product.is_featured"
@change="product.is_featured = !product.is_featured"
color="rgb(66, 121, 240)"
/>
</view>
</view>
<!-- 详情说明 -->
<view class="section">
<view class="section-title">详情说明</view>
<view class="form-item">
<text class="label">服务描述</text>
<textarea
class="textarea"
v-model="product.description"
placeholder="请输入服务详细说明"
:maxlength="2000"
/>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-bar">
<view class="submit-btn primary" @click="saveProduct">
{{ isEdit ? '保存修改' : '发布服务项目' }}
</view>
</view>
</view>
</template>
<script lang="uts">
import supa from '@/components/supadb/aksupainstance.uts'
type CategoryType = {
id: string
name: string
}
type BrandType = {
id: string
name: string
logo_url: string
}
type MemberLevelType = {
id: string
name: string
level_rank: number
discount_rate: number
price: string // 绑定输入框用
}
export default {
data() {
return {
productId: '',
isEdit: false,
categories: [] as CategoryType[],
categoryIndex: -1,
selectedCategory: null as CategoryType | null,
brands: [] as BrandType[],
brandIndex: -1,
selectedBrand: null as BrandType | null,
memberLevels: [] as MemberLevelType[],
product: {
name: '',
subtitle: '',
category_id: '',
brand_id: '',
main_image_url: '',
imageList: [] as string[],
base_price: '',
market_price: '',
cost_price: '',
total_stock: '',
warning_stock: '10',
unit: '件',
is_hot: false,
is_new: false,
is_featured: false,
is_vip_discount: true,
vip_discount_rate: '',
description: ''
},
merchantId: ''
}
},
onLoad(options: any) {
let productId = ''
if (options) {
const keys = Object.keys(options as object)
for (let i = 0; i < keys.length; i++) {
if (keys[i] === 'productId') {
productId = String((options as Record<string, any>)[keys[i]])
}
}
if (!productId && options['productId']) {
productId = String(options['productId'])
}
// 兼容某些平台
if (!productId) {
try {
const optsStr = JSON.stringify(options)
const optsObj = JSON.parse(optsStr) as Record<string, any>
if (optsObj['productId']) {
productId = String(optsObj['productId'])
}
} catch(e) {}
}
}
if (productId && productId !== '') {
this.productId = productId
this.isEdit = true
uni.setNavigationBarTitle({ title: '编辑服务' })
this.loadProductDetail(productId)
} else {
uni.setNavigationBarTitle({ title: '发布服务' })
}
this.initMerchantId()
this.loadCategories()
this.loadBrands()
this.loadMemberLevels()
},
methods: {
async initMerchantId() {
try {
const session = supa.getSession()
if (session != null && session.user != null) {
this.merchantId = (session.user as any)['id'] != null ? String((session.user as any)['id']) : ''
}
if (!this.merchantId) {
this.merchantId = uni.getStorageSync('user_id') || ''
}
} catch (e) {
console.error('获取商户ID失败:', e)
}
},
async loadMemberLevels() {
try {
const response = await supa
.from('ml_member_levels')
.select('*')
.eq('is_active', true)
.order('level_rank', { ascending: true })
.execute()
if (response.error != null) {
console.error('获取会员等级失败:', response.error)
return
}
const rawData = response.data as any[]
if (rawData == null) return
this.memberLevels = []
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i] as any
this.memberLevels.push({
id: item['id'] != null ? String(item['id']) : '',
name: item['name'] != null ? String(item['name']) : '',
level_rank: item['level_rank'] != null ? parseInt(String(item['level_rank'])) : 0,
discount_rate: item['discount_rate'] != null ? parseFloat(String(item['discount_rate'])) : 1.0,
price: ''
} as MemberLevelType)
}
// 如果是编辑模式,还需要加载已有的会员价
if (this.isEdit) {
this.loadMemberPrices()
}
} catch (e) {
console.error('获取会员等级异常:', e)
}
},
async loadMemberPrices() {
try {
const response = await supa
.from('ml_product_member_prices')
.select('*')
.eq('product_id', this.productId)
.execute()
if (response.error != null) {
console.error('获取会员价失败:', response.error)
return
}
const rawData = response.data as any[]
if (rawData == null || rawData.length == 0) return
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i] as any
const levelId = String(item['level_id'])
const price = String(item['member_price'])
const index = this.memberLevels.findIndex(lv => lv.id === levelId)
if (index >= 0) {
this.memberLevels[index].price = price
}
}
} catch (e) {
console.error('获取会员价异常:', e)
}
},
async loadCategories() {
try {
const response = await supa
.from('ml_categories')
.select('id, name')
.eq('is_active', true)
.order('sort_order', { ascending: true })
.execute()
if (response.error != null) {
console.error('获取分类失败:', response.error)
}
const rawData = response.data as any[]
if (rawData != null && rawData.length > 0) {
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i] as any
this.categories.push({
id: item['id'] != null ? String(item['id']) : '',
name: item['name'] != null ? String(item['name']) : ''
} as CategoryType)
}
} else {
// 演示版默认医养分类
this.categories = [
{ id: 'med', name: '医疗服务' },
{ id: 'drug', name: '药品器械' },
{ id: 'care', name: '居家护理' },
{ id: 'life', name: '生活服务' },
{ id: 'health', name: '健康管理' }
] as CategoryType[]
}
} catch (e) {
console.error('获取分类异常:', e)
this.categories = [
{ id: 'med', name: '医疗服务' },
{ id: 'drug', name: '药品器械' },
{ id: 'care', name: '居家护理' },
{ id: 'life', name: '生活服务' },
{ id: 'health', name: '健康管理' }
] as CategoryType[]
}
},
async loadBrands() {
try {
const response = await supa
.from('ml_brands')
.select('id, name, logo_url')
.eq('is_active', true)
.order('name', { ascending: true })
.execute()
if (response.error != null) {
console.error('获取品牌失败:', response.error)
return
}
const rawData = response.data as any[]
if (rawData == null) return
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i] as any
this.brands.push({
id: item['id'] != null ? String(item['id']) : '',
name: item['name'] != null ? String(item['name']) : '',
logo_url: item['logo_url'] != null ? String(item['logo_url']) : ''
} as BrandType)
}
} catch (e) {
console.error('获取品牌异常:', e)
}
},
async loadProductDetail(productId: string) {
try {
uni.showLoading({ title: '加载商品中...' })
const response = await supa
.from('ml_products')
.select('*')
.eq('id', productId)
.single()
.execute()
uni.hideLoading()
if (response.error != null) {
console.error('获取详情失败:', response.error)
uni.showToast({ title: '没有找到该商品', icon: 'none' })
return
}
let rawData = response.data as any
if (rawData == null) return
// 防止Supabase某些版本把single()仍返回数组的坑
if (Array.isArray(rawData) && rawData.length > 0) {
rawData = rawData[0]
}
const getStr = (key: string): string => { try { return rawData[key] != null ? String(rawData[key]) : '' } catch(e){ return '' } }
const getBool = (key: string): boolean => { try { return rawData[key] === true || rawData[key] === 'true' } catch(e){ return false } }
this.product.name = getStr('name')
this.product.subtitle = getStr('subtitle')
this.product.category_id = getStr('category_id')
this.product.brand_id = getStr('brand_id')
this.product.main_image_url = getStr('main_image_url')
this.product.imageList = this.parseImageUrls(getStr('image_urls'))
this.product.base_price = getStr('base_price')
this.product.market_price = getStr('market_price')
this.product.cost_price = getStr('cost_price')
this.product.total_stock = getStr('total_stock')
this.product.warning_stock = getStr('warning_stock') || '10'
this.product.unit = getStr('unit') || '件'
this.product.is_hot = getBool('is_hot')
this.product.is_new = getBool('is_new')
this.product.is_featured = getBool('is_featured')
const _isVip = rawData['is_vip_discount']
this.product.is_vip_discount = _isVip == null ? true : getBool('is_vip_discount')
this.product.vip_discount_rate = getStr('vip_discount_rate')
this.product.description = getStr('description')
if (this.product.category_id) {
this.categoryIndex = this.categories.findIndex(c => c.id === this.product.category_id)
if (this.categoryIndex >= 0) {
this.selectedCategory = this.categories[this.categoryIndex]
}
}
if (this.product.brand_id) {
this.brandIndex = this.brands.findIndex(b => b.id === this.product.brand_id)
if (this.brandIndex >= 0) {
this.selectedBrand = this.brands[this.brandIndex]
}
}
} catch (e) {
uni.hideLoading()
console.error('获取商品详情异常:', e)
uni.showToast({ title: '加载异常: ' + String(e), icon: 'none', duration: 3000 })
}
},
parseImageUrls(urlsStr: string): string[] {
if (!urlsStr) return []
try {
const arr = JSON.parse(urlsStr)
return Array.isArray(arr) ? arr : []
} catch {
return []
}
},
onCategoryChange(e: any) {
const index = e.detail.value as number
this.categoryIndex = index
this.selectedCategory = this.categories[index]
this.product.category_id = this.selectedCategory.id
},
onBrandChange(e: any) {
const index = e.detail.value as number
this.brandIndex = index
this.selectedBrand = this.brands[index]
this.product.brand_id = this.selectedBrand.id
},
chooseMainImage() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
success: (res) => {
this.product.main_image_url = res.tempFilePaths[0]
}
})
},
chooseImages() {
const remainCount = 5 - this.product.imageList.length
uni.chooseImage({
count: remainCount,
sizeType: ['compressed'],
success: (res) => {
this.product.imageList = [...this.product.imageList, ...res.tempFilePaths]
}
})
},
removeImage(index: number) {
this.product.imageList.splice(index, 1)
},
async uploadImageToSupa(localPath: string): Promise<string> {
if (localPath.startsWith('http://') || localPath.startsWith('https://')) {
return localPath
}
let ext = '.jpg'
const dotIndex = localPath.lastIndexOf('.')
if (dotIndex > -1) {
ext = localPath.substring(dotIndex).toLowerCase()
}
const uuid = Date.now().toString() + '_' + Math.floor(Math.random() * 1000)
const remotePath = `products/${this.merchantId}_${uuid}${ext}`
try {
const uploadResult = await supa.storage.from('zhipao').upload(remotePath, localPath, {})
if (uploadResult.error != null) {
console.error('上传图片失败:', uploadResult.error)
return localPath
}
return supa.storage.getPublicUrl('zhipao', remotePath)
} catch (e) {
console.error('上传图片异常:', e)
return localPath
}
},
async saveProduct() {
if (!this.product.name) {
uni.showToast({ title: '请输入商品名称', icon: 'none' })
return
}
if (!this.product.category_id) {
uni.showToast({ title: '请选择商品分类', icon: 'none' })
return
}
if (!this.product.base_price) {
uni.showToast({ title: '请输入销售价', icon: 'none' })
return
}
if (!this.product.total_stock) {
uni.showToast({ title: '请输入总库存', icon: 'none' })
return
}
if (this.product.is_vip_discount && this.product.vip_discount_rate !== '') {
const rate = parseFloat(this.product.vip_discount_rate)
if (isNaN(rate) || rate <= 0 || rate > 1) {
uni.showToast({ title: 'VIP折扣率需在0~1之间', icon: 'none' })
return
}
}
uni.showLoading({ title: '保存中...' })
try {
let finalMainImage = this.product.main_image_url
if (finalMainImage != '') {
finalMainImage = await this.uploadImageToSupa(finalMainImage)
}
const finalImageList = [] as string[]
for (let i = 0; i < this.product.imageList.length; i++) {
const img = await this.uploadImageToSupa(this.product.imageList[i])
finalImageList.push(img)
}
const imageUrlsStr = JSON.stringify(finalImageList)
const productData = {
merchant_id: this.merchantId,
name: this.product.name,
subtitle: this.product.subtitle,
category_id: this.product.category_id,
brand_id: this.product.brand_id || null,
main_image_url: finalMainImage,
image_urls: imageUrlsStr,
base_price: this.product.base_price ? parseFloat(this.product.base_price) : 0,
market_price: this.product.market_price ? parseFloat(this.product.market_price) : null,
cost_price: this.product.cost_price ? parseFloat(this.product.cost_price) : null,
total_stock: parseInt(this.product.total_stock),
available_stock: parseInt(this.product.total_stock),
is_hot: this.product.is_hot,
is_new: this.product.is_new,
is_featured: this.product.is_featured,
is_vip_discount: this.product.is_vip_discount,
vip_discount_rate: this.product.vip_discount_rate ? parseFloat(this.product.vip_discount_rate) : null,
description: this.product.description,
status: 1,
updated_at: new Date().toISOString()
} as UTSJSONObject
let response : any = null
if (this.isEdit) {
const updateData = {} as UTSJSONObject
const keys = UTSJSONObject.keys(productData)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (key != 'status') {
updateData[key] = productData[key]
}
}
console.log('执行产品更新, ID:', this.productId)
const updateResponse = await supa
.from('ml_products')
.update(updateData)
.eq('id', this.productId)
.execute()
if (updateResponse.error != null) {
throw new Error('产品更新失败: ' + String(updateResponse.error!.message))
}
response = updateResponse
} else {
productData['created_at'] = new Date().toISOString()
productData['product_code'] = 'P' + Date.now().toString()
console.log('执行新产品插入')
const insertResponse = await supa
.from('ml_products')
.insert(productData)
.execute()
if (insertResponse.error != null) {
throw new Error('产品发布失败: ' + String(insertResponse.error!.message))
}
response = insertResponse
}
// 保存会员价
let targetProductId = this.isEdit ? this.productId : ''
if (response != null && response.data != null) {
const responseData = response.data
if (Array.isArray(responseData)) {
const dataArr = responseData as any[]
if (dataArr.length > 0) {
const firstRow = dataArr[0] as UTSJSONObject
if (firstRow['id'] != null) {
targetProductId = String(firstRow['id'])
}
}
} else if (responseData instanceof UTSJSONObject) {
const dataObj = responseData as UTSJSONObject
if (dataObj['id'] != null) {
targetProductId = String(dataObj['id'])
}
}
}
console.log('最终目标产品ID:', targetProductId)
if (targetProductId && targetProductId !== '' && targetProductId !== 'undefined') {
// 1. 先删除旧的会员价
if (this.isEdit) {
console.log('删除旧会员价:', targetProductId)
await supa.from('ml_product_member_prices').delete().eq('product_id', targetProductId).execute()
}
// 2. 插入新的会员价
for (let i = 0; i < this.memberLevels.length; i++) {
const level = this.memberLevels[i]
if (level.price && level.price > 0) {
const memberPriceData = {
product_id: targetProductId,
level_id: level.id,
member_price: level.price,
created_at: new Date().toISOString()
} as UTSJSONObject
const insertRes = await supa
.from('ml_product_member_prices')
.insert(memberPriceData)
.execute()
if (insertRes.error != null) {
console.error('插入会员价失败', insertRes.error)
}
}
}
}
uni.hideLoading()
uni.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (e) {
uni.hideLoading()
console.error('保存商品异常:', e)
uni.showToast({ title: '保存异常: ' + String(e), icon: 'none' })
}
}
}
}
</script>
<style>
.product-edit-page {
background-color: #f5f5f5;
min-height: 100vh;
padding-bottom: 160rpx;
}
.section {
background-color: #fff;
margin-bottom: 20rpx;
padding: 30rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f5f5f5;
padding-left: 16rpx;
border-left: 6rpx solid rgb(66, 121, 240);
}
.section-desc {
font-size: 24rpx;
color: #999;
margin-top: -20rpx;
margin-bottom: 30rpx;
}
.form-item {
margin-bottom: 30rpx;
}
.label {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 16rpx;
}
.input {
height: 72rpx;
border: 1rpx solid #e5e5e5;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
}
.price-input {
display: flex;
flex-direction: row;
align-items: center;
border: 1rpx solid #e5e5e5;
border-radius: 8rpx;
padding: 0 20rpx;
}
.price-input .unit {
font-size: 28rpx;
color: #666;
margin-right: 10rpx;
}
.price-input .input {
flex: 1;
border: none;
padding: 0;
}
.picker {
height: 72rpx;
border: 1rpx solid #e5e5e5;
border-radius: 8rpx;
padding: 0 20rpx;
display: flex;
flex-direction: row;
align-items: center;
}
.picker-value {
font-size: 28rpx;
color: #333;
}
.switch-item {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.switch-item:last-child {
border-bottom: none;
}
.textarea {
width: 100%;
height: 200rpx;
border: 1rpx solid #e5e5e5;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.image-section {
margin-bottom: 30rpx;
}
.image-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 20rpx;
}
.image-item {
position: relative;
width: 150rpx;
height: 150rpx;
border-radius: 8rpx;
overflow: hidden;
}
.main-image {
width: 200rpx;
height: 200rpx;
}
.preview-image {
width: 100%;
height: 100%;
}
.add-image {
width: 100%;
height: 100%;
background-color: #f5f5f5;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
font-size: 60rpx;
color: #999;
border: 2rpx dashed #ddd;
border-radius: 8rpx;
}
.delete-btn {
position: absolute;
top: 5rpx;
right: 5rpx;
width: 36rpx;
height: 36rpx;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 28rpx;
border-radius: 50%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.submit-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background-color: #fff;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.submit-btn {
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 32rpx;
font-weight: bold;
border-radius: 44rpx;
}
.submit-btn.primary {
background: linear-gradient(135deg, rgb(66, 121, 240) 0%, #5856D6 100%);
color: #fff;
}
</style>