From de93292fa25351bc23b50d57bd6019811f23d6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 16 Jun 2026 12:52:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(shop):=20=E6=96=B0=E5=A2=9E=E5=95=86?= =?UTF-8?q?=E5=9F=8E=E5=9F=BA=E7=A1=80=E8=AE=BE=E7=BD=AE=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增商城基础信息配置界面支持店铺名称、Logo、描述、电话、地址和开关配置 - 实现图片选择和删除功能,支持Logo的上传回显 - 集成表单校验和保存接口调用,提供保存状态反馈 - 优化响应式布局适配不同屏幕尺寸 fix(cms): 防止文章编辑内容的XSS攻击 - 在文章编辑组件中对动态HTML内容添加DOMPurify消毒 - 替换 v-html 渲染为安全消毒后的内容展现 - 确保富文本内容安全,防止跨站脚本漏洞 refactor(system-setting): 优化系统设置基本信息组件逻辑 - 替换ico文件上传组件,改用SelectFile实现图片选择和删除功能 - 简化图标上传流程,移除上传接口调用相关代码 - 统一表单数据处理,增强设置数据解析和回显兼容性 - 调整保存逻辑,支持根据是否存在主键调用新增或更新接口 - 改进watch数据响应逻辑,支持多种数据结构兼容 fix(system-setting): 修正清理设置组件数据重置逻辑 - 统一清理设置组件的 settingKey 值为 clear,避免混淆 - 优化数据监听回调,支持不同数据结构和空数据重置表单 - 确保组件初始化状态正确,避免遗留数据影响展示 fix(store): 修正 chat store 定义方式 - 按 pinia 官方规范简化 store 定义参数 - 修复 store id 错误传递问题,确保正确注册和使用 --- CONFIG_MANAGEMENT.md | 149 - README.md | 6 +- docs/coupon-backend-upgrade.md | 232 + index.html | 4 +- package-lock.json | 19250 ---------------- package.json | 60 +- patches/ele-admin-pro.patch | 55 + pnpm-lock.yaml | 3477 ++- public/tinymce/plugins/accordion/index.js | 7 + public/tinymce/plugins/accordion/plugin.js | 1054 + .../tinymce/plugins/accordion/plugin.min.js | 5 + public/tinymce/plugins/advlist/index.js | 7 + public/tinymce/plugins/advlist/plugin.js | 259 + public/tinymce/plugins/advlist/plugin.min.js | 5 + public/tinymce/plugins/anchor/index.js | 7 + public/tinymce/plugins/anchor/plugin.js | 214 + public/tinymce/plugins/anchor/plugin.min.js | 5 + public/tinymce/plugins/autolink/index.js | 7 + public/tinymce/plugins/autolink/plugin.js | 228 + public/tinymce/plugins/autolink/plugin.min.js | 5 + public/tinymce/plugins/autoresize/index.js | 7 + public/tinymce/plugins/autoresize/plugin.js | 192 + .../tinymce/plugins/autoresize/plugin.min.js | 5 + public/tinymce/plugins/autosave/index.js | 7 + public/tinymce/plugins/autosave/plugin.js | 233 + public/tinymce/plugins/autosave/plugin.min.js | 5 + public/tinymce/plugins/charmap/index.js | 7 + public/tinymce/plugins/charmap/plugin.js | 1658 ++ public/tinymce/plugins/charmap/plugin.min.js | 5 + public/tinymce/plugins/code/index.js | 7 + public/tinymce/plugins/code/plugin.js | 85 + public/tinymce/plugins/code/plugin.min.js | 5 + public/tinymce/plugins/codesample/index.js | 7 + public/tinymce/plugins/codesample/plugin.js | 2471 ++ .../tinymce/plugins/codesample/plugin.min.js | 13 + .../tinymce/plugins/directionality/index.js | 7 + .../tinymce/plugins/directionality/plugin.js | 395 + .../plugins/directionality/plugin.min.js | 5 + public/tinymce/plugins/emoticons/index.js | 7 + .../plugins/emoticons/js/emojiimages.js | 1 + .../plugins/emoticons/js/emojiimages.min.js | 1 + public/tinymce/plugins/emoticons/js/emojis.js | 1 + .../plugins/emoticons/js/emojis.min.js | 1 + public/tinymce/plugins/emoticons/plugin.js | 595 + .../tinymce/plugins/emoticons/plugin.min.js | 5 + public/tinymce/plugins/fullscreen/index.js | 7 + public/tinymce/plugins/fullscreen/plugin.js | 1249 + .../tinymce/plugins/fullscreen/plugin.min.js | 5 + public/tinymce/plugins/help/index.js | 7 + .../tinymce/plugins/help/js/i18n/keynav/ar.js | 90 + .../plugins/help/js/i18n/keynav/bg_BG.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/ca.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/cs.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/da.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/de.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/el.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/en.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/es.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/eu.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/fa.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/fi.js | 90 + .../plugins/help/js/i18n/keynav/fr_FR.js | 90 + .../plugins/help/js/i18n/keynav/he_IL.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/hi.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/hr.js | 90 + .../plugins/help/js/i18n/keynav/hu_HU.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/id.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/it.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/ja.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/kk.js | 90 + .../plugins/help/js/i18n/keynav/ko_KR.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/ms.js | 90 + .../plugins/help/js/i18n/keynav/nb_NO.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/nl.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/pl.js | 90 + .../plugins/help/js/i18n/keynav/pt_BR.js | 90 + .../plugins/help/js/i18n/keynav/pt_PT.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/ro.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/ru.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/sk.js | 90 + .../plugins/help/js/i18n/keynav/sl_SI.js | 90 + .../plugins/help/js/i18n/keynav/sv_SE.js | 90 + .../plugins/help/js/i18n/keynav/th_TH.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/tr.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/uk.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/vi.js | 90 + .../plugins/help/js/i18n/keynav/zh_CN.js | 84 + .../plugins/help/js/i18n/keynav/zh_TW.js | 90 + public/tinymce/plugins/help/plugin.js | 898 + public/tinymce/plugins/help/plugin.min.js | 5 + public/tinymce/plugins/image/index.js | 7 + public/tinymce/plugins/image/plugin.js | 1505 ++ public/tinymce/plugins/image/plugin.min.js | 5 + public/tinymce/plugins/importcss/index.js | 7 + public/tinymce/plugins/importcss/plugin.js | 344 + .../tinymce/plugins/importcss/plugin.min.js | 5 + .../tinymce/plugins/insertdatetime/index.js | 7 + .../tinymce/plugins/insertdatetime/plugin.js | 187 + .../plugins/insertdatetime/plugin.min.js | 5 + public/tinymce/plugins/link/index.js | 7 + public/tinymce/plugins/link/plugin.js | 1242 + public/tinymce/plugins/link/plugin.min.js | 5 + public/tinymce/plugins/lists/index.js | 7 + public/tinymce/plugins/lists/plugin.js | 2172 ++ public/tinymce/plugins/lists/plugin.min.js | 5 + public/tinymce/plugins/media/index.js | 7 + public/tinymce/plugins/media/plugin.js | 1217 + public/tinymce/plugins/media/plugin.min.js | 5 + public/tinymce/plugins/nonbreaking/index.js | 7 + public/tinymce/plugins/nonbreaking/plugin.js | 123 + .../tinymce/plugins/nonbreaking/plugin.min.js | 5 + public/tinymce/plugins/pagebreak/index.js | 7 + public/tinymce/plugins/pagebreak/plugin.js | 117 + .../tinymce/plugins/pagebreak/plugin.min.js | 5 + public/tinymce/plugins/preview/index.js | 7 + public/tinymce/plugins/preview/plugin.js | 97 + public/tinymce/plugins/preview/plugin.min.js | 5 + public/tinymce/plugins/quickbars/index.js | 7 + public/tinymce/plugins/quickbars/plugin.js | 447 + .../tinymce/plugins/quickbars/plugin.min.js | 5 + public/tinymce/plugins/save/index.js | 7 + public/tinymce/plugins/save/plugin.js | 118 + public/tinymce/plugins/save/plugin.min.js | 5 + public/tinymce/plugins/searchreplace/index.js | 7 + .../tinymce/plugins/searchreplace/plugin.js | 1093 + .../plugins/searchreplace/plugin.min.js | 5 + public/tinymce/plugins/table/index.js | 7 + public/tinymce/plugins/table/plugin.js | 3462 +++ public/tinymce/plugins/table/plugin.min.js | 5 + public/tinymce/plugins/template/index.js | 7 + public/tinymce/plugins/template/plugin.js | 567 + public/tinymce/plugins/template/plugin.min.js | 5 + public/tinymce/plugins/visualblocks/index.js | 7 + public/tinymce/plugins/visualblocks/plugin.js | 98 + .../plugins/visualblocks/plugin.min.js | 5 + public/tinymce/plugins/visualchars/index.js | 7 + public/tinymce/plugins/visualchars/plugin.js | 560 + .../tinymce/plugins/visualchars/plugin.min.js | 5 + public/tinymce/plugins/wordcount/index.js | 7 + public/tinymce/plugins/wordcount/plugin.js | 405 + .../tinymce/plugins/wordcount/plugin.min.js | 5 + public/tinymce/tinymce.min.js | 11 + scripts/antd4-less-compat.js | 56 + scripts/fix-ele-admin-pro.sh | 73 + scripts/scan-missing-less-vars.py | 217 + src/api/glt/gltTicketOrder/index.ts | 105 - src/api/glt/gltTicketOrder/model/index.ts | 77 - src/api/glt/gltTicketTemplate/index.ts | 105 - src/api/glt/gltTicketTemplate/model/index.ts | 55 - src/api/glt/gltUserTicket/model/index.ts | 53 - src/api/glt/gltUserTicketLog/index.ts | 105 - src/api/glt/gltUserTicketLog/model/index.ts | 53 - .../glt/gltUserTicketRelease/model/index.ts | 38 - .../shopActivity}/index.ts | 52 +- src/api/shop/shopActivity/model/index.ts | 52 + .../shopActivitySignup}/index.ts | 50 +- .../shop/shopActivitySignup/model/index.ts | 41 + src/api/shop/shopCategory/index.ts | 102 + src/api/shop/shopCommissionRecord.ts | 57 + .../shop/shopCommissionRecord/model/index.ts | 59 + src/api/shop/shopCoupon/model/index.ts | 16 +- src/api/shop/shopDealerCapital/model/index.ts | 2 + src/api/shop/shopDealerOrder/index.ts | 14 + src/api/shop/shopEvent/index.ts | 129 + src/api/shop/shopEvent/model.ts | 119 + src/api/shop/shopGoods/index.ts | 32 +- src/api/shop/shopGoods/model/index.ts | 20 +- src/api/shop/shopGoodsCategory/model/index.ts | 1 + src/api/shop/shopMemberBenefit.ts | 116 + src/api/shop/shopMemberBenefit/model/index.ts | 102 + src/api/shop/shopMemberRegister.ts | 29 + src/api/shop/shopPointsProduct.ts | 133 + src/api/shop/shopRechargeCode/index.ts | 98 + src/api/shop/shopRechargeCode/model/index.ts | 75 + src/api/shop/shopRechargeRecord/index.ts | 30 + src/api/shop/shopRechargeRecord/model.ts | 37 + src/api/shop/shopSetting/index.ts | 128 + src/api/shop/shopSetting/model/index.ts | 90 + src/api/shop/shopSpec/index.ts | 16 + src/api/shop/shopSpecValue/model/index.ts | 2 + src/api/shop/shopStore/index.ts | 51 + src/api/shop/shopStore/model/index.ts | 6 + src/api/shop/shopStoreUser/index.ts | 32 + src/api/shop/shopStoreUser/model/index.ts | 29 +- src/api/shop/shopWithdrawRecord.ts | 89 + .../shop/shopWithdrawRecord/model/index.ts | 78 + src/components/ByteMdViewer/index.vue | 7 +- src/components/SelectSpec/index.vue | 12 +- src/components/TinymceEditor/index.vue | 189 +- src/components/TinymceEditor/util.ts | 14 +- src/config/menu.ts | 5 + src/config/setting.ts | 2 +- src/layout/components/header-tools.vue | 2 +- src/lib/port-manager.ts | 2 +- src/lib/tenant-port-manager.ts | 4 +- src/store/modules/chat.ts | 3 +- src/store/modules/financeDashboard.ts | 329 + src/store/modules/params.ts | 3 +- src/store/modules/setting.ts | 2 +- src/store/modules/site.ts | 3 +- src/store/modules/statistics.ts | 3 +- src/store/modules/template.ts | 2 +- src/store/modules/tenant.ts | 3 +- src/store/modules/theme.ts | 7 +- src/store/modules/user.ts | 3 +- src/styles/antd-less-stub/dark.less | 40 + src/styles/antd-less-stub/default.less | 915 + src/styles/antd-less-stub/empty-style.js | 4 + src/styles/as-needed.less | 2 - src/styles/component.less | 90 +- src/styles/global-import.less | 1 - src/styles/index.less | 42 +- src/utils/oss.js | 43 - src/utils/oss.ts | 113 + src/utils/request.ts | 24 +- src/utils/token-util.ts | 3 +- src/views/cms/cmsWebsiteField/index.vue | 8 +- src/views/cms/dashboard/index.vue | 2 +- src/views/cms/help/components/articleEdit.vue | 8 +- src/views/cms/help/index.vue | 18 +- .../glt/gltTicketOrder/components/search.vue | 51 - src/views/glt/gltTicketOrder/index.vue | 512 - .../components/gltTicketTemplateEdit.vue | 461 - .../components/gltUserTicketEdit.vue | 262 - .../glt/gltUserTicket/components/search.vue | 50 - src/views/glt/gltUserTicket/index.vue | 300 - .../components/gltUserTicketLogEdit.vue | 292 - .../gltUserTicketLog/components/search.vue | 48 - src/views/glt/gltUserTicketLog/index.vue | 280 - .../components/search.vue | 49 - src/views/passport/login/index.vue | 58 +- src/views/shop/dashboard/index.vue | 726 +- .../shopActivity}/components/search.vue | 0 .../components/shopActivityEdit.vue} | 220 +- .../shopActivity}/index.vue | 150 +- .../shopActivitySignup/components/search.vue | 42 + .../components/shopActivitySignupEdit.vue} | 116 +- .../shopActivitySignup}/index.vue | 198 +- src/views/shop/shopBooking/index.vue | 603 + src/views/shop/shopCommissionRecord/index.vue | 154 + .../shopCoupon/components/shopCouponEdit.vue | 267 +- src/views/shop/shopCoupon/index.vue | 71 +- src/views/shop/shopDealerCapital/index.vue | 34 +- src/views/shop/shopDealerOrder/index.vue | 85 +- src/views/shop/shopDealerUser/index.vue | 2 +- .../shop/shopDealerUserDelivery/index.vue | 2 +- src/views/shop/shopDealerUserShop/index.vue | 2 +- .../shop/shopEvent/components/search.vue | 33 + .../shopEvent/components/shopEventEdit.vue | 344 + .../components/shopEventRegistrationList.vue | 215 + src/views/shop/shopEvent/index.vue | 276 + .../shop/shopGoods/components/search.vue | 47 +- .../components/shop-goods-collect.vue | 154 + .../components/shop-goods-import.vue | 83 + .../shopGoods/components/shopGoodsEdit.vue | 934 +- src/views/shop/shopGoods/index.vue | 142 +- .../shopGoodsCategory/components/search.vue | 72 + .../components/shopGoodsCategoryEdit.vue | 266 + src/views/shop/shopGoodsCategory/index.vue | 275 + .../shop/shopMemberBenefitExchange/index.vue | 187 + .../shop/shopMemberBenefitPackage/index.vue | 244 + src/views/shop/shopMemberRegister/index.vue | 173 + .../shop/shopOrder/components/orderInfo.vue | 10 +- src/views/shop/shopOrder/index.vue | 85 +- .../shopPointsProduct/components/search.vue | 62 + .../components/shopPointsProductEdit.vue | 249 + src/views/shop/shopPointsProduct/index.vue | 247 + src/views/shop/shopRechargeCode/index.vue | 744 + src/views/shop/shopRechargeRecord/index.vue | 306 + .../shop/shopSetting/components/basic.vue | 154 + .../shop/shopSetting/components/dealer.vue | 164 + .../shopSetting/components/miniprogram.vue | 206 + .../shop/shopSetting/components/notify.vue | 87 + .../shop/shopSetting/components/order.vue | 165 + .../shop/shopSetting/components/payment.vue | 158 + .../shop/shopSetting/components/points.vue | 162 + src/views/shop/shopSetting/components/sms.vue | 192 + .../shop/shopSetting/components/upload.vue | 215 + src/views/shop/shopSetting/index.vue | 59 + src/views/shop/shopSpec/components/search.vue | 49 +- .../shop/shopSpec/components/shopSpecEdit.vue | 139 +- .../shopSpec/components/specValuePanel.vue | 372 + src/views/shop/shopSpec/index.vue | 154 +- .../shop/shopSpecValue/components/search.vue | 86 +- .../components/shopSpecValueEdit.vue | 225 +- src/views/shop/shopSpecValue/index.vue | 120 +- .../shopStore/components/shopStoreEdit.vue | 181 +- .../shop/shopStoreUser/components/search.vue | 13 +- .../components/shopStoreUserEdit.vue | 278 +- src/views/shop/shopStoreUser/index.vue | 114 +- src/views/shop/shopWithdrawConfig/index.vue | 138 + src/views/shop/shopWithdrawRecord/index.vue | 250 + .../system/developer/components/CodeInfo.vue | 4 +- .../system/developer/components/ParamInfo.vue | 4 +- src/views/system/setting/components/basic.vue | 133 +- src/views/system/setting/components/clear.vue | 56 +- .../system/setting/components/developer.vue | 86 +- .../system/setting/components/mp-weixin.vue | 59 +- .../system/setting/components/payment.vue | 59 +- .../system/setting/components/printer.vue | 59 +- .../system/setting/components/privacy.vue | 47 +- .../system/setting/components/register.vue | 61 +- src/views/system/setting/components/sms.vue | 64 +- .../system/setting/components/upload.vue | 438 +- .../system/setting/components/website.vue | 81 +- .../system/setting/components/wx-official.vue | 59 +- .../system/setting/components/wx-work.vue | 59 +- src/views/system/setting/index.vue | 14 +- src/views/system/version/index.vue | 8 +- src/views/user/chat-message/index.vue | 10 +- src/views/user/profile/index.vue | 4 +- tsconfig.json | 2 - vite.config.ts | 157 +- 313 files changed, 44898 insertions(+), 25567 deletions(-) delete mode 100644 CONFIG_MANAGEMENT.md create mode 100644 docs/coupon-backend-upgrade.md delete mode 100644 package-lock.json create mode 100644 patches/ele-admin-pro.patch create mode 100644 public/tinymce/plugins/accordion/index.js create mode 100644 public/tinymce/plugins/accordion/plugin.js create mode 100644 public/tinymce/plugins/accordion/plugin.min.js create mode 100644 public/tinymce/plugins/advlist/index.js create mode 100644 public/tinymce/plugins/advlist/plugin.js create mode 100644 public/tinymce/plugins/advlist/plugin.min.js create mode 100644 public/tinymce/plugins/anchor/index.js create mode 100644 public/tinymce/plugins/anchor/plugin.js create mode 100644 public/tinymce/plugins/anchor/plugin.min.js create mode 100644 public/tinymce/plugins/autolink/index.js create mode 100644 public/tinymce/plugins/autolink/plugin.js create mode 100644 public/tinymce/plugins/autolink/plugin.min.js create mode 100644 public/tinymce/plugins/autoresize/index.js create mode 100644 public/tinymce/plugins/autoresize/plugin.js create mode 100644 public/tinymce/plugins/autoresize/plugin.min.js create mode 100644 public/tinymce/plugins/autosave/index.js create mode 100644 public/tinymce/plugins/autosave/plugin.js create mode 100644 public/tinymce/plugins/autosave/plugin.min.js create mode 100644 public/tinymce/plugins/charmap/index.js create mode 100644 public/tinymce/plugins/charmap/plugin.js create mode 100644 public/tinymce/plugins/charmap/plugin.min.js create mode 100644 public/tinymce/plugins/code/index.js create mode 100644 public/tinymce/plugins/code/plugin.js create mode 100644 public/tinymce/plugins/code/plugin.min.js create mode 100644 public/tinymce/plugins/codesample/index.js create mode 100644 public/tinymce/plugins/codesample/plugin.js create mode 100644 public/tinymce/plugins/codesample/plugin.min.js create mode 100644 public/tinymce/plugins/directionality/index.js create mode 100644 public/tinymce/plugins/directionality/plugin.js create mode 100644 public/tinymce/plugins/directionality/plugin.min.js create mode 100644 public/tinymce/plugins/emoticons/index.js create mode 100644 public/tinymce/plugins/emoticons/js/emojiimages.js create mode 100644 public/tinymce/plugins/emoticons/js/emojiimages.min.js create mode 100644 public/tinymce/plugins/emoticons/js/emojis.js create mode 100644 public/tinymce/plugins/emoticons/js/emojis.min.js create mode 100644 public/tinymce/plugins/emoticons/plugin.js create mode 100644 public/tinymce/plugins/emoticons/plugin.min.js create mode 100644 public/tinymce/plugins/fullscreen/index.js create mode 100644 public/tinymce/plugins/fullscreen/plugin.js create mode 100644 public/tinymce/plugins/fullscreen/plugin.min.js create mode 100644 public/tinymce/plugins/help/index.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/ar.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/bg_BG.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/ca.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/cs.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/da.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/de.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/el.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/en.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/es.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/eu.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/fa.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/fi.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/fr_FR.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/he_IL.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/hi.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/hr.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/hu_HU.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/id.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/it.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/ja.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/kk.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/ko_KR.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/ms.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/nb_NO.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/nl.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/pl.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/pt_BR.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/pt_PT.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/ro.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/ru.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/sk.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/sl_SI.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/sv_SE.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/th_TH.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/tr.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/uk.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/vi.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/zh_CN.js create mode 100644 public/tinymce/plugins/help/js/i18n/keynav/zh_TW.js create mode 100644 public/tinymce/plugins/help/plugin.js create mode 100644 public/tinymce/plugins/help/plugin.min.js create mode 100644 public/tinymce/plugins/image/index.js create mode 100644 public/tinymce/plugins/image/plugin.js create mode 100644 public/tinymce/plugins/image/plugin.min.js create mode 100644 public/tinymce/plugins/importcss/index.js create mode 100644 public/tinymce/plugins/importcss/plugin.js create mode 100644 public/tinymce/plugins/importcss/plugin.min.js create mode 100644 public/tinymce/plugins/insertdatetime/index.js create mode 100644 public/tinymce/plugins/insertdatetime/plugin.js create mode 100644 public/tinymce/plugins/insertdatetime/plugin.min.js create mode 100644 public/tinymce/plugins/link/index.js create mode 100644 public/tinymce/plugins/link/plugin.js create mode 100644 public/tinymce/plugins/link/plugin.min.js create mode 100644 public/tinymce/plugins/lists/index.js create mode 100644 public/tinymce/plugins/lists/plugin.js create mode 100644 public/tinymce/plugins/lists/plugin.min.js create mode 100644 public/tinymce/plugins/media/index.js create mode 100644 public/tinymce/plugins/media/plugin.js create mode 100644 public/tinymce/plugins/media/plugin.min.js create mode 100644 public/tinymce/plugins/nonbreaking/index.js create mode 100644 public/tinymce/plugins/nonbreaking/plugin.js create mode 100644 public/tinymce/plugins/nonbreaking/plugin.min.js create mode 100644 public/tinymce/plugins/pagebreak/index.js create mode 100644 public/tinymce/plugins/pagebreak/plugin.js create mode 100644 public/tinymce/plugins/pagebreak/plugin.min.js create mode 100644 public/tinymce/plugins/preview/index.js create mode 100644 public/tinymce/plugins/preview/plugin.js create mode 100644 public/tinymce/plugins/preview/plugin.min.js create mode 100644 public/tinymce/plugins/quickbars/index.js create mode 100644 public/tinymce/plugins/quickbars/plugin.js create mode 100644 public/tinymce/plugins/quickbars/plugin.min.js create mode 100644 public/tinymce/plugins/save/index.js create mode 100644 public/tinymce/plugins/save/plugin.js create mode 100644 public/tinymce/plugins/save/plugin.min.js create mode 100644 public/tinymce/plugins/searchreplace/index.js create mode 100644 public/tinymce/plugins/searchreplace/plugin.js create mode 100644 public/tinymce/plugins/searchreplace/plugin.min.js create mode 100644 public/tinymce/plugins/table/index.js create mode 100644 public/tinymce/plugins/table/plugin.js create mode 100644 public/tinymce/plugins/table/plugin.min.js create mode 100644 public/tinymce/plugins/template/index.js create mode 100644 public/tinymce/plugins/template/plugin.js create mode 100644 public/tinymce/plugins/template/plugin.min.js create mode 100644 public/tinymce/plugins/visualblocks/index.js create mode 100644 public/tinymce/plugins/visualblocks/plugin.js create mode 100644 public/tinymce/plugins/visualblocks/plugin.min.js create mode 100644 public/tinymce/plugins/visualchars/index.js create mode 100644 public/tinymce/plugins/visualchars/plugin.js create mode 100644 public/tinymce/plugins/visualchars/plugin.min.js create mode 100644 public/tinymce/plugins/wordcount/index.js create mode 100644 public/tinymce/plugins/wordcount/plugin.js create mode 100644 public/tinymce/plugins/wordcount/plugin.min.js create mode 100644 public/tinymce/tinymce.min.js create mode 100644 scripts/antd4-less-compat.js create mode 100755 scripts/fix-ele-admin-pro.sh create mode 100644 scripts/scan-missing-less-vars.py delete mode 100644 src/api/glt/gltTicketOrder/index.ts delete mode 100644 src/api/glt/gltTicketOrder/model/index.ts delete mode 100644 src/api/glt/gltTicketTemplate/index.ts delete mode 100644 src/api/glt/gltTicketTemplate/model/index.ts delete mode 100644 src/api/glt/gltUserTicket/model/index.ts delete mode 100644 src/api/glt/gltUserTicketLog/index.ts delete mode 100644 src/api/glt/gltUserTicketLog/model/index.ts delete mode 100644 src/api/glt/gltUserTicketRelease/model/index.ts rename src/api/{glt/gltUserTicket => shop/shopActivity}/index.ts (54%) create mode 100644 src/api/shop/shopActivity/model/index.ts rename src/api/{glt/gltUserTicketRelease => shop/shopActivitySignup}/index.ts (51%) create mode 100644 src/api/shop/shopActivitySignup/model/index.ts create mode 100644 src/api/shop/shopCategory/index.ts create mode 100644 src/api/shop/shopCommissionRecord.ts create mode 100644 src/api/shop/shopCommissionRecord/model/index.ts create mode 100644 src/api/shop/shopEvent/index.ts create mode 100644 src/api/shop/shopEvent/model.ts create mode 100644 src/api/shop/shopMemberBenefit.ts create mode 100644 src/api/shop/shopMemberBenefit/model/index.ts create mode 100644 src/api/shop/shopMemberRegister.ts create mode 100644 src/api/shop/shopPointsProduct.ts create mode 100644 src/api/shop/shopRechargeCode/index.ts create mode 100644 src/api/shop/shopRechargeCode/model/index.ts create mode 100644 src/api/shop/shopRechargeRecord/index.ts create mode 100644 src/api/shop/shopRechargeRecord/model.ts create mode 100644 src/api/shop/shopSetting/index.ts create mode 100644 src/api/shop/shopSetting/model/index.ts create mode 100644 src/api/shop/shopWithdrawRecord.ts create mode 100644 src/api/shop/shopWithdrawRecord/model/index.ts create mode 100644 src/store/modules/financeDashboard.ts create mode 100644 src/styles/antd-less-stub/dark.less create mode 100644 src/styles/antd-less-stub/default.less create mode 100644 src/styles/antd-less-stub/empty-style.js delete mode 100644 src/utils/oss.js create mode 100644 src/utils/oss.ts delete mode 100644 src/views/glt/gltTicketOrder/components/search.vue delete mode 100644 src/views/glt/gltTicketOrder/index.vue delete mode 100644 src/views/glt/gltTicketTemplate/components/gltTicketTemplateEdit.vue delete mode 100644 src/views/glt/gltUserTicket/components/gltUserTicketEdit.vue delete mode 100644 src/views/glt/gltUserTicket/components/search.vue delete mode 100644 src/views/glt/gltUserTicket/index.vue delete mode 100644 src/views/glt/gltUserTicketLog/components/gltUserTicketLogEdit.vue delete mode 100644 src/views/glt/gltUserTicketLog/components/search.vue delete mode 100644 src/views/glt/gltUserTicketLog/index.vue delete mode 100644 src/views/glt/gltUserTicketRelease/components/search.vue rename src/views/{glt/gltTicketTemplate => shop/shopActivity}/components/search.vue (100%) rename src/views/{glt/gltTicketOrder/components/gltTicketOrderEdit.vue => shop/shopActivity/components/shopActivityEdit.vue} (51%) rename src/views/{glt/gltUserTicketRelease => shop/shopActivity}/index.vue (63%) create mode 100644 src/views/shop/shopActivitySignup/components/search.vue rename src/views/{glt/gltUserTicketRelease/components/gltUserTicketReleaseEdit.vue => shop/shopActivitySignup/components/shopActivitySignupEdit.vue} (62%) rename src/views/{glt/gltTicketTemplate => shop/shopActivitySignup}/index.vue (55%) create mode 100644 src/views/shop/shopBooking/index.vue create mode 100644 src/views/shop/shopCommissionRecord/index.vue create mode 100644 src/views/shop/shopEvent/components/search.vue create mode 100644 src/views/shop/shopEvent/components/shopEventEdit.vue create mode 100644 src/views/shop/shopEvent/components/shopEventRegistrationList.vue create mode 100644 src/views/shop/shopEvent/index.vue create mode 100644 src/views/shop/shopGoods/components/shop-goods-collect.vue create mode 100644 src/views/shop/shopGoods/components/shop-goods-import.vue create mode 100644 src/views/shop/shopGoodsCategory/components/search.vue create mode 100644 src/views/shop/shopGoodsCategory/components/shopGoodsCategoryEdit.vue create mode 100644 src/views/shop/shopGoodsCategory/index.vue create mode 100644 src/views/shop/shopMemberBenefitExchange/index.vue create mode 100644 src/views/shop/shopMemberBenefitPackage/index.vue create mode 100644 src/views/shop/shopMemberRegister/index.vue create mode 100644 src/views/shop/shopPointsProduct/components/search.vue create mode 100644 src/views/shop/shopPointsProduct/components/shopPointsProductEdit.vue create mode 100644 src/views/shop/shopPointsProduct/index.vue create mode 100644 src/views/shop/shopRechargeCode/index.vue create mode 100644 src/views/shop/shopRechargeRecord/index.vue create mode 100644 src/views/shop/shopSetting/components/basic.vue create mode 100644 src/views/shop/shopSetting/components/dealer.vue create mode 100644 src/views/shop/shopSetting/components/miniprogram.vue create mode 100644 src/views/shop/shopSetting/components/notify.vue create mode 100644 src/views/shop/shopSetting/components/order.vue create mode 100644 src/views/shop/shopSetting/components/payment.vue create mode 100644 src/views/shop/shopSetting/components/points.vue create mode 100644 src/views/shop/shopSetting/components/sms.vue create mode 100644 src/views/shop/shopSetting/components/upload.vue create mode 100644 src/views/shop/shopSetting/index.vue create mode 100644 src/views/shop/shopSpec/components/specValuePanel.vue create mode 100644 src/views/shop/shopWithdrawConfig/index.vue create mode 100644 src/views/shop/shopWithdrawRecord/index.vue diff --git a/CONFIG_MANAGEMENT.md b/CONFIG_MANAGEMENT.md deleted file mode 100644 index 9f52d35..0000000 --- a/CONFIG_MANAGEMENT.md +++ /dev/null @@ -1,149 +0,0 @@ -# 后端配置管理说明 - -## 概述 - -本项目实现了与小程序端一致的配置管理机制,后端管理端现在也支持优先使用后台配置的API地址。 - -## 核心文件 - -1. `src/store/modules/config.ts` - 配置状态管理模块 -2. `src/composables/useConfig.ts` - 配置初始化组合式函数 -3. `src/views/system/config-demo.vue` - 配置演示页面 -4. `src/utils/request.ts` - 更新后的请求工具,支持API地址优先级 - -## 功能特性 - -### 1. API地址优先级 -- 优先使用后台配置的API地址(存储在config中的ApiUrl字段) -- 如果未配置,则回退使用本地配置的API_BASE_URL - -### 2. 配置存储 -- 使用Pinia进行状态管理 -- 同时存储在localStorage中,支持持久化 -- 提供获取、设置、刷新、清除配置的方法 - -### 3. 自动初始化 -- 应用启动时自动加载配置 -- 支持从缓存中快速恢复配置 - -## 使用方法 - -### 在组件中使用配置store - -```typescript -import { useConfigStore } from '@/store/modules/config'; - -export default defineComponent({ - setup() { - const configStore = useConfigStore(); - - // 获取配置 - const config = configStore.config; - - // 获取API地址 - const apiUrl = configStore.getApiUrl; - - // 获取网站名称 - const siteName = configStore.getSiteName; - - // 刷新配置 - const refreshConfig = async () => { - try { - await configStore.refetchConfig(); - } catch (error) { - console.error('刷新配置失败:', error); - } - }; - - return { - config, - apiUrl, - siteName, - refreshConfig - }; - } -}); -``` - -### 在组合式API中使用 - -```typescript -import { useConfigStore } from '@/store/modules/config'; - -export default defineComponent({ - setup() { - const configStore = useConfigStore(); - - // 监听配置变化 - watch(() => configStore.config, (newConfig) => { - console.log('配置已更新:', newConfig); - }); - - return {}; - } -}); -``` - -### 在请求工具中使用 - -请求工具会自动优先使用配置中的API地址: - -```typescript -// src/utils/request.ts -const getBaseUrl = (): string => { - // 尝试从配置store获取后台配置的API地址 - try { - const configStore = useConfigStore(); - if (configStore.config && configStore.config.ApiUrl) { - return configStore.config.ApiUrl; - } - - // 回退到localStorage - const configStr = localStorage.getItem('config'); - if (configStr) { - const config = typeof configStr === 'string' ? JSON.parse(configStr) : configStr; - if (config && config.ApiUrl) { - return config.ApiUrl; - } - } - } catch (error) { - console.warn('获取后台配置API地址失败:', error); - } - - // 最后回退到本地配置 - return API_BASE_URL; -}; -``` - -## 配置字段说明 - -配置对象包含以下字段: - -- `siteName` - 网站名称 -- `siteLogo` - 网站Logo -- `domain` - 域名 -- `icpNo` - ICP备案号 -- `copyright` - 版权信息 -- `loginBgImg` - 登录背景图 -- `address` - 联系地址 -- `tel` - 联系电话 -- `kefu2` - 客服2 -- `kefu1` - 客服1 -- `email` - 邮箱 -- `loginTitle` - 登录标题 -- `sysLogo` - 系统Logo -- `ApiUrl` - API地址(新增) -- `theme` - 主题(新增) - -## 菜单配置 - -配置演示页面已添加到系统管理菜单中: -- 路径:`/system/config-demo` -- 组件:`/src/views/system/config-demo.vue` -- 图标:`ExperimentOutlined` - -## 注意事项 - -1. 配置数据会自动存储在localStorage中,键名为`config` -2. 主题配置会存储在localStorage中,键名为`user_theme` -3. 如果需要自定义配置字段,需要更新`src/api/cms/cmsWebsiteField/model/index.ts`中的Config接口定义 \ No newline at end of file diff --git a/README.md b/README.md index 4597550..6bc96f6 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,8 @@ WebSoftAdmin 是一个基于 **Vue 3 + Ant Design Vue** 构建的现代化企业 ### 1. 克隆项目 ```bash -git clone https://github.com/websoft-top/mp-vue.git -cd mp-vue +git clone https://github.com/websoft-top/paopao-vue.git +cd paopao-vue ``` ### 2. 安装依赖 @@ -183,4 +183,4 @@ src/ ├── store/ # 状态管理 ├── utils/ # 工具函数 └── assets/ # 静态资源 -``` \ No newline at end of file +``` diff --git a/docs/coupon-backend-upgrade.md b/docs/coupon-backend-upgrade.md new file mode 100644 index 0000000..ce0aced --- /dev/null +++ b/docs/coupon-backend-upgrade.md @@ -0,0 +1,232 @@ +# 优惠券模块后端改造方案 + +> 前端 Vue 改造已完成(paopao-vue),本文档为后端 Java 端对应的改动清单。 + +--- + +## 一、DDL — shop_coupon 表新增字段 + +```sql +-- ============================================================ +-- 优惠券模块升级:新增发放对象控制 + 场地使用券支持 +-- 执行前请先备份表数据! +-- ============================================================ + +ALTER TABLE `shop_coupon` + -- 发放对象(0全部用户 1仅会员 2仅非会员 3指定用户) + ADD COLUMN `receive_target` TINYINT NOT NULL DEFAULT 0 COMMENT '发放对象(0全部用户 1仅会员 2仅非会员 3指定用户)' AFTER `enabled`, + -- 指定用户ID列表(JSON数组),receive_target=3 时使用 + ADD COLUMN `receive_user_ids` VARCHAR(1000) DEFAULT NULL COMMENT '指定用户ID列表(JSON数组格式),receiveTarget=3时使用' AFTER `receive_target`, + -- 场地使用券相关字段 (type=50 时使用) + ADD COLUMN `venue_type` TINYINT DEFAULT NULL COMMENT '场地使用券-场地类型' AFTER `receive_user_ids`, + ADD COLUMN `venue_id` INT DEFAULT NULL COMMENT '场地使用券-指定场地ID' AFTER `venue_type`, + ADD COLUMN `use_count` INT NOT NULL DEFAULT -1 COMMENT '场地使用券-可用次数(-1无限制)' AFTER `venue_id`, + ADD COLUMN `use_duration` INT DEFAULT NULL COMMENT '场地使用券-使用时长(分钟)' AFTER `use_count`; +``` + +### 字段说明 + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `receive_target` | TINYINT | `0` | 0=全部, 1=仅会员, 2=仅非会员, 3=指定用户 | +| `receive_user_ids` | VARCHAR(1000) | NULL | JSON 数组如 `[101,202,303]`, receiveTarget=3 时有值 | +| `venue_type` | TINYINT | NULL | 场地类型,type=50 时填写 | +| `venue_id` | INT | NULL | 具体场地 ID,type=50 时填写 | +| `use_count` | INT | `-1` | 可用次数,-1=不限制 | +| `use_duration` | INT | NULL | 使用时长(分钟) | + +--- + +## 二、Java Entity — ShopCoupon.java 新增字段 + +在实体类中新增以下字段(与前端 model/index.ts 对齐): + +```java +// ========== 以下是新增字段 ========== + +/** + * 发放对象(0全部用户 1仅会员 2仅非会员 3指定用户) + */ +@TableField("receive_target") +private Integer receiveTarget; + +/** + * 指定用户ID列表(JSON数组格式),receiveTarget=3时使用 + * 存储格式: ["101","202","303"] 或 [101,202,303] + */ +@TableField("receive_user_ids") +private String receiveUserIds; + +/** + * 场地使用券-场地类型 (type=50时使用) + */ +@TableField("venue_type") +private Integer venueType; + +/** + * 场地使用券-指定场地ID (type=50时使用) + */ +@TableField("venue_id") +private Integer venueId; + +/** + * 场地使用券-可用次数(-1表示无限制) + */ +@TableField("use_count") +private Integer useCount; + +/** + * 场地使用券-使用时长(分钟) + */ +@TableField("use_duration") +private Integer useDuration; +``` + +> 注意:如果项目使用了 Lombok `@Data` 或手动 getter/setter,确保新字段也有对应的访问方法。 + +--- + +## 三、领券/用券校验逻辑 + +### 3.1 领券接口校验(用户领取优惠券时) + +在领券 Controller 或 Service 中增加校验: + +```java +/** + * 校验用户是否有资格领取该优惠券 + * + * @param coupon 优惠券信息 + * @param userId 当前用户ID + * @param userGradeId 用户会员等级ID (0=非会员, >0=会员) + * @throws BusinessException 校验不通过时抛出业务异常 + */ +public void validateCouponReceiveTarget(ShopCoupon coupon, Long userId, Integer userGradeId) { + Integer target = coupon.getReceiveTarget(); + + // target=0 全部用户可领,直接通过 + if (target == null || target == 0) { + return; + } + + // target=1 仅会员可领 + if (target == 1) { + if (userGradeId == null || userGradeId <= 0) { + throw new BusinessException("该优惠券仅限会员领取"); + } + return; + } + + // target=2 仅非会员可领 + if (target == 2) { + if (userGradeId != null && userGradeId > 0) { + throw new BusinessException("该优惠券仅限非会员领取"); + } + return; + } + + // target=3 指定用户可领 + if (target == 3) { + String receiveUserIds = coupon.getReceiveUserIds(); + if (StringUtils.isBlank(receiveUserIds)) { + throw new BusinessException("该优惠券未设置指定用户"); + } + // 解析 JSON 数组 + List allowedUserIds = JsonUtils.parseArray(receiveUserIds, Long.class); + if (allowedUserIds == null || !allowedUserIds.contains(userId)) { + throw new BusinessException("您不在该优惠券的发放范围内"); + } + return; + } + + // 未知的 target 值,默认放行(向后兼容) +} +``` + +### 3.2 调用位置建议 + +``` +用户点击"领取优惠券" + │ + ▼ +┌──────────────────────┐ +│ 1. 检查优惠券是否启用 │ +│ 2. 检查是否已过期 │ +│ 3. 检查发放数量是否已完 │ +│ 4. 检查每人限领数量 │ ◄── 在此步骤之后、实际发券之前插入 +│ ★5. 校验发放对象 ★ │ validateCouponReceiveTarget() +│ 6. 创建用户优惠券记录 │ +└──────────────────────┘ +``` + +### 3.3 用券/下单时的校验(可选增强) + +下单抵扣时除了常规的金额/时间/商品范围校验外,可追加: + +```java +/** + * 下单使用优惠券时的额外校验 + */ +public void validateCouponForOrder(ShopCoupon coupon, ShopUser user, OrderContext ctx) { + // ... 已有的金额/有效期/适用范围校验 ... + + // 场地使用券(type=50)特殊校验 + if (coupon.getType() != null && coupon.getType() == 50) { + // 验证订单是否包含场地服务 + if (!ctx.hasVenueItem()) { + throw new BusinessException("该券仅可用于场地预订"); + } + // 如果指定了场地类型/ID,校验是否匹配 + if (coupon.getVenueType() != null && !coupon.getVenueType().equals(ctx.getVenueType())) { + throw new BusinessException("场地类型不匹配"); + } + if (coupon.getVenueId() != null && !coupon.getVenueId().equals(ctx.getVenueId())) { + throw new BusinessException("指定场地不匹配"); + } + } +} +``` + +--- + +## 四、JSON 工具方法说明 + +`receive_user_ids` 字段的读写需要 JSON 序列化/反序列化: + +**写入(保存优惠券时):** +```java +// 前端传来的是 JSON 字符串 "[101,202,303]",直接存即可 +coupon.setReceiveUserIds(receiveUserIdsJsonString); +``` + +**读取(校验时解析):** +```java +// 推荐使用 Jackson / Fastjson / Gson (项目已有的 JSON 库) +// 示例(Jackson): +ObjectMapper mapper = new ObjectMapper(); +List userIds = mapper.readValue(coupon.getReceiveUserIds(), + new TypeReference>() {}); +``` + +--- + +## 五、前端-后端字段对照表 + +| 前端字段 (TypeScript) | 后端字段 (Java/DB) | 类型 | +|------------------------|--------------------|------| +| `receiveTarget` | `receive_target` | Integer/TINYINT | +| `receiveUserIds` | `receive_user_ids` | String/VARCHAR | +| `venueType` | `venue_type` | Integer/TINYINT | +| `venueId` | `venue_id` | Integer/INT | +| `useCount` | `use_count` | Integer/INT | +| `useDuration` | `use_duration` | Integer/INT | + +--- + +## 六、注意事项 + +1. **向后兼容**:`receive_target` 默认值 `0` 表示全部用户,不影响已有优惠券的领用行为 +2. **JSON 格式一致性**:前端提交时已序列化为 JSON 字符串,后端直接存储;读取时按 JSON 解析 +3. **场地使用券 (type=50)** 是独立于普通商品券的类型,下单系统需单独适配其核销逻辑 +4. **`gradeId` 判断会员**:依赖 `shop_user.grade_id` 字段,`grade_id > 0` 为会员,`=0` 或 `NULL` 为非会员 +5. **建议先在测试环境执行 DDL 并验证完整流程后再上生产** diff --git a/index.html b/index.html index 61c5811..2dbe0b0 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,9 @@ - 麦芽知电子商务 + 桂礼序 + + '; + } + const bodyId = getBodyId(editor); + const bodyClass = getBodyClass(editor); + const isMetaKeyPressed = global$1.os.isMacOS() || global$1.os.isiOS() ? 'e.metaKey' : 'e.ctrlKey && !e.altKey'; + const preventClicksOnLinksScript = ' '; + const directionality = editor.getBody().dir; + const dirAttr = directionality ? ' dir="' + encode(directionality) + '"' : ''; + const previewHtml = '' + '' + '' + headHtml + '' + '' + editor.getContent() + preventClicksOnLinksScript + '' + ''; + return previewHtml; + }; + + const open = editor => { + const content = getPreviewHtml(editor); + const dataApi = editor.windowManager.open({ + title: 'Preview', + size: 'large', + body: { + type: 'panel', + items: [{ + name: 'preview', + type: 'iframe', + sandboxed: true, + transparent: false + }] + }, + buttons: [{ + type: 'cancel', + name: 'close', + text: 'Close', + primary: true + }], + initialData: { preview: content } + }); + dataApi.focus('close'); + }; + + const register$1 = editor => { + editor.addCommand('mcePreview', () => { + open(editor); + }); + }; + + const register = editor => { + const onAction = () => editor.execCommand('mcePreview'); + editor.ui.registry.addButton('preview', { + icon: 'preview', + tooltip: 'Preview', + onAction + }); + editor.ui.registry.addMenuItem('preview', { + icon: 'preview', + text: 'Preview', + onAction + }); + }; + + var Plugin = () => { + global$2.add('preview', editor => { + register$1(editor); + register(editor); + }); + }; + + Plugin(); + +})(); diff --git a/public/tinymce/plugins/preview/plugin.min.js b/public/tinymce/plugins/preview/plugin.min.js new file mode 100644 index 0000000..fef9ec0 --- /dev/null +++ b/public/tinymce/plugins/preview/plugin.min.js @@ -0,0 +1,5 @@ +/** + * TinyMCE version 6.8.6 (TBD) + */ + +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=e=>t=>t.options.get(e),i=n("content_style"),s=n("content_css_cors"),c=n("body_class"),r=n("body_id");e.add("preview",(e=>{(e=>{e.addCommand("mcePreview",(()=>{(e=>{const n=(e=>{var n;let l="";const a=e.dom.encode,d=null!==(n=i(e))&&void 0!==n?n:"";l+='';const m=s(e)?' crossorigin="anonymous"':"";o.each(e.contentCSS,(t=>{l+='"})),d&&(l+='");const y=r(e),u=c(e),v=' '; + const directionality = editor.getBody().dir; + const dirAttr = directionality ? ' dir="' + encode(directionality) + '"' : ''; + previewHtml = '' + '' + '' + '' + contentCssEntries + preventClicksOnLinksScript + '' + '' + previewHtml + '' + ''; + } + return replaceTemplateValues(previewHtml, getPreviewReplaceValues(editor)); + }; + const open = (editor, templateList) => { + const createTemplates = () => { + if (!templateList || templateList.length === 0) { + const message = editor.translate('No templates defined.'); + editor.notificationManager.open({ + text: message, + type: 'info' + }); + return Optional.none(); + } + return Optional.from(global$2.map(templateList, (template, index) => { + const isUrlTemplate = t => t.url !== undefined; + return { + selected: index === 0, + text: template.title, + value: { + url: isUrlTemplate(template) ? Optional.from(template.url) : Optional.none(), + content: !isUrlTemplate(template) ? Optional.from(template.content) : Optional.none(), + description: template.description + } + }; + })); + }; + const createSelectBoxItems = templates => map(templates, t => ({ + text: t.text, + value: t.text + })); + const findTemplate = (templates, templateTitle) => find(templates, t => t.text === templateTitle); + const loadFailedAlert = api => { + editor.windowManager.alert('Could not load the specified template.', () => api.focus('template')); + }; + const getTemplateContent = t => t.value.url.fold(() => Promise.resolve(t.value.content.getOr('')), url => fetch(url).then(res => res.ok ? res.text() : Promise.reject())); + const onChange = (templates, updateDialog) => (api, change) => { + if (change.name === 'template') { + const newTemplateTitle = api.getData().template; + findTemplate(templates, newTemplateTitle).each(t => { + api.block('Loading...'); + getTemplateContent(t).then(previewHtml => { + updateDialog(api, t, previewHtml); + }).catch(() => { + updateDialog(api, t, ''); + api.setEnabled('save', false); + loadFailedAlert(api); + }); + }); + } + }; + const onSubmit = templates => api => { + const data = api.getData(); + findTemplate(templates, data.template).each(t => { + getTemplateContent(t).then(previewHtml => { + editor.execCommand('mceInsertTemplate', false, previewHtml); + api.close(); + }).catch(() => { + api.setEnabled('save', false); + loadFailedAlert(api); + }); + }); + }; + const openDialog = templates => { + const selectBoxItems = createSelectBoxItems(templates); + const buildDialogSpec = (bodyItems, initialData) => ({ + title: 'Insert Template', + size: 'large', + body: { + type: 'panel', + items: bodyItems + }, + initialData, + buttons: [ + { + type: 'cancel', + name: 'cancel', + text: 'Cancel' + }, + { + type: 'submit', + name: 'save', + text: 'Save', + primary: true + } + ], + onSubmit: onSubmit(templates), + onChange: onChange(templates, updateDialog) + }); + const updateDialog = (dialogApi, template, previewHtml) => { + const content = getPreviewContent(editor, previewHtml); + const bodyItems = [ + { + type: 'listbox', + name: 'template', + label: 'Templates', + items: selectBoxItems + }, + { + type: 'htmlpanel', + html: `

