Browse Source

feat: 现代化管理端商品与订单页面

统一绿色主题,优化商品编辑、列表与订单管理界面。

Co-authored-by: Cursor <cursoragent@cursor.com>
Pchen. 3 weeks ago
parent
commit
c4dc283386

+ 367 - 132
src/pages/admin/goods/addGoods.vue

@@ -1,169 +1,404 @@
 <template>
 <template>
-    <div class="container">
-        <Breadcrumb />
-
-        <a-card title="添加商品">
-            <a-form :model="form" :rules="rules" layout="vertical" @submit-success="handleSubmit">
-                <a-form-item field="name" label="商品名称" :style="{ width: '600px' }">
-                    <a-input v-model="form.name" :max-length="25" allow-clear show-word-limit />
-                </a-form-item>
-
-                <a-form-item field="price" label="价格" :style="{ width: '300px' }">
-                    <a-input-number v-model="form.price" :step="0.01" :precision="2" />
-                </a-form-item>
-
-                <a-form-item field="lepao_count" label="乐跑次数" :style="{ width: '300px' }">
-                    <a-input-number v-model="form.lepao_count" :step="1" :precision="0" />
-                </a-form-item>
-
-                <a-form-item field="num" label="库存数量" :style="{ width: '300px' }">
-                    <a-input-number v-model="form.num" :step="1" :precision="0" />
-                </a-form-item>
-
-                <a-form-item field="state" label="商品状态" :style="{ width: '300px' }">
-                    <a-radio-group v-model="form.state">
-                        <a-radio :value="1">正常</a-radio>
-                        <a-radio :value="0">下架</a-radio>
-                    </a-radio-group>
-                </a-form-item>
-
-                <a-form-item field="content" label="商品详情">
-                    <WangEditor v-model="form.content" @change="contentChange"></WangEditor>
-                </a-form-item>
-
-                <a-form-item>
-                    <a-button html-type="submit" :loading="loading">保存</a-button>
-                </a-form-item>
-            </a-form>
-        </a-card>
-    </div>
+  <div class="container">
+    <Breadcrumb />
+
+    <a-card :bordered="false" class="page-card" :title="isEdit ? '编辑商品' : '新增商品'">
+      <a-form :model="form" :rules="rules" layout="vertical" @submit-success="handleSubmit">
+        <a-row :gutter="24">
+          <a-col :xs="24" :md="12">
+            <a-form-item field="name" label="商品名称">
+              <a-input v-model="form.name" :max-length="25" allow-clear show-word-limit />
+            </a-form-item>
+          </a-col>
+          <a-col :xs="24" :md="12">
+            <a-form-item field="state" label="上架状态">
+              <a-radio-group v-model="form.state" type="button">
+                <a-radio :value="1">上架</a-radio>
+                <a-radio :value="0">下架</a-radio>
+              </a-radio-group>
+            </a-form-item>
+          </a-col>
+          <a-col :xs="24">
+            <a-form-item field="description" label="商品简介">
+              <a-textarea
+                v-model="form.description"
+                placeholder="展示在商城列表卡片上,建议 1~2 句话"
+                :max-length="80"
+                show-word-limit
+                :auto-size="{ minRows: 2, maxRows: 3 }"
+              />
+            </a-form-item>
+          </a-col>
+          <a-col :xs="24" :sm="8">
+            <a-form-item field="price" label="价格(元)">
+              <a-input-number v-model="form.price" :step="0.01" :precision="2" :min="0" style="width: 100%" />
+            </a-form-item>
+          </a-col>
+          <a-col :xs="24" :sm="8">
+            <a-form-item field="lepao_count" label="乐跑次数">
+              <a-input-number v-model="form.lepao_count" :step="1" :precision="0" :min="0" style="width: 100%" />
+            </a-form-item>
+          </a-col>
+          <a-col :xs="24" :sm="8">
+            <a-form-item field="num" label="库存">
+              <a-input-number v-model="form.num" :step="1" :precision="0" :min="0" style="width: 100%" />
+            </a-form-item>
+          </a-col>
+          <a-col :xs="24">
+            <a-form-item field="icon" label="商品图标(Emoji)">
+              <div class="emoji-panel">
+                <div class="emoji-panel__preview">
+                  <span class="emoji-panel__emoji">{{ displayEmoji }}</span>
+                  <span class="emoji-panel__hint">当前图标</span>
+                </div>
+                <div class="emoji-panel__main">
+                  <div class="emoji-panel__label">快速选择</div>
+                  <div class="emoji-grid">
+                    <button
+                      v-for="emoji in presetEmojis"
+                      :key="emoji"
+                      type="button"
+                      class="emoji-grid__item"
+                      :class="{ 'emoji-grid__item--active': form.icon === emoji }"
+                      :title="emoji"
+                      @click="form.icon = emoji"
+                    >
+                      {{ emoji }}
+                    </button>
+                  </div>
+                  <div class="emoji-panel__label emoji-panel__label--custom">自定义</div>
+                  <a-input
+                    v-model="form.icon"
+                    class="emoji-custom-input"
+                    placeholder="输入或粘贴一个 emoji"
+                    :max-length="8"
+                    allow-clear
+                  />
+                </div>
+              </div>
+            </a-form-item>
+          </a-col>
+          <a-col :xs="24">
+            <a-form-item label="商品卖点">
+              <div class="features-editor">
+                <p class="features-editor__tip">展示在商城列表与详情页,最多 6 条</p>
+                <div v-for="(_, index) in featureItems" :key="index" class="features-editor__row">
+                  <span class="features-editor__index">{{ index + 1 }}</span>
+                  <a-input
+                    v-model="featureItems[index]"
+                    placeholder="例如:支付后自动到账"
+                    :max-length="40"
+                    allow-clear
+                  />
+                  <a-button
+                    type="text"
+                    status="danger"
+                    :disabled="featureItems.length <= 1"
+                    @click="removeFeature(index)"
+                  >
+                    <template #icon><icon-delete /></template>
+                  </a-button>
+                </div>
+                <a-button
+                  type="dashed"
+                  long
+                  class="features-editor__add"
+                  :disabled="featureItems.length >= 6"
+                  @click="addFeature"
+                >
+                  <template #icon><icon-plus /></template>
+                  添加卖点
+                </a-button>
+              </div>
+            </a-form-item>
+          </a-col>
+        </a-row>
+
+        <a-form-item field="content" label="商品详情(展示在商城详情页)">
+          <WangEditor v-model="form.content" @change="contentChange" />
+        </a-form-item>
+
+        <a-form-item>
+          <a-space>
+            <a-button type="primary" html-type="submit" :loading="loading">保存商品</a-button>
+            <a-button @click="$router.push('/goodsManage/goods/goodsList')">返回列表</a-button>
+          </a-space>
+        </a-form-item>
+      </a-form>
+    </a-card>
+  </div>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
-import { ref, reactive, onMounted } from 'vue'
+import { ref, reactive, computed, onMounted } from 'vue'
 import { adminGetGoods, addGoods } from '@/api/goods'
 import { adminGetGoods, addGoods } from '@/api/goods'
 import { useRoute } from 'vue-router'
 import { useRoute } from 'vue-router'
 import { Notification } from '@arco-design/web-vue'
 import { Notification } from '@arco-design/web-vue'
 import WangEditor from '@/components/Editor/WangEditor.vue'
 import WangEditor from '@/components/Editor/WangEditor.vue'
