- 统一从 app_product.product_id=65 和 app_subscription 获取面板数据,替代原硬编码和 tenant/siteStore - 字段映射调整,软件名称、版本号、运行状态、创建时间等均改用 product 与 subscription 字段 - 删除硬编码的 systemInfo reactive,移除不必要的 reactive 导入 - 新增 productStatusText 计算属性,对 publishStatus 状态做文字和颜色映射 - 重写运行天数计算,改用订阅生效时间 startTime,修正续费重置计数的边界响应 - 模板绑定全部字段,增加骨架屏 loading 状态,避免界面闪烁 - 添加「系统运行」字段提示信息,明确起算时间语义 - 保持 loadSubscriptionInfo 调用 getProductDetail,无新增接口请求 - 确保 vue-tsc 类型校验无新增错误
17 KiB
17 KiB
2026-07-15
shopOrder 选项卡默认改为全部
- 需求:
/shop/shopOrder选项卡默认从"待发货"改为"全部"。 - 改动
src/views/shop/shopOrder/index.vue:activeKey初始值在路由参数tab无效时的回退值由'undelivered'改为'all'。 datasource中getStatusFilterByTab('all')返回undefined,即不传statusFilter,查全部订单,符合预期。
修复 shopOrder 页面默认待发货tab数据不正确
- 根因:
activeKey默认为'undelivered',但datasource函数只设了where.type = 0,没有设statusFilter。statusFilter只在用户点击 tab 触发onTabs时才设置。所以页面初次加载时查的是全部订单而非待发货订单。 - 修复:提取
getStatusFilterByTab(key)公共映射函数,在datasource中当where.statusFilter未设置时从activeKey自动推导。onTabs也简化为复用同一函数。 - 文件:
src/views/shop/shopOrder/index.vue
Dashboard 订单总数点击跳转支持指定 tab
- Dashboard 中"订单总数"/"总营业额"跳转链接改为
/shop/shopOrder?tab=all,"待发货订单"改为?tab=undelivered,"退款申请"改为?tab=refunded。 - shopOrder 页面读取
route.query.tab初始化activeKey,无效值回退到'undelivered'。 - 文件:
src/views/shop/dashboard/index.vue、src/views/shop/shopOrder/index.vue
Dashboard 待处理事项新增"待付款订单"
- 在待发货订单上方新增"待付款订单"统计项,使用
statusFilter=0查询,点击跳转/shop/shopOrder?tab=unpaid。 - shopOrder 页面取消注释"待付款"tab,
validTabs加入unpaid。 - 新增
dot-gold样式。 - 文件:
src/views/shop/dashboard/index.vue、src/views/shop/shopOrder/index.vue
Dashboard 引入 useTenantStore,创建时间读租户信息
- 引入
useTenantStore,loadData中调用tenantStore.fetchTenantInfo()替代直接调用getTenantInfo()API。 - 基本信息"创建时间"改为读取
tenantStore.company?.createTime。 - 运行天数也改为从
tenantStore.company?.createTime计算,移除独立的tenantCreateTimeref。 - 文件:
src/views/shop/dashboard/index.vue
Dashboard 欢迎横幅新增商城Logo
- 通过
getShopSettingCategoryValues('basic')获取商城设置,读取shopLogo字段。 - 使用
getCompressedImageUrl(shopLogo, { width: 200, quality: 90 })压缩图片。 - 在 welcome-banner 左侧用
a-avatar(64px, square) 展示,无 Logo 时不显示。 - 文件:
src/views/shop/dashboard/index.vue
新增 useAppSubscriptionStore(websopy 应用订阅特殊接口)
- 新增
src/api/app/appSubscription/model.ts:AppSubscription 实体及参数/返回类型,对照后端com.gxwebsoft.app.entity.AppSubscription。 - 新增
src/api/app/appSubscription/index.ts:封装AppSubscriptionController全部 14 个接口。- 特殊接口域名
https://websopy-api.websoft.top/api(与项目主 API 域名不同),复用@/utils/request(共享登录态 token、401 处理),传绝对 URL。 - 后端无 context-path;baseURL=
https://websopy-api.websoft.top/api,接口路径用/app/subscription/xxx。 - 后端
BaseController成功 code=0;success(IPage)转成PageResult{list,count},与项目@/api的 PageResult 结构一致。
- 特殊接口域名
- 新增
src/store/modules/appSubscription.ts:useAppSubscriptionStore(Pinia Options API 风格,对齐 site.ts/statistics.ts),聚合列表/详情/支付状态/订阅管理方法。列表/详情缓存5分钟;checkStatus/checkPurchased实时请求;订阅/支付/管理类操作成功后调invalidateCache()失效缓存。
确认线下收款新增上传凭证功能
- 背景:原"确认线下收款"用
Modal.confirm+createVNode动态拼一个 input,只有备注,没有上传凭证。用户需要加凭证上传。 - 字段决策:新增
paymentVoucher?: string字段(不复用 comments/merchantRemarks,因为凭证是图片URL非文本)。顺带修复buyerRemarks/merchantRemarks类型 bug(undefined→string?)。 - 重构为独立组件
src/views/shop/shopOrder/components/OfflinePaymentModal.vue:- 用
<a-modal>+<a-form>+<a-upload list-type="picture-card">单张图片上传。 - 上传走
uploadOss(src/api/system/file),存储返回的path(与项目UploadCert组件约定一致),预览用getUrl(path)补全。 - 凭证必填,备注可选;校验 jpg/png、≤10MB;支持点击预览大图。
- 用
- API
confirmOfflinePayment(id, remarks?, paymentVoucher?)新增第三参数,通过 query params 传给后端。 index.vue:移除原Modal.confirm/createVNode逻辑,改为打开新组件(v-model:visible+:data="current"+@done="reload"),与DeliveryModal模式一致。- 类型检查:新增 0 错误,顺带消掉原
okType: 'success'的类型错误(antdv4 ButtonType 不含 success,新组件去掉了 ok-type)。 - 改动文件:
src/api/shop/shopOrder/model/index.ts、src/api/shop/shopOrder/index.ts、src/views/shop/shopOrder/components/OfflinePaymentModal.vue(新增)、src/views/shop/shopOrder/index.vue - 后端待办(已完成):后端项目
/Users/gxwebsoft/JAVA/guilixu-java(MyBatis-Plus 3.4.3.3,无 Flyway/Liquibase,DDL 手动管理,SQL 脚本放sql/目录):ShopOrder.java实体新增paymentVoucher字段(payTime 之后)。ShopOrderController.confirmOfflinePayment新增@RequestParam paymentVoucher。ShopOrderService接口 +ShopOrderServiceImpl实现方法签名加paymentVoucher参数,lambdaUpdate 加.set(paymentVoucher 非空, ShopOrder::getPaymentVoucher, paymentVoucher)。- DDL 脚本
sql/shop_order_payment_voucher.sql:ALTER TABLE shop_order ADD COLUMN payment_voucher VARCHAR(500) NULL ... AFTER pay_time。 - 注意:方法签名从 2 参变 3 参,全项目仅 Controller 一处调用,无断裂。
Dashboard 到期时间显示订阅信息 + 立即订阅/扫码支付
- 改造
src/views/shop/dashboard/index.vue基本信息「到期时间」行:三态渲染(active 显示 expireTime + 已激活 tag + ≤7天到期提醒;pending 显示待支付 + 去支付;无订阅显示立即订阅)。 - productId 关联条件:
app_product.tenantId = userStore.info.tenantId(非 siteInfo.appId)。通过pageProducts({tenantId,current:1,size:1})取该租户应用。 - 新增
src/api/app/appProduct/{model.ts,index.ts}:AppProduct 类型 +pageProducts接口(GET /api/app/product/page,分页参数 current/size,注意与项目 page/limit 不同)。 - 「立即订阅」流程:选 month/year → 免费应用调
subscribe()直接激活;付费应用调generatePayQrcode()生成小程序码 → Modal 展示二维码 → 每 2.5s 轮询checkStatus(subscriptionNo),paid 后关闭+刷新(最多 5 分钟)。envVersion 按import.meta.env.DEV自动判断 trial/release。 - pending 订阅「去支付」复用
subscriptionStore.pay(id,'wechat')(用'miniappQrcode' in result类型守卫窄化)生成小程序码,不重复创建订阅。 - 小程序端支付页
websopy-taro/src/passport/pay/index.tsx:扫码 scene=subscriptionNo → detail-by-no → mp-prepay → requestPayment → mp-confirm,后端写 Rediswxpay:paid:{no}=1,Web 端 checkStatus 据此感知。 - 移除 dashboard 不再使用的
siteInfo解构(原到期时间读siteInfo.expirationTime,已改为订阅 expireTime),修复 noUnusedLocals 报错。
修复后端 generatePayQrcode 报错 "Field 'price_type' doesn't have a default value"
- 现象:dashboard 点「立即订阅」付费应用调
generatePayQrcode时,后端 insert app_subscription 报price_type无默认值。 - 根因:后端
AppSubscriptionController.generatePayQrcode(websopy-java)创建订阅时漏了setPriceType(),而 DBprice_type为 NOT NULL 无默认值。对比subscribe方法有setPriceType。 - 参考
websopy-pc/app/pages/console/pay/[subscriptionNo].vue:它用subscribe创建订阅 +pay(id,'wechat')生成二维码(两步),避开了generatePayQrcode,所以没遇到此 bug。 - 修复(后端
AppSubscriptionController.javageneratePayQrcode 方法):补全setPriceType(product.getPriceType())+setOriginalPrice/setPayStatus(0)/setTenantId,对齐 subscribe。前端无需改动,重启后端即可。 - 注意:
subscribe价格计算用price/100(当分转元),generatePayQrcode直接用price(当元),两者不一致;schema 标注 price 为「元」,故generatePayQrcode价格逻辑更合理,保留不动。
修复价格多了100倍(product.price 实际单位是分)
- 用户反馈:
generatePayQrcode算出的金额多了100倍。确认product.price实际存的是分(非 schema 标注的元),故subscribe的/100是对的,generatePayQrcode直接用 price 是错的。 - 后端
AppSubscriptionController.generatePayQrcode价格计算改为price/100转元(对齐 subscribe),年付按10个月(10,对齐 subscribe 年付优惠),季付3,月付*1。 - 前端
dashboard/index.vue参考价同步修正:monthPrice = price/100,yearPrice = monthPrice*10(原为 price 直接 + *12)。 - 结论:product.price 实际单位是分,所有价格展示/计算都需 /100 转元。
改用 subscribe+pay 避开 generatePayQrcode 的 price_type 报错
- 现象:用户反馈
generatePayQrcode仍报price_type无默认值(报错 SQL 字段集是修复前的,说明后端未重启/未重编译,setPriceType修复未生效)。 - 关键认知:
generatePayQrcode后端不接收 priceType 参数(从product.getPriceType()取),所以前端传 priceType 给它也没用,无法绕过。 - 修复(前端方案,不依赖后端重启):dashboard
confirmSubscribe改用subscribe+pay两步(参考 websopy-pc 的pay/[subscriptionNo].vue模式):subscribe创建订阅(后端有setPriceType,不报错;价格/100正确)→ 免费status=active直接成功- 付费
status=pending→pay(subscriptionId,'wechat')生成小程序码 → Modal 展示 + 轮询checkStatus
- 移除
confirmSubscribe里的isFreeProduct分支(改用subResult.status判断);isFreeProduct仍用于订阅 Modal 的免费提示。 - vue-tsc 验证 dashboard 零 error TS(总错误 1778 不变)。
- 后端
generatePayQrcode的setPriceType修复仍保留(重启后端后该接口也能用),但前端已不依赖它。
订阅 productId 改为固定 65(小程序商城)
- 需求:dashboard 立即订阅请求的 productId 从动态(按
tenantId查app_product)改为写死65。 - 改动
src/views/shop/dashboard/index.vue:- imports:
pageProducts→getProductDetail。 loadSubscriptionInfo:移除userStore.info.tenantId判断和pageProducts({tenantId,current:1,size:1})调用,改为getProductDetail(65)查固定产品详情;定义FIXED_PRODUCT_ID = 65常量,订阅列表过滤和currentProduct.productId统一使用该常量。
- imports:
confirmSubscribe无需改动(用currentProduct.productId,已自动是 65)。getProductDetail(65)返回的 product 含 name/priceType/price,前端参考价monthPrice = price/100、yearPrice = monthPrice*10仍适用。- vue-tsc 验证 dashboard 零 error TS(总错误 1778 不变)。
shopGoodsBrowse 列表商品图片改用 OSS 压缩函数
- 文件:
src/views/shop/shopGoodsBrowse/index.vue - 引入
getCompressedImageUrl(来自src/utils/image.ts),将商品图片:src="record.goodsImage"改为:src="getCompressedImageUrl(record.goodsImage)"。 - 与
shopGoods、shopOrder、dashboard等页面保持一致:默认宽度 240px、质量 90、自动补全 OSS 域名、跳过已含x-oss-process的 URL。 - 预览行为未改(仍走 a-image 默认 src 预览)。
电脑版后台新订单红点提示(全局)
- 需求:电脑版后台有新订单时显示红点,让管理员知道有新订单。
- 关键发现:系统已有菜单红点基础设施(
menu-title.vue读item.meta.badge渲染徽章;user.tssetMenuBadge(path,value,color);layout/index.vue有.ele-menu-badge样式),但useOrderNotify原为组件级(dashboard/cms dashboard/shopOrder 三处各自独立轮询),未接通红点。 - 改造
src/views/shop/shopOrder/useOrderNotify.ts为模块级全局单例:- 轮询/状态/红点全部模块级变量(enabled、hasNewOrder、lastOrderId、timer),多组件调用只一个轮询。
- 检测到新订单 → triggerNotify(叮声+语音+遍历回调Set)+
setOrderBadge(true)调userStore.setMenuBadge('/shop/shopOrder','dot','#ff4d4f')。 - 新增
clearBadge():立即清红点 + 重查最新订单同步 lastOrderId(标记"已查看")。 onNewOrder回调用 Set 管理,组件 onBeforeUnmount 自动移除;start()幂等,由 layout 调一次。
src/layout/components/menu-title.vue:badge==='dot'时用<a-badge dot />纯红点,否则原数字徽章逻辑。src/layout/index.vue:setup 调useOrderNotify().start()全局启动,后台任意页面生效。src/views/shop/shopOrder/index.vue:onMounted+onActivated调clearBadge()(兼容 keep-alivecache-key,用orderBadgeMounted标志防首次双触发)。src/views/shop/dashboard/index.vue:quickLinks改 computed,订单管理项badge: hasNewOrder.value;quick-icon加红点 span + pulse 动画样式。- 决策(用户确认):红点=新订单未查看(进订单页清除);位置=订单管理菜单+dashboard快速入口订单管理图标;声音保留并全局化。
- 构建验证:
vite build --outDir dist_verify通过(EXIT:0)。原pnpm build失败仅因 vite 清空dist/assets被安全删除保护拦截(83文件>50阈值),与代码无关。
shopOrder 订单详情页(orderInfo.vue)支付方式下增加支付凭证图
- 背景:之前
OfflinePaymentModal确认线下收款时已支持上传paymentVoucher(OSS path),但订单详情页只展示「线下付款」tag,看不到凭证图。 - 改动
src/views/shop/shopOrder/components/orderInfo.vue:- 模板:在「支付方式」
<a-descriptions-item>之后、「开票状态」之前新增「支付凭证」项,v-if="form.paymentVoucher",:span="3"占满整行。 - 缩略图用
a-image,:width="120",getCompressedImageUrl(form.paymentVoucher, { width: 750 })(按用户要求宽度 750),preview-src-list用ensureFullUrl(form.paymentVoucher)给原图预览。 - 引入
ensureFullUrl来自@/utils/image(已有getCompressedImageUrl)。 - form reactive 默认值新增
paymentVoucher: undefined,对齐ShopOrdermodel。
- 模板:在「支付方式」
ShopOrdermodel 中paymentVoucher?: string字段早就存在(payTime 之后),无需改 model/API。- 用户场景:仅线下付款(payType=9)的订单才有此凭证图,条件渲染自然处理。
Dashboard 基本信息 6 字段数据源重构(product + subscription)
- 需求:dashboard「基本信息」面板原先混用硬编码/tenant/siteStore,改为统一从
app_product.product_id=65+app_subscription读取,语义更准确。 - 字段映射决策(用户确认):
- 系统名称 →
currentProduct.productName(原硬编码 '小程序商城') - 版本号 →
currentProduct.version(原硬编码 '2.0.0') - 运行状态 →
currentProduct.publishStatus+ 中文映射 + a-tag 颜色(原siteStore.statusText) - 创建时间 →
currentSubscription.startTime(原tenantStore.company?.createTime) - 到期时间 →
currentSubscription.expireTime(保持不变,原本就是这个) - 系统运行天数 → 自
currentSubscription.startTime起累计(原自tenantStore.company?.createTime;后改为 startTime,与"创建时间"行同源,语义统一)
- 系统名称 →
- 改动
src/views/shop/dashboard/index.vue:- 删除硬编码
systemInforeactive(已确认 cms 那两个 systemInfo 是独立 const,互不影响)。 - import 移除
reactive。 - 新增
productStatusTextcomputed:publishStatus → {text,color} 映射(published=已发布/green、pending_review=审核中/orange、developing=开发中/blue、rejected=已驳回/red、deprecated=已下架/red),未知状态显示原始值,无 product 显示 '-'。 - 重写
runDayscomputed:数据源由tenantStore.company?.createTime改为currentSubscription.value?.startTime(与"创建时间"行同源),注释说明续费新增订阅记录会重置计数的边界行为。 - 模板 6 个字段全部重绑,每个字段加
subscriptionLoading骨架屏分支(避免空白/老数据闪现)。 - 「系统运行」加
a-tooltip title="自您首次开通小程序商城起"(用户要求)。
- 删除硬编码
- 数据流复用:
loadSubscriptionInfo()原本就调getProductDetail(65)填充currentProduct,无需新增接口调用。 - 类型校验:vue-tsc 对
shop/dashboard/index.vue行级错误 0 条;cms 那批历史错误与本次无关。