${ htmlEscape(template.value.description) }

` + }, + { + label: 'Preview', + type: 'iframe', + name: 'preview', + sandboxed: false, + transparent: false + } + ]; + const initialData = { + template: template.text, + preview: content + }; + dialogApi.unblock(); + dialogApi.redial(buildDialogSpec(bodyItems, initialData)); + dialogApi.focus('template'); + }; + const dialogApi = editor.windowManager.open(buildDialogSpec([], { + template: '', + preview: '' + })); + dialogApi.block('Loading...'); + getTemplateContent(templates[0]).then(previewHtml => { + updateDialog(dialogApi, templates[0], previewHtml); + }).catch(() => { + updateDialog(dialogApi, templates[0], ''); + dialogApi.setEnabled('save', false); + loadFailedAlert(dialogApi); + }); + }; + const optTemplates = createTemplates(); + optTemplates.each(openDialog); + }; + + const showDialog = editor => templates => { + open(editor, templates); + }; + const register$1 = editor => { + editor.addCommand('mceInsertTemplate', curry(insertTemplate, editor)); + editor.addCommand('mceTemplate', createTemplateList(editor, showDialog(editor))); + }; + + const setup = editor => { + editor.on('PreProcess', o => { + const dom = editor.dom, dateFormat = getMdateFormat(editor); + global$2.each(dom.select('div', o.node), e => { + if (dom.hasClass(e, 'mceTmpl')) { + global$2.each(dom.select('*', e), e => { + if (hasAnyClasses(dom, e, getModificationDateClasses(editor))) { + e.innerHTML = getDateTime(editor, dateFormat); + } + }); + replaceVals(editor, e); + } + }); + }); + }; + + const onSetupEditable = editor => api => { + const nodeChanged = () => { + api.setEnabled(editor.selection.isEditable()); + }; + editor.on('NodeChange', nodeChanged); + nodeChanged(); + return () => { + editor.off('NodeChange', nodeChanged); + }; + }; + const register = editor => { + const onAction = () => editor.execCommand('mceTemplate'); + editor.ui.registry.addButton('template', { + icon: 'template', + tooltip: 'Insert template', + onSetup: onSetupEditable(editor), + onAction + }); + editor.ui.registry.addMenuItem('template', { + icon: 'template', + text: 'Insert template...', + onSetup: onSetupEditable(editor), + onAction + }); + }; + + var Plugin = () => { + global$3.add('template', editor => { + register$2(editor); + register(editor); + register$1(editor); + setup(editor); + }); + }; + + Plugin(); + +})(); diff --git a/public/tinymce/plugins/template/plugin.min.js b/public/tinymce/plugins/template/plugin.min.js new file mode 100644 index 0000000..3478ccc --- /dev/null +++ b/public/tinymce/plugins/template/plugin.min.js @@ -0,0 +1,5 @@ +/** + * TinyMCE version 6.8.6 (TBD) + */ + +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(a=n=e,(r=String).prototype.isPrototypeOf(a)||(null===(s=n.constructor)||void 0===s?void 0:s.name)===r.name)?"string":t;var a,n,r,s})(t)===e,a=t("string"),n=t("object"),r=t("array"),s=("function",e=>"function"==typeof e);const l=(!1,()=>false);var o=tinymce.util.Tools.resolve("tinymce.util.Tools");const c=e=>t=>t.options.get(e),i=c("template_cdate_classes"),u=c("template_mdate_classes"),m=c("template_selected_content_classes"),p=c("template_preview_replace_values"),d=c("template_replace_values"),h=c("templates"),g=c("template_cdate_format"),v=c("template_mdate_format"),f=c("content_style"),y=c("content_css_cors"),b=c("body_class"),_=(e,t)=>{if((e=""+e).length{const n="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),r="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),s="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),l="January February March April May June July August September October November December".split(" ");return(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=t.replace("%D","%m/%d/%Y")).replace("%r","%I:%M:%S %p")).replace("%Y",""+a.getFullYear())).replace("%y",""+a.getYear())).replace("%m",_(a.getMonth()+1,2))).replace("%d",_(a.getDate(),2))).replace("%H",""+_(a.getHours(),2))).replace("%M",""+_(a.getMinutes(),2))).replace("%S",""+_(a.getSeconds(),2))).replace("%I",""+((a.getHours()+11)%12+1))).replace("%p",a.getHours()<12?"AM":"PM")).replace("%B",""+e.translate(l[a.getMonth()]))).replace("%b",""+e.translate(s[a.getMonth()]))).replace("%A",""+e.translate(r[a.getDay()]))).replace("%a",""+e.translate(n[a.getDay()]))).replace("%%","%")};class T{constructor(e,t){this.tag=e,this.value=t}static some(e){return new T(!0,e)}static none(){return T.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?T.some(e(this.value)):T.none()}bind(e){return this.tag?e(this.value):T.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:T.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return null==e?T.none():T.some(e)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}T.singletonNone=new T(!1);const S=Object.hasOwnProperty;var x=tinymce.util.Tools.resolve("tinymce.html.Serializer");const C={'"':""","<":"<",">":">","&":"&","'":"'"},w=e=>e.replace(/["'<>&]/g,(e=>{return(t=C,a=e,((e,t)=>S.call(e,t))(t,a)?T.from(t[a]):T.none()).getOr(e);var t,a})),O=(e,t,a)=>((a,n)=>{for(let n=0,s=a.length;nx({validate:!0},e.schema).serialize(e.parser.parse(t,{insert:!0})),D=(e,t)=>(o.each(t,((t,a)=>{s(t)&&(t=t(a)),e=e.replace(new RegExp("\\{\\$"+a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"\\}","g"),t)})),e),N=(e,t)=>{const a=e.dom,n=d(e);o.each(a.select("*",t),(e=>{o.each(n,((t,n)=>{a.hasClass(e,n)&&s(t)&&t(e)}))}))},I=(e,t,a)=>{const n=e.dom,r=e.selection.getContent();a=D(a,d(e));let s=n.create("div",{},A(e,a));const l=n.select(".mceTmpl",s);l&&l.length>0&&(s=n.create("div"),s.appendChild(l[0].cloneNode(!0))),o.each(n.select("*",s),(t=>{O(n,t,i(e))&&(t.innerHTML=M(e,g(e))),O(n,t,u(e))&&(t.innerHTML=M(e,v(e))),O(n,t,m(e))&&(t.innerHTML=r)})),N(e,s),e.execCommand("mceInsertContent",!1,s.innerHTML),e.addVisual()};var E=tinymce.util.Tools.resolve("tinymce.Env");const k=(e,t)=>{const a=(e,t)=>((e,t,a)=>{for(let n=0,r=e.length;ne.text===t),l),n=t=>{e.windowManager.alert("Could not load the specified template.",(()=>t.focus("template")))},r=e=>e.value.url.fold((()=>Promise.resolve(e.value.content.getOr(""))),(e=>fetch(e).then((e=>e.ok?e.text():Promise.reject())))),s=(e,t)=>(s,l)=>{if("template"===l.name){const l=s.getData().template;a(e,l).each((e=>{s.block("Loading..."),r(e).then((a=>{t(s,e,a)})).catch((()=>{t(s,e,""),s.setEnabled("save",!1),n(s)}))}))}},c=t=>s=>{const l=s.getData();a(t,l.template).each((t=>{r(t).then((t=>{e.execCommand("mceInsertTemplate",!1,t),s.close()})).catch((()=>{s.setEnabled("save",!1),n(s)}))}))};(()=>{if(!t||0===t.length){const t=e.translate("No templates defined.");return e.notificationManager.open({text:t,type:"info"}),T.none()}return T.from(o.map(t,((e,t)=>{const a=e=>void 0!==e.url;return{selected:0===t,text:e.title,value:{url:a(e)?T.from(e.url):T.none(),content:a(e)?T.none():T.from(e.content),description:e.description}}})))})().each((t=>{const a=(e=>((e,t)=>{const a=e.length,n=new Array(a);for(let t=0;t({title:"Insert Template",size:"large",body:{type:"panel",items:e},initialData:a,buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],onSubmit:c(t),onChange:s(t,i)}),i=(t,n,r)=>{const s=((e,t)=>{var a;let n=A(e,t);if(-1===t.indexOf("")){let t="";const r=null!==(a=f(e))&&void 0!==a?a:"",s=y(e)?' crossorigin="anonymous"':"";o.each(e.contentCSS,(a=>{t+='"})),r&&(t+='");const l=b(e),c=e.dom.encode,i=' diff --git a/src/components/TinymceEditor/index.vue b/src/components/TinymceEditor/index.vue index 7c580f8..9a9c7cf 100644 --- a/src/components/TinymceEditor/index.vue +++ b/src/components/TinymceEditor/index.vue @@ -14,41 +14,11 @@ nextTick, useAttrs } from 'vue'; - import tinymce from 'tinymce/tinymce'; - import type { - Editor as TinyMCEEditor, - EditorEvent, - RawEditorSettings - } from 'tinymce'; - import 'tinymce/themes/silver'; - import 'tinymce/icons/default'; - import 'tinymce/plugins/code'; - import 'tinymce/plugins/preview'; - import 'tinymce/plugins/fullscreen'; - import 'tinymce/plugins/paste'; - import 'tinymce/plugins/searchreplace'; - import 'tinymce/plugins/save'; - import 'tinymce/plugins/autosave'; - import 'tinymce/plugins/link'; - import 'tinymce/plugins/autolink'; - import 'tinymce/plugins/image'; - import 'tinymce/plugins/media'; - import 'tinymce/plugins/table'; - import 'tinymce/plugins/codesample'; - import 'tinymce/plugins/lists'; - import 'tinymce/plugins/advlist'; - import 'tinymce/plugins/hr'; - import 'tinymce/plugins/charmap'; - import 'tinymce/plugins/emoticons'; - import 'tinymce/plugins/anchor'; - import 'tinymce/plugins/directionality'; - import 'tinymce/plugins/pagebreak'; - import 'tinymce/plugins/quickbars'; - import 'tinymce/plugins/nonbreaking'; - import 'tinymce/plugins/visualblocks'; - import 'tinymce/plugins/visualchars'; - import 'tinymce/plugins/wordcount'; - import 'tinymce/plugins/emoticons/js/emojis'; + // TinyMCE 类型定义(避免引入实际模块) + type TinyMCEEditor = any; + type EditorEvent = any; + type RawEditorSettings = any; + import { storeToRefs } from 'pinia'; import { useThemeStore } from '@/store/modules/theme'; import { @@ -60,6 +30,7 @@ setupTextIndent } from './util'; import type { AlertOption } from './util'; + import { message } from 'ant-design-vue'; const props = withDefaults( defineProps<{ @@ -81,12 +52,15 @@ autoTheme?: boolean; // 不跟随框架主题时是否使用暗黑主题 darkTheme?: boolean; + // 编辑器是否可见(用于控制初始化时机) + editorVisible?: boolean; }>(), { inline: false, modelEvents: 'change input undo redo', tagName: 'div', - autoTheme: true + autoTheme: true, + editorVisible: true } ); @@ -98,6 +72,9 @@ const themeStore = useThemeStore(); const { darkMode } = storeToRefs(themeStore); + // TinyMCE 通过 CDN 加载在 window.tinymce + const getTinymce = () => (window as any).tinymce; + // 编辑器唯一 id const elementId: string = props.id || uuid('tiny-vue'); @@ -125,41 +102,112 @@ /* 渲染编辑器 */ const render = () => { + // 如果 prop 标记为不可见,跳过初始化 + if (!props.editorVisible) { + console.log('[TinymceEditor] Skipping render - editorVisible is false'); + return; + } + const isDark = props.autoTheme ? darkMode.value : props.darkTheme; - tinymce.init({ - ...DEFAULT_CONFIG, - ...(isDark ? DARK_CONFIG : {}), - ...props.init, - selector: `#${elementId}`, - readonly: props.disabled, - inline: inlineEditor, - setup: (editor: TinyMCEEditor) => { - editorIns = editor; - - // 添加首行缩进功能 - setupTextIndent(editor); - - editor.on('init', (e: EditorEvent) => { - // 回显初始值 - if (props.value) { - setContent(props.value); - } - // v-model - editor.on(props.modelEvents, () => { - updateValue(editor.getContent()); - }); - // valid events - bindHandlers(e, attrs, editor); - }); - if (typeof props.init?.setup === 'function') { - props.init.setup(editor); + const tinymce = getTinymce(); + if (!tinymce) { + console.warn('[TinymceEditor] window.tinymce is not available, retrying in 100ms...'); + setTimeout(render, 100); + return; + } + // 如果容器元素不可见(如在隐藏的 tab 里),等待其可见后再初始化 + const el = document.getElementById(elementId); + if (!el) { + // DOM 还未就绪,稍后重试 + setTimeout(render, 50); + return; + } + { + // 检测元素是否真正可见:display:none 或 visibility:hidden 或 height:0 都算隐藏 + const isVisible = (() => { + let node: Element | null = el; + while (node && node !== document.body) { + const style = window.getComputedStyle(node); + if (style.display === 'none' || style.visibility === 'hidden') return false; + if (style.height === '0px' && (style.overflow === 'hidden' || style.overflowY === 'hidden')) return false; + node = node.parentElement; } + return true; + })(); + if (!isVisible) { + const checkVisible = () => { + const el2 = document.getElementById(elementId); + if (!el2) return false; + let node: Element | null = el2; + while (node && node !== document.body) { + const s = window.getComputedStyle(node); + if (s.display === 'none' || s.visibility === 'hidden') return false; + if (s.height === '0px' && (s.overflow === 'hidden' || s.overflowY === 'hidden')) return false; + node = node.parentElement; + } + return true; + }; + const observer = new MutationObserver(() => { + if (checkVisible()) { + observer.disconnect(); + render(); + } + }); + // 监听父级 DOM 变化(tab 切换时会修改 style) + let parent: Element | null = el.parentElement; + while (parent) { + observer.observe(parent, { attributes: true, attributeFilter: ['style', 'class'] }); + parent = parent.parentElement; + if (parent === document.body) break; + } + return; } - }); + } + // 如果已经初始化过,不重复初始化 + if (editorIns) return; + try { + tinymce.init({ + ...DEFAULT_CONFIG, + ...(isDark ? DARK_CONFIG : {}), + ...(props.init || {}), + selector: `#${elementId}`, + readonly: props.disabled, + inline: inlineEditor, + setup: (editor: TinyMCEEditor) => { + editorIns = editor; + + // 添加首行缩进功能 + setupTextIndent(editor); + + editor.on('init', (e: EditorEvent) => { + // 回显初始值 + if (props.value) { + setContent(props.value); + } + // v-model + editor.on(props.modelEvents, () => { + updateValue(editor.getContent()); + }); + // valid events + bindHandlers(e, attrs, editor); + }); + if (typeof props.init?.setup === 'function') { + props.init.setup(editor); + } + } + }).catch((err: any) => { + console.error('[TinymceEditor] init failed:', err); + message.error('编辑器初始化失败: ' + (err?.message || err)); + }); + } catch (err: any) { + console.error('[TinymceEditor] init threw error:', err); + message.error('编辑器初始化异常: ' + (err?.message || err)); + } }; /* 销毁编辑器 */ const destory = () => { + const tinymce = getTinymce(); if (tinymce != null && editorIns != null) { tinymce.remove(editorIns as any); editorIns = null; @@ -214,6 +262,19 @@ } }); + // 监听 editorVisible 变化,当变为可见时触发渲染 + watch( + () => props.editorVisible, + (visible) => { + if (visible) { + console.log('[TinymceEditor] editorVisible changed to true, triggering render'); + nextTick(() => { + render(); + }); + } + } + ); + onMounted(() => { render(); }); diff --git a/src/components/TinymceEditor/util.ts b/src/components/TinymceEditor/util.ts index 617c6f2..6997f11 100644 --- a/src/components/TinymceEditor/util.ts +++ b/src/components/TinymceEditor/util.ts @@ -1,8 +1,8 @@ -import type { - Editor as TinyMCEEditor, - EditorEvent, - RawEditorSettings -} from 'tinymce'; +// TinyMCE 类型定义(避免引入实际模块) +type TinyMCEEditor = any; +type EditorEvent = any; +type RawEditorSettings = any; + const BASE_URL = import.meta.env.BASE_URL; // 默认加载插件 @@ -10,7 +10,7 @@ const PLUGINS: string = [ 'code', 'preview', 'fullscreen', - 'paste', + // paste 已内置,无需配置 'searchreplace', 'save', 'autosave', @@ -22,7 +22,7 @@ const PLUGINS: string = [ 'codesample', 'lists', 'advlist', - 'hr', + // hr 已内置,无需配置 'charmap', 'emoticons', 'anchor', diff --git a/src/config/menu.ts b/src/config/menu.ts index 2f0c0c0..3b5b745 100644 --- a/src/config/menu.ts +++ b/src/config/menu.ts @@ -43,6 +43,11 @@ export default [ path: '/shop/shopMerchantApply', component: '/shop/shopMerchantApply', meta: { title: '商户入驻申请', icon: 'UserAddOutlined' } + }, + { + path: '/shop/shopEvent', + component: '/shop/shopEvent', + meta: { title: '赛事管理', icon: 'TrophyOutlined' } } ] }, diff --git a/src/config/setting.ts b/src/config/setting.ts index 0b11d7c..516e620 100644 --- a/src/config/setting.ts +++ b/src/config/setting.ts @@ -1,5 +1,5 @@ // 租户ID -export const TENANT_ID = import.meta.env.VITE_TENANT_ID || 10258; +export const TENANT_ID = import.meta.env.VITE_TENANT_ID || 10606; // 模板ID export const TEMPLATE_ID = import.meta.env.VITE_TEMPLATE_ID || 10258; // appSecret diff --git a/src/layout/components/header-tools.vue b/src/layout/components/header-tools.vue index 9cc1c07..f1e7ed6 100644 --- a/src/layout/components/header-tools.vue +++ b/src/layout/components/header-tools.vue @@ -218,7 +218,7 @@ } else if (key === 'skin') { settingVisible.value = true; } else if (key === 'system') { - push('/system/setting'); + push('/system/menu'); } else if (key === 'logout') { // 退出登录 Modal.confirm({ diff --git a/src/lib/port-manager.ts b/src/lib/port-manager.ts index 1b8490d..c1cb930 100644 --- a/src/lib/port-manager.ts +++ b/src/lib/port-manager.ts @@ -196,7 +196,7 @@ export class PortManager { preferredPort?: number; }): Promise { const tenantId = options?.tenantId || (await getTenantId()); - const projectName = options?.projectName || 'mp-vue'; + const projectName = options?.projectName || 'paopao-vue'; const portKey = PortUtils.generatePortKey( tenantId, this.environment, diff --git a/src/lib/tenant-port-manager.ts b/src/lib/tenant-port-manager.ts index 4503cc2..844a20e 100644 --- a/src/lib/tenant-port-manager.ts +++ b/src/lib/tenant-port-manager.ts @@ -149,7 +149,7 @@ export class TenantPortManager { // 3. 分配新端口 const portConfig = await this.portManager.getRecommendedPort({ tenantId, - projectName: tenantInfo?.name || 'mp-vue', + projectName: tenantInfo?.name || 'paopao-vue', preferredPort: options?.preferredPort }); @@ -223,7 +223,7 @@ export class TenantPortManager { createdAt: Date.now(), lastUsed: Date.now(), metadata: { - projectName: portConfig.projectName || 'mp-vue', + projectName: portConfig.projectName || 'paopao-vue', version: '1.0.0', description: `${tenantInfo?.name || '租户'} - ${ this.currentEnvironment diff --git a/src/store/modules/chat.ts b/src/store/modules/chat.ts index 666320a..439cdad 100644 --- a/src/store/modules/chat.ts +++ b/src/store/modules/chat.ts @@ -21,8 +21,7 @@ export interface ChatState { conversations: ChatConversation[]; } -export const useChatStore = defineStore({ - id: 'chat', +export const useChatStore = defineStore('chat', { state: (): ChatState => ({ socket: undefined, conversations: [] diff --git a/src/store/modules/financeDashboard.ts b/src/store/modules/financeDashboard.ts new file mode 100644 index 0000000..fefd520 --- /dev/null +++ b/src/store/modules/financeDashboard.ts @@ -0,0 +1,329 @@ +/** + * 财务看板 Store + */ +import { defineStore } from 'pinia'; +import { + getFinanceDashboardSummary, + getCommissionDetail, + getProfitShareDetail, + getFinanceTrendData, + getTodayFinanceData, + getYesterdayFinanceData +} from '@/api/finance/dashboard'; +import type { + FinanceDashboardData, + FinanceDashboardParam, + CommissionItem, + ProfitShareItem, + FinanceTrendData +} from '@/api/finance/dashboard/model'; + +export type TimeTabType = 'today' | 'yesterday' | 'custom'; + +export interface FinanceDashboardState { + // 当前选中的时间维度 + activeTab: TimeTabType; + // 自定义日期 + customDate: string | null; + // 财务数据 + dashboardData: FinanceDashboardData | null; + // 佣金明细列表 + commissionList: CommissionItem[]; + // 分润明细列表 + profitShareList: ProfitShareItem[]; + // 趋势数据 + trendData: FinanceTrendData | null; + // 加载状态 + loading: boolean; + // 明细加载状态 + detailLoading: boolean; + // 趋势加载状态 + trendLoading: boolean; + // 最后更新时间 + lastUpdateTime: number | null; + // 自动刷新定时器 + refreshTimer: number | null; +} + +export const useFinanceDashboardStore = defineStore('financeDashboard', { + state: (): FinanceDashboardState => ({ + activeTab: 'today', + customDate: null, + dashboardData: null, + commissionList: [], + profitShareList: [], + trendData: null, + loading: false, + detailLoading: false, + trendLoading: false, + lastUpdateTime: null, + refreshTimer: null + }), + + getters: { + /** + * 获取产生佣金 + */ + commissionAmount: (state): number => { + return state.dashboardData?.commissionAmount || 0; + }, + + /** + * 获取分润金额 + */ + profitShareAmount: (state): number => { + return state.dashboardData?.profitShareAmount || 0; + }, + + /** + * 获取佣金趋势 + */ + commissionTrend: (state): number => { + return state.dashboardData?.commissionTrend || 0; + }, + + /** + * 获取分润趋势 + */ + profitShareTrend: (state): number => { + return state.dashboardData?.profitShareTrend || 0; + }, + + /** + * 获取订单数量 + */ + orderCount: (state): number => { + return state.dashboardData?.orderCount || 0; + }, + + /** + * 获取交易用户数 + */ + userCount: (state): number => { + return state.dashboardData?.userCount || 0; + }, + + /** + * 获取对比文本 + */ + compareText: (state): string => { + switch (state.activeTab) { + case 'today': + return '昨日'; + case 'yesterday': + return '前日'; + case 'custom': + return '上期'; + default: + return '上期'; + } + }, + + /** + * 获取当前日期文本 + */ + currentDateText: (state): string => { + const today = new Date().toISOString().split('T')[0]; + switch (state.activeTab) { + case 'today': + return '今日'; + case 'yesterday': + return '昨日'; + case 'custom': + return state.customDate || today; + default: + return '今日'; + } + }, + + /** + * 获取查询日期 + */ + queryDate: (state): string => { + const today = new Date().toISOString().split('T')[0]; + switch (state.activeTab) { + case 'today': + return today; + case 'yesterday': + return new Date(Date.now() - 86400000).toISOString().split('T')[0]; + case 'custom': + return state.customDate || today; + default: + return today; + } + } + }, + + actions: { + /** + * 获取财务看板数据 + */ + async fetchDashboardData() { + this.loading = true; + try { + let data: FinanceDashboardData; + + switch (this.activeTab) { + case 'today': + data = await getTodayFinanceData(); + break; + case 'yesterday': + data = await getYesterdayFinanceData(); + break; + case 'custom': + default: + const params: FinanceDashboardParam = { + date: this.queryDate, + type: this.activeTab + }; + data = await getFinanceDashboardSummary(params); + break; + } + + this.dashboardData = data; + this.lastUpdateTime = Date.now(); + return data; + } catch (error) { + console.error('获取财务看板数据失败:', error); + throw error; + } finally { + this.loading = false; + } + }, + + /** + * 获取佣金明细 + */ + async fetchCommissionDetail(page = 1, limit = 10) { + this.detailLoading = true; + try { + const params: FinanceDashboardParam = { + date: this.queryDate, + type: this.activeTab, + page, + limit + }; + const result = await getCommissionDetail(params); + this.commissionList = result.list || []; + return result; + } catch (error) { + console.error('获取佣金明细失败:', error); + throw error; + } finally { + this.detailLoading = false; + } + }, + + /** + * 获取分润明细 + */ + async fetchProfitShareDetail(page = 1, limit = 10) { + this.detailLoading = true; + try { + const params: FinanceDashboardParam = { + date: this.queryDate, + type: this.activeTab, + page, + limit + }; + const result = await getProfitShareDetail(params); + this.profitShareList = result.list || []; + return result; + } catch (error) { + console.error('获取分润明细失败:', error); + throw error; + } finally { + this.detailLoading = false; + } + }, + + /** + * 获取趋势数据 + */ + async fetchTrendData(days = 7) { + this.trendLoading = true; + try { + const endDate = this.queryDate; + const startDate = new Date(Date.now() - days * 86400000) + .toISOString() + .split('T')[0]; + + const params: FinanceDashboardParam = { + startDate, + endDate, + type: this.activeTab + }; + const data = await getFinanceTrendData(params); + this.trendData = data; + return data; + } catch (error) { + console.error('获取趋势数据失败:', error); + throw error; + } finally { + this.trendLoading = false; + } + }, + + /** + * 切换时间维度 + */ + async switchTab(tab: TimeTabType) { + this.activeTab = tab; + await this.fetchDashboardData(); + }, + + /** + * 设置自定义日期 + */ + async setCustomDate(date: string) { + this.customDate = date; + this.activeTab = 'custom'; + await this.fetchDashboardData(); + }, + + /** + * 刷新所有数据 + */ + async refreshAllData() { + await Promise.all([ + this.fetchDashboardData(), + this.fetchTrendData() + ]); + }, + + /** + * 开始自动刷新 + * @param interval 刷新间隔(毫秒),默认5分钟 + */ + startAutoRefresh(interval = 5 * 60 * 1000) { + this.stopAutoRefresh(); + this.refreshTimer = window.setInterval(() => { + this.fetchDashboardData().catch(console.error); + }, interval); + }, + + /** + * 停止自动刷新 + */ + stopAutoRefresh() { + if (this.refreshTimer) { + clearInterval(this.refreshTimer); + this.refreshTimer = null; + } + }, + + /** + * 重置状态 + */ + reset() { + this.activeTab = 'today'; + this.customDate = null; + this.dashboardData = null; + this.commissionList = []; + this.profitShareList = []; + this.trendData = null; + this.lastUpdateTime = null; + this.stopAutoRefresh(); + } + } +}); diff --git a/src/store/modules/params.ts b/src/store/modules/params.ts index 1e20ad1..3397bd8 100644 --- a/src/store/modules/params.ts +++ b/src/store/modules/params.ts @@ -10,8 +10,7 @@ export interface ParamsState { redirect: string | null | undefined; } -export const useParamsStore = defineStore({ - id: 'params', +export const useParamsStore = defineStore('params', { state: (): ParamsState => ({ // 标题 title: '操作成功', diff --git a/src/store/modules/setting.ts b/src/store/modules/setting.ts index 9592507..d4d8449 100644 --- a/src/store/modules/setting.ts +++ b/src/store/modules/setting.ts @@ -7,7 +7,7 @@ export interface ParamsState { setting: any | null; } -export const useWebsiteSettingStore = defineStore({ +export const useWebsiteSettingStore = defineStore('setting', { id: 'setting', state: (): ParamsState => ({ // 初始化时确保所有字段都已赋值 diff --git a/src/store/modules/site.ts b/src/store/modules/site.ts index a9bd794..e68755d 100644 --- a/src/store/modules/site.ts +++ b/src/store/modules/site.ts @@ -16,8 +16,7 @@ export interface SiteState { cacheExpiry: number; } -export const useSiteStore = defineStore({ - id: 'site', +export const useSiteStore = defineStore('site', { state: (): SiteState => ({ siteInfo: null, loading: false, diff --git a/src/store/modules/statistics.ts b/src/store/modules/statistics.ts index c0aff55..c5fcab6 100644 --- a/src/store/modules/statistics.ts +++ b/src/store/modules/statistics.ts @@ -25,8 +25,7 @@ export interface StatisticsState { refreshTimer: number | null; } -export const useStatisticsStore = defineStore({ - id: 'statistics', +export const useStatisticsStore = defineStore('statistics', { state: (): StatisticsState => ({ statistics: null, loading: false, diff --git a/src/store/modules/template.ts b/src/store/modules/template.ts index fb9b771..840b044 100644 --- a/src/store/modules/template.ts +++ b/src/store/modules/template.ts @@ -11,7 +11,7 @@ export interface ConfigState { error: Error | null; } -export const useConfigStore = defineStore({ +export const useConfigStore = defineStore('template', { id: 'config', state: (): ConfigState => ({ // 网站配置数据 diff --git a/src/store/modules/tenant.ts b/src/store/modules/tenant.ts index bf19ca2..8621fd6 100644 --- a/src/store/modules/tenant.ts +++ b/src/store/modules/tenant.ts @@ -15,8 +15,7 @@ export interface UserState { menus: MenuItem[] | null | undefined; } -export const useTenantStore = defineStore({ - id: 'tenant', +export const useTenantStore = defineStore('tenant', { state: (): UserState => ({ // 租户信息 tenant: null, diff --git a/src/store/modules/theme.ts b/src/store/modules/theme.ts index 87e7b7b..6469444 100644 --- a/src/store/modules/theme.ts +++ b/src/store/modules/theme.ts @@ -47,7 +47,7 @@ const DEFAULT_STATE: ThemeState = Object.freeze({ // 顶栏风格: light(亮色), dark(暗色), primary(主色) headStyle: 'light', // 侧栏风格: light(亮色), dark(暗色) - sideStyle: 'light', + sideStyle: 'dark', // 布局风格: side(默认), top(顶栏导航), mix(混合导航) layoutStyle: 'mix', // 侧栏菜单风格: default(默认), mix(双排侧栏) @@ -65,7 +65,7 @@ const DEFAULT_STATE: ThemeState = Object.freeze({ // 内容区域宽度铺满 bodyFull: true, // logo 是否自适应宽度 - logoAutoSize: true, + logoAutoSize: false, // 侧栏是否彩色图标 colorfulIcon: false, // 侧栏是否只保持一个子菜单展开 @@ -177,8 +177,7 @@ function disableTransition() { }, 100) as unknown as number; } -export const useThemeStore = defineStore({ - id: 'theme', +export const useThemeStore = defineStore('theme', { state: (): ThemeState => { const state = { ...DEFAULT_STATE }; // 读取本地缓存 diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index a78dbbe..18055c7 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -21,8 +21,7 @@ export interface UserState { roles: (string | undefined)[]; } -export const useUserStore = defineStore({ - id: 'user', +export const useUserStore = defineStore('user', { state: (): UserState => ({ // 当前登录用户的信息 info: null, diff --git a/src/styles/antd-less-stub/dark.less b/src/styles/antd-less-stub/dark.less new file mode 100644 index 0000000..06678f0 --- /dev/null +++ b/src/styles/antd-less-stub/dark.less @@ -0,0 +1,40 @@ +/** + * Ant Design Vue 4 Dark 主题 Less 变量兼容层 + * AntDv4 移除了 Less 主题文件,此文件提供 ele-admin-pro 所需的暗色模式变量。 + */ + +@import './default.less'; + +// ========== 暗色覆盖 ========== +@primary-color: #1668dc; +@component-background: #141414; +@background-color-light: #1d1d1d; +@background-color-base: #1f1f1f; +@heading-color: rgba(255, 255, 255, 0.88); +@text-color: rgba(255, 255, 255, 0.85); +@text-color-secondary: rgba(255, 255, 255, 0.65); +@border-color-base: #424242; +@border-color-split: #303030; +@item-active-bg: #111b26; +@item-hover-bg: #262626; + +// ========== Menu Dark ========== +@menu-bg: #1f1f1f; +@menu-item-color: rgba(255, 255, 255, 0.65); +@menu-highlight-color: @primary-color; +@menu-dark-item-selected-bg: @primary-color; +@menu-dark-item-color: rgba(255, 255, 255, 0.65); +@menu-dark-selected-item-text-color: #fff; + +// ========== Layout Dark ========== +@layout-body-background: #000000; +@layout-header-background: #1f1f1f; +@layout-sider-background: #1f1f1f; + +// ========== Modal Dark ========== +@modal-content-bg: #1f1f1f; +@modal-heading-color: rgba(255, 255, 255, 0.88); +@modal-close-color: rgba(255, 255, 255, 0.45); + +// ========== 其他 ========== +@disabled-color: rgba(255, 255, 255, 0.25); diff --git a/src/styles/antd-less-stub/default.less b/src/styles/antd-less-stub/default.less new file mode 100644 index 0000000..46f5b53 --- /dev/null +++ b/src/styles/antd-less-stub/default.less @@ -0,0 +1,915 @@ +/** + * Ant Design Vue 4 Less 变量兼容层 + * 基于 AntDv3 default.less 的完整变量存根 + * 所有 colorPalette() 调用已替换为 AntDv4 实际色值 + */ + +// ========== 主题配置 ========== +@theme: default; +@ant-prefix: ant; +@html-selector: html; + +// ========== 颜色 - 主色系 ========== +@primary-color: #1677ff; +@primary-1: #e6f4ff; +@primary-2: #bae0ff; +@primary-3: #91caff; +@primary-4: #69b1ff; +@primary-5: #4096ff; +@primary-6: @primary-color; +@primary-7: #0958d9; +@primary-8: #003eb3; +@primary-9: #002c8c; +@primary-10: #001d66; +@primary-color-hover: #4096ff; +@primary-color-active: #0958d9; +@primary-color-outline: rgba(22, 119, 255, 0.2); + +// ========== 颜色 - 信息 ========== +@info-color: @primary-color; +@info-color-deprecated-bg: #e6f4ff; +@info-color-deprecated-border: #91caff; + +// ========== 颜色 - 成功 ========== +@success-color: #52c41a; +@success-color-hover: #73d13d; +@success-color-active: #49aa19; +@success-color-outline: rgba(82, 196, 26, 0.2); +@success-color-deprecated-bg: #f6ffed; +@success-color-deprecated-border: #b7eb8f; + +// ========== 颜色 - 警告 ========== +@warning-color: #faad14; +@warning-color-hover: #ffc53d; +@warning-color-active: #d48806; +@warning-color-outline: rgba(250, 173, 20, 0.2); +@warning-color-deprecated-bg: #fffbe6; +@warning-color-deprecated-border: #ffe58f; + +// ========== 颜色 - 错误 ========== +@error-color: #ff4d4f; +@error-color-hover: #ff7875; +@error-color-active: #d9363e; +@error-color-outline: rgba(255, 77, 79, 0.2); +@error-color-deprecated-bg: #fff2f0; +@error-color-deprecated-border: #ffccc7; + +// ========== 颜色 - 其他 ========== +@highlight-color: @error-color; +@normal-color: #d9d9d9; +@white: #fff; +@black: #000; + +// ========== 基础样式 ========== +@body-background: #f0f2f5; +@component-background: #ffffff; +@popover-background: @component-background; +@font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; +@code-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; +@text-color: rgba(0, 0, 0, 0.88); +@text-color-secondary: rgba(0, 0, 0, 0.65); +@text-color-dark: rgba(255, 255, 255, 0.65); +@text-color-inverse: @white; +@icon-color: inherit; +@icon-color-hover: #4096ff; +@heading-color: rgba(0, 0, 0, 0.88); +@text-selection-bg: @primary-color; +@font-variant-base: tabular-nums; +@font-feature-settings-base: 'tnum'; +@font-size-base: 14px; +@font-size-lg: 16px; +@font-size-sm: 12px; +@heading-1-size: 38px; +@heading-2-size: 30px; +@heading-3-size: 24px; +@heading-4-size: 20px; +@heading-5-size: 16px; +@line-height-base: 1.5715; +@border-radius-base: 6px; +@border-radius-sm: 4px; +@padding-lg: 24px; +@padding-md: 16px; +@padding-sm: 12px; +@padding-xs: 8px; +@padding-xss: 4px; +@control-padding-horizontal: @padding-sm; +@control-padding-horizontal-sm: @padding-xs; +@margin-lg: 24px; +@margin-md: 16px; +@margin-sm: 12px; +@margin-xs: 8px; +@margin-xss: 4px; +@height-base: 32px; +@height-lg: 40px; +@height-sm: 24px; +@item-active-bg: #e6f4ff; +@item-hover-bg: #f5f5f5; + +// ========== 图标 ========== +@iconfont-css-prefix: anticon; + +// ========== 链接 ========== +@link-color: @primary-color; +@link-hover-color: #4096ff; +@link-active-color: #0958d9; +@link-decoration: none; +@link-hover-decoration: none; +@link-focus-decoration: none; +@link-focus-outline: 0; + +// ========== 动画/缓动 ========== +@ease-base-out: cubic-bezier(0.7, 0.3, 0.1, 1); +@ease-base-in: cubic-bezier(0.9, 0, 0.3, 0.7); +@ease-out: cubic-bezier(0.215, 0.61, 0.355, 1); +@ease-in: cubic-bezier(0.55, 0.055, 0.675, 0.19); +@ease-in-out: cubic-bezier(0.645, 0.045, 0.355, 1); +@ease-out-back: cubic-bezier(0.12, 0.4, 0.29, 1.46); +@ease-in-back: cubic-bezier(0.71, -0.46, 0.88, 0.6); +@ease-in-out-back: cubic-bezier(0.71, -0.46, 0.29, 1.46); +@ease-out-circ: cubic-bezier(0.08, 0.82, 0.17, 1); +@ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.34); +@ease-in-out-circ: cubic-bezier(0.78, 0.14, 0.15, 0.86); +@ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1); +@ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06); +@ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1); +@duration-base: 0.3s; + +// ========== 边框 ========== +@border-color-base: #d9d9d9; +@border-color-split: #f0f0f0; +@border-color-inverse: @white; +@border-width-base: 1px; +@border-style-base: solid; + +// ========== Outline ========== +@outline-blur-size: 0; +@outline-width: 2px; +@outline-color: @primary-color; +@outline-fade: 20%; + +// ========== 背景 ========== +@background-color-light: #fafafa; +@background-color-base: #f5f5f5; + +// ========== 禁用状态 ========== +@disabled-color: rgba(0, 0, 0, 0.25); +@disabled-bg: #f5f5f5; +@disabled-active-bg: #f5f5f5; + +// ========== 阴影 ========== +@shadow-color: rgba(0, 0, 0, 0.15); +@shadow-color-inverse: @component-background; +@box-shadow-base: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); +@shadow-1-up: 0 -6px 16px -8px rgba(0, 0, 0, 0.08), 0 -9px 28px 0 rgba(0, 0, 0, 0.05), 0 -12px 48px 16px rgba(0, 0, 0, 0.03); +@shadow-1-down: 0 6px 16px -8px rgba(0, 0, 0, 0.08), 0 9px 28px 0 rgba(0, 0, 0, 0.05), 0 12px 48px 16px rgba(0, 0, 0, 0.03); +@shadow-1-left: -6px 0 16px -8px rgba(0, 0, 0, 0.08), -9px 0 28px 0 rgba(0, 0, 0, 0.05), -12px 0 48px 16px rgba(0, 0, 0, 0.03); +@shadow-1-right: 6px 0 16px -8px rgba(0, 0, 0, 0.08), 9px 0 28px 0 rgba(0, 0, 0, 0.05), 12px 0 48px 16px rgba(0, 0, 0, 0.03); +@shadow-2: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); + +// ========== 按钮 ========== +@btn-font-weight: 400; +@btn-border-radius-base: @border-radius-base; +@btn-border-radius-sm: @border-radius-base; +@btn-border-width: @border-width-base; +@btn-border-style: @border-style-base; +@btn-shadow: 0 2px 0 rgba(0, 0, 0, 0.015); +@btn-primary-shadow: 0 2px 0 rgba(0, 0, 0, 0.045); +@btn-text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12); +@btn-primary-color: #fff; +@btn-primary-bg: @primary-color; +@btn-default-color: @text-color; +@btn-default-bg: @component-background; +@btn-default-border: @border-color-base; +@btn-danger-color: #fff; +@btn-danger-bg: @error-color; +@btn-danger-border: @error-color; +@btn-disable-color: @disabled-color; +@btn-disable-bg: @disabled-bg; +@btn-disable-border: @border-color-base; +@btn-default-ghost-color: @component-background; +@btn-default-ghost-bg: transparent; +@btn-default-ghost-border: @component-background; +@btn-font-size-lg: @font-size-lg; +@btn-font-size-sm: @font-size-base; +@btn-padding-horizontal-base: 15px; +@btn-padding-horizontal-lg: @btn-padding-horizontal-base; +@btn-padding-horizontal-sm: 7px; +@btn-height-base: @height-base; +@btn-height-lg: @height-lg; +@btn-height-sm: @height-sm; +@btn-line-height: @line-height-base; +@btn-circle-size: @btn-height-base; +@btn-circle-size-lg: @btn-height-lg; +@btn-circle-size-sm: @btn-height-sm; +@btn-square-size: @btn-height-base; +@btn-square-size-lg: @btn-height-lg; +@btn-square-size-sm: @btn-height-sm; +@btn-square-only-icon-size: 16px; +@btn-square-only-icon-size-sm: @font-size-base; +@btn-square-only-icon-size-lg: 18px; +@btn-group-border: #4096ff; +@btn-link-hover-bg: transparent; +@btn-text-hover-bg: rgba(0, 0, 0, 0.018); + +// ========== 多选框 ========== +@checkbox-size: 16px; +@checkbox-color: @primary-color; +@checkbox-check-color: #fff; +@checkbox-check-bg: @checkbox-check-color; +@checkbox-border-width: @border-width-base; +@checkbox-border-radius: @border-radius-base; +@checkbox-group-item-margin-right: 8px; + +// ========== 描述列表 ========== +@descriptions-bg: #fafafa; +@descriptions-title-margin-bottom: 20px; +@descriptions-default-padding: @padding-md @padding-lg; +@descriptions-middle-padding: @padding-sm @padding-lg; +@descriptions-small-padding: @padding-xs @padding-md; +@descriptions-item-padding-bottom: @padding-md; +@descriptions-item-trailing-colon: true; +@descriptions-item-label-colon-margin-right: 8px; +@descriptions-item-label-colon-margin-left: 2px; +@descriptions-extra-color: @text-color; + +// ========== 分割线 ========== +@divider-text-padding: 1em; +@divider-orientation-margin: 5%; +@divider-color: rgba(0, 0, 0, 0.06); +@divider-vertical-gutter: 8px; + +// ========== 下拉菜单 ========== +@dropdown-selected-color: @primary-color; +@dropdown-menu-submenu-disabled-bg: @component-background; +@dropdown-selected-bg: @item-active-bg; +@dropdown-menu-bg: @component-background; +@dropdown-vertical-padding: 5px; +@dropdown-edge-child-vertical-padding: 4px; +@dropdown-font-size: @font-size-base; +@dropdown-line-height: 22px; + +// ========== 空白 ========== +@empty-font-size: @font-size-base; + +// ========== 单选框 ========== +@radio-size: 16px; +@radio-top: 0.2em; +@radio-border-width: 1px; +@radio-dot-size: 8px; +@radio-dot-color: @primary-color; +@radio-dot-disabled-color: rgba(0, 0, 0, 0.2); +@radio-solid-checked-color: @component-background; +@radio-button-bg: @btn-default-bg; +@radio-button-checked-bg: @btn-default-bg; +@radio-button-color: @btn-default-color; +@radio-button-hover-color: #4096ff; +@radio-button-active-color: #0958d9; +@radio-button-padding-horizontal: 15px; +@radio-disabled-button-checked-bg: @disabled-active-bg; +@radio-disabled-button-checked-color: @disabled-color; +@radio-wrapper-margin-right: 8px; + +// ========== 媒体查询/断点 ========== +@screen-xs: 480px; +@screen-xs-min: @screen-xs; +@screen-xs-max: (@screen-sm-min - 1px); +@screen-sm: 576px; +@screen-sm-min: @screen-sm; +@screen-sm-max: (@screen-md-min - 1px); +@screen-md: 768px; +@screen-md-min: @screen-md; +@screen-md-max: (@screen-lg-min - 1px); +@screen-lg: 992px; +@screen-lg-min: @screen-lg; +@screen-lg-max: (@screen-xl-min - 1px); +@screen-xl: 1200px; +@screen-xl-min: @screen-xl; +@screen-xl-max: (@screen-xxl-min - 1px); +@screen-xxl: 1600px; +@screen-xxl-min: @screen-xxl; +@screen-xxl-max: 1919px; +@screen-xxxl: 2000px; +@screen-xxxl-min: @screen-xxxl; + +// ========== 网格 ========== +@grid-columns: 24; + +// ========== 布局 ========== +@layout-body-background: #f0f2f5; +@layout-header-background: #001529; +@layout-header-height: 64px; +@layout-header-padding: 0 50px; +@layout-header-color: @text-color; +@layout-footer-padding: 24px 50px; +@layout-footer-background: @layout-body-background; +@layout-sider-background: @layout-header-background; +@layout-trigger-height: 48px; +@layout-trigger-background: #002140; +@layout-trigger-color: #fff; +@layout-zero-trigger-width: 36px; +@layout-zero-trigger-height: 42px; +@layout-sider-background-light: #fff; +@layout-trigger-background-light: #fff; +@layout-trigger-color-light: @text-color; + +// ========== z-index ========== +@zindex-badge: auto; +@zindex-table-fixed: 2; +@zindex-affix: 10; +@zindex-back-top: 10; +@zindex-picker-panel: 10; +@zindex-popup-close: 10; +@zindex-modal: 1000; +@zindex-modal-mask: 1000; +@zindex-message: 1010; +@zindex-notification: 1010; +@zindex-popover: 1030; +@zindex-dropdown: 1050; +@zindex-picker: 1050; +@zindex-popconfirm: 1060; +@zindex-tooltip: 1070; +@zindex-image: 1080; + +// ========== 动画持续时间 ========== +@animation-duration-slow: 0.3s; +@animation-duration-base: 0.2s; +@animation-duration-fast: 0.1s; + +// ========== 折叠面板 ========== +@collapse-panel-border-radius: @border-radius-base; + +// ========== 表单 ========== +@label-required-color: @highlight-color; +@label-color: @heading-color; +@form-warning-input-bg: @input-bg; +@form-item-margin-bottom: 24px; +@form-item-trailing-colon: true; +@form-vertical-label-padding: 0 0 8px; +@form-vertical-label-margin: 0; +@form-item-label-font-size: @font-size-base; +@form-item-label-height: @input-height-base; +@form-item-label-colon-margin-right: 8px; +@form-item-label-colon-margin-left: 2px; +@form-error-input-bg: @input-bg; + +// ========== 输入框 ========== +@input-height-base: @height-base; +@input-height-lg: @height-lg; +@input-height-sm: @height-sm; +@input-padding-horizontal: 11px; +@input-padding-horizontal-base: @input-padding-horizontal; +@input-padding-horizontal-sm: 7px; +@input-padding-horizontal-lg: @input-padding-horizontal; +@input-padding-vertical-base: 4px; +@input-padding-vertical-sm: 0px; +@input-padding-vertical-lg: 8px; +@input-placeholder-color: rgba(0, 0, 0, 0.25); +@input-color: @text-color; +@input-icon-color: @input-color; +@input-border-color: @border-color-base; +@input-bg: @component-background; +@input-number-hover-border-color: #4096ff; +@input-number-handler-bg: @component-background; +@input-number-handler-border-color: @border-color-base; +@input-addon-bg: @background-color-light; +@input-hover-border-color: #4096ff; +@input-disabled-bg: @disabled-bg; +@input-outline-offset: 0 0; +@input-icon-hover-color: rgba(0, 0, 0, 0.85); +@input-disabled-color: @disabled-color; + +// ========== Mentions ========== +@mentions-dropdown-bg: @component-background; +@mentions-dropdown-menu-item-hover-bg: @mentions-dropdown-bg; + +// ========== 选择器 ========== +@select-border-color: @border-color-base; +@select-item-selected-color: @text-color; +@select-item-selected-font-weight: 600; +@select-dropdown-bg: @component-background; +@select-item-selected-bg: @primary-1; +@select-item-active-bg: @item-hover-bg; +@select-dropdown-vertical-padding: @dropdown-vertical-padding; +@select-dropdown-font-size: @dropdown-font-size; +@select-dropdown-line-height: @dropdown-line-height; +@select-dropdown-height: 32px; +@select-background: @component-background; +@select-clear-background: @select-background; +@select-selection-item-bg: #f5f5f5; +@select-selection-item-border-color: #f0f0f0; +@select-single-item-height-lg: 40px; +@select-multiple-item-height: 24px; +@select-multiple-item-height-lg: 32px; +@select-multiple-item-spacing-half: 4px; +@select-multiple-disabled-background: @input-disabled-bg; +@select-multiple-item-disabled-color: #bfbfbf; +@select-multiple-item-disabled-border-color: @select-border-color; +@select-clear-width: 20px; + +// ========== 级联选择 ========== +@cascader-bg: @component-background; +@cascader-item-selected-bg: @primary-1; +@cascader-menu-bg: @component-background; +@cascader-menu-border-color-split: @border-color-split; +@cascader-dropdown-vertical-padding: @dropdown-vertical-padding; +@cascader-dropdown-edge-child-vertical-padding: @dropdown-edge-child-vertical-padding; +@cascader-dropdown-font-size: @dropdown-font-size; +@cascader-dropdown-line-height: @dropdown-line-height; + +// ========== 锚点 ========== +@anchor-bg: transparent; +@anchor-border-color: @border-color-split; +@anchor-link-top: 7px; +@anchor-link-left: 16px; +@anchor-link-padding: @anchor-link-top 0 @anchor-link-top @anchor-link-left; + +// ========== 工具提示 ========== +@tooltip-max-width: 250px; +@tooltip-color: #fff; +@tooltip-bg: rgba(0, 0, 0, 0.75); +@tooltip-arrow-width: 5px; +@tooltip-distance: 8px; +@tooltip-arrow-color: @tooltip-bg; + +// ========== 气泡卡片 ========== +@popover-bg: @component-background; +@popover-color: @text-color; +@popover-min-width: 177px; +@popover-min-height: 32px; +@popover-arrow-width: 6px; +@popover-arrow-color: @popover-bg; +@popover-arrow-outer-color: @popover-bg; +@popover-distance: 10px; +@popover-padding-horizontal: @padding-md; + +// ========== 模态框 ========== +@modal-header-padding-vertical: @padding-md; +@modal-header-padding-horizontal: @padding-lg; +@modal-body-padding: @padding-lg; +@modal-header-bg: @component-background; +@modal-header-padding: @modal-header-padding-vertical @modal-header-padding-horizontal; +@modal-header-border-width: @border-width-base; +@modal-header-border-style: @border-style-base; +@modal-header-title-line-height: 22px; +@modal-header-title-font-size: @font-size-lg; +@modal-header-border-color-split: @border-color-split; +@modal-header-close-size: 56px; +@modal-content-bg: @component-background; +@modal-heading-color: @heading-color; +@modal-close-color: @text-color-secondary; +@modal-footer-bg: transparent; +@modal-footer-border-color-split: @border-color-split; +@modal-footer-border-style: @border-style-base; +@modal-footer-padding-vertical: 10px; +@modal-footer-padding-horizontal: 16px; +@modal-footer-border-width: @border-width-base; +@modal-mask-bg: rgba(0, 0, 0, 0.45); +@modal-confirm-body-padding: 32px 32px 24px; +@modal-confirm-title-font-size: @font-size-lg; + +// ========== 进度条 ========== +@progress-default-color: @primary-color; +@progress-remaining-color: @background-color-base; +@progress-info-text-color: @progress-text-color; +@progress-radius: 100px; +@progress-steps-item-bg: #f3f3f3; +@progress-text-font-size: 1em; +@progress-text-color: @text-color; +@progress-circle-text-font-size: 1em; + +// ========== 菜单 ========== +@menu-inline-toplevel-item-height: 40px; +@menu-item-height: 40px; +@menu-item-group-height: @line-height-base; +@menu-collapsed-width: 80px; +@menu-bg: @component-background; +@menu-popup-bg: @component-background; +@menu-item-color: @text-color; +@menu-inline-submenu-bg: @background-color-light; +@menu-highlight-color: @primary-color; +@menu-highlight-danger-color: @error-color; +@menu-item-active-bg: @primary-1; +@menu-item-active-danger-bg: #fff2f0; +@menu-item-active-border-width: 3px; +@menu-item-group-title-color: @text-color-secondary; +@menu-item-vertical-margin: 4px; +@menu-item-font-size: @font-size-base; +@menu-item-boundary-margin: 8px; +@menu-item-padding-horizontal: 20px; +@menu-item-padding: 0 @menu-item-padding-horizontal; +@menu-horizontal-line-height: 46px; +@menu-icon-margin-right: 10px; +@menu-icon-size: @menu-item-font-size; +@menu-icon-size-lg: @font-size-lg; +@menu-dark-color: rgba(255, 255, 255, 0.65); +@menu-dark-danger-color: @error-color; +@menu-dark-bg: @layout-header-background; +@menu-dark-arrow-color: #fff; +@menu-dark-inline-submenu-bg: #000c17; +@menu-dark-highlight-color: #fff; +@menu-dark-item-active-bg: @primary-color; +@menu-dark-item-active-danger-bg: @error-color; +@menu-dark-selected-item-icon-color: @white; +@menu-dark-selected-item-text-color: @white; + +// ========== 加载动画 ========== +@spin-dot-size-sm: 14px; +@spin-dot-size: 20px; +@spin-dot-size-lg: 32px; + +// ========== 表格 ========== +@table-bg: @component-background; +@table-header-bg: @background-color-light; +@table-header-color: @heading-color; +@table-header-sort-bg: @background-color-base; +@table-body-sort-bg: #fafafa; +@table-row-hover-bg: @background-color-light; +@table-selected-row-color: inherit; +@table-selected-row-bg: @primary-1; +@table-body-selected-sort-bg: @table-selected-row-bg; +@table-selected-row-hover-bg: #e6f4ff; +@table-expanded-row-bg: #fbfbfb; +@table-padding-vertical: 16px; +@table-padding-horizontal: 16px; +@table-padding-vertical-md: 12px; +@table-padding-horizontal-md: 8px; +@table-padding-vertical-sm: 8px; +@table-padding-horizontal-sm: 8px; +@table-border-color: #f0f0f0; +@table-border-radius-base: @border-radius-base; +@table-footer-bg: @background-color-light; +@table-footer-color: @heading-color; +@table-header-bg-sm: @table-header-bg; +@table-font-size: @font-size-base; +@table-font-size-md: @table-font-size; +@table-font-size-sm: @table-font-size; +@table-header-cell-split-color: rgba(0, 0, 0, 0.06); +@table-header-sort-active-bg: rgba(0, 0, 0, 0.04); +@table-fixed-header-sort-active-bg: #f0f0f0; +@table-header-filter-active-bg: rgba(0, 0, 0, 0.04); +@table-filter-btns-bg: inherit; +@table-filter-dropdown-bg: @component-background; +@table-expand-icon-bg: @component-background; +@table-selection-column-width: 32px; +@table-sticky-scroll-bar-bg: rgba(0, 0, 0, 0.35); +@table-sticky-scroll-bar-radius: 4px; + +// ========== 标签 ========== +@tag-default-bg: @background-color-light; +@tag-default-color: @text-color; +@tag-font-size: @font-size-sm; +@tag-line-height: 20px; + +// ========== 选择器/时间选择器 ========== +@picker-bg: @component-background; +@picker-basic-cell-hover-color: @item-hover-bg; +@picker-basic-cell-active-with-range-color: @primary-1; +@picker-basic-cell-hover-with-range-color: #69b1ff; +@picker-basic-cell-disabled-bg: rgba(0, 0, 0, 0.04); +@picker-border-color: @border-color-split; +@picker-date-hover-range-border-color: #69b1ff; +@picker-date-hover-range-color: @picker-basic-cell-hover-with-range-color; +@picker-time-panel-column-width: 56px; +@picker-time-panel-column-height: 224px; +@picker-time-panel-cell-height: 28px; +@picker-panel-cell-height: 24px; +@picker-panel-cell-width: 36px; +@picker-text-height: 40px; +@picker-panel-without-time-cell-height: 66px; + +// ========== 日历 ========== +@calendar-bg: @component-background; +@calendar-input-bg: @input-bg; +@calendar-border-color: @border-color-inverse; +@calendar-item-active-bg: @item-active-bg; +@calendar-column-active-bg: rgba(230, 244, 255, 0.2); +@calendar-full-bg: @calendar-bg; +@calendar-full-panel-bg: @calendar-full-bg; + +// ========== 轮播 ========== +@carousel-dot-width: 16px; +@carousel-dot-height: 3px; +@carousel-dot-active-width: 24px; + +// ========== 徽章 ========== +@badge-height: 20px; +@badge-height-sm: 14px; +@badge-dot-size: 6px; +@badge-font-size: @font-size-sm; +@badge-font-size-sm: @font-size-sm; +@badge-font-weight: normal; +@badge-status-size: 6px; +@badge-text-color: @component-background; +@badge-color: @highlight-color; + +// ========== 评分 ========== +@rate-star-color: #fadb14; +@rate-star-bg: @border-color-split; +@rate-star-size: 20px; +@rate-star-hover-scale: scale(1.1); + +// ========== 卡片 ========== +@card-head-color: @heading-color; +@card-head-background: transparent; +@card-head-font-size: @font-size-lg; +@card-head-font-size-sm: @font-size-base; +@card-head-padding: 16px; +@card-head-padding-sm: 8px; +@card-head-height: 48px; +@card-head-height-sm: 36px; +@card-inner-head-padding: 12px; +@card-padding-base: 24px; +@card-padding-base-sm: 12px; +@card-actions-background: @component-background; +@card-actions-li-margin: 12px 0; +@card-skeleton-bg: #cfd8dc; +@card-background: @component-background; +@card-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.16), 0 3px 6px 0 rgba(0, 0, 0, 0.12), 0 5px 12px 4px rgba(0, 0, 0, 0.09); +@card-radius: @border-radius-base; +@card-head-tabs-margin-bottom: -17px; +@card-head-extra-color: @text-color; + +// ========== 评论 ========== +@comment-bg: inherit; +@comment-padding-base: @padding-md 0; +@comment-nest-indent: 44px; +@comment-font-size-base: @font-size-base; +@comment-font-size-sm: @font-size-sm; +@comment-author-name-color: @text-color-secondary; +@comment-author-time-color: #ccc; +@comment-action-color: @text-color-secondary; +@comment-action-hover-color: #595959; +@comment-actions-margin-bottom: inherit; +@comment-actions-margin-top: @margin-sm; +@comment-content-detail-margin-bottom: inherit; + +// ========== 标签页 ========== +@tabs-card-head-background: @background-color-light; +@tabs-card-height: 40px; +@tabs-card-active-color: @primary-color; +@tabs-card-horizontal-padding: 16px 20px; +@tabs-card-horizontal-padding-sm: 6px @padding-md; +@tabs-card-horizontal-padding-lg: 7px @padding-md 6px; +@tabs-title-font-size: @font-size-base; +@tabs-title-font-size-lg: @font-size-lg; +@tabs-title-font-size-sm: @font-size-base; +@tabs-ink-bar-color: @primary-color; +@tabs-bar-margin: 0 0 @margin-md 0; +@tabs-horizontal-gutter: 32px; +@tabs-horizontal-margin: 0 0 0 @tabs-horizontal-gutter; +@tabs-horizontal-padding: @padding-sm 0; +@tabs-horizontal-padding-lg: @padding-md 0; +@tabs-horizontal-padding-sm: @padding-xs 0; +@tabs-vertical-padding: @padding-xs @padding-lg; +@tabs-vertical-margin: @margin-md 0 0 0; +@tabs-scrolling-size: 32px; +@tabs-highlight-color: @primary-color; +@tabs-hover-color: #4096ff; +@tabs-active-color: #0958d9; +@tabs-card-gutter: 2px; +@tabs-card-tab-active-border-top: 2px solid transparent; + +// ========== 回到顶部 ========== +@back-top-color: #fff; +@back-top-bg: rgba(0, 0, 0, 0.45); +@back-top-hover-bg: @text-color; + +// ========== 头像 ========== +@avatar-size-base: 32px; +@avatar-size-lg: 40px; +@avatar-size-sm: 24px; +@avatar-font-size-base: 18px; +@avatar-font-size-lg: 24px; +@avatar-font-size-sm: 14px; +@avatar-bg: #ccc; +@avatar-color: #fff; +@avatar-border-radius: @border-radius-base; +@avatar-group-overlapping: -8px; +@avatar-group-space: 3px; +@avatar-group-border-color: #fff; + +// ========== 开关 ========== +@switch-height: 22px; +@switch-sm-height: 16px; +@switch-min-width: 44px; +@switch-sm-min-width: 28px; +@switch-disabled-opacity: 0.4; +@switch-color: @primary-color; +@switch-bg: @component-background; +@switch-shadow-color: rgba(0, 35, 11, 0.2); +@switch-padding: 2px; +@switch-inner-margin-min: 7px; +@switch-inner-margin-max: 26px; +@switch-inner-sm-margin-min: 5px; +@switch-inner-sm-margin-max: 20px; + +// ========== 分页 ========== +@pagination-item-bg: @component-background; +@pagination-item-size: @height-base; +@pagination-item-size-sm: 24px; +@pagination-font-family: @font-family; +@pagination-font-weight-active: 500; +@pagination-item-bg-active: @component-background; +@pagination-item-link-bg: @component-background; +@pagination-item-disabled-color-active: @disabled-color; +@pagination-item-disabled-bg-active: @disabled-active-bg; +@pagination-item-input-bg: @component-background; +@pagination-mini-options-size-changer-top: 0px; + +// ========== 页面头部 ========== +@page-header-padding: @padding-lg; +@page-header-padding-vertical: @padding-md; +@page-header-padding-breadcrumb: @padding-sm; +@page-header-content-padding-vertical: @padding-sm; +@page-header-back-color: #000; +@page-header-ghost-bg: inherit; +@page-header-heading-title: @heading-4-size; +@page-header-heading-sub-title: 14px; +@page-header-tabs-tab-font-size: 16px; + +// ========== 面包屑 ========== +@breadcrumb-base-color: @text-color-secondary; +@breadcrumb-last-item-color: @text-color; +@breadcrumb-font-size: @font-size-base; +@breadcrumb-icon-font-size: @font-size-base; +@breadcrumb-link-color: @text-color-secondary; +@breadcrumb-link-color-hover: #4096ff; +@breadcrumb-separator-color: @text-color-secondary; +@breadcrumb-separator-margin: 0 @padding-xs; + +// ========== 滑块 ========== +@slider-margin: 10px 6px 10px; +@slider-rail-background-color: @background-color-base; +@slider-rail-background-color-hover: #e1e1e1; +@slider-track-background-color: #91caff; +@slider-track-background-color-hover: #69b1ff; +@slider-handle-border-width: 2px; +@slider-handle-background-color: @component-background; +@slider-handle-color: #91caff; +@slider-handle-color-hover: #69b1ff; +@slider-handle-color-focus: rgba(22, 119, 255, 0.2); +@slider-handle-color-focus-shadow: rgba(22, 119, 255, 0.12); +@slider-handle-color-tooltip-open: @primary-color; +@slider-handle-size: 14px; +@slider-handle-margin-top: -5px; +@slider-handle-shadow: 0; +@slider-dot-border-color: @border-color-split; +@slider-dot-border-color-active: rgba(22, 119, 255, 0.5); +@slider-disabled-color: @disabled-color; +@slider-disabled-background-color: @component-background; + +// ========== 树 ========== +@tree-bg: @component-background; +@tree-title-height: 24px; +@tree-child-padding: 18px; +@tree-directory-selected-color: #fff; +@tree-directory-selected-bg: @primary-color; +@tree-node-hover-bg: @item-hover-bg; +@tree-node-selected-bg: #bae0ff; + +// ========== 折叠 ========== +@collapse-header-padding: @padding-sm @padding-md; +@collapse-header-padding-extra: 40px; +@collapse-header-bg: @background-color-light; +@collapse-content-padding: @padding-md; +@collapse-content-bg: @component-background; +@collapse-header-arrow-left: 16px; + +// ========== 骨架屏 ========== +@skeleton-color: #f0f0f0; +@skeleton-to-color: #e8e8e8; +@skeleton-paragraph-margin-top: 28px; +@skeleton-paragraph-li-margin-top: @margin-md; +@skeleton-paragraph-li-height: 16px; +@skeleton-title-height: 16px; +@skeleton-title-paragraph-margin-top: @margin-lg; + +// ========== 穿梭框 ========== +@transfer-header-height: 40px; +@transfer-item-height: @height-base; +@transfer-disabled-bg: @disabled-bg; +@transfer-list-height: 200px; +@transfer-item-hover-bg: @item-hover-bg; +@transfer-item-selected-hover-bg: #e6f4ff; +@transfer-item-padding-vertical: 6px; +@transfer-list-search-icon-top: 12px; + +// ========== 消息 ========== +@message-notice-content-padding: 10px 16px; +@message-notice-content-bg: @component-background; + +// ========== 动画 ========== +@wave-animation-width: 6px; + +// ========== 警告提示 ========== +@alert-success-border-color: #b7eb8f; +@alert-success-bg-color: #f6ffed; +@alert-success-icon-color: @success-color; +@alert-info-border-color: #91caff; +@alert-info-bg-color: #e6f4ff; +@alert-info-icon-color: @info-color; +@alert-warning-border-color: #ffe58f; +@alert-warning-bg-color: #fffbe6; +@alert-warning-icon-color: @warning-color; +@alert-error-border-color: #ffccc7; +@alert-error-bg-color: #fff2f0; +@alert-error-icon-color: @error-color; +@alert-message-color: @heading-color; +@alert-text-color: @text-color; +@alert-close-color: @text-color-secondary; +@alert-close-hover-color: @icon-color-hover; +@alert-no-icon-padding-vertical: @padding-xs; +@alert-with-description-no-icon-padding-vertical: @padding-md; +@alert-with-description-padding-vertical: @padding-md; +@alert-with-description-padding: @alert-with-description-padding-vertical 15px @alert-with-description-no-icon-padding-vertical @alert-with-description-icon-size; +@alert-icon-top: 8px; +@alert-with-description-icon-size: 24px; + +// ========== 列表 ========== +@list-header-background: transparent; +@list-footer-background: transparent; +@list-empty-text-padding: @padding-md; +@list-item-padding: @padding-sm 0; +@list-item-padding-sm: @padding-xs @padding-md; +@list-item-padding-lg: 16px 24px; +@list-item-meta-margin-bottom: @padding-md; +@list-item-meta-avatar-margin-right: @padding-md; +@list-item-meta-title-margin-bottom: @padding-sm; +@list-customize-card-bg: @component-background; +@list-item-meta-description-font-size: @font-size-base; + +// ========== 统计 ========== +@statistic-title-font-size: @font-size-base; +@statistic-content-font-size: 24px; +@statistic-unit-font-size: 24px; +@statistic-font-family: @font-family; + +// ========== 抽屉 ========== +@drawer-header-padding: @padding-md @padding-lg; +@drawer-body-padding: @padding-lg; +@drawer-bg: @component-background; +@drawer-footer-padding-vertical: @modal-footer-padding-vertical; +@drawer-footer-padding-horizontal: @modal-footer-padding-horizontal; +@drawer-header-close-size: 56px; +@drawer-title-font-size: @font-size-lg; +@drawer-title-line-height: 22px; + +// ========== 时间轴 ========== +@timeline-width: 2px; +@timeline-color: @border-color-split; +@timeline-dot-border-width: 2px; +@timeline-dot-color: @primary-color; +@timeline-dot-bg: @component-background; +@timeline-item-padding-bottom: 20px; + +// ========== 排版 ========== +@typography-title-font-weight: 600; +@typography-title-margin-top: 1.2em; +@typography-title-margin-bottom: 0.5em; + +// ========== 上传 ========== +@upload-actions-color: @text-color-secondary; + +// ========== 步骤 ========== +@process-tail-color: @border-color-split; +@steps-nav-arrow-color: rgba(0, 0, 0, 0.25); +@steps-background: @component-background; +@steps-icon-size: 32px; +@steps-icon-custom-size: @steps-icon-size; +@steps-icon-custom-top: 0px; +@steps-icon-custom-font-size: 24px; +@steps-icon-top: -0.5px; +@steps-icon-font-size: @font-size-lg; +@steps-icon-margin: 0 8px 0 0; +@steps-title-line-height: @height-base; +@steps-small-icon-size: 24px; +@steps-small-icon-margin: 0 8px 0 0; +@steps-dot-size: 8px; +@steps-dot-top: 2px; +@steps-current-dot-size: 10px; +@steps-description-max-width: 140px; +@steps-nav-content-max-width: auto; +@steps-vertical-icon-width: 16px; +@steps-vertical-tail-width: 16px; +@steps-vertical-tail-width-sm: 12px; + +// ========== 通知 ========== +@notification-bg: @component-background; +@notification-padding-vertical: 16px; +@notification-padding-horizontal: 24px; + +// ========== 结果 ========== +@result-title-font-size: 24px; +@result-subtitle-font-size: @font-size-base; +@result-icon-font-size: 72px; +@result-extra-margin: 24px 0 0 0; + +// ========== 图片 ========== +@image-size-base: 48px; +@image-font-size-base: 24px; +@image-bg: #f5f5f5; +@image-color: #fff; +@image-mask-font-size: 16px; +@image-preview-operation-size: 18px; +@image-preview-operation-color: @text-color-dark; +@image-preview-operation-disabled-color: rgba(255, 255, 255, 0.25); diff --git a/src/styles/antd-less-stub/empty-style.js b/src/styles/antd-less-stub/empty-style.js new file mode 100644 index 0000000..ab791e2 --- /dev/null +++ b/src/styles/antd-less-stub/empty-style.js @@ -0,0 +1,4 @@ +// AntDv4 兼容:空模块 +// AntDv4 使用 CSS-in-JS,不再需要单独的样式导入 +// 此文件用于吸收 ele-admin-pro 中的 ant-design-vue/es/xxx/style 引用 +export default {}; diff --git a/src/styles/as-needed.less b/src/styles/as-needed.less index 1bda515..06d9db7 100644 --- a/src/styles/as-needed.less +++ b/src/styles/as-needed.less @@ -1,6 +1,4 @@ /** 按需引入 */ -@import 'ant-design-vue/es/message/style/index.less'; -@import 'ant-design-vue/es/notification/style/index.less'; @import 'ele-admin-pro/es/style/nprogress.less'; @import 'ele-admin-pro/es/style/display.less'; @import 'ele-admin-pro/es/style/common.less'; diff --git a/src/styles/component.less b/src/styles/component.less index 4d713e9..dd4b067 100644 --- a/src/styles/component.less +++ b/src/styles/component.less @@ -2,16 +2,86 @@ //@input-item: 300px; /* ele-pro-table 全局美化样式 */ -.ele-pro-table .ant-table-thead > tr > th { - border-bottom: 2px solid #f0f0f0; - background-color: #fafafa; - font-weight: 600; -} +.ele-pro-table { + // 表头样式 - 更现代的渐变效果 + .ant-table-thead > tr > th { + border-bottom: 2px solid #e8e8e8; + background: linear-gradient(180deg, #fafbfc 0%, #f5f7fa 100%); + font-weight: 600; + color: #374151; + font-size: 13px; + letter-spacing: 0.02em; -.ele-pro-table .ant-table-tbody > tr > td { - border-bottom: 1px solid #f5f5f5; -} + &::before { + display: none; + } + } -.ele-pro-table .ant-table-tbody > tr:hover > td { - background-color: #f8f9fa; + // 表格行样式 - 添加斑马纹 + .ant-table-tbody > tr { + transition: all 0.2s ease; + + &:nth-child(even) { + background-color: #fafbfc; + } + + > td { + border-bottom: 1px solid #f0f0f0; + padding: 14px 16px; + } + + // 悬停效果 + &:hover { + background-color: #f0f7ff !important; + + > td { + background-color: transparent; + } + } + + // 选中效果 + &.ant-table-row-selected { + background-color: #e6f4ff; + + &:hover { + background-color: #bae0ff !important; + } + } + } + + // 圆角卡片效果 + .ant-table { + border-radius: 12px; + overflow: hidden; + } + + // 滚动条美化 + .ant-table-body { + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; + } + + &::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; + + &:hover { + background: #a8a8a8; + } + } + } + + // 分页器样式 + .ant-pagination { + margin-top: 16px; + padding: 12px 16px; + background: #fafbfc; + border-radius: 0 0 12px 12px; + } } diff --git a/src/styles/global-import.less b/src/styles/global-import.less index 7fd1f81..cbc57d8 100644 --- a/src/styles/global-import.less +++ b/src/styles/global-import.less @@ -1,4 +1,3 @@ /** 全局引入 */ @import 'cropperjs/dist/cropper.css'; -@import 'ant-design-vue/dist/antd.less'; @import 'ele-admin-pro/es/style/index.less'; diff --git a/src/styles/index.less b/src/styles/index.less index 6f059b4..e0e133d 100644 --- a/src/styles/index.less +++ b/src/styles/index.less @@ -1,14 +1,52 @@ @tailwind base; @tailwind components; @tailwind utilities; + +/* 重置浏览器默认 margin/padding */ +html, +body { + margin: 0; + padding: 0; +} /** 全局样式 */ @style-entry-file: as-needed; @import './@{style-entry-file}.less'; @import './transition/index.less'; @import "./component.less"; -// 主题 -@import 'ele-admin-pro/es/style/themes/dynamic.less'; +// 侧边栏菜单高亮颜色(深红棕色) +@menu-dark-item-active-bg: #6b2d2d; +@menu-dark-selected-item-text-color: #fff; + +// 主题(AntDv4 使用 CSS-in-JS token 体系,不再需要 Less 动态主题) + +// 覆盖 ant-design-vue 深色菜单选中高亮样式 +// 使用更柔和的半透明背景 + 圆角 +:where(.ele-admin-layout) { + // 侧边栏深色菜单选中样式 + .ant-menu-dark.ant-menu-root { + // 选中项:半透明深红棕背景 + 8px圆角 + 宽度减少5px + .ant-menu-item-selected, + .ant-menu-item-active { + background-color: rgba(107, 45, 45, 0.5) !important; + border-radius: 8px; + color: rgba(255, 255, 255, 0.9) !important; + } + + // 悬停效果 + .ant-menu-item:hover { + background-color: rgba(107, 45, 45, 0.3) !important; + border-radius: 8px; + } + + // 子菜单选中项 + .ant-menu-submenu-selected > .ant-menu-submenu-title { + background-color: rgba(107, 45, 45, 0.5) !important; + border-radius: 8px; + color: rgba(255, 255, 255, 0.9) !important; + } + } +} :root { // 灰色主题 diff --git a/src/utils/oss.js b/src/utils/oss.js deleted file mode 100644 index ace265c..0000000 --- a/src/utils/oss.js +++ /dev/null @@ -1,43 +0,0 @@ -import OSS from 'ali-oss'; -const baseUrl = 'http://oss-aishangjia.oss-cn-shenzhen.aliyuncs.com'; -export const uploadAliOss = async (file, options) => { - // 构建上传文件参数 - - // 获取上传文件所需要的STS Token - // 直接通过node.js上传 - // console.log(token) - const client = new OSS({ - region: 'oss-cn-shenzhen', - accessKeyId: 'LTAI4GKGZ9Z2Z8JZ77c3GNZP', - accessKeySecret: 'BiDkpS7UXj72HWwDWaFZxiXjNFBNCM', - bucket: 'oss-aishangjia', - secure: true - }); - - const headers = { - // 指定该Object被下载时的网页缓存行为。 - 'Cache-Control': 'no-cache', - // 指定该Object被下载时的名称。 - // 指定该Object被下载时的内容编码格式。 - 'Content-Encoding': 'utf-8', - // 指定过期时间,单位为毫秒。 - Expires: '1000', - // 指定Object的存储类型。 - 'x-oss-storage-class': 'Standard', - // 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。 - 'x-oss-forbid-overwrite': 'true' - }; - - const suffix = file.name.substring(file.name.lastIndexOf('.')); // .txt - const objectName = Date.now() + suffix; - - // object-name可以自定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至当前Bucket或Bucket下的指定目录。 - const result = await client.multipartUpload(objectName, file, { - ...options, - parallel: 4, - partSize: 1024 * 1024 * 5 - }); - - result.url = `${baseUrl}/${result.name}`; - return Promise.resolve(result); -}; diff --git a/src/utils/oss.ts b/src/utils/oss.ts new file mode 100644 index 0000000..1bd2651 --- /dev/null +++ b/src/utils/oss.ts @@ -0,0 +1,113 @@ +import OSS from 'ali-oss'; +import { getShopSettingByCategory } from '@/api/shop/shopSetting'; +import type { ShopSetting } from '@/api/shop/shopSetting/model'; + +// OSS 配置缓存 +let ossConfigCache: { + baseUrl: string; + region: string; + accessKeyId: string; + accessKeySecret: string; + bucket: string; +} | null = null; + +// 获取 OSS 配置 +async function getOssConfig() { + // 如果已有缓存,直接返回 + if (ossConfigCache) { + return ossConfigCache; + } + + try { + // 从后台设置获取 upload 分类的配置 + const settings = await getShopSettingByCategory('upload'); + + // 从设置中找到 OSS 相关配置 + const ossSettings: Partial> = {}; + settings.forEach((item: ShopSetting) => { + if (item.settingKey && item.settingValue) { + ossSettings[item.settingKey] = item.settingValue; + } + }); + + // 检查必要的配置 + const region = ossSettings.ossRegion || 'oss-cn-shenzhen'; + const bucket = ossSettings.ossBucket || 'oss-aishangjia'; + const accessKeyId = ossSettings.ossAccessKeyId; + const accessKeySecret = ossSettings.ossAccessKeySecret; + const baseUrl = ossSettings.ossBaseUrl || `https://${bucket}.${region}.aliyuncs.com`; + + if (!accessKeyId || !accessKeySecret) { + throw new Error('OSS 配置不完整,请在后台设置中配置 accessKeyId 和 accessKeySecret'); + } + + // 缓存配置 + ossConfigCache = { + baseUrl, + region, + bucket, + accessKeyId, + accessKeySecret + }; + + return ossConfigCache; + } catch (error) { + console.error('获取 OSS 配置失败:', error); + throw error; + } +} + +// 清除 OSS 配置缓存(用于配置更新后重新加载) +export function clearOssConfigCache() { + ossConfigCache = null; +} + +/** + * 阿里云 OSS 上传 + * @param file 文件对象 + * @param options 上传选项 + */ +export const uploadAliOss = async (file: File, options?: Record) => { + // 获取 OSS 配置 + const config = await getOssConfig(); + + // 构建 OSS 客户端 + const client = new OSS({ + region: config.region, + accessKeyId: config.accessKeyId, + accessKeySecret: config.accessKeySecret, + bucket: config.bucket, + secure: true // 使用 HTTPS + }); + + const headers = { + // 指定该Object被下载时的网页缓存行为 + 'Cache-Control': 'no-cache', + // 指定该Object被下载时的名称 + // 指定该Object被下载时的内容编码格式 + 'Content-Encoding': 'utf-8', + // 指定过期时间,单位为毫秒 + Expires: '1000', + // 指定Object的存储类型 + 'x-oss-storage-class': 'Standard', + // 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object + 'x-oss-forbid-overwrite': 'true' + }; + + // 生成文件名:时间戳 + 原始后缀 + const suffix = file.name.substring(file.name.lastIndexOf('.')); + const objectName = `uploads/${Date.now()}${suffix}`; + + // 上传文件 + const result = await client.multipartUpload(objectName, file, { + ...options, + parallel: 4, + partSize: 1024 * 1024 * 5, + headers + }); + + // 设置完整 URL + result.url = `${config.baseUrl}/${result.name}`; + + return result; +}; diff --git a/src/utils/request.ts b/src/utils/request.ts index f361d91..ffddaf2 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -6,7 +6,7 @@ import type { AxiosResponse } from 'axios'; import { unref } from 'vue'; import router from '@/router'; import { Modal } from 'ant-design-vue'; -import { API_BASE_URL, TOKEN_HEADER_NAME, LAYOUT_PATH } from '@/config/setting'; +import {API_BASE_URL, TOKEN_HEADER_NAME, LAYOUT_PATH, TENANT_ID} from '@/config/setting'; import { getToken, setToken } from './token-util'; import { logout } from './page-tab-util'; import type { ApiResult } from '@/api'; @@ -40,36 +40,28 @@ const service = axios.create({ */ service.interceptors.request.use( (config) => { - const TENANT_ID = getTenantId(); const token = getToken(); - // 添加 token 到 header + // 添加 token 到 header(Axios 1.x 直接设置 headers) if (token && config.headers) { - config.headers.common[TOKEN_HEADER_NAME] = token; + config.headers[TOKEN_HEADER_NAME] = token; } // 获取租户ID if (config.headers) { // 附加企业ID const companyId = localStorage.getItem('CompanyId'); if (companyId) { - config.headers.common['CompanyId'] = companyId; + config.headers['CompanyId'] = companyId; } // 附加商户ID if (getMerchantId()) { - config.headers.common['MerchantId'] = getMerchantId(); + config.headers['MerchantId'] = getMerchantId(); } // 通过网站域名获取租户ID if (getHostname()) { - config.headers.common['Domain'] = getHostname(); - } - // 解析二级域名获取租户ID - if (getTenantId()) { - config.headers.common['TenantId'] = getTenantId(); - return config; - } - if (TENANT_ID) { - config.headers.common['TenantId'] = TENANT_ID; - return config; + config.headers['Domain'] = getHostname(); } + // 解析二级域名获取租户ID(确保始终设置TenantId) + config.headers['TenantId'] = getTenantId() || TENANT_ID; } return config; }, diff --git a/src/utils/token-util.ts b/src/utils/token-util.ts index a878c9d..96ca7d2 100644 --- a/src/utils/token-util.ts +++ b/src/utils/token-util.ts @@ -32,12 +32,11 @@ export function setToken(token?: string, remember?: boolean) { } /** - * 移除 token + * 移除 token(只清除 token 相关数据,不影响其他 localStorage 数据) */ export function removeToken() { localStorage.removeItem(TOKEN_STORE_NAME); sessionStorage.removeItem(TOKEN_STORE_NAME); - localStorage.clear(); } // 封装签名 diff --git a/src/views/cms/cmsWebsiteField/index.vue b/src/views/cms/cmsWebsiteField/index.vue index aa815f0..c89272b 100644 --- a/src/views/cms/cmsWebsiteField/index.vue +++ b/src/views/cms/cmsWebsiteField/index.vue @@ -70,7 +70,7 @@ {{ decrypt(record.value) }} -
+
+ @@ -76,7 +79,7 @@ import router from '@/router'; import { toTreeData } from 'ele-admin-pro'; import { toDateString } from 'ele-admin-pro'; - import { detail, getPageTitle } from '@/utils/common'; + import {detail, getPageTitle, openUrl} from '@/utils/common'; import { listCmsNavigation } from '@/api/cms/cmsNavigation'; import { CmsNavigation } from '@/api/cms/cmsNavigation/model'; import { CmsArticleCategory } from '@/api/cms/cmsArticleCategory/model'; @@ -138,10 +141,15 @@ // align: 'center' // }, { - title: '文章标题', + title: '标题', dataIndex: 'title', key: 'title' }, + { + title: '立即下载', + dataIndex: 'pdfUrl', + key: 'pdfUrl' + }, // { // title: '栏目名称', // dataIndex: 'categoryName', @@ -201,9 +209,7 @@ dataIndex: 'createTime', key: 'createTime', align: 'center', - width: 180, - customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd'), - sorter: true + customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd') } // { // title: '操作', @@ -309,7 +315,7 @@ return { // 行点击事件 onClick: () => { - openEdit(record); + // openEdit(record); }, // 行双击事件 onDblclick: () => { diff --git a/src/views/glt/gltTicketOrder/components/search.vue b/src/views/glt/gltTicketOrder/components/search.vue deleted file mode 100644 index 006d57b..0000000 --- a/src/views/glt/gltTicketOrder/components/search.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - diff --git a/src/views/glt/gltTicketOrder/index.vue b/src/views/glt/gltTicketOrder/index.vue deleted file mode 100644 index 81118a2..0000000 --- a/src/views/glt/gltTicketOrder/index.vue +++ /dev/null @@ -1,512 +0,0 @@ - - - - - - - diff --git a/src/views/glt/gltTicketTemplate/components/gltTicketTemplateEdit.vue b/src/views/glt/gltTicketTemplate/components/gltTicketTemplateEdit.vue deleted file mode 100644 index 767679e..0000000 --- a/src/views/glt/gltTicketTemplate/components/gltTicketTemplateEdit.vue +++ /dev/null @@ -1,461 +0,0 @@ - - - - - - diff --git a/src/views/glt/gltUserTicket/components/gltUserTicketEdit.vue b/src/views/glt/gltUserTicket/components/gltUserTicketEdit.vue deleted file mode 100644 index 2649172..0000000 --- a/src/views/glt/gltUserTicket/components/gltUserTicketEdit.vue +++ /dev/null @@ -1,262 +0,0 @@ - - - - diff --git a/src/views/glt/gltUserTicket/components/search.vue b/src/views/glt/gltUserTicket/components/search.vue deleted file mode 100644 index ff39e31..0000000 --- a/src/views/glt/gltUserTicket/components/search.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - - diff --git a/src/views/glt/gltUserTicket/index.vue b/src/views/glt/gltUserTicket/index.vue deleted file mode 100644 index f5b84f1..0000000 --- a/src/views/glt/gltUserTicket/index.vue +++ /dev/null @@ -1,300 +0,0 @@ - - - - - - - diff --git a/src/views/glt/gltUserTicketLog/components/gltUserTicketLogEdit.vue b/src/views/glt/gltUserTicketLog/components/gltUserTicketLogEdit.vue deleted file mode 100644 index dda28a0..0000000 --- a/src/views/glt/gltUserTicketLog/components/gltUserTicketLogEdit.vue +++ /dev/null @@ -1,292 +0,0 @@ - - - - diff --git a/src/views/glt/gltUserTicketLog/components/search.vue b/src/views/glt/gltUserTicketLog/components/search.vue deleted file mode 100644 index 6349b7f..0000000 --- a/src/views/glt/gltUserTicketLog/components/search.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - - diff --git a/src/views/glt/gltUserTicketLog/index.vue b/src/views/glt/gltUserTicketLog/index.vue deleted file mode 100644 index cfd3cc5..0000000 --- a/src/views/glt/gltUserTicketLog/index.vue +++ /dev/null @@ -1,280 +0,0 @@ - - - - - - - diff --git a/src/views/glt/gltUserTicketRelease/components/search.vue b/src/views/glt/gltUserTicketRelease/components/search.vue deleted file mode 100644 index ce86527..0000000 --- a/src/views/glt/gltUserTicketRelease/components/search.vue +++ /dev/null @@ -1,49 +0,0 @@ - - - - diff --git a/src/views/passport/login/index.vue b/src/views/passport/login/index.vue index 1f9c514..133850d 100644 --- a/src/views/passport/login/index.vue +++ b/src/views/passport/login/index.vue @@ -5,14 +5,13 @@ ['', 'login-form-right', 'login-form-left'][direction] ]" :style="{ - backgroundImage: 'url(' + config?.loginBgImg + ')' + backgroundImage: 'url(https://oss.wsdns.cn/20250105/314d2a3da10048b09ef0f0464fdacbff.jpg)' }" > @@ -186,6 +203,17 @@ + + @@ -127,7 +138,7 @@ + + + @@ -62,10 +75,13 @@ } from 'ele-admin-pro/es/ele-pro-table/types'; import Search from './components/search.vue'; import ShopSpecEdit from './components/shopSpecEdit.vue'; + import SpecValuePanel from './components/specValuePanel.vue'; import { pageShopSpec, removeShopSpec, - removeBatchShopSpec + removeBatchShopSpec, + updateShopSpec, + syncGoodsSpec } from '@/api/shop/shopSpec'; import type { ShopSpec, ShopSpecParam } from '@/api/shop/shopSpec/model'; @@ -78,22 +94,9 @@ const current = ref(null); // 是否显示编辑弹窗 const showEdit = ref(false); - // 是否显示批量移动弹窗 - const showMove = ref(false); - // 加载状态 - const loading = ref(true); // 表格数据源 - const datasource: DatasourceFunction = ({ - page, - limit, - where, - orders, - filters - }) => { - if (filters) { - where.status = filters.status; - } + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { return pageShopSpec({ ...where, ...orders, @@ -105,73 +108,54 @@ // 表格列配置 const columns = ref([ { - title: '规格ID', + title: 'ID', dataIndex: 'specId', key: 'specId', align: 'center', - width: 90 + width: 70 }, { title: '规格名称', dataIndex: 'specName', key: 'specName', - align: 'center' + align: 'left', + minWidth: 160 }, { - title: '规格值', - dataIndex: 'specValue', - key: 'specValue', - align: 'center' + title: '状态', + dataIndex: 'status', + key: 'status', + align: 'center', + width: 90 }, { - title: '商户ID', - dataIndex: 'merchantId', - key: 'merchantId', - align: 'center' - }, - { - title: '创建用户', - dataIndex: 'userId', - key: 'userId', - align: 'center' - }, - { - title: '更新者', - dataIndex: 'updater', - key: 'updater', - align: 'center' + title: '排序', + dataIndex: 'sortNumber', + key: 'sortNumber', + align: 'center', + width: 80, + sorter: true }, { title: '备注', dataIndex: 'comments', key: 'comments', - align: 'center' - }, - { - title: '状态, 0正常, 1待修,2异常已修,3异常未修', - dataIndex: 'status', - key: 'status', - align: 'center' - }, - { - title: '排序号', - dataIndex: 'sortNumber', - key: 'sortNumber', - align: 'center' + align: 'left', + ellipsis: true }, { title: '创建时间', dataIndex: 'createTime', key: 'createTime', align: 'center', + width: 120, sorter: true, - ellipsis: true, customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd') }, { title: '操作', key: 'action', - width: 180, + width: 140, fixed: 'right', align: 'center', hideInSetting: true @@ -190,15 +174,23 @@ showEdit.value = true; }; - /* 打开批量移动弹窗 */ - const openMove = () => { - showMove.value = true; + /* 切换显示/隐藏状态 */ + const toggleStatus = (row: ShopSpec, val: boolean) => { + const newStatus = val ? 0 : 1; + updateShopSpec({ ...row, status: newStatus }) + .then((msg) => { + message.success(msg); + row.status = newStatus; + }) + .catch((e) => { + message.error(e.message); + }); }; /* 删除单个 */ const remove = (row: ShopSpec) => { const hide = message.loading('请求中..', 0); - removeShopSpec(row.shopSpecId) + removeShopSpec(row.specId) .then((msg) => { hide(); message.success(msg); @@ -210,6 +202,21 @@ }); }; + /* 同步更新商品规格 */ + const syncSpec = (row: ShopSpec) => { + const hide = message.loading('正在同步更新..', 0); + syncGoodsSpec(row.specId!) + .then((res) => { + hide(); + const updatedCount = res?.data?.updatedCount ?? 0; + message.success(`同步完成,已更新 ${updatedCount} 个商品`); + }) + .catch((e) => { + hide(); + message.error(e.message || '同步失败'); + }); + }; + /* 批量删除 */ const removeBatch = () => { if (!selection.value.length) { @@ -218,12 +225,12 @@ } Modal.confirm({ title: '提示', - content: '确定要删除选中的记录吗?', + content: '确定要删除选中的规格吗?删除后相关商品规格数据将受影响!', icon: createVNode(ExclamationCircleOutlined), maskClosable: true, onOk: () => { const hide = message.loading('请求中..', 0); - removeBatchShopSpec(selection.value.map((d) => d.shopSpecId)) + removeBatchShopSpec(selection.value.map((d) => d.specId)) .then((msg) => { hide(); message.success(msg); @@ -237,25 +244,14 @@ }); }; - /* 查询 */ - const query = () => { - loading.value = true; - }; - /* 自定义行属性 */ const customRow = (record: ShopSpec) => { return { - // 行点击事件 - onClick: () => { - // console.log(record); - }, - // 行双击事件 onDblclick: () => { openEdit(record); } }; }; - query(); diff --git a/src/views/shop/shopSpecValue/components/shopSpecValueEdit.vue b/src/views/shop/shopSpecValue/components/shopSpecValueEdit.vue index 5eb2471..d2d08ac 100644 --- a/src/views/shop/shopSpecValue/components/shopSpecValueEdit.vue +++ b/src/views/shop/shopSpecValue/components/shopSpecValueEdit.vue @@ -1,10 +1,9 @@ - + + + diff --git a/src/views/shop/shopSpecValue/index.vue b/src/views/shop/shopSpecValue/index.vue index dc8120d..2fc169d 100644 --- a/src/views/shop/shopSpecValue/index.vue +++ b/src/views/shop/shopSpecValue/index.vue @@ -4,10 +4,11 @@ @@ -17,23 +18,28 @@ :selection="selection" @add="openEdit" @remove="removeBatch" - @batchMove="openMove" />