+import {
+  decodeGoodsContent,
+  DEFAULT_GOODS_EMOJI,
+  getGoodsEmoji,
+  parseFeatures,
+  serializeFeatures
+} from '@/utils/storeFormat'
+
+const presetEmojis = ['🏃', '⚡', '🎁', '🔥', '💪', '🏆', '📦', '⭐', '🎯', '✨']
 
 
 const loading = ref(false)
 const loading = ref(false)
 const route = useRoute()
 const route = useRoute()
+const isEdit = computed(() => Boolean(route.params.id))
+const featureItems = ref([''])
+
+const displayEmoji = computed(() => getGoodsEmoji(form.icon))
 
 
 const rules = {
 const rules = {
-    name: [
-        {
-            required: true,
-            message: '请输入商品名称',
-        },
-    ],
-    price: [
-        {
-            required: true,
-            message: '请填写商品价格',
-        }
-    ],
-    lepao_count: [
-        {
-            required: true,
-            message: '请填写乐跑次数',
-        }
-    ],
-    num: [
-        {
-            required: true,
-            message: '请填写商品库存',
-        }
-    ],
-    state: [
-        {
-            required: true,
-            message: '请选择商品状态',
-        }
-    ]
+  name: [{ required: true, message: '请输入商品名称' }],
+  price: [{ required: true, message: '请填写商品价格' }],
+  lepao_count: [{ required: true, message: '请填写乐跑次数' }],
+  num: [{ required: true, message: '请填写商品库存' }],
+  state: [{ required: true, message: '请选择商品状态' }]
 }
 }
 
 
 const form = reactive({
 const form = reactive({
-    name: '',
-    price: 0.00,
-    num: 999999,
-    files: [],
-    lepao_count: 0,
-    ic_count: 0,
-    content: '',
-    state: 1
+  name: '',
+  description: '',
+  price: 0.0,
+  num: 999999,
+  lepao_count: 0,
+  ic_count: 0,
+  content: '',
+  state: 1,
+  icon: DEFAULT_GOODS_EMOJI
 })
 })
 
 
+const addFeature = () => {
+  if (featureItems.value.length < 6) featureItems.value.push('')
+}
+
+const removeFeature = (index) => {
+  if (featureItems.value.length <= 1) {
+    featureItems.value[0] = ''
+    return
+  }
+  featureItems.value.splice(index, 1)
+}
+
 const contentChange = (value) => {
 const contentChange = (value) => {
-    form.content = value
+  form.content = value
 }
 }
 
 
 const handleSubmit = async () => {
 const handleSubmit = async () => {
-    try {
-        loading.value = true
-        let data = {
-            ...form,
-            id: route.params.id ?? null
-        }
-        data.content = btoa(encodeURI(data.content))
-        const res = await addGoods(data)
-        if (!res || res.code !== 0)
-            return Notification.error({
-                title: '保存商品失败!',
-                content: res?.msg ?? '请稍后再试'
-            })
-        Notification.success({
-            title: '保存成功!'
-        })
-        // router.push(`/service/orderDetail/${res.data}`)
-    } catch (error) {
-        Notification.error({
-            title: '保存商品失败!',
-            content: error.message || '请稍后再试'
-        })
-    } finally {
-        loading.value = false
+  try {
+    loading.value = true
+    const data = {
+      ...form,
+      id: route.params.id ?? null,
+      icon: getGoodsEmoji(form.icon),
+      features: serializeFeatures(featureItems.value)
+    }
+    data.content = btoa(encodeURI(data.content))
+    const res = await addGoods(data)
+    if (!res || res.code !== 0) {
+      return Notification.error({
+        title: '保存商品失败!',
+        content: res?.msg ?? '请稍后再试'
+      })
     }
     }
+    Notification.success({ title: '保存成功!' })
+  } catch (error) {
+    Notification.error({
+      title: '保存商品失败!',
+      content: error.message || '请稍后再试'
+    })
+  } finally {
+    loading.value = false
+  }
 }
 }
 
 
 const getGoodsDetail = async () => {
 const getGoodsDetail = async () => {
-    if (!route.params.id) return
-    try {
-        loading.value = true
-        const res = await adminGetGoods({ id: route.params.id })
-        if (!res || res.code !== 0)
-            return Notification.error({
-                title: '获取商品信息失败!',
-                content: res?.msg ?? '请稍后再试'
-            })
-        const { name, price, num, content, state, lepao_count } = res.data
-        form.content = decodeURI(atob(content))
-        form.name = name
-        form.price = Number(price)
-        form.lepao_count = lepao_count
-        form.num = num
-        form.state = state
-    } catch (error) {
-        Notification.error({
-            title: '获取商品信息失败!',
-            content: error.message || '请稍后再试'
-        })
-    } finally {
-        loading.value = false
+  if (!route.params.id) return
+  try {
+    loading.value = true
+    const res = await adminGetGoods({ id: route.params.id })
+    if (!res || res.code !== 0) {
+      return Notification.error({
+        title: '获取商品信息失败!',
+        content: res?.msg ?? '请稍后再试'
+      })
     }
     }
+    const { name, price, num, content, state, lepao_count, icon, description, features } = res.data
+    form.content = decodeGoodsContent(content)
+    form.name = name
+    form.description = description || ''
+    form.price = Number(price)
+    form.lepao_count = lepao_count
+    form.num = num
+    form.state = state
+    form.icon = getGoodsEmoji(icon)
+    const parsed = parseFeatures(features)
+    featureItems.value = parsed.length ? [...parsed] : ['']
+  } catch (error) {
+    Notification.error({
+      title: '获取商品信息失败!',
+      content: error.message || '请稍后再试'
+    })
+  } finally {
+    loading.value = false
+  }
 }
 }
 
 
 onMounted(() => {
 onMounted(() => {
-    getGoodsDetail()
+  getGoodsDetail()
 })
 })
-
 </script>
 </script>
 
 
-<style scoped>
+<style scoped lang="less">
 .container {
 .container {
-    padding: 0 20px 20px 20px;
+  padding: 0 20px 20px;
+}
+
+.page-card {
+  border-radius: 12px;
 }
 }
 
 
