Browse Source

style: 添加云商城全局主题与设计工具函数

引入 store-theme 设计令牌、全局样式,以及 storeFormat 时间/订单等格式化工具。

Co-authored-by: Cursor <cursoragent@cursor.com>
Pchen. 3 weeks ago
parent
commit
b4363e9ce7
3 changed files with 170 additions and 0 deletions
  1. 1 0
      src/main.js
  2. 72 0
      src/styles/store-theme.less
  3. 97 0
      src/utils/storeFormat.js

+ 1 - 0
src/main.js

@@ -1,5 +1,6 @@
 import { createApp } from 'vue'
 import './style.css'
+import '@/styles/store-theme.less'
 
 import { isElectron } from '@/utils/electron'
 

+ 72 - 0
src/styles/store-theme.less

@@ -0,0 +1,72 @@
+// 云商城 — 与首页一致的绿色系设计令牌
+@store-primary: #1b3022;
+@store-primary-light: #2d4a38;
+@store-accent: #52b878;
+@store-accent-hover: #3fa866;
+@store-bg: #f4faf6;
+@store-card-bg: #ffffff;
+@store-card-border: #e2efe6;
+@store-text-muted: #4a5c52;
+@store-radius: 16px;
+@store-radius-sm: 12px;
+@store-shadow: 0 4px 24px rgba(27, 48, 34, 0.06);
+@store-shadow-hover: 0 12px 40px rgba(27, 48, 34, 0.12);
+@store-price: #e85d04;
+
+// 由 style.css 全局引入,避免 scoped 导致样式失效
+.store-page {
+  padding: 0 20px 32px;
+  width: 100%;
+  max-width: 1200px;
+  margin: 0 auto;
+  box-sizing: border-box;
+}
+
+// 售后服务 — 窄栏在内容区居中(与 order-page 一致)
+.service-page {
+  width: 100%;
+  max-width: none;
+}
+
+.service-page__inner {
+  width: 100%;
+  max-width: 720px;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+// 宿舍电费 — 略宽容器以容纳更多信息
+.power-page {
+  width: 100%;
+  max-width: none;
+}
+
+.power-page__inner {
+  width: 100%;
+  max-width: 960px;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+// Arco Spin 默认为 inline-block,会导致子内容宽度塌陷
+.store-spin {
+  display: block !important;
+  width: 100% !important;
+
+  .arco-spin-children {
+    width: 100%;
+  }
+}
+
+.store-section-title {
+  margin: 0 0 4px;
+  font-size: 1.5rem;
+  font-weight: 600;
+  color: @store-primary;
+}
+
+.store-section-desc {
+  margin: 0 0 20px;
+  font-size: 0.9rem;
+  color: @store-text-muted;
+}

+ 97 - 0
src/utils/storeFormat.js

@@ -0,0 +1,97 @@
+export function formatStoreTime(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'
+  })
+}
+
+export function formatStoreTimeFull(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'
+  })
+}
+
+export const PAY_TYPE_MAP = {
+  alipay: { label: '支付宝', icon: 'alipay' },
+  wxpay: { label: '微信支付', icon: 'wechat' },
+  qqpay: { label: 'QQ支付', icon: 'qq' }
+}
+
+export function getPayTypeLabel(type) {
+  return PAY_TYPE_MAP[type]?.label ?? type ?? '-'
+}
+
+export const ORDER_STATE_MAP = {
+  0: { label: '待支付', color: 'orangered' },
+  1: { label: '待处理', color: 'arcoblue' },
+  2: { label: '已完成', color: 'green' },
+  3: { label: '已关闭', color: 'gray' },
+  4: { label: '处理异常', color: 'red' }
+}
+
+export function getOrderStateMeta(state) {
+  return ORDER_STATE_MAP[state] ?? { label: '未知', color: 'gray' }
+}
+
+export const DEFAULT_GOODS_EMOJI = '🏃'
+
+/** 商品展示用 emoji(icon 字段存 emoji,若为图片 URL 则回退默认) */
+export function getGoodsEmoji(icon, fallback = DEFAULT_GOODS_EMOJI) {
+  if (icon == null || icon === '') return fallback
+  const value = String(icon).trim()
+  if (!value) return fallback
+  if (/^https?:\/\//i.test(value) || value.startsWith('data:') || /\.(png|jpe?g|gif|webp|svg)(\?|$)/i.test(value)) {
+    return fallback
+  }
+  return value
+}
+
+export function stockLabel(num) {
+  if (num == null) return '-'
+  return num > 99 ? '充足' : String(num)
+}
+
+export function parseFeatures(features) {
+  if (!features) return []
+  if (Array.isArray(features)) return features
+  try {
+    const parsed = JSON.parse(features)
+    return Array.isArray(parsed) ? parsed : []
+  } catch {
+    return String(features)
+      .split(/[,,\n]/)
+      .map((s) => s.trim())
+      .filter(Boolean)
+  }
+}
+
+export function serializeFeatures(list) {
+  const arr = (list || []).map((s) => String(s).trim()).filter(Boolean)
+  return JSON.stringify(arr)
+}
+
+export function decodeGoodsContent(content) {
+  if (!content) return ''
+  const text = String(content).trim()
+  // 已是 HTML 明文则直接展示
+  if (text.startsWith('<')) return text
+  try {
+    return decodeURI(atob(text))
+  } catch {
+    return text
+  }
+}
+
+export function hasPayData(payData) {
+  return payData && typeof payData === 'object' && Object.keys(payData).length > 0
+}