完成积分商品

This commit is contained in:
2026-03-18 20:01:26 +08:00
parent f1a6c18dfb
commit 45084c5e5b
4 changed files with 243 additions and 65 deletions

View File

@@ -0,0 +1 @@
INSERT INTO "public"."ml_point_exchanges" ("id", "user_id", "product_id", "quantity", "points_used", "status", "tracking_no", "address_snapshot", "created_at", "updated_at") VALUES ('258edec4-a3ab-4804-b440-11a6710dd768', 'b653fded-7d5e-4950-aa0d-725595543e3c', 'f6b200e8-cb36-4746-8305-cd6bcca62bce', '1', '100', '0', null, null, '2026-03-06 10:46:18.215965+08', '2026-03-06 10:46:18.215965+08'), ('3075168e-506e-4791-b003-d44472d7034a', 'b653fded-7d5e-4950-aa0d-725595543e3c', 'f6b200e8-cb36-4746-8305-cd6bcca62bce', '1', '100', '0', null, null, '2026-03-06 10:20:54.133614+08', '2026-03-06 10:20:54.133614+08'), ('4f187412-68d9-4ad9-91d0-b6358f431089', 'b653fded-7d5e-4950-aa0d-725595543e3c', 'f6b200e8-cb36-4746-8305-cd6bcca62bce', '1', '100', '0', null, null, '2026-03-06 10:30:15.543449+08', '2026-03-06 10:30:15.543449+08'), ('6a8a78d0-f67f-4f62-9ea8-4f45430e294a', 'b653fded-7d5e-4950-aa0d-725595543e3c', '0ec42706-b1c7-4339-af18-c63cd1772e70', '1', '500', '0', null, null, '2026-03-06 10:20:44.575908+08', '2026-03-06 10:20:44.575908+08'), ('876e2e54-2f54-4bcf-850e-e8dfa69fc776', 'b653fded-7d5e-4950-aa0d-725595543e3c', 'f6b200e8-cb36-4746-8305-cd6bcca62bce', '1', '100', '0', null, null, '2026-03-06 10:47:01.33147+08', '2026-03-06 10:47:01.33147+08'), ('b072c00d-7e84-4bb2-a559-c8a78b7c5254', 'b653fded-7d5e-4950-aa0d-725595543e3c', '21b3cb64-289a-47db-bb53-38c4a40a4030', '1', '200', '0', null, null, '2026-03-06 11:01:39.38454+08', '2026-03-06 11:01:39.38454+08'), ('b6fd919a-8082-4fb6-b413-f5b96c053ff8', 'b653fded-7d5e-4950-aa0d-725595543e3c', '0ec42706-b1c7-4339-af18-c63cd1772e70', '1', '500', '0', null, null, '2026-03-06 11:01:45.253972+08', '2026-03-06 11:01:45.253972+08'), ('cefbc7e6-3e4c-4403-97a2-6ad5d3b623d5', 'b653fded-7d5e-4950-aa0d-725595543e3c', '0ec42706-b1c7-4339-af18-c63cd1772e70', '1', '500', '0', null, null, '2026-03-06 11:01:47.572621+08', '2026-03-06 11:01:47.572621+08'), ('d30eee0b-0def-408a-b19a-4273b30bdac6', 'b653fded-7d5e-4950-aa0d-725595543e3c', 'f6b200e8-cb36-4746-8305-cd6bcca62bce', '1', '100', '0', null, null, '2026-03-06 10:40:32.883693+08', '2026-03-06 10:40:32.883693+08'), ('d4cfb114-8a1b-47d0-8d64-da1be9172307', 'b653fded-7d5e-4950-aa0d-725595543e3c', 'f6b200e8-cb36-4746-8305-cd6bcca62bce', '1', '100', '0', null, null, '2026-03-06 10:32:42.169439+08', '2026-03-06 10:32:42.169439+08'), ('e15d6e3e-df65-4198-ad24-05001b32a510', 'b653fded-7d5e-4950-aa0d-725595543e3c', '21b3cb64-289a-47db-bb53-38c4a40a4030', '1', '200', '0', null, null, '2026-03-06 10:20:27.085858+08', '2026-03-06 10:20:27.085858+08'), ('e235c779-c01d-43b0-9022-cb0708b9621b', 'b653fded-7d5e-4950-aa0d-725595543e3c', 'f6b200e8-cb36-4746-8305-cd6bcca62bce', '1', '100', '0', null, null, '2026-03-06 09:49:21.714247+08', '2026-03-06 09:49:21.714247+08'), ('f036372c-11b8-4938-b9f0-c353339ed66a', 'b653fded-7d5e-4950-aa0d-725595543e3c', 'f6b200e8-cb36-4746-8305-cd6bcca62bce', '1', '100', '0', null, null, '2026-03-06 11:01:06.877626+08', '2026-03-06 11:01:06.877626+08'), ('f1a04882-7ffb-451e-92c2-4545e3b2219d', 'b653fded-7d5e-4950-aa0d-725595543e3c', 'f6b200e8-cb36-4746-8305-cd6bcca62bce', '1', '100', '0', null, null, '2026-03-06 09:46:12.48919+08', '2026-03-06 09:46:12.48919+08');