-.table {
-    margin-top: 15px;
+.emoji-panel {
+  display: flex;
+  gap: 24px;
+  padding: 20px;
+  background: var(--color-fill-1);
+  border: 1px solid var(--color-border-2);
+  border-radius: 12px;
+  align-items: stretch;
+
+  @media (max-width: 640px) {
+    flex-direction: column;
+  }
+
+  &__preview {
+    flex-shrink: 0;
+    width: 120px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+    background: var(--color-bg-2);
+    border-radius: 12px;
+    border: 1px solid var(--color-border-2);
+  }
+
+  &__emoji {
+    font-size: 3.5rem;
+    line-height: 1;
+  }
+
+  &__hint {
+    font-size: 12px;
+    color: var(--color-text-3);
+  }
+
+  &__main {
+    flex: 1;
+    min-width: 0;
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  &__label {
+    font-size: 13px;
+    font-weight: 500;
+    color: var(--color-text-2);
+
+    &--custom {
+      margin-top: 4px;
+    }
+  }
+}
+
+.emoji-grid {
+  display: grid;
+  grid-template-columns: repeat(5, 52px);
+  gap: 10px;
+
+  @media (max-width: 480px) {
+    grid-template-columns: repeat(5, 1fr);
+  }
+
+  &__item {
+    width: 52px;
+    height: 52px;
+    padding: 0;
+    font-size: 26px;
+    line-height: 1;
+    border: 2px solid var(--color-border-2);
+    border-radius: 10px;
+    background: var(--color-bg-2);
+    cursor: pointer;
+    transition: border-color 0.2s, background 0.2s, transform 0.15s;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    @media (max-width: 480px) {
+      width: 100%;
+      aspect-ratio: 1;
+      height: auto;
+    }
+
+    &:hover {
+      transform: scale(1.06);
+      border-color: rgb(var(--primary-5));
+    }
+
+    &--active {
+      border-color: rgb(var(--primary-6));
+      background: var(--color-primary-light-1);
+      box-shadow: 0 0 0 2px rgba(var(--primary-6), 0.15);
+    }
+  }
+}
+
+.emoji-custom-input {
+  max-width: 320px;
+  height: 40px;
+}
+
+.features-editor {
+  padding: 16px;
+  background: var(--color-fill-1);
+  border: 1px solid var(--color-border-2);
+  border-radius: 12px;
+
+  &__tip {
+    margin: 0 0 12px;
+    font-size: 12px;
+    color: var(--color-text-3);
+  }
+
+  &__row {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    margin-bottom: 10px;
+  }
+
+  &__index {
+    flex-shrink: 0;
+    width: 22px;
+    height: 22px;
+    border-radius: 50%;
+    background: var(--color-fill-3);
+    color: var(--color-text-2);
+    font-size: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  &__add {
+    margin-top: 4px;
+  }
 }
 }
-</style>
+</style>

+ 158 - 176
src/pages/admin/goods/goodsList.vue

@@ -1,209 +1,191 @@
 <template>
 <template>
-    <div class="container">
-        <Breadcrumb />
-
-        <a-card title="商品列表">
-            <a-row>
-                <a-col :flex="1">
-                    <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
-                        label-align="left">
-                        <a-row :gutter="16">
-                            <a-col :span="8">
-                                <a-form-item field="keyword" label="商品名称">
-                                    <a-input v-model="queryData.keyword" placeholder="请输入商品名称关键词" />
-                                </a-form-item>
-                            </a-col>
-                        </a-row>
-                    </a-form>
-                </a-col>
-                <a-divider style="height: 84px" direction="vertical" />
-                <a-col :flex="'86px'" style="text-align: right">
-                    <a-space direction="vertical" :size="18">
-                        <a-button type="primary" @click="search">
-                            <template #icon>
-                                <icon-search />
-                            </template>
-                            搜索
-                        </a-button>
-                        <a-button @click="reset">
-                            <template #icon>
-                                <icon-refresh />
-                            </template>
-                            重置
-                        </a-button>
-                    </a-space>
-                </a-col>
-            </a-row>
-
-            <a-table :bordered="false" :data="data" stripe hoverable column-resizable class="table" :loading="loading" :columns="columns"
-                :pagination="{
-                    showPageSize: true,
-                    showJumper: true,
-                    showTotal: true,
-                    pageSize: pagination.pagesize,
-                    current: pagination.current,
-                    total: pagination.total
-                }" @page-change="handlePageChange" @page-size-change="handlePageSizeChange">
-                <template #username="{ record }">
-                    <a-avatar :size="30">
-                        <IconUser v-if="!record.avatar" />
-                        <img :alt="record.username ?? ''" :src="record.avatar" v-else />
-                    </a-avatar>
-                    {{ record.username }}
+  <div class="admin-goods-page">
+    <Breadcrumb />
+
+    <a-card :bordered="false" class="page-card">
+      <template #title>
+        <div class="card-title-row">
+          <span>商品管理</span>
+          <a-button type="primary" @click="$router.push('/goodsManage/goods/addGoods')">
+            <template #icon><icon-plus /></template>
+            新增商品
+          </a-button>
+        </div>
+      </template>
+
+      <a-form :model="queryData" layout="inline" class="filter-form">
+        <a-form-item label="商品名称">
+          <a-input
+            v-model="queryData.keyword"
+            placeholder="关键词搜索"
+            allow-clear
+            style="width: 220px"
+            @press-enter="search"
+          />
+        </a-form-item>
+        <a-form-item>
+          <a-space>
+            <a-button type="primary" @click="search">
+              <template #icon><icon-search /></template>
+              搜索
+            </a-button>
+            <a-button @click="reset">
+              <template #icon><icon-refresh /></template>
+              重置
+            </a-button>
+          </a-space>
+        </a-form-item>
+      </a-form>
+
+      <a-table
+        :bordered="false"
+        :data="data"
+        stripe
+        :loading="loading"
+        :columns="columns"
+        class="data-table"
+        :pagination="{
+          showPageSize: true,
+          showJumper: true,
+          showTotal: true,
+          pageSize: pagination.pagesize,
+          current: pagination.current,
+          total: pagination.total
+        }"
+        @page-change="handlePageChange"
+        @page-size-change="handlePageSizeChange"
+      >
+                <template #icon="{ record }">
+                    <GoodsEmoji :icon="record.icon" size="sm" />
                 </template>
                 </template>
-                <template #create_time="{ record }">
-                    {{ stramptoTime(record.create_time) }}
-                </template>
-                <template #update_time="{ record }">
-                    {{ stramptoTime(record.update_time) }}
-                </template>
-                <template #state="{ record }">
-                    <a-tag color="orangered" v-if="record.state === 0">
-                        已下架
-                    </a-tag>
-                    <a-tag color="green" v-else-if="record.state === 1">
-                        正常
-                    </a-tag>
-                </template>
-                <template #optional="{ record }">
-                    <a-button @click="$router.push(`/goodsManage/goods/addGoods/${record.id}`)">编辑商品</a-button>
-                </template>
-            </a-table>
-        </a-card>
-    </div>
+                <template #price="{ record }">
+          <span class="price-cell">¥{{ record.price }}</span>
+        </template>
+        <template #create_time="{ record }">
+          {{ formatStoreTimeFull(record.create_time) }}
+        </template>
+        <template #update_time="{ record }">
+          {{ formatStoreTimeFull(record.update_time) }}
+        </template>
+        <template #state="{ record }">
+          <a-tag :color="record.state === 1 ? 'green' : 'orangered'">
+            {{ record.state === 1 ? '上架' : '下架' }}
+          </a-tag>
+        </template>
+        <template #optional="{ record }">
+          <a-button type="text" size="small" @click="$router.push(`/goodsManage/goods/addGoods/${record.id}`)">
+            编辑
+          </a-button>
+        </template>
+      </a-table>
+    </a-card>
+  </div>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
 import { ref, reactive, onMounted } from 'vue'
 import { adminGetGoodsList } from '@/api/goods'
 import { adminGetGoodsList } from '@/api/goods'
 import { Notification } from '@arco-design/web-vue'
 import { Notification } from '@arco-design/web-vue'
+import { formatStoreTimeFull } from '@/utils/storeFormat'
+import GoodsEmoji from '@/components/store/GoodsEmoji.vue'
 
 
-const pagination = reactive({
-    total: 0,
-    current: 1,
-    pagesize: 20
-})
+const pagination = reactive({ total: 0, current: 1, pagesize: 20 })
+const queryData = reactive({ keyword: '' })
+const loading = ref(false)
+const data = ref([])
 
 
-const queryData = reactive({
-    keyword: '',
-})
+const columns = [
+    { title: 'ID', dataIndex: 'id', width: 70 },
+    { title: '图标', slotName: 'icon', width: 70 },
+    { title: '商品名称', dataIndex: 'name', ellipsis: true, tooltip: true },
+  { title: '价格', slotName: 'price', width: 100 },
+  { title: '库存', dataIndex: 'num', width: 80 },
+  { title: '乐跑次数', dataIndex: 'lepao_count', width: 100 },
+  { title: '浏览量', dataIndex: 'views', width: 90 },
+  { title: '状态', slotName: 'state', width: 90 },
+  { title: '创建时间', slotName: 'create_time', width: 170 },
+  { title: '创建人', dataIndex: 'create_user', width: 120, ellipsis: true },
+  { title: '更新时间', slotName: 'update_time', width: 170 },
+  { title: '操作', slotName: 'optional', fixed: 'right', width: 90 }
+]
 
 
 const search = () => {
 const search = () => {
-    pagination.current = 1
-    getList()
+  pagination.current = 1
+  getList()
 }
 }
 
 
 const reset = () => {
 const reset = () => {
-    pagination.current = 1
-    queryData.id = ''
-    queryData.state = -1
-    getList()
+  queryData.keyword = ''
+  pagination.current = 1
+  getList()
 }
 }
 
 
-const columns = [
-    {
-        title: '商品ID',
-        dataIndex: 'id',
-    },
-    {
-        title: '商品名称',
-        dataIndex: 'name',
-    }, {
-        title: '商品价格',
-        dataIndex: 'price',
-    },
-    {
-        title: '库存数量',
-        dataIndex: 'num',
-    }, {
-        title: '乐跑次数',
-        dataIndex: 'lepao_count',
-    }, {
-        title: '浏览量',
-        dataIndex: 'views',
-    }, {
-        title: '创建时间',
-        slotName: 'create_time'
-    }, 
-    {
-        title: '创建人',
-        dataIndex: 'create_user',
-    },{
-        title: '最后更新时间',
-        slotName: 'update_time'
-    }, {
-        title: '更新人',
-        dataIndex: 'update_user',
-    },{
-        title: '状态',
-        slotName: 'state'
-    }, {
-        title: '操作',
-        slotName: 'optional'
-    }
-]
-
-const loading = ref(false)
-const data = ref([])
-
 const getList = async () => {
 const getList = async () => {
-    try {
-        loading.value = true
-
-        const reqData = {
-            ...queryData,
-            pagesize: pagination.pagesize,
-            current: pagination.current
-        }
-
-        const res = await adminGetGoodsList(reqData)
-        if (!res || res.code !== 0)
-            return Notification.error({
-                title: '获取商品失败!',
-                content: res?.msg ?? '请稍后再试'
-            })
-
-        data.value = res.data
-        console.log(res.data)
-        pagination.total = res.pagination.total
-    } catch (error) {
-        Notification.error({
-            title: '获取商品失败!',
-            content: error.message || '请稍后再试'
-        })
-    } finally {
-        loading.value = false
+  try {
+    loading.value = true
+    const res = await adminGetGoodsList({
+      ...queryData,
+      pagesize: pagination.pagesize,
+      current: pagination.current
+    })
+    if (!res || res.code !== 0) {
+      return Notification.error({
+        title: '获取商品失败',
+        content: res?.msg ?? '请稍后再试'
+      })
     }
     }
+    data.value = res.data ?? []
+    pagination.total = res.pagination?.total ?? 0
+  } catch (error) {
+    Notification.error({
+      title: '获取商品失败',
+      content: error.message || '请稍后再试'
+    })
+  } finally {
+    loading.value = false
+  }
 }
 }
 
 
-// 分页 - 页码变化
 const handlePageChange = (page) => {
 const handlePageChange = (page) => {
-    pagination.current = page
-    getOrderList()
+  pagination.current = page
+  getList()
 }
 }
 
 
-// 分页 - 每页条数变化
 const handlePageSizeChange = (size) => {
 const handlePageSizeChange = (size) => {
-    pagination.pagesize = size
-    pagination.current = 1 // 页大小变化后回到第一页
-    getList()
+  pagination.pagesize = size
+  pagination.current = 1
+  getList()
 }
 }
 
 
-onMounted(() => {
-    getList()
-})
+onMounted(getList)
+</script>
 
 
-const stramptoTime = (time) => {
-    return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })
+<style scoped lang="less">
+.admin-goods-page {
+  padding: 0 20px 20px;
+}
+
+.page-card {
+  border-radius: 12px;
+}
+
+.card-title-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+}
+
+.filter-form {
+  margin-bottom: 16px;
 }
 }
-</script>
 
 
-<style scoped>
-.container {
-    padding: 0 20px 20px 20px;
+.data-table {
+  margin-top: 8px;
 }
 }
 
 
-.table {
-    margin-top: 15px;
+.price-cell {
+  font-weight: 600;
+  color: #e85d04;
 }
 }
-</style>
+</style>

+ 133 - 147
src/pages/admin/goods/orderDetail.vue

@@ -1,33 +1,68 @@
 <template>
 <template>