View File

@@ -0,0 +1 @@
INSERT INTO "public"."ml_point_products" ("id", "name", "description", "image_url", "product_type", "points_required", "original_price", "stock", "status", "sort_order", "created_at", "updated_at") VALUES ('0ec42706-b1c7-4339-af18-c63cd1772e70', '精美手机支架', '通用手机支架,多色可选', '', 'physical', '500', '15.00', '98', '1', '3', '2026-03-05 17:27:20.672132+08', '2026-03-05 17:27:20.672132+08'), ('17f06dcc-b4d0-4ecb-beeb-3e5be546fa99', '满100减30优惠券', '全场通用满100元可用', '', 'coupon', '500', '30.00', '200', '1', '5', '2026-03-05 17:27:20.672132+08', '2026-03-05 17:27:20.672132+08'), ('21b3cb64-289a-47db-bb53-38c4a40a4030', '满50减10优惠券', '全场通用满50元可用', '', 'coupon', '200', '10.00', '499', '1', '2', '2026-03-05 17:27:20.672132+08', '2026-03-05 17:27:20.672132+08'), ('45335433-3934-41e8-a6b7-4528e1c7681e', '品牌保温杯', '304不锈钢保温杯500ml', '', 'physical', '2000', '59.00', '50', '1', '6', '2026-03-05 17:27:20.672132+08', '2026-03-05 17:27:20.672132+08'), ('9b37f52b-7147-4bf8-9ed5-95d46c8e2854', '会员月卡', '享受会员专属权益30天', '', 'virtual', '1000', '29.90', '999', '1', '4', '2026-03-05 17:27:20.672132+08', '2026-03-05 17:27:20.672132+08'), ('f6b200e8-cb36-4746-8305-cd6bcca62bce', '满10减5优惠券', '全场通用满10元可用', '', 'coupon', '100', '5.00', '999', '1', '1', '2026-03-05 17:27:20.672132+08', '2026-03-05 17:27:20.672132+08');

View File