-    <div class="container">
-        <Breadcrumb />
-        <a-spin :loading="loading" style="width: 100%">
-            <a-space direction="vertical" :size="16" fill>
-                <a-card class="general-card" title="订单状态">
-                    <a-steps :current="current" :status="stepStatus">
-                        <a-step description="用户提交订单">待支付</a-step>
-                        <a-step description="支付成功,系统处理中">待处理</a-step>
-                        <a-step description="商品权益已发放">已完成</a-step>
-                    </a-steps>
-                    <div class="state-tip">
-                        <a-tag :color="getStateColor(data.state)" size="large">{{ getStateLabel(data.state) }}</a-tag>
-                    </div>
-                </a-card>
-
-                <a-card class="general-card" title="订单信息">
-                    <a-descriptions :data="orderDescriptions" size="large" :column="2" />
-                </a-card>
-
-                <a-card class="general-card" title="买家信息">
-                    <a-descriptions :data="buyerDescriptions" size="large" :column="2" />
-                </a-card>
-
-                <a-card v-if="goodsContent" class="general-card" title="商品详情">
-                    <div class="goods-content" v-html="goodsContent"></div>
-                </a-card>
-            </a-space>
-        </a-spin>
-    </div>
+  <div class="admin-order-detail">
+    <Breadcrumb />
+    <a-spin :loading="loading" style="width: 100%">
+      <div v-if="!loading && data.orderId" class="detail-grid">
+        <a-card :bordered="false" class="detail-card detail-card--status">
+          <template #title>订单进度</template>
+          <a-steps :current="stepCurrent" :status="stepStatus" label-placement="vertical">
+            <a-step description="用户提交订单">待支付</a-step>
+            <a-step description="支付成功,系统处理">待处理</a-step>
+            <a-step description="权益已发放">已完成</a-step>
+          </a-steps>
+          <div class="state-center">
+            <a-tag :color="stateMeta.color" size="large">{{ stateMeta.label }}</a-tag>
+          </div>
+        </a-card>
+
+        <a-card :bordered="false" class="detail-card">
+          <template #title>订单信息</template>
+          <a-descriptions :column="{ xs: 1, sm: 2 }" bordered>
+            <a-descriptions-item label="订单号">{{ data.orderId }}</a-descriptions-item>
+            <a-descriptions-item label="商品">{{ data.name || '-' }}</a-descriptions-item>
+            <a-descriptions-item v-if="data.original_price" label="商品原价">
+              ¥{{ data.original_price }}
+            </a-descriptions-item>
+            <a-descriptions-item v-if="data.coupon_code" label="优惠码">
+              {{ data.coupon_code }}
+            </a-descriptions-item>
+            <a-descriptions-item v-if="data.discount_amount > 0" label="优惠减免">
+              ¥{{ data.discount_amount }}
+            </a-descriptions-item>
+            <a-descriptions-item label="实付金额">
+              <span class="price">¥{{ data.price }}</span>
+            </a-descriptions-item>
+            <a-descriptions-item label="支付方式">{{ getPayTypeLabel(data.pay_type) }}</a-descriptions-item>
+            <a-descriptions-item label="下单时间">{{ formatStoreTimeFull(data.create_time) }}</a-descriptions-item>
+            <a-descriptions-item label="支付时间">{{ formatStoreTimeFull(data.pay_time) }}</a-descriptions-item>
+            <a-descriptions-item v-if="data.pay_id" label="支付平台单号" :span="2">
+              {{ data.pay_id }}
+            </a-descriptions-item>
+            <a-descriptions-item v-if="data.lepao_count != null" label="乐跑次数">
+              {{ data.lepao_count }}
+            </a-descriptions-item>
+            <a-descriptions-item v-if="data.ic_count != null" label="IC次数">
+              {{ data.ic_count }}
+            </a-descriptions-item>
+          </a-descriptions>
+        </a-card>
+
+        <a-card :bordered="false" class="detail-card">
+          <template #title>买家信息</template>
+          <a-descriptions :column="{ xs: 1, sm: 2 }" bordered>
+            <a-descriptions-item label="用户名">{{ data.username || '-' }}</a-descriptions-item>
+            <a-descriptions-item label="邮箱">{{ data.user_email || '-' }}</a-descriptions-item>
+            <a-descriptions-item label="用户 UUID" :span="2">{{ data.create_user || '-' }}</a-descriptions-item>
+          </a-descriptions>
+        </a-card>
+
+        <a-card v-if="goodsContent" :bordered="false" class="detail-card detail-card--full">
+          <template #title>商品详情</template>
+          <div class="goods-content" v-html="goodsContent" />
+        </a-card>
+      </div>
+    </a-spin>
+  </div>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
@@ -35,149 +70,100 @@ import { ref, computed, onMounted } from 'vue'
 import { useRoute } from 'vue-router'
 import { useRoute } from 'vue-router'
 import { adminOrderDetail } from '@/api/order'
 import { adminOrderDetail } from '@/api/order'
 import { Notification } from '@arco-design/web-vue'
 import { Notification } from '@arco-design/web-vue'
+import {
+  formatStoreTimeFull,
+  getPayTypeLabel,
+  getOrderStateMeta,
+  decodeGoodsContent
+} from '@/utils/storeFormat'
 
 
 const route = useRoute()
 const route = useRoute()
 const loading = ref(true)
 const loading = ref(true)
 const data = ref({})
 const data = ref({})
 const goodsContent = ref('')
 const goodsContent = ref('')
 
 
-const getStateLabel = (state) => {
-    const map = {
-        0: '待支付',
-        1: '待处理',
-        2: '已完成',
-        3: '已关闭',
-        4: '处理异常'
-    }
-    return map[state] ?? '未知'
-}
+const stateMeta = computed(() => getOrderStateMeta(data.value?.state ?? -1))
 
 
-const getStateColor = (state) => {
-    const map = {
-        0: 'orangered',
-        1: 'arcoblue',
-        2: 'green',
-        3: 'gray',
-        4: 'red'
-    }
-    return map[state] ?? 'gray'
-}
+const stepCurrent = computed(() => {
+  const state = data.value?.state
+  if (state === 0 || state === 3) return 1
+  if (state === 1) return 2
+  if (state === 2 || state === 4) return 3
+  return 1
+})
 
 
-const getPayTypeLabel = (type) => {
-    const map = { wxpay: '微信支付', alipay: '支付宝', qqpay: 'QQ支付' }
-    return map[type] || '-'
-}
+const stepStatus = computed(() => {
+  const state = data.value?.state
+  if (state === 3 || state === 4) return 'error'
+  if (state === 2) return 'finish'
+  return 'process'
+})
 
 
-const stramptoTime = (time) => {
-    if (!time) return '-'
-    return new Date(time).toLocaleString('zh-CN', {
-        year: 'numeric',
-        month: '2-digit',
-        day: '2-digit',
-        hour: '2-digit',
-        minute: '2-digit',
-        second: '2-digit'
+const fetchDetail = async () => {
+  try {
+    loading.value = true
+    const res = await adminOrderDetail({ orderId: route.params.orderId })
+    if (!res || res.code !== 0) {
+      return Notification.error({
+        title: '获取订单详情失败',
+        content: res?.msg ?? '请稍后再试'
+      })
+    }
+    data.value = res.data || {}
+    goodsContent.value = decodeGoodsContent(res.data?.content)
+  } catch (error) {
+    Notification.error({
+      title: '获取订单详情失败',
+      content: error.message || '请稍后再试'
     })
     })
+  } finally {
+    loading.value = false
+  }
 }
 }
 
 
-const current = computed(() => {
-    const state = data.value?.state
-    if (state === 0 || state === 3) return 1
-    if (state === 1) return 2
-    if (state === 2 || state === 4) return 3
-    return 1
-})
+onMounted(fetchDetail)
+</script>
 
 
-const stepStatus = computed(() => {
-    const state = data.value?.state
-    if (state === 3) return 'error'
-    if (state === 2) return 'finish'
-    if (state === 4) return 'error'
-    return 'process'
-})
+<style scoped lang="less">
+.admin-order-detail {
+  padding: 0 20px 20px;
+}
 
 
-const orderDescriptions = computed(() => {
-    const d = data.value
-    if (!d.orderId) return []
-    const items = [
-        { label: '订单号', value: d.orderId },
-        { label: '商品名称', value: d.name || '-' },
-        { label: '订单金额', value: d.price != null ? `¥${d.price}` : '-' },
-        { label: '支付方式', value: getPayTypeLabel(d.pay_type) },
-        { label: '下单时间', value: stramptoTime(d.create_time) },
-        { label: '支付时间', value: stramptoTime(d.pay_time) }
-    ]
-    if (d.pay_id) {
-        items.push({ label: '支付平台订单号', value: d.pay_id })
-    }
-    if (d.lepao_count != null) {
-        items.push({ label: '乐跑次数', value: String(d.lepao_count) })
-    }
-    if (d.ic_count != null) {
-        items.push({ label: 'IC次数', value: String(d.ic_count) })
-    }
-    if (d.vip != null) {
-        items.push({ label: 'VIP', value: d.vip ? '是' : '否' })
-    }
-    return items
-})
+.detail-grid {
+  display: grid;
+  grid-template-columns: 1fr;
+  gap: 16px;
 
 
-const buyerDescriptions = computed(() => {
-    const d = data.value
-    if (!d.orderId) return []
-    return [
-        { label: '用户名', value: d.username || '-' },
-        { label: '邮箱', value: d.user_email || '-' },
-        { label: '用户UUID', value: d.create_user || '-' }
-    ]
-})
+  @media (min-width: 960px) {
+    grid-template-columns: 1fr 1fr;
 
 
-const fetchDetail = async () => {
-    try {
-        loading.value = true
-        const res = await adminOrderDetail({ orderId: route.params.orderId })
-        if (!res || res.code !== 0) {
-            return Notification.error({
-                title: '获取订单详情失败',
-                content: res?.msg ?? '请稍后再试'
-            })
-        }
-        data.value = res.data || {}
-        try {
-            goodsContent.value = decodeURI(atob(res.data?.content || ''))
-        } catch {
-            goodsContent.value = res.data?.content || ''
-        }
-    } catch (error) {
-        Notification.error({
-            title: '获取订单详情失败',
-            content: error.message || '请稍后再试'
-        })
-    } finally {
-        loading.value = false
+    .detail-card--status,
+    .detail-card--full {
+      grid-column: 1 / -1;
     }
     }
+  }
 }
 }
 
 
-onMounted(() => {
-    fetchDetail()
-})
-</script>
-
-<style scoped>
-.container {
-    padding: 0 20px 20px 20px;
+.detail-card {
+  border-radius: 12px;
+  border: 1px solid var(--color-border-2);
 }
 }
 
 
-.general-card {
-    border-radius: 5px;
+.state-center {
+  margin-top: 20px;
+  text-align: center;
 }
 }
 
 
-.state-tip {
-    margin-top: 20px;
-    text-align: center;
+.price {
+  font-weight: 700;
+  color: #e85d04;
 }
 }
 
 
 .goods-content {
 .goods-content {
-    line-height: 1.6;
+  line-height: 1.75;
+
+  :deep(img) {
+    max-width: 100%;
+  }
 }
 }
 </style>
 </style>

+ 212 - 229
src/pages/admin/goods/orderList.vue

@@ -1,119 +1,127 @@
 <template>
 <template>
-    <div class="container">
-        <Breadcrumb />
+  <div class="admin-order-page">
+    <Breadcrumb />
 
 
-        <a-card title="订单管理">
-            <a-row>
-                <a-col :flex="1">
-                    <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
-                        label-align="left">
-                        <a-row :gutter="16">
-                            <a-col :span="6">
-                                <a-form-item field="orderId" label="订单号">
-                                    <a-input v-model="queryData.orderId" placeholder="支持模糊搜索" allow-clear />
-                                </a-form-item>
-                            </a-col>
-                            <a-col :span="6">
-                                <a-form-item field="goods_name" label="商品名称">
-                                    <a-input v-model="queryData.goods_name" placeholder="支持模糊搜索" allow-clear />
-                                </a-form-item>
-                            </a-col>
-                            <a-col :span="6">
-                                <a-form-item field="username" label="用户名">
-                                    <a-input v-model="queryData.username" placeholder="支持模糊搜索" allow-clear />
-                                </a-form-item>
-                            </a-col>
-                            <a-col :span="6">
-                                <a-form-item field="user_email" label="用户邮箱">
-                                    <EmailAutoComplete v-model="queryData.user_email" placeholder="支持模糊搜索" allow-clear />
-                                </a-form-item>
-                            </a-col>
-                            <a-col :span="6">
-                                <a-form-item field="state" label="订单状态">
-                                    <a-select v-model="queryData.state" :options="stateOptions" placeholder="请选择状态" />
-                                </a-form-item>
-                            </a-col>
-                            <a-col :span="6">
-                                <a-form-item field="pay_type" label="支付方式">
-                                    <a-select v-model="queryData.pay_type" placeholder="全部方式" allow-clear>
-                                        <a-option value="">全部</a-option>
-                                        <a-option v-for="item in payTypeOptions" :key="item.value" :value="item.value">
-                                            {{ item.label }}
-                                        </a-option>
-                                    </a-select>
-                                </a-form-item>
-                            </a-col>
-                            <a-col :span="6">
-                                <a-form-item field="queryTime" label="下单时间">
-                                    <a-range-picker v-model="queryData.queryTime" show-time format="YY-MM-DD HH:mm"
-                                        value-format="x" @ok="search()" />
-                                </a-form-item>
-                            </a-col>
-                        </a-row>
-                    </a-form>
-                </a-col>
-                <a-divider style="height: 84px" direction="vertical" />
-                <a-col :flex="'86px'" style="text-align: right">
-                    <a-space direction="vertical" :size="18">
-                        <a-button type="primary" @click="search">
-                            <template #icon>
-                                <icon-search />
-                            </template>
-                            搜索
-                        </a-button>
-                        <a-button @click="reset">
-                            <template #icon>
-                                <icon-refresh />
-                            </template>
-                            重置
-                        </a-button>
-                    </a-space>
-                </a-col>
+    <a-card :bordered="false" class="page-card" title="订单管理">
+      <a-collapse :default-active-key="['filter']" :bordered="false" class="filter-collapse">
+        <a-collapse-item key="filter" header="筛选条件">
+          <a-form :model="queryData" layout="vertical" class="filter-grid">
+            <a-row :gutter="16">
+              <a-col :xs="24" :sm="12" :md="8" :lg="6">
+                <a-form-item label="订单号">
+                  <a-input v-model="queryData.orderId" placeholder="模糊搜索" allow-clear />
+                </a-form-item>
+              </a-col>
+              <a-col :xs="24" :sm="12" :md="8" :lg="6">
+                <a-form-item label="商品名称">
+                  <a-input v-model="queryData.goods_name" placeholder="模糊搜索" allow-clear />
+                </a-form-item>
+              </a-col>
+              <a-col :xs="24" :sm="12" :md="8" :lg="6">
+                <a-form-item label="用户名">
+                  <a-input v-model="queryData.username" placeholder="模糊搜索" allow-clear />
+                </a-form-item>
+              </a-col>
+              <a-col :xs="24" :sm="12" :md="8" :lg="6">
+                <a-form-item label="用户邮箱">
+                  <EmailAutoComplete v-model="queryData.user_email" placeholder="模糊搜索" allow-clear />
+                </a-form-item>
+              </a-col>
+              <a-col :xs="24" :sm="12" :md="8" :lg="6">
+                <a-form-item label="订单状态">
+                  <a-select v-model="queryData.state" :options="stateOptions" placeholder="全部" />
+                </a-form-item>
+              </a-col>
+              <a-col :xs="24" :sm="12" :md="8" :lg="6">
+                <a-form-item label="支付方式">
+                  <a-select v-model="queryData.pay_type" placeholder="全部" allow-clear>
+                    <a-option value="">全部</a-option>
+                    <a-option v-for="item in payTypeOptions" :key="item.value" :value="item.value">
+                      {{ item.label }}
+                    </a-option>
+                  </a-select>
+                </a-form-item>
+              </a-col>
+              <a-col :xs="24" :sm="24" :md="16" :lg="12">
+                <a-form-item label="下单时间">
+                  <a-range-picker
+                    v-model="queryData.queryTime"
+                    show-time
+                    format="YY-MM-DD HH:mm"
+                    value-format="x"
+                    style="width: 100%"
+                  />
+                </a-form-item>
+              </a-col>
             </a-row>
             </a-row>