@@ -0,0 +1 @@
INSERT INTO "public"."ml_point_records" ("id", "user_id", "points", "type", "description", "created_at", "expires_at", "is_expired", "expired_at", "balance_after") VALUES ('0b4f09f1-9563-416a-9b84-a2ef879d505b', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-200', 'redeem', '积分兑换商品', '2026-03-06 11:01:39.547481+08', null, 'false', null, null), ('13c2b1cc-0c57-4bcc-9588-087ac406c07f', 'b653fded-7d5e-4950-aa0d-725595543e3c', '5000', 'admin', '系统测试赠送积分', '2026-02-04 10:38:39.079685+08', null, 'false', null, null), ('3eb5b7cd-3f0c-4e1b-ad27-bae9108aa647', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-100', 'redeem', '积分兑换商品', '2026-03-06 11:01:07.006313+08', null, 'false', null, null), ('59127184-630f-49a1-b7ee-8005964973af', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-100', 'redeem', '积分兑换商品', '2026-03-06 10:46:18.298857+08', null, 'false', null, null), ('70715756-0b39-4db0-a50a-f1d83345a9aa', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-100', 'redeem', '积分兑换商品', '2026-03-06 09:49:21.941885+08', null, 'false', null, null), ('76b4af8a-10a3-4de7-88f3-31b423973842', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-100', 'redeem', '积分兑换商品', '2026-03-06 10:47:01.417553+08', null, 'false', null, null), ('7ccfd660-f22b-46bd-9aa6-b3949d8f5a6c', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-500', 'redeem', '积分兑换商品', '2026-03-06 11:01:45.327581+08', null, 'false', null, null), ('891b3c5f-0ea0-43bb-abe0-ee2099208126', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-100', 'redeem', '积分兑换商品', '2026-03-06 10:32:42.224647+08', null, 'false', null, null), ('a24b1887-acfb-4ea3-981f-7efdbab1e756', 'b653fded-7d5e-4950-aa0d-725595543e3c', '5000', 'admin', '系统测试赠送积分', '2026-02-04 11:02:22.732285+08', null, 'false', null, null), ('c2180f4b-2d8a-498e-b623-c8809e6ab180', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-100', 'redeem', '积分兑换商品', '2026-03-06 10:30:15.64436+08', null, 'false', null, null), ('c5da3038-fc33-42a5-93b5-be62142ce88f', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-500', 'redeem', '积分兑换商品', '2026-03-06 10:20:44.638083+08', null, 'false', null, null), ('d260fa39-5870-46cb-8372-c976cd152af0', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-100', 'redeem', '积分兑换商品', '2026-03-06 09:46:12.612441+08', null, 'false', null, null), ('deacc1f0-80c1-4c1b-a9a7-ae15f7a1dd5b', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-200', 'redeem', '积分兑换商品', '2026-03-06 10:20:27.157006+08', null, 'false', null, null), ('edfdd3bf-62e3-4146-b0b5-b34f5d979384', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-500', 'redeem', '积分兑换商品', '2026-03-06 11:01:47.634036+08', null, 'false', null, null), ('f324e368-7d39-4a8c-ab12-8613d0c4da5f', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-100', 'redeem', '积分兑换商品', '2026-03-06 10:40:33.997246+08', null, 'false', null, null), ('f6cfffdb-6ff4-4233-bd9d-16373a0e444f', 'b653fded-7d5e-4950-aa0d-725595543e3c', '-100', 'redeem', '积分兑换商品', '2026-03-06 10:20:54.192055+08', null, 'false', null, null);

View File

@@ -13,10 +13,17 @@
</view> </view>
<view class="filter-item"> <view class="filter-item">
<text class="label-txt">上架状态:</text> <text class="label-txt">上架状态:</text>
<picker
mode="selector"
:range="statusOptions"
range-key="label"
@change="handleStatusChange"
>
<view class="select-mock"> <view class="select-mock">
<text class="select-val">请选择</text> <text class="select-val">{{ currentStatusLabel }}</text>
<text class="arrow-down">▼</text> <text class="arrow-down">▼</text>
</view> </view>
</picker>
</view> </view>
<view class="filter-item"> <view class="filter-item">
<text class="label-txt">商品搜索:</text> <text class="label-txt">商品搜索:</text>
@@ -52,13 +59,27 @@
</view> </view>
<view class="table-body"> <view class="table-body">
<view v-for="(item, index) in productList" :key="item.id" class="table-row"> <!-- 加载中状态 -->
<view class="td td-id"><text class="td-txt">{{ item.id }}</text></view> <view v-if="loading" class="empty-row">
<text class="empty-txt">加载中...</text>
</view>
<!-- 空状态 -->
<view v-else-if="productList.length === 0" class="empty-row">
<text class="empty-txt">暂无积分商品</text>
</view>
<!-- 数据行 -->
<view v-else v-for="(item, index) in productList" :key="item.id" class="table-row">
<view class="td td-id"><text class="td-txt-small td-id-val">{{ item.id }}</text></view>
<view class="td td-img"> <view class="td td-img">
<image class="product-thumb" :src="item.image" mode="aspectFill"></image> <image class="product-thumb" :src="item.image" mode="aspectFill"></image>
</view> </view>
<view class="td td-title"> <view class="td td-title">
<view class="title-cell">
<text class="title-txt line-clamp-2">{{ item.title }}</text> <text class="title-txt line-clamp-2">{{ item.title }}</text>
<view v-if="item.productType" class="type-tag">
<text class="type-tag-txt">{{ getProductTypeLabel(item.productType) }}</text>
</view>
</view>
</view> </view>
<view class="td td-integral"><text class="td-txt">{{ item.integral }}</text></view> <view class="td td-integral"><text class="td-txt">{{ item.integral }}</text></view>
<view class="td td-limit"><text class="td-txt">{{ item.limit }}</text></view> <view class="td td-limit"><text class="td-txt">{{ item.limit }}</text></view>
@@ -108,68 +129,94 @@
</template> </template>
<script setup lang="uts"> <script setup lang="uts">
import { ref, reactive, computed } from 'vue' import { ref, computed, onMounted } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue' import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
import supa from '@/components/supadb/aksupainstance.uts'
interface ProductItem { // ──────────────────────────────────────────────
id: number // 数据结构(对应 ml_point_products 真实字段)
image: string // ──────────────────────────────────────────────
title: string interface PointProductItem {
integral: number id: string // UUID
limit: number image: string // image_url为空时用默认占位
remain: number title: string // name
createTime: string productType: string // product_typephysical/coupon/virtual
sort: number integral: number // points_required
status: boolean originalPrice: string // original_price格式化后
limit: number // stock
remain: string // 数据库无此字段,固定显示 '-'
createTime: string // created_at格式化后
sort: number // sort_order
status: boolean // status === 1
} }
const searchQuery = ref('') // 商品类型标签映射
const total = ref(3) function getProductTypeLabel(type: string): string {
if (type === 'physical') return '实物'
if (type === 'coupon') return '优惠券'
if (type === 'virtual') return '虚拟'
return type
}
const productList = ref<ProductItem[]>([ // 时间格式化2026-03-05T09:27:20.672132+00:00 → 2026-03-05 17:27:20
{ function formatDateTime(raw: string): string {
id: 48, if (!raw) return '-'
image: 'https://img14.360buyimg.com/n1/jfs/t1/172605/32/17036/114175/609a473eE6997455c/df82c6168e36712b.jpg', try {
title: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤UWG440060', const d = new Date(raw)
integral: 0, const pad = (n: number): string => n < 10 ? '0' + n : '' + n
limit: 4, return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
remain: 0, } catch (_) {
createTime: '2025-10-24 14:29:19', return raw
sort: 9999,
status: true
},
{
id: 43,
image: 'https://img12.360buyimg.com/n1/jfs/t1/185449/19/11995/4379/60d96d27E6a877c8e/3c38d4e92a2a7a5a.jpg',
title: '阿迪达斯官网 adidas BBALL CAP COT 男女训练运动帽子FQ5270 传奇水蓝/传...',
integral: 100,
limit: 1,
remain: 0,
createTime: '2025-05-13 15:37:46',
sort: 9998,
status: true
},
{
id: 44,
image: 'https://img13.360buyimg.com/n1/jfs/t1/192173/5/11913/21447/60e57e95Ef82688f3/bc875f643e8c95a3.jpg',
title: '劳伦斯意式极简大平层设计师款直排真皮沙发简约客厅别墅大小户型',
integral: 6860,
limit: 1,
remain: 0,
createTime: '2025-05-13 15:38:02',
sort: 9996,
status: true
} }
])
const handleSearch = () => { console.log('Searching...') }
const handleAdd = () => { console.log('Adding...') }
const handleEdit = (item: ProductItem) => { console.log('Editing...', item.id) }
const toggleStatus = (index: number) => {
productList.value[index].status = !productList.value[index].status
} }
// 分页适配状态 // 将数据库原始行映射为页面展示对象(统一转换层)
function mapPointProduct(row: UTSJSONObject): PointProductItem {
const rawPrice = row.get('original_price')
const priceNum = rawPrice != null ? Number(rawPrice) : 0
return {
id: (row.get('id') as string) ?? '-',
image: (row.get('image_url') as string) || '/static/image/default-product.png',
title: (row.get('name') as string) || '未命名商品',
productType: (row.get('product_type') as string) || '',
integral: row.get('points_required') != null ? Number(row.get('points_required')) : 0,
originalPrice: priceNum > 0 ? '¥' + priceNum.toFixed(2) : '-',
limit: row.get('stock') != null ? Number(row.get('stock')) : 0,
remain: '-', // 数据库暂无剩余库存字段
createTime: formatDateTime((row.get('created_at') as string) ?? ''),
sort: row.get('sort_order') != null ? Number(row.get('sort_order')) : 0,
status: Number(row.get('status')) === 1,
}
}
// ──────────────────────────────────────────────
// 搜索条件状态
// ──────────────────────────────────────────────
const searchQuery = ref('') // 商品名称搜索
const statusFilter = ref(-1) // -1 = 全部1 = 上架0 = 下架
// 状态下拉选项
const statusOptions = [
{ label: '全部', value: -1 },
{ label: '上架', value: 1 },
{ label: '下架', value: 0 },
]
const currentStatusLabel = computed(() => {
const found = statusOptions.find((o) => o.value === statusFilter.value)
return found != null ? found.label : '请选择'
})
const handleStatusChange = (e: any) => {
const idx = Number(e.detail.value)
statusFilter.value = statusOptions[idx]?.value ?? -1
}
// ──────────────────────────────────────────────
// 列表 & 分页状态
// ──────────────────────────────────────────────
const productList = ref<PointProductItem[]>([])
const total = ref(0)
const loading = ref(false)
const currentPage = ref(1) const currentPage = ref(1)
const pageSize = ref(15) const pageSize = ref(15)
let jumpPageInput = '' let jumpPageInput = ''
@@ -188,16 +235,114 @@ const visiblePages = computed(() => {
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t] if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t] return [1, -1, cur - 1, cur, cur + 1, -1, t]
}) })
const handlePageChange = (p: number) => { currentPage.value = p }
// ──────────────────────────────────────────────
// 核心:服务端分页查询(只请求当前页)
// ──────────────────────────────────────────────
async function fetchProducts() {
if (loading.value) return
loading.value = true
try {
const query = supa
.from('ml_point_products')
.select('id, name, image_url, product_type, points_required, original_price, stock, status, sort_order, created_at')
// 状态筛选
if (statusFilter.value === 1) {
query.eq('status', 1)
} else if (statusFilter.value === 0) {
query.neq('status', 1)
}
// 商品名称模糊搜索
if (searchQuery.value.trim() !== '') {
query.ilike('name', '%' + searchQuery.value.trim() + '%')
}
const { data, error, total: serverTotal } = await query
.order('sort_order', { ascending: true })
.limit(pageSize.value)
.page(currentPage.value)
.execute()
if (error) {
uni.showToast({ title: '加载失败: ' + (error as UTSJSONObject).getString('message'), icon: 'none' })
return
}
if (data != null) {
const rows = data as Array<UTSJSONObject>
productList.value = rows.map((row: UTSJSONObject): PointProductItem => mapPointProduct(row))
total.value = serverTotal > 0 ? serverTotal : rows.length
} else {
productList.value = []
total.value = 0
}
} catch (err: any) {
console.error('[PointProducts] 加载失败:', err)
uni.showToast({ title: '加载失败: ' + (err.message || '请检查网络'), icon: 'none' })
} finally {
loading.value = false
}
}
// ──────────────────────────────────────────────
// 状态切换(同步写回数据库)
// ──────────────────────────────────────────────
async function toggleStatus(index: number) {
const item = productList.value[index]
const newStatus = item.status ? 0 : 1
try {
const { error } = await supa
.from('ml_point_products')
.update({ status: newStatus } as UTSJSONObject)
.eq('id', item.id)
.execute()
if (error) {
uni.showToast({ title: '状态更新失败', icon: 'none' })
return
}
productList.value[index].status = !item.status
} catch (err: any) {
console.error('[PointProducts] 状态更新失败:', err)
uni.showToast({ title: '操作失败', icon: 'none' })
}
}
// ──────────────────────────────────────────────
// 搜索 / 翻页 / 切换 pageSize
// ──────────────────────────────────────────────
const handleSearch = () => {
currentPage.value = 1
fetchProducts()
}
const handleAdd = () => { console.log('Adding...') }
const handleEdit = (item: PointProductItem) => { console.log('Editing...', item.id) }
const handlePageChange = (p: number) => {
currentPage.value = p
fetchProducts()
}
const handlePageSizeChange = (e: any) => { const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value) const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0] pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1 currentPage.value = 1
fetchProducts()
} }
const handleJumpPage = () => { const handleJumpPage = () => {
const p = parseInt(jumpPageInput) const p = parseInt(jumpPageInput)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p if (!isNaN(p) && p >= 1 && p <= totalPage.value) {
currentPage.value = p
fetchProducts()
}
} }
// ──────────────────────────────────────────────
// 生命周期
// ──────────────────────────────────────────────
onMounted(() => {
fetchProducts()
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -266,7 +411,7 @@ const handleJumpPage = () => {
justify-content: space-between; justify-content: space-between;
padding: 0 12px; padding: 0 12px;
} }
.select-val { font-size: 14px; color: #c0c4cc; } .select-val { font-size: 14px; color: #606266; }
.arrow-down { font-size: 10px; color: #c0c4cc; } .arrow-down { font-size: 10px; color: #c0c4cc; }
.search-input { .search-input {
@@ -340,7 +485,7 @@ const handleJumpPage = () => {
.td-txt-small { font-size: 13px; color: #515a6e; } .td-txt-small { font-size: 13px; color: #515a6e; }
/* 列表各列宽度控制 */ /* 列表各列宽度控制 */
.th-id, .td-id { width: 60px; } /* .th-id, .td-id 宽度在下方覆盖为 160pxUUID 展示需要) */
.th-img, .td-img { width: 80px; } .th-img, .td-img { width: 80px; }
.th-title, .td-title { flex: 1; min-width: 200px; } .th-title, .td-title { flex: 1; min-width: 200px; }
.th-integral, .td-integral { width: 100px; } .th-integral, .td-integral { width: 100px; }
@@ -396,5 +541,35 @@ const handleJumpPage = () => {
/* 分页区域已迁至 CommonPagination 组件 */ /* 分页区域已迁至 CommonPagination 组件 */
/* ID 列UUID 需要更多宽度 */
.th-id, .td-id { width: 160px; }
.td-id-val { font-size: 11px; color: #aaa; word-break: break-all; }
/* 商品标题单元格:名称 + 类型标签 */
.title-cell {
display: flex;
flex-direction: column;
gap: 4px;
}
.type-tag {
display: inline-flex;
align-items: center;
background-color: #ecf5ff;
border-radius: 3px;
padding: 1px 6px;
align-self: flex-start;
}
.type-tag-txt { font-size: 11px; color: #409eff; }
/* 空状态 / 加载中行 */
.empty-row {
display: flex;
align-items: center;
justify-content: center;
padding: 48px 0;
width: 100%;
}
.empty-txt { font-size: 14px; color: #999; }
</style> </style>