+            <a-space>
+              <a-button type="primary" @click="search">
+                <template #icon><icon-search /></template>
+                搜索
+              </a-button>
+              <a-button @click="reset">
+                <template #icon><icon-refresh /></template>
+                重置
+              </a-button>
+            </a-space>
+          </a-form>
+        </a-collapse-item>
+      </a-collapse>
 
 
-            <a-table :bordered="false" :data="data" stripe hoverable class="table" :loading="loading" :columns="columns" :pagination="{
-                showPageSize: true,
-                showJumper: true,
-                showTotal: true,
-                pageSize: pagination.pagesize,
-                current: pagination.current,
-                total: pagination.total
-            }" @page-change="handlePageChange" @page-size-change="handlePageSizeChange">
-                <template #username="{ record }">
-                    <a-space>
-                        <a-avatar :size="28">
-                            <img v-if="record.avatar" :alt="record.username ?? ''" :src="record.avatar" />
-                            <icon-user v-else />
-                        </a-avatar>
-                        {{ record.username || '-' }}
-                    </a-space>
-                </template>
-                <template #price="{ record }">
-                    ¥{{ record.price }}
-                </template>
-                <template #pay_type="{ record }">
-                    <a-space>
-                        <icon-wechatpay v-if="record.pay_type === 'wxpay'" />
-                        <icon-alipay-circle v-else-if="record.pay_type === 'alipay'" />
-                        <icon-qq v-else-if="record.pay_type === 'qqpay'" />
-                        <span>{{ getPayTypeLabel(record.pay_type) }}</span>
-                    </a-space>
-                </template>
-                <template #state="{ record }">
-                    <a-tag :color="getStateColor(record.state)">{{ getStateLabel(record.state) }}</a-tag>
-                </template>
-                <template #create_time="{ record }">
-                    {{ stramptoTime(record.create_time) }}
-                </template>
-                <template #pay_time="{ record }">
-                    {{ record.pay_time ? stramptoTime(record.pay_time) : '-' }}
-                </template>
-                <template #optional="{ record }">
-                    <a-button @click="goDetail(record.orderId)">查看详情</a-button>
-                </template>
-            </a-table>
-        </a-card>
-    </div>
+      <a-table
+        :bordered="false"
+        :data="data"
+        stripe
+        :loading="loading"
+        :columns="columns"
+        class="data-table"
+        :scroll="{ x: 1200 }"
+        :pagination="{
+          showPageSize: true,
+          showJumper: true,
+          showTotal: true,
+          pageSize: pagination.pagesize,
+          current: pagination.current,
+          total: pagination.total
+        }"
+        @page-change="handlePageChange"
+        @page-size-change="handlePageSizeChange"
+      >
+        <template #username="{ record }">
+          <a-space>
+            <a-avatar :size="28">
+              <img v-if="record.avatar" :alt="record.username ?? ''" :src="record.avatar" />
+              <icon-user v-else />
+            </a-avatar>
+            {{ record.username || '-' }}
+          </a-space>
+        </template>
+        <template #price="{ record }">
+          <span class="price-cell">¥{{ record.price }}</span>
+        </template>
+        <template #pay_type="{ record }">
+          <a-space size="mini">
+            <icon-wechatpay v-if="record.pay_type === 'wxpay'" />
+            <icon-alipay-circle v-else-if="record.pay_type === 'alipay'" />
+            <icon-qq v-else-if="record.pay_type === 'qqpay'" />
+            <span>{{ getPayTypeLabel(record.pay_type) }}</span>
+          </a-space>
+        </template>
+        <template #state="{ record }">
+          <a-tag :color="getStateColor(record.state)">{{ getStateLabel(record.state) }}</a-tag>
+        </template>
+        <template #create_time="{ record }">
+          {{ formatStoreTimeFull(record.create_time) }}
+        </template>
+        <template #pay_time="{ record }">
+          {{ record.pay_time ? formatStoreTimeFull(record.pay_time) : '-' }}
+        </template>
+        <template #optional="{ record }">
+          <a-button type="text" size="small" @click="goDetail(record.orderId)">详情</a-button>
+        </template>
+      </a-table>
+    </a-card>
+  </div>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
@@ -122,165 +130,140 @@ import { useRouter } from 'vue-router'
 import { adminOrderList } from '@/api/order'
 import { adminOrderList } from '@/api/order'
 import { Notification } from '@arco-design/web-vue'
 import { Notification } from '@arco-design/web-vue'
 import { getSemesterTimestamps } from '@/utils/util'
 import { getSemesterTimestamps } from '@/utils/util'
+import { formatStoreTimeFull, getPayTypeLabel, getOrderStateMeta } from '@/utils/storeFormat'
 
 
 const router = useRouter()
 const router = useRouter()
-
-const pagination = reactive({
-    total: 0,
-    current: 1,
-    pagesize: 20
-})
+const pagination = reactive({ total: 0, current: 1, pagesize: 20 })
 
 
 const stateOptions = [
 const stateOptions = [
-    { label: '全部', value: -1 },
-    { label: '待支付', value: 0 },
-    { label: '待处理', value: 1 },
-    { label: '已完成', value: 2 },
-    { label: '已关闭', value: 3 },
-    { label: '处理异常', value: 4 }
+  { label: '全部', value: -1 },
+  { label: '待支付', value: 0 },
+  { label: '待处理', value: 1 },
+  { label: '已完成', value: 2 },
+  { label: '已关闭', value: 3 },
+  { label: '处理异常', value: 4 }
 ]
 ]
 
 
 const payTypeOptions = [
 const payTypeOptions = [
-    { label: '微信支付', value: 'wxpay' },
-    { label: '支付宝', value: 'alipay' },
-    { label: 'QQ支付', value: 'qqpay' }
+  { label: '微信支付', value: 'wxpay' },
+  { label: '支付宝', value: 'alipay' },
+  { label: 'QQ支付', value: 'qqpay' }
 ]
 ]
 
 
 const queryData = reactive({
 const queryData = reactive({
-    orderId: '',
-    goods_name: '',
-    username: '',
-    user_email: '',
-    state: -1,
-    pay_type: '',
-    queryTime: []
+  orderId: '',
+  goods_name: '',
+  username: '',
+  user_email: '',
+  state: -1,
+  pay_type: '',
+  queryTime: []
 })
 })
 
 
 const loading = ref(false)
 const loading = ref(false)
 const data = ref([])
 const data = ref([])
 
 
 const columns = [
 const columns = [
-    { title: '订单号', dataIndex: 'orderId', width: 180, ellipsis: true, tooltip: true },
-    { title: '商品名称', dataIndex: 'goods_name', width: 160, ellipsis: true, tooltip: true },
-    { title: '用户', slotName: 'username', width: 160 },
-    { title: '用户邮箱', dataIndex: 'user_email', width: 180, ellipsis: true, tooltip: true },
-    { title: '金额', slotName: 'price', width: 100 },
-    { title: '支付方式', slotName: 'pay_type', width: 130 },
-    { title: '订单状态', slotName: 'state', width: 110 },
-    { title: '下单时间', slotName: 'create_time', width: 170 },
-    { title: '支付时间', slotName: 'pay_time', width: 170 },
-    { title: '操作', slotName: 'optional', fixed: 'right', width: 110 }
+  { title: '订单号', dataIndex: 'orderId', width: 180, ellipsis: true, tooltip: true },
+  { title: '商品', dataIndex: 'goods_name', width: 140, ellipsis: true, tooltip: true },
+  { title: '用户', slotName: 'username', width: 150 },
+  { title: '邮箱', dataIndex: 'user_email', width: 180, ellipsis: true, tooltip: true },
+  { title: '金额', slotName: 'price', width: 90 },
+  { title: '支付', slotName: 'pay_type', width: 120 },
+  { title: '状态', slotName: 'state', width: 100 },
+  { title: '下单时间', slotName: 'create_time', width: 165 },
+  { title: '支付时间', slotName: 'pay_time', width: 165 },
+  { title: '操作', slotName: 'optional', fixed: 'right', width: 80 }
 ]
 ]
 
 
-const getPayTypeLabel = (type) => {
-    const map = { wxpay: '微信支付', alipay: '支付宝', qqpay: 'QQ支付' }
-    return map[type] || '-'
-}
-
-const getStateLabel = (state) => {
-    const map = {
-        0: '待支付',
-        1: '待处理',
-        2: '已完成',
-        3: '已关闭',
-        4: '处理异常'
-    }
-    return map[state] ?? '未知'
-}
-
-const getStateColor = (state) => {
-    const map = {
-        0: 'orangered',
-        1: 'arcoblue',
-        2: 'green',
-        3: 'gray',
-        4: 'red'
-    }
-    return map[state] ?? 'gray'
-}
-
-const stramptoTime = (time) => {
-    if (!time) return '-'
-    return new Date(time).toLocaleString('zh-CN', {
-        year: 'numeric',
-        month: '2-digit',
-        day: '2-digit',
-        hour: '2-digit',
-        minute: '2-digit',
-        second: '2-digit'
-    })
-}
+const getStateLabel = (state) => getOrderStateMeta(state).label
+const getStateColor = (state) => getOrderStateMeta(state).color
 
 
 const search = () => {
 const search = () => {
-    pagination.current = 1
-    getList()
+  pagination.current = 1
+  getList()
 }
 }
 
 
 const reset = () => {
 const reset = () => {
-    queryData.orderId = ''
-    queryData.goods_name = ''
-    queryData.username = ''
-    queryData.user_email = ''
-    queryData.state = -1
-    queryData.pay_type = ''
-    queryData.queryTime = getSemesterTimestamps()
-    pagination.current = 1
-    getList()
+  queryData.orderId = ''
+  queryData.goods_name = ''
+  queryData.username = ''
+  queryData.user_email = ''
+  queryData.state = -1
+  queryData.pay_type = ''
+  queryData.queryTime = getSemesterTimestamps()
+  pagination.current = 1
+  getList()
 }
 }
 
 
 const getList = async () => {
 const getList = async () => {
-    try {
-        loading.value = true
-        const res = await adminOrderList({
-            ...queryData,
-            pagesize: pagination.pagesize,
-            current: pagination.current
-        })
-        if (!res || res.code !== 0) {
-            return Notification.error({
-                title: '获取订单列表失败',
-                content: res?.msg ?? '请稍后再试'
-            })
-        }
-        data.value = res.data || []
-        pagination.total = res.pagination?.total ?? 0
-    } catch (error) {
-        Notification.error({
-            title: '获取订单列表失败',
-            content: error.message || '请稍后再试'
-        })
-    } finally {
-        loading.value = false
+  try {
+    loading.value = true
+    const res = await adminOrderList({
+      ...queryData,
+      pagesize: pagination.pagesize,
+      current: pagination.current
+    })
+    if (!res || res.code !== 0) {
+      return Notification.error({
+        title: '获取订单列表失败',
+        content: res?.msg ?? '请稍后再试'
+      })
     }
     }
+    data.value = res.data || []
+    pagination.total = res.pagination?.total ?? 0
+  } catch (error) {
+    Notification.error({
+      title: '获取订单列表失败',
+      content: error.message || '请稍后再试'
+    })
+  } finally {
+    loading.value = false
+  }
 }
 }
 
 
-const goDetail = (orderId) => {
-    router.push(`/goodsManage/goods/orderDetail/${orderId}`)
-}
+const goDetail = (orderId) => router.push(`/goodsManage/goods/orderDetail/${orderId}`)
 
 
 const handlePageChange = (page) => {
 const handlePageChange = (page) => {
-    pagination.current = page
-    getList()
+  pagination.current = page
+  getList()
 }
 }
 
 
 const handlePageSizeChange = (size) => {
 const handlePageSizeChange = (size) => {
-    pagination.pagesize = size
-    pagination.current = 1
-    getList()
+  pagination.pagesize = size
+  pagination.current = 1
+  getList()
 }
 }
 
 
 onMounted(() => {
 onMounted(() => {
-    queryData.queryTime = getSemesterTimestamps()
-    getList()
+  queryData.queryTime = getSemesterTimestamps()
+  getList()
 })
 })
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.container {
-    padding: 0 20px 20px 20px;
+.admin-order-page {
+  padding: 0 20px 20px;
+}
+
+.page-card {
+  border-radius: 12px;
+}
+
+.filter-collapse {
+  margin-bottom: 16px;
+}
+
+.filter-grid {
+  padding-top: 8px;
+}
+
+.data-table {
+  margin-top: 8px;
 }
 }
 
 
-.table {
-    margin-top: 15px;
+.price-cell {
+  font-weight: 600;
+  color: #e85d04;
 }
 }
 </style>
 </style>