184 Commits
Author SHA1 Message Date
YunaiV a0ce1f3a5b 🔥 2.0.0 版本发布! 2024-01-26 21:53:59 +08:00
芋道源码 10a5d4d265 !373 update src/permission.ts.
Merge pull request !373 from AKING/N/A
2024-01-26 13:45:59 +00:00
AKING 024d08555b update src/permission.ts.
解决了跳转不带参数的问题

Signed-off-by: AKING <2734339436@qq.com>
2024-01-24 02:44:33 +00:00
YunaiV 948ef08533 ✨ PAY:增加钱包、微信条码支付的渠道配置界面 2024-01-18 23:40:38 +08:00
YunaiV bf3e051d74 BPM:抄送任务的 code review 2024-01-18 19:07:12 +08:00
YunaiV 49a806a6f9 Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev 2024-01-18 12:47:44 +08:00
YunaiV 2dcd131097 ✨ 调整启动命令,恢复 npm run dev;如果要连接远程服务 2024-01-18 12:47:23 +08:00
芋道源码 d852103929 !365 流程增加抄送功能
Merge pull request !365 from 云开/feature/BPM_CC
2024-01-18 04:46:09 +00:00
kyle 2c9393702d feat: 新增流程抄送 2024-01-17 09:40:05 +08:00
YunaiV 9fa769b16a 📖 CRM:跟进记录的 code review 2024-01-15 21:30:25 +08:00
芋道源码 163613e90c !363 crm:新增跟进组件、转移表单组件封装,完善按钮数据权限控制、客户公海抽离
Merge pull request !363 from puhui999/dev-crm
2024-01-15 04:36:13 +00:00
YunaiV 1e70a1d57e 📖 CRM:待办事项的 code review 2024-01-15 12:15:59 +08:00
芋道源码 68fc1760a5 !364 wip: CRM-待办事项
Merge pull request !364 from dhb52/crm-msg
2024-01-15 01:34:14 +00:00
dhb52 24849335a8 wip: 待办事项 2024-01-14 22:44:25 +08:00
puhui999 2a55d88a44 Merge remote-tracking branch 'yudao/dev' into dev-crm
# Conflicts:
#	src/router/modules/remaining.ts
2024-01-14 21:53:31 +08:00
puhui999 9cbb3aa84a crm-客户:完善转移和按钮权限 2024-01-14 21:48:20 +08:00
puhui999 5151824110 crm-客户:公海抽离,完善跟进 2024-01-14 20:57:06 +08:00
dhb52 25347b3ea5 Merge remote-tracking branch 'yudao/dev' into dev 2024-01-14 11:50:26 +08:00
puhui999 d29dfef7c7 新增客户跟进 2024-01-14 00:00:50 +08:00
YunaiV 26bb847383 ✨ MALL:简化商品分类的 VO 转化 2024-01-13 22:58:32 +08:00
芋道源码 fdfb028dd9 !360 Review代码修改
Merge pull request !360 from 疯狂的世界/dev
2024-01-13 03:16:16 +00:00
YunaiV a77658b336 Merge remote-tracking branch 'origin/dev' into dev 2024-01-13 10:24:54 +08:00
芋道源码 66c48a913c 📖 CRM:产品模块的 review 2024-01-13 10:24:48 +08:00
芋道源码 2be14817a0 !361 新增:产品详情页面
Merge pull request !361 from 安浩浩/dev
2024-01-13 02:04:20 +00:00
YunaiV a9400c5171 ✨ MALL:简化 SPU 属性的维护逻辑 2024-01-12 23:17:48 +08:00
YunaiV 10afbe8e2f 📖 MALL:商品编辑 => 优化 DescriptionForm 表单 2024-01-12 21:01:10 +08:00
YunaiV a38ddb699a 📖 MALL:商品编辑 => 优化 DeliveryForm 表单 2024-01-12 20:53:17 +08:00
YunaiV 20b4a7fd66 📖 MALL:商品编辑 => 优化 SKU 表单 2024-01-11 22:22:07 +08:00
anhaohao 4f89eb6ba9 新增:产品详情页面 2024-01-11 17:51:45 +08:00
owen 2b8eb49f71 Review代码修改 2024-01-11 12:25:34 +08:00
owen bdf95dc0f8 Merge remote-tracking branch 'origin/dev' into dev 2024-01-11 11:35:08 +08:00
owen a9c231bed6 营销:统一装修预览图片字段名称 2024-01-11 11:21:23 +08:00
YunaiV efe702d383 📖 MALL:商品编辑 => 简化 InfoForm 基础设置 2024-01-11 09:42:23 +08:00
YunaiV 2c76d3aeee 📖 MALL:商品编辑 => 将 detail 还是使用 form 复用,减少维护成本 2024-01-11 00:25:39 +08:00
YunaiV c0905fdc8e 📖 MALL:商品编辑的简化 2024-01-11 00:00:43 +08:00
YunaiV f1c858b9af 📖 MALL:商品编辑的简化 2024-01-10 23:50:19 +08:00
YunaiV a5d6d18b6a 📖 MALL:优化商品列表的样式 2024-01-10 13:51:00 +08:00
YunaiV ab64a1d1df 📖 MALL:code review 满减送活动 2024-01-07 22:41:17 +08:00
芋道源码 1e68cd53a0 !352 满减送模块
Merge pull request !352 from Bluemark/dev
2024-01-07 14:16:02 +00:00
芋道源码 804859bbd8 !357 update src/views/bpm/model/ModelForm.vue.
Merge pull request !357 from 水人/N/A
2024-01-07 13:25:10 +00:00
YunaiV ec87c1b61d 📖 CRM:code review crm 客户列表 2024-01-07 21:20:00 +08:00
芋道源码 4eb3c8a2f9 !358 fix: 修复多文件上传一个一个选择上传第二个文件上传失效的问题
Merge pull request !358 from puhui999/dev-crm
2024-01-07 12:53:49 +00:00
YunaiV a0f7775321 📖 CRM:code review crm 联系人新增操作日志 2024-01-07 18:19:15 +08:00
芋道源码 09fa30b0d6 !359 crm联系人新增操作日志
Merge pull request !359 from zyna/dev
2024-01-07 10:15:05 +00:00
YunaiV 29e7161eba 📖 MALL:code review 商品统计的代码 2024-01-07 16:23:24 +08:00
芋道源码 9bdba0f67e !351 商品统计
Merge pull request !351 from 疯狂的世界/dev
2024-01-07 07:23:49 +00:00
zyna 3af6811c72 crm联系人新增操作日志 2024-01-06 21:04:58 +08:00
answer-huang 8801c82e15 fix: remove bg color with xl size 2024-01-06 21:00:02 +08:00
puhui999 17be1abb65 客户详情完善 2024-01-06 12:29:35 +08:00
YunaiV d36c4ab36c ✨ MALL:去除分销提现的银行配置 2024-01-05 22:46:22 +08:00
owen 1fb7701e6e Merge remote-tracking branch 'origin/dev' into dev 2024-01-05 15:26:41 +08:00
水人 81d84877bc update src/views/bpm/model/ModelForm.vue.
创建的是用路由地址,但那个表单查看的是VUE源码的组件地址,不是路由。给我造成了很大的困扰~

Signed-off-by: 水人 <417693680@qq.com>
2024-01-04 07:17:02 +00:00
puhui999 7fd564307d fix: 修复多文件上传一个一个选择上传第二个文件上传失效的问题 2024-01-04 12:10:01 +08:00
YunaiV 5e265a9094 ✨ CRM:【联系人】统一字段 2024-01-03 23:30:29 +08:00
YunaiV d71097ac31 📖 CRM:【客户配置】调整目录结构 2024-01-03 20:09:56 +08:00
YunaiV 4fbb3b88e6 📖 CRM:【客户】微调客户列表界面 2024-01-03 19:38:55 +08:00
YunaiV 7448f6e08c 📖 CRM:【客户】微调客户列表界面 2024-01-02 21:44:38 +08:00
YunaiV 0a4023ef00 📖 CRM:code review 商机模块 2024-01-02 18:48:47 +08:00
芋道源码 45521c139c !350 crm联系人商机review修改
Merge pull request !350 from zyna/dev
2023-12-30 13:54:56 +00:00
芋道源码 0338caeef6 !355 修复一些 issues 提到的问题
Merge pull request !355 from puhui999/dev-crm
2023-12-30 13:26:46 +00:00
YunaiV 1eefa02c4c 📖 操作日志的界面实现 2023-12-30 21:25:08 +08:00
芋道源码 631c105f94 !346 feat:新增公共操作日志详情组件
Merge pull request !346 from puhui999/dev-crm
2023-12-30 12:54:53 +00:00
puhui999 d766b36e72 fix: 修复请假申请提交后页面不返回的bug 2023-12-30 20:53:44 +08:00
YunaiV 8aa49f2e1d Merge remote-tracking branch 'origin/dev' into dev 2023-12-30 19:12:35 +08:00
YunaiV ccb997ac2a 🐛 修复 Excel 导出失败时,依然下载的问题 2023-12-30 19:11:29 +08:00
puhui999 aadad39a69 fix: 修复文件上传数据回显错误 2023-12-30 18:46:44 +08:00
puhui999 261d8b2aa4 crm-客户:集成操作日志详情组件 2023-12-30 17:06:09 +08:00
芋道源码 67630e31b2 !353 Update return list API endpoint
Merge pull request !353 from AhJindeg/fix/return-list
2023-12-29 15:06:29 +00:00
芋道源码 32dab6bc08 !348 update src/api/bpm/task/index.ts.
Merge pull request !348 from 水人/N/A
2023-12-29 15:06:04 +00:00
AhJindeg 2223f7f197 fix(task): 🐞 Update return list API endpoint
- 修改获取回退节点列表接口地址
2023-12-26 11:32:40 +08:00
落日晚风 21e8dbf985 满减送数据对接 2023-12-22 18:04:29 +08:00
YunaiV 9591ed7084 📖 code review:店铺装修逻辑 2023-12-20 23:01:09 +08:00
YunaiV b86f082306 📖 code review:店铺装修逻辑 2023-12-19 23:10:58 +08:00
zyna 2cfe006def crm联系人商机修改 2023-12-17 17:54:42 +08:00
zyna dd4482fad2 crm联系人商机修改 2023-12-17 17:37:40 +08:00
zyna a1fd3afc32 crm联系人商机修改 2023-12-17 17:34:32 +08:00
owen ca35e1a4af 统计:增加商品统计 2023-12-16 23:48:09 +08:00
YunaiV df1c565cd9 Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev 2023-12-16 20:49:56 +08:00
YunaiV be4750a174 Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev 2023-12-16 20:49:47 +08:00
水人 346863a8a9 update src/api/bpm/task/index.ts.
后台接口地址是return-list,前台写成了get-return-list

Signed-off-by: 水人 <417693680@qq.com>
2023-12-16 00:00:14 +00:00
芋道源码 1abff21e56 !347 商城装修
Merge pull request !347 from 疯狂的世界/dev
2023-12-15 15:37:01 +00:00
owen 80355f3acd 营销:优化商城装修组件 2023-12-15 17:12:02 +08:00
owen 33162a12c0 营销:适配商城装修组件【标题栏】 2023-12-15 13:26:28 +08:00
owen 41a404a52c 营销:适配商城装修组件【热区】 2023-12-14 20:22:52 +08:00
puhui999 455688eaa8 crm-客户:集成操作日志详情组件 2023-12-14 18:19:34 +08:00
puhui999 59c6670775 feat:新增公共操作日志详情组件 2023-12-14 18:18:55 +08:00
芋道源码 26a082788d !345 商城装修
Merge pull request !345 from 疯狂的世界/dev
2023-12-10 01:33:19 +00:00
owen edfdee8cc1 营销:适配商城装修组件【悬浮按钮】 2023-12-09 21:33:47 +08:00
owen 76025b6821 营销:适配商城装修组件【弹窗广告】 2023-12-09 16:45:54 +08:00
芋道源码 605f906056 !344 商城装修
Merge pull request !344 from 疯狂的世界/dev
2023-12-09 03:41:45 +00:00
owen 59ff6b13fb 封装拖拽组件,提供移动、新增、删除功能 2023-12-08 21:54:11 +08:00
owen 9ff4f1f7a9 商城装修:修复同一类型的组件切换时,右侧属性面板不更新的问题 2023-12-07 21:18:51 +08:00
owen a7512ba2c7 修复商城首页,快捷入口的图标宽高不一致的问题 2023-12-07 21:02:07 +08:00
owen 5002fc5fcc local不能作为环境名,改为local-dev 2023-12-07 20:53:30 +08:00
owen 16aac73858 整理运行环境配置文件 2023-12-07 20:43:58 +08:00
owen cd89f75b07 营销:完善装修编辑器预览功能 2023-12-07 19:59:32 +08:00
owen 784623724d 营销:完善装修编辑器预览功能 2023-12-07 19:58:52 +08:00
owen eec3a21852 营销:完善装修编辑器重置功能 2023-12-07 16:25:15 +08:00
owen 9128fa98f5 修复首页头像不显示 2023-12-07 12:19:46 +08:00
芋道源码 b4aa09aa6c !327 【新增】ureport2 集成
Merge pull request !327 from souvc/master
2023-12-06 12:57:44 +00:00
芋道源码 1e6ceabd3b !343 ✨ 优化:商品库存为0时,商品列表商品封面显示已售罄,商品详情页用户无法下单
Merge pull request !343 from 奕泽云/master
2023-12-06 12:16:28 +00:00
YunaiV ce79dd68a3 📈 CRM:code review 联系人商机功能 2023-12-06 19:26:39 +08:00
芋道源码 d6291382e8 !341 crm联系人商机功能
Merge pull request !341 from zyna/dev
2023-12-06 05:55:59 +00:00
YunaiV f8c4700c2e 🔨 CRM:优化产品分类代码的实现 2023-12-06 12:56:55 +08:00
YunaiV b3d66cd0aa 🔨 CRM:优化产品代码的实现 2023-12-06 12:32:51 +08:00
芋道源码 ee9667504f !342 【CRM:优化产品以及产品分类】
Merge pull request !342 from ZanGe丶/master
2023-12-05 14:36:16 +00:00
YunaiV 0c613e94a8 🐛 修复 CodegenColumnVO 的 autoIncrement 类型不正确 2023-12-05 20:03:40 +08:00
ZENGlei11350 dee8215da2 优化:添加商品时0库存商品无法保存改为可以保存 2023-12-04 13:42:18 +08:00
liuhongfeng d9ed3d1a6b Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 2023-12-03 23:01:52 +08:00
ZanGe丶 dde61c29a0 【crm:优化产品及产品分类】 2023-12-03 21:24:38 +08:00
zyna ba49b2851c 联系人商机功能 2023-12-03 19:56:46 +08:00
YunaiV 2f10f0ffc1 Merge remote-tracking branch 'origin/master' 2023-12-03 19:26:33 +08:00
Marvin aac60380bc feat: 跳转到例外一个标签页预览 2023-12-03 19:26:22 +08:00
Marvin 2a0b329565 feat: 新增文件内容项,图片时展示,pdf时跳转到预览,其他文件下载 2023-12-03 19:26:22 +08:00
Marvin 19e3297667 feat: 文件列表增加图片的展示和预览 2023-12-03 19:26:22 +08:00
puhui999 55c64d9c1b fix:修改 UploadImg 组件预览图片的方式为 createImageViewer,解决商城列表预览图片层级出现错误 2023-12-03 19:26:22 +08:00
Evol郑天 3009c04d0a 修复公众号-素材管理 上传时缺少accountId参数
Signed-off-by: Evol郑天 <jpevol@163.com>
2023-12-03 19:26:22 +08:00
芋道源码 c47aabe880 !333 文件列表增加图片的展示和预览
Merge pull request !333 from boide/file-preview
2023-12-03 11:02:48 +00:00
芋道源码 f9994c585c !340 fix:修改 UploadImg 组件预览图片的方式为 createImageViewer,解决列表预览图片层级出现错误
Merge pull request !340 from puhui999/master
2023-12-03 11:01:11 +00:00
YunaiV 7748704639 🐛 修复 accessLog、errorLog 在 IDEA 报错的问题 2023-12-03 18:33:33 +08:00
YunaiV 5eb30c7e85 🐛 修复 job 在 IDEA 报错的问题 2023-12-03 18:11:54 +08:00
liuhongfeng 0b9cf35b17 【修改】ureport2 集成 2023-12-03 17:00:00 +08:00
YunaiV 85fe4ad8d6 🐛 修复 file 在 IDEA 报错的问题 2023-12-03 10:34:08 +08:00
YunaiV aecc9bb8cb 🐛 修复 IDEA 在 v-for="dict in getIntDictOptions(...)" 时,el-option 的 key 会告警的问题 2023-12-03 09:52:13 +08:00
YunaiV 3d2d48b601 🐛 修复 user 在 IDEA 报错的问题 2023-12-03 01:03:41 +08:00
puhui999 f3e89c0c55 fix:修改 UploadImg 组件预览图片的方式为 createImageViewer,解决商城列表预览图片层级出现错误 2023-12-03 00:14:04 +08:00
YunaiV fa421d0895 🐛 修复 tenant 在 IDEA 报错的问题 2023-12-02 23:43:16 +08:00
YunaiV a6deb69ddc 🐛 修复 social 在 IDEA 报错的问题 2023-12-02 23:01:31 +08:00
YunaiV 3407cc66a9 🐛 修复 sms 在 IDEA 报错的问题 2023-12-02 22:39:52 +08:00
YunaiV dd4a77ff22 🐛 修复 sensitiveWord 在 IDEA 报错的问题 2023-12-02 22:02:20 +08:00
YunaiV 6593c603a2 🐛 修复 menu 在 IDEA 报错的问题 2023-12-02 21:47:20 +08:00
YunaiV 2ceb3b978c 🐛 修复 oauth2 在 IDEA 报错的问题 2023-12-02 20:49:08 +08:00
YunaiV 5766dc81d1 🐛 修复 notify 在 IDEA 报错的问题 2023-12-02 20:25:29 +08:00
YunaiV b103c40874 🐛 修复 notice 在 IDEA 报错的问题 2023-12-02 20:08:05 +08:00
YunaiV 1066551fd1 🐛 修复 operateLog 和 loginLog 在 IDEA 报错的问题 2023-12-02 19:25:50 +08:00
YunaiV 757fbddef2 🐛 修复 dictType 和 dictData 在 IDEA 报错的问题 2023-12-02 18:22:02 +08:00
YunaiV a77173f2ae 🐛 修复 post 和 dept 在 IDEA 报错的问题 2023-12-02 10:20:09 +08:00
芋道源码 d6ff66dc1d !339 商城装修
Merge pull request !339 from 疯狂的世界/dev
2023-12-02 01:01:14 +00:00
owen b982397449 Merge remote-tracking branch 'origin/dev' into dev 2023-12-02 00:00:50 +08:00
owen 3c8ffc8c21 营销:适配商城装修组件【用户卡券】 2023-12-01 23:58:57 +08:00
owen c058048fac 营销:适配商城装修组件【用户资产】 2023-12-01 23:40:07 +08:00
owen 2c1edfa781 营销:适配商城装修组件【用户订单】 2023-12-01 23:34:19 +08:00
owen af67db577d 营销:适配商城装修组件【用户卡片】 2023-12-01 23:12:44 +08:00
YunaiV b0c1e55c6a crm: 增加基于客户查询回款计划分页 2023-12-01 22:46:20 +08:00
owen d5f9d31d32 营销:适配商城装修组件【拼团】(待重写) 2023-12-01 21:00:39 +08:00
owen 7e44b621a5 营销:适配商城装修组件【秒杀】(待重写) 2023-12-01 20:24:55 +08:00
YunaiV bdc80ea55b crm: 增加基于客户查询回款分页 2023-12-01 19:37:51 +08:00
owen b4ba1043d6 eslint 2023-12-01 18:57:46 +08:00
Marvin a711e96b23 feat: 跳转到例外一个标签页预览 2023-12-01 09:53:41 +08:00
Marvin 915702183d feat: 新增文件内容项,图片时展示,pdf时跳转到预览,其他文件下载 2023-12-01 09:50:39 +08:00
owen 08be359578 Merge remote-tracking branch 'origin/dev' into dev 2023-12-01 00:39:55 +08:00
owen 23a6bf5ef5 营销:商城装修增加控件【APP 链接选择】 2023-12-01 00:39:10 +08:00
YunaiV 88e5e67a89 crm: 增加基于客户查询商机分页 2023-11-30 20:21:45 +08:00
YunaiV 31a42fc3f7 crm:增加合同 List 组件 2023-11-30 19:12:57 +08:00
YunaiV 5f26c4afe9 crm:完善数据权限的界面 2023-11-30 13:42:49 +08:00
YunaiV 7de0e93d5a crm:联系人增加 ContactList 组件,提供给其它模块内嵌 2023-11-29 21:42:10 +08:00
YunaiV 2cda0cdd9f Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	src/views/crm/contact/ContactForm.vue
2023-11-29 14:01:20 +08:00
YunaiV 8b787176b6 crm:code review 客户详情 2023-11-29 13:59:53 +08:00
wanwan 443f65b5dc !334 feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
2023-11-29 13:59:52 +08:00
YunaiV ddfceddf4a crm:code review 联系人详情 2023-11-29 13:57:01 +08:00
YunaiV ae8cb9218c crm:code review 客户详情 2023-11-29 13:55:53 +08:00
wanwan 85810c0786 !334 feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
2023-11-29 13:55:53 +08:00
YunaiV c5b2fdecd5 crm:code review 商机类型 2023-11-29 13:55:53 +08:00
ljlleo 50430bb9cd 商机、商机状态类型 2023-11-29 13:55:53 +08:00
YunaiV 661dc2ff67 crm:code review 成员 2023-11-29 13:55:53 +08:00
YunaiV d69134adfa Merge remote-tracking branch 'origin/dev' into dev 2023-11-27 20:38:49 +08:00
YunaiV 1ba450103e crm:code review 联系人详情 2023-11-27 20:33:53 +08:00
YunaiV 24773a6eaa crm:code review 客户详情 2023-11-27 20:31:23 +08:00
wanwan e1f9e16a77 !334 feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
2023-11-27 20:31:23 +08:00
YunaiV 3c19cb1a85 crm:code review 商机类型 2023-11-27 20:31:23 +08:00
ljlleo b3515b2db5 商机、商机状态类型 2023-11-27 20:31:23 +08:00
YunaiV 4e1748caa3 crm:code review 成员 2023-11-27 20:31:23 +08:00
YunaiV cec8582a90 crm:code review 客户详情 2023-11-26 20:18:33 +08:00
wanwan 324d1e859e !334 feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
* feat: 客户配置 review 修改
2023-11-26 12:02:08 +00:00
Marvin c2e8eaed60 feat: 文件列表增加图片的展示和预览 2023-11-25 22:56:31 +08:00
liuhongfeng 7df7719be3 Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 2023-11-25 17:45:32 +08:00
owen d4dc105b4f 营销:适配商城装修组件【营销文章】 2023-11-25 15:53:07 +08:00
YunaiV 36702f5f90 crm:code review 商机类型 2023-11-25 12:01:35 +08:00
芋道源码 937fc17150 !330 商机、商机状态类型页面
Merge pull request !330 from 刘先生/dev
2023-11-25 03:56:08 +00:00
YunaiV eb97447bb7 crm:code review 成员 2023-11-25 10:42:44 +08:00
liuhongfeng 764e1715be Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 2023-11-24 22:56:39 +08:00
ljlleo 2700b08c69 商机、商机状态类型 2023-11-24 14:21:21 +08:00
liuhongfeng aceac6daae 【修改】ureport2 集成 2023-11-22 22:09:48 +08:00
liuhongfeng 4440ec866c 【新增】ureport2 集成 2023-11-20 23:32:54 +08:00
297 changed files with 10785 additions and 3818 deletions

No files matched your search

-19
View File
@@ -1,19 +0,0 @@
# 本地开发环境
NODE_ENV=development
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://127.0.0.1:48080'
# 上传路径
VITE_UPLOAD_URL='http://127.0.0.1:48080/admin-api/infra/file/upload'
# 接口前缀
VITE_API_BASEPATH=/dev-api
# 接口地址
VITE_API_URL=/admin-api
# 打包路径
VITE_BASE_PATH=/
+16 -9
View File
@@ -1,13 +1,14 @@
# 开发环境
# 开发环境:本地只启动前端项目,依赖开发环境(后端、APP)
NODE_ENV=development
VITE_DEV=false
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://localhost:48080'
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
# VITE_BASE_URL='http://dofast.demo.huizhizao.vip:20001'
# 上传路径
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
VITE_UPLOAD_URL='http://api-dashboard.yudao.iocoder.cn/admin-api/infra/file/upload'
# 接口前缀
VITE_API_BASEPATH=/dev-api
@@ -15,17 +16,23 @@ VITE_API_BASEPATH=/dev-api
# 接口地址
VITE_API_URL=/admin-api
# 打包路径
VITE_BASE_PATH=/
# 是否删除debugger
VITE_DROP_DEBUGGER=true
VITE_DROP_DEBUGGER=false
# 是否删除console.log
VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=false
VITE_SOURCEMAP=true
# 打包路径
VITE_BASE_PATH=/
# 输出路径
VITE_OUT_DIR=dist
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=false
+10 -10
View File
@@ -1,13 +1,13 @@
# 本地开发环境
# 本地开发环境:本地启动所有项目(前端、后端、APP)时使用,不依赖外部环境
NODE_ENV=development
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
VITE_BASE_URL='http://localhost:48080'
# 上传路径
VITE_UPLOAD_URL='http://api-dashboard.yudao.iocoder.cn/admin-api/infra/file/upload'
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
# 接口前缀
VITE_API_BASEPATH=/dev-api
@@ -15,12 +15,6 @@ VITE_API_BASEPATH=/dev-api
# 接口地址
VITE_API_URL=/admin-api
# 打包路径
VITE_BASE_PATH=/
# 项目本地运行端口号, 与.vscode/launch.json配合
VITE_PORT=80
# 是否删除debugger
VITE_DROP_DEBUGGER=false
@@ -28,7 +22,13 @@ VITE_DROP_DEBUGGER=false
VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=true
VITE_SOURCEMAP=false
# 打包路径
VITE_BASE_PATH=/
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://localhost:3000'
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=false
+5 -2
View File
@@ -1,4 +1,4 @@
# 生产环境
# 生产环境:只在打包时使用
NODE_ENV=production
VITE_DEV=false
@@ -28,4 +28,7 @@ VITE_SOURCEMAP=false
VITE_BASE_PATH=/
# 输出路径
VITE_OUT_DIR=dist-pro
VITE_OUT_DIR=dist-prod
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
+4 -1
View File
@@ -1,4 +1,4 @@
# 生产环境
# 预发布环境:只在打包时使用
NODE_ENV=production
VITE_DEV=false
@@ -29,3 +29,6 @@ VITE_BASE_PATH='http://static-vue3.yudao.iocoder.cn/'
# 输出路径
VITE_OUT_DIR=dist-stage
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
+5 -2
View File
@@ -1,4 +1,4 @@
# 开发环境
# 测试环境:只在打包时使用
NODE_ENV=production
VITE_DEV=false
@@ -28,4 +28,7 @@ VITE_SOURCEMAP=false
VITE_BASE_PATH=/admin-ui-vue3/
# 输出路径
VITE_OUT_DIR=dist-dev
VITE_OUT_DIR=dist-test
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
+1 -1
View File
@@ -83,7 +83,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
+9 -12
View File
@@ -1,23 +1,22 @@
{
"name": "yudao-ui-admin-vue3",
"version": "1.8.3-snapshot",
"version": "2.0.0-snapshot",
"description": "基于vue3、vite4、element-plus、typesScript",
"author": "xingyu",
"private": false,
"scripts": {
"i": "pnpm install",
"dev": "vite --mode base",
"front": "vite --mode front",
"dev": "vite --mode local-dev",
"dev-server": "vite --mode dev",
"ts:check": "vue-tsc --noEmit",
"build:pro": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode pro",
"build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode dev",
"build:base": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode base",
"build:local-dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev",
"build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev",
"build:test": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode test",
"build:stage": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage",
"build:static": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode static",
"build:front": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode front",
"serve:pro": "vite preview --mode pro",
"build:prod": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode prod",
"serve:dev": "vite preview --mode dev",
"preview": "pnpm build:base && vite preview",
"serve:prod": "vite preview --mode prod",
"preview": "pnpm build:local-dev && vite preview",
"clean": "npx rimraf node_modules",
"clean:cache": "npx rimraf node_modules/.cache",
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
@@ -58,7 +57,6 @@
"pinia": "^2.1.7",
"qrcode": "^1.5.3",
"qs": "^6.11.2",
"sortablejs": "^1.15.0",
"steady-xml": "^0.1.0",
"url": "^0.11.3",
"video.js": "^7.21.5",
@@ -82,7 +80,6 @@
"@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.5",
"@types/qs": "^6.9.10",
"@types/sortablejs": "^1.15.5",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@unocss/transformer-variant-group": "^0.57.4",
+29
View File
@@ -20,6 +20,17 @@ export type ProcessInstanceVO = {
endTime: string
}
export type ProcessInstanceCCVO = {
type: number,
taskName: string,
taskKey: string,
processInstanceName: string,
processInstanceKey: string,
startUserId: string,
options:string [],
reason: string
}
export const getMyProcessInstancePage = async (params) => {
return await request.get({ url: '/bpm/process-instance/my-page', params })
}
@@ -39,3 +50,21 @@ export const cancelProcessInstance = async (id: number, reason: string) => {
export const getProcessInstance = async (id: number) => {
return await request.get({ url: '/bpm/process-instance/get?id=' + id })
}
/**
* 抄送
* @param data 抄送数据
* @returns 是否抄送成功
*/
export const createProcessInstanceCC = async (data) => {
return await request.post({ url: '/bpm/process-instance/cc/create', data: data })
}
/**
* 抄送列表
* @param params
* @returns
*/
export const getProcessInstanceCCPage = async (params) => {
return await request.get({ url: '/bpm/process-instance/cc/my-page', params })
}
+1 -1
View File
@@ -44,7 +44,7 @@ export const exportTask = async (params) => {
// 获取所有可回退的节点
export const getReturnList = async (params) => {
return await request.get({ url: '/bpm/task/get-return-list', params })
return await request.get({ url: '/bpm/task/return-list', params })
}
// 回退
+72
View File
@@ -0,0 +1,72 @@
import request from '@/config/axios'
export interface BusinessVO {
id: number
name: string
statusTypeId: number
statusId: number
contactNextTime: Date
customerId: number
dealTime: Date
price: number
discountPercent: number
productPrice: number
remark: string
ownerUserId: number
roUserIds: string
rwUserIds: string
endStatus: number
endRemark: string
contactLastTime: Date
followUpStatus: number
}
// 查询 CRM 商机列表
export const getBusinessPage = async (params) => {
return await request.get({ url: `/crm/business/page`, params })
}
// 查询 CRM 商机列表,基于指定客户
export const getBusinessPageByCustomer = async (params) => {
return await request.get({ url: `/crm/business/page-by-customer`, params })
}
// 查询 CRM 商机详情
export const getBusiness = async (id: number) => {
return await request.get({ url: `/crm/business/get?id=` + id })
}
// 获得 CRM 商机列表(精简)
export const getSimpleBusinessList = async () => {
return await request.get({ url: `/crm/business/simple-all-list` })
}
// 新增 CRM 商机
export const createBusiness = async (data: BusinessVO) => {
return await request.post({ url: `/crm/business/create`, data })
}
// 修改 CRM 商机
export const updateBusiness = async (data: BusinessVO) => {
return await request.put({ url: `/crm/business/update`, data })
}
// 删除 CRM 商机
export const deleteBusiness = async (id: number) => {
return await request.delete({ url: `/crm/business/delete?id=` + id })
}
// 导出 CRM 商机 Excel
export const exportBusiness = async (params) => {
return await request.download({ url: `/crm/business/export-excel`, params })
}
// 联系人关联商机列表
export const getBusinessPageByContact = async (params) => {
return await request.get({ url: `/crm/business/page-by-contact`, params })
}
// 获得 CRM 商机列表
export const getBusinessListByIds = async (val: number[]) => {
return await request.get({ url: '/crm/business/list-by-ids', params: { ids: val.join(',') } })
}
+48
View File
@@ -0,0 +1,48 @@
import request from '@/config/axios'
export interface BusinessStatusTypeVO {
id: number
name: string
deptIds: number[]
status: boolean
}
// 查询商机状态类型列表
export const getBusinessStatusTypePage = async (params) => {
return await request.get({ url: `/crm/business-status-type/page`, params })
}
// 查询商机状态类型详情
export const getBusinessStatusType = async (id: number) => {
return await request.get({ url: `/crm/business-status-type/get?id=` + id })
}
// 新增商机状态类型
export const createBusinessStatusType = async (data: BusinessStatusTypeVO) => {
return await request.post({ url: `/crm/business-status-type/create`, data })
}
// 修改商机状态类型
export const updateBusinessStatusType = async (data: BusinessStatusTypeVO) => {
return await request.put({ url: `/crm/business-status-type/update`, data })
}
// 删除商机状态类型
export const deleteBusinessStatusType = async (id: number) => {
return await request.delete({ url: `/crm/business-status-type/delete?id=` + id })
}
// 导出商机状态类型 Excel
export const exportBusinessStatusType = async (params) => {
return await request.download({ url: `/crm/business-status-type/export-excel`, params })
}
// 获取商机状态类型信息列表
export const getBusinessStatusTypeList = async () => {
return await request.get({ url: `/crm/business-status-type/get-simple-list` })
}
// 根据类型ID获取商机状态信息列表
export const getBusinessStatusListByTypeId = async (typeId: number) => {
return await request.get({ url: `/crm/business-status-type/get-status-list?typeId=` + typeId })
}
+42 -17
View File
@@ -1,10 +1,3 @@
/*
* @Author: zyna
* @Date: 2023-11-05 13:34:41
* @LastEditTime: 2023-11-26 20:47:04
* @FilePath: \yudao-ui-admin-vue3\src\api\crm\contact\index.ts
* @Description:
*/
import request from '@/config/axios'
export interface ContactVO {
@@ -15,7 +8,7 @@ export interface ContactVO {
email: string
post: string
customerId: number
address: string
detailAddress: string
remark: string
ownerUserId: string
lastTime: Date
@@ -28,40 +21,72 @@ export interface ContactVO {
creatorName: string
updateTime?: Date
createTime?: Date
customerName: string,
areaName: string,
customerName: string
areaName: string
ownerUserName: string
}
// 查询crm联系人列表
export interface ContactBusinessReqVO {
contactId: number
businessIds: number[]
}
// 查询 CRM 联系人列表
export const getContactPage = async (params) => {
return await request.get({ url: `/crm/contact/page`, params })
}
// 查询crm联系人详情
// 查询 CRM 联系人列表,基于指定客户
export const getContactPageByCustomer = async (params: any) => {
return await request.get({ url: `/crm/contact/page-by-customer`, params })
}
// 查询 CRM 联系人详情
export const getContact = async (id: number) => {
return await request.get({ url: `/crm/contact/get?id=` + id })
}
// 新增crm联系人
// 新增 CRM 联系人
export const createContact = async (data: ContactVO) => {
return await request.post({ url: `/crm/contact/create`, data })
}
// 修改crm联系人
// 修改 CRM 联系人
export const updateContact = async (data: ContactVO) => {
return await request.put({ url: `/crm/contact/update`, data })
}
// 删除crm联系人
// 删除 CRM 联系人
export const deleteContact = async (id: number) => {
return await request.delete({ url: `/crm/contact/delete?id=` + id })
}
// 导出crm联系人 Excel
// 导出 CRM 联系人 Excel
export const exportContact = async (params) => {
return await request.download({ url: `/crm/contact/export-excel`, params })
}
export const simpleAllList = async () => {
// 获得 CRM 联系人列表(精简)
export const getSimpleContactList = async () => {
return await request.get({ url: `/crm/contact/simple-all-list` })
}
// 获得 CRM 联系人列表
export const getContactListByIds = async (val: number[]) => {
return await request.get({ url: '/crm/contact/list-by-ids', params: { ids: val.join(',') } })
}
// 批量新增联系人商机关联
export const createContactBusinessList = async (data: ContactBusinessReqVO) => {
return await request.post({ url: `/crm/contact/create-business-list`, data })
}
// 解除联系人商机关联
export const deleteContactBusinessList = async (data: ContactBusinessReqVO) => {
return await request.delete({ url: `/crm/contact/delete-business-list`, data })
}
// 查询联系人操作日志
export const getOperateLogPage = async (params: any) => {
return await request.get({ url: '/crm/contact/operate-log-page', params })
}
+11 -6
View File
@@ -22,32 +22,37 @@ export interface ContractVO {
remark: string
}
// 查询合同列表
// 查询 CRM 合同列表
export const getContractPage = async (params) => {
return await request.get({ url: `/crm/contract/page`, params })
}
// 查询合同详情
// 查询 CRM 联系人列表,基于指定客户
export const getContractPageByCustomer = async (params: any) => {
return await request.get({ url: `/crm/contract/page-by-customer`, params })
}
// 查询 CRM 合同详情
export const getContract = async (id: number) => {
return await request.get({ url: `/crm/contract/get?id=` + id })
}
// 新增合同
// 新增 CRM 合同
export const createContract = async (data: ContractVO) => {
return await request.post({ url: `/crm/contract/create`, data })
}
// 修改合同
// 修改 CRM 合同
export const updateContract = async (data: ContractVO) => {
return await request.put({ url: `/crm/contract/update`, data })
}
// 删除合同
// 删除 CRM 合同
export const deleteContract = async (id: number) => {
return await request.delete({ url: `/crm/contract/delete?id=` + id })
}
// 导出合同 Excel
// 导出 CRM 合同 Excel
export const exportContract = async (params) => {
return await request.download({ url: `/crm/contract/export-excel`, params })
}
+39 -5
View File
@@ -59,10 +59,44 @@ export const deleteCustomer = async (id: number) => {
}
// 导出客户 Excel
export const exportCustomer = async (params) => {
export const exportCustomer = async (params: any) => {
return await request.download({ url: `/crm/customer/export-excel`, params })
}
//客户列表
export const queryAllList = async () => {
return await request.get({ url: `/crm/customer/query-all-list` })
}
// 客户列表
export const getSimpleCustomerList = async () => {
return await request.get({ url: `/crm/customer/list-all-simple` })
}
// 查询客户操作日志
export const getOperateLogPage = async (id: number) => {
return await request.get({ url: '/crm/customer/operate-log-page?id=' + id })
}
// ======================= 业务操作 =======================
export interface TransferReqVO {
id: number | undefined // 客户编号
newOwnerUserId: number | undefined // 新负责人的用户编号
oldOwnerPermissionLevel: number | undefined // 老负责人加入团队后的权限级别
}
// 客户转移
export const transfer = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/customer/transfer', data })
}
// 锁定/解锁客户
export const lockCustomer = async (id: number, lockStatus: boolean) => {
return await request.put({ url: `/crm/customer/lock`, data: { id, lockStatus } })
}
// 领取公海客户
export const receiveCustomer = async (ids: any[]) => {
return await request.put({ url: '/crm/customer/receive', params: { ids: ids.join(',') } })
}
// 客户放入公海
export const putCustomerPool = async (id: number) => {
return await request.put({ url: `/crm/customer/put-pool?id=${id}` })
}
@@ -9,6 +9,20 @@ export interface CustomerLimitConfigVO {
dealCountEnabled?: boolean
}
/**
* 客户限制配置类型
*/
export enum LimitConfType {
/**
* 拥有客户数限制
*/
CUSTOMER_QUANTITY_LIMIT = 1,
/**
* 锁定客户数限制
*/
CUSTOMER_LOCK_LIMIT = 2
}
// 查询客户限制配置列表
export const getCustomerLimitConfigPage = async (params) => {
return await request.get({ url: `/crm/customer-limit-config/page`, params })
@@ -5,7 +5,7 @@ export interface CustomerPoolConfigVO {
contactExpireDays?: number
dealExpireDays?: number
notifyEnabled?: boolean
notifyDays: number
notifyDays?: number
}
// 获取客户公海规则设置
@@ -14,6 +14,6 @@ export const getCustomerPoolConfig = async () => {
}
// 更新客户公海规则设置
export const updateCustomerPoolConfig = async (data: ConfigVO) => {
return await request.put({ url: `/crm/customer-pool-config/update`, data })
export const saveCustomerPoolConfig = async (data: CustomerPoolConfigVO) => {
return await request.put({ url: `/crm/customer-pool-config/save`, data })
}
+46
View File
@@ -0,0 +1,46 @@
import request from '@/config/axios'
// 跟进记录 VO
export interface FollowUpRecordVO {
id: number // 编号
bizType: number // 数据类型
bizId: number // 数据编号
type: number // 跟进类型
content: string // 跟进内容
nextTime: Date // 下次联系时间
businessIds: number[] // 关联的商机编号数组
contactIds: number[] // 关联的联系人编号数组
}
// 跟进记录 API
export const FollowUpRecordApi = {
// 查询跟进记录分页
getFollowUpRecordPage: async (params: any) => {
return await request.get({ url: `/crm/follow-up-record/page`, params })
},
// 查询跟进记录详情
getFollowUpRecord: async (id: number) => {
return await request.get({ url: `/crm/follow-up-record/get?id=` + id })
},
// 新增跟进记录
createFollowUpRecord: async (data: FollowUpRecordVO) => {
return await request.post({ url: `/crm/follow-up-record/create`, data })
},
// 修改跟进记录
updateFollowUpRecord: async (data: FollowUpRecordVO) => {
return await request.put({ url: `/crm/follow-up-record/update`, data })
},
// 删除跟进记录
deleteFollowUpRecord: async (id: number) => {
return await request.delete({ url: `/crm/follow-up-record/delete?id=` + id })
},
// 导出跟进记录 Excel
exportFollowUpRecord: async (params) => {
return await request.download({ url: `/crm/follow-up-record/export-excel`, params })
}
}
+40
View File
@@ -0,0 +1,40 @@
import request from '@/config/axios'
export interface CustomerVO {
id?: number
name: string
industryId: number
level: number
source: number
followUpStatus?: boolean
lockStatus?: boolean
dealStatus?: boolean
mobile: string
telephone: string
website: string
qq: string
wechat: string
email: string
description: string
remark: string
ownerUserId?: number
ownerUserName?: string
ownerUserDept?: string
roUserIds?: string
rwUserIds?: string
areaId?: number
areaName?: string
detailAddress: string
contactLastTime?: Date
contactNextTime: Date
createTime?: Date
updateTime?: Date
creator?: string
creatorName?: string
}
// 查询客户列表
// TODO @芋艿:看看是不是后续融合到 getCustomerPage 里;
export const getTodayCustomerPage = async (params) => {
return await request.get({ url: `/crm/message/todayCustomer`, params })
}
+29 -17
View File
@@ -12,37 +12,49 @@ export interface PermissionVO {
createTime?: Date
}
// 查询团队成员列表
/**
* CRM 业务类型枚举
*
* @author HUIHUI
*/
export enum BizTypeEnum {
CRM_LEADS = 1, // 线索
CRM_CUSTOMER = 2, // 客户
CRM_CONTACT = 3, // 联系人
CRM_BUSINESS = 5, // 商机
CRM_CONTRACT = 6 // 合同
}
/**
* CRM 数据权限级别枚举
*/
export enum PermissionLevelEnum {
OWNER = 1, // 负责人
READ = 2, // 只读
WRITE = 3 // 读写
}
// 获得数据权限列表(查询团队成员列表)
export const getPermissionList = async (params) => {
return await request.get({ url: `/crm/permission/list`, params })
}
// 新增团队成员
// 创建数据权限(新增团队成员)
export const createPermission = async (data: PermissionVO) => {
return await request.post({ url: `/crm/permission/create`, data })
}
// 修改团队成员权限级别
// 编辑数据权限(修改团队成员权限级别)
export const updatePermission = async (data) => {
return await request.put({ url: `/crm/permission/update`, data })
}
// 删除团队成员
export const deletePermission = async (params) => {
// 删除数据权限(删除团队成员)
export const deletePermissionBatch = async (params) => {
return await request.delete({ url: '/crm/permission/delete', params })
}
// 退出团队
export const quitTeam = async (id) => {
// 删除自己的数据权限(退出团队)
export const deleteSelfPermission = async (id) => {
return await request.delete({ url: '/crm/permission/quit-team?id=' + id })
}
// 领取公海数据
export const receive = async (data: { bizType: number; bizId: number }) => {
return await request.put({ url: `/crm/permission/receive`, data })
}
// 数据放入公海
export const putPool = async (data: { bizType: number; bizId: number }) => {
return await request.put({ url: `/crm/permission/put-pool`, data })
}
+6 -1
View File
@@ -4,7 +4,7 @@ export interface ProductVO {
id: number
name: string
no: string
unit: string
unit: number
price: number
status: number
categoryId: number
@@ -41,3 +41,8 @@ export const deleteProduct = async (id: number) => {
export const exportProduct = async (params) => {
return await request.download({ url: `/crm/product/export-excel`, params })
}
// 查询产品操作日志
export const getOperateLogPage = async (params: any) => {
return await request.get({ url: '/crm/product/operate-log-page', params })
}
+12 -11
View File
@@ -6,46 +6,47 @@ export interface ReceivableVO {
planId: number
customerId: number
contractId: number
checkStatus: number
auditStatus: number
processInstanceId: number
returnTime: Date
returnType: string
price: number
ownerUserId: number
batchId: number
sort: number
dataScope: number
dataScopeDeptIds: string
status: number
remark: string
}
// 查询回款管理列表
// 查询回款列表
export const getReceivablePage = async (params) => {
return await request.get({ url: `/crm/receivable/page`, params })
}
// 查询回款管理详情
// 查询回款列表
export const getReceivablePageByCustomer = async (params) => {
return await request.get({ url: `/crm/receivable/page-by-customer`, params })
}
// 查询回款详情
export const getReceivable = async (id: number) => {
return await request.get({ url: `/crm/receivable/get?id=` + id })
}
// 新增回款管理
// 新增回款
export const createReceivable = async (data: ReceivableVO) => {
return await request.post({ url: `/crm/receivable/create`, data })
}
// 修改回款管理
// 修改回款
export const updateReceivable = async (data: ReceivableVO) => {
return await request.put({ url: `/crm/receivable/update`, data })
}
// 删除回款管理
// 删除回款
export const deleteReceivable = async (id: number) => {
return await request.delete({ url: `/crm/receivable/delete?id=` + id })
}
// 导出回款管理 Excel
// 导出回款 Excel
export const exportReceivable = async (params) => {
return await request.download({ url: `/crm/receivable/export-excel`, params })
}
@@ -23,6 +23,11 @@ export const getReceivablePlanPage = async (params) => {
return await request.get({ url: `/crm/receivable-plan/page`, params })
}
// 查询回款计划列表
export const getReceivablePlanPageByCustomer = async (params) => {
return await request.get({ url: `/crm/receivable-plan/page-by-customer`, params })
}
// 查询回款计划详情
export const getReceivablePlan = async (id: number) => {
return await request.get({ url: `/crm/receivable-plan/get?id=` + id })
+1 -1
View File
@@ -28,7 +28,7 @@ export type CodegenColumnVO = {
columnComment: string
nullable: number
primaryKey: number
autoIncrement: string
autoIncrement: boolean
ordinalPosition: number
javaType: string
javaField: string
+1 -1
View File
@@ -37,4 +37,4 @@ export const deleteDemo01Contact = async (id: number) => {
// 导出示例联系人 Excel
export const exportDemo01Contact = async (params) => {
return await request.download({ url: `/infra/demo01-contact/export-excel`, params })
}
}
+1 -1
View File
@@ -34,4 +34,4 @@ export const deleteDemo02Category = async (id: number) => {
// 导出示例分类 Excel
export const exportDemo02Category = async (params) => {
return await request.download({ url: `/infra/demo02-category/export-excel`, params })
}
}
+1 -1
View File
@@ -88,4 +88,4 @@ export const deleteDemo03Grade = async (id: number) => {
// 获得学生班级
export const getDemo03Grade = async (id: number) => {
return await request.get({ url: `/infra/demo03-student/demo03-grade/get?id=` + id })
}
}
+7 -3
View File
@@ -42,12 +42,16 @@ export const exportDemo03Student = async (params) => {
// 获得学生课程列表
export const getDemo03CourseListByStudentId = async (studentId) => {
return await request.get({ url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId })
return await request.get({
url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId
})
}
// ==================== 子表(学生班级) ====================
// 获得学生班级
export const getDemo03GradeByStudentId = async (studentId) => {
return await request.get({ url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId })
}
return await request.get({
url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId
})
}
+7 -3
View File
@@ -42,12 +42,16 @@ export const exportDemo03Student = async (params) => {
// 获得学生课程列表
export const getDemo03CourseListByStudentId = async (studentId) => {
return await request.get({ url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId })
return await request.get({
url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId
})
}
// ==================== 子表(学生班级) ====================
// 获得学生班级
export const getDemo03GradeByStudentId = async (studentId) => {
return await request.get({ url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId })
}
return await request.get({
url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId
})
}
+1 -1
View File
@@ -17,7 +17,7 @@ export interface FileClientConfig {
export interface FileConfigVO {
id: number
name: string
storage: any
storage?: number
master: boolean
visible: boolean
config: FileClientConfig
+2 -2
View File
@@ -7,8 +7,8 @@ export interface JobLogVO {
handlerParam: string
cronExpression: string
executeIndex: string
beginTime: string
endTime: string
beginTime: Date
endTime: Date
duration: string
status: number
createTime: string
-4
View File
@@ -20,10 +20,6 @@ export interface CategoryVO {
* 移动端分类图
*/
picUrl: string
/**
* PC 端分类图
*/
bigPicUrl?: string
/**
* 分类排序
*/
-10
View File
@@ -65,16 +65,6 @@ export const getPropertyPage = (params: PageParam) => {
return request.get({ url: '/product/property/page', params })
}
// 获得属性项列表
export const getPropertyList = (params: any) => {
return request.get({ url: '/product/property/list', params })
}
// 获得属性项列表
export const getPropertyListAndValue = (data: any) => {
return request.post({ url: '/product/property/get-value-list', data })
}
// ------------------------ 属性值 -------------------
// 获得属性值分页
+3 -8
View File
@@ -33,14 +33,15 @@ export interface GiveCouponTemplate {
export interface Spu {
id?: number
name?: string // 商品名称
categoryId?: number | undefined // 商品分类
categoryId?: number // 商品分类
keyword?: string // 关键字
unit?: number | undefined // 单位
picUrl?: string // 商品封面图
sliderPicUrls?: string[] // 商品轮播图
introduction?: string // 商品简介
deliveryTypes?: number[] // 配送方式
deliveryTemplateId?: number | undefined // 运费模版
brandId?: number | undefined // 商品品牌编号
brandId?: number // 商品品牌编号
specType?: boolean // 商品规格
subCommissionType?: boolean // 分销类型
skus?: Sku[] // sku数组
@@ -48,11 +49,6 @@ export interface Spu {
sort?: number // 商品排序
giveIntegral?: number // 赠送积分
virtualSalesCount?: number // 虚拟销量
recommendHot?: boolean // 是否热卖
recommendBenefit?: boolean // 是否优惠
recommendBest?: boolean // 是否精品
recommendNew?: boolean // 是否新品
recommendGood?: boolean // 是否优品
price?: number // 商品价格
salesCount?: number // 商品销量
marketPrice?: number // 市场价
@@ -60,7 +56,6 @@ export interface Spu {
stock?: number // 商品库存
createTime?: Date // 商品创建时间
status?: number // 商品状态
activityOrders: number[] // 活动排序
}
// 获得 Spu 列表
+1 -1
View File
@@ -17,7 +17,7 @@ export interface ArticleVO {
}
// 查询文章管理列表
export const getArticlePage = async (params) => {
export const getArticlePage = async (params: any) => {
return await request.get({ url: `/promotion/article/page`, params })
}
@@ -18,15 +18,10 @@ export interface CombinationRecordVO {
}
// 查询拼团记录列表
export const getCombinationRecordPage = async (params) => {
export const getCombinationRecordPage = async (params: any) => {
return await request.get({ url: '/promotion/combination-record/page', params })
}
// 查询一个拼团的完整拼团记录
export const getCombinationRecordPageByHeadId = async (params) => {
return await request.get({ url: '/promotion/combination-record/page-by-headId', params })
}
// 获得拼团记录的概要信息
export const getCombinationRecordSummary = async () => {
return await request.get({ url: '/promotion/combination-record/get-summary' })
+1 -1
View File
@@ -5,7 +5,7 @@ export interface DiyPageVO {
templateId?: number
name: string
remark: string
previewImageUrls: string[]
previewPicUrls: string[]
property: string
}
+1 -1
View File
@@ -7,7 +7,7 @@ export interface DiyTemplateVO {
used: boolean
usedTime?: Date
remark: string
previewImageUrls: string[]
previewPicUrls: string[]
property: string
}
@@ -0,0 +1,48 @@
import request from '@/config/axios'
export interface DiscountActivityVO {
id?: number
name?: string
startTime?: Date
endTime?: Date
remark?: string
conditionType?: number
productScope?: number
productSpuIds?: number[]
rules?: DiscountProductVO[]
}
// 优惠规则
export interface DiscountProductVO {
limit: number
discountPrice: number
freeDelivery: boolean
point: number
couponIds: number[]
couponCounts: number[]
}
// 新增满减送活动
export const createRewardActivity = async (data: DiscountActivityVO) => {
return await request.post({ url: '/promotion/reward-activity/create', data })
}
// 更新满减送活动
export const updateRewardActivity = async (data: DiscountActivityVO) => {
return await request.put({ url: '/promotion/reward-activity/update', data })
}
// 查询满减送活动列表
export const getRewardActivityPage = async (params) => {
return await request.get({ url: '/promotion/reward-activity/page', params })
}
// 查询满减送活动详情
export const getReward = async (id: number) => {
return await request.get({ url: '/promotion/reward-activity/get?id=' + id })
}
// 删除限时折扣活动
export const deleteRewardActivity = async (id: number) => {
return await request.delete({ url: '/promotion/reward-activity/delete?id=' + id })
}
+52
View File
@@ -0,0 +1,52 @@
import request from '@/config/axios'
import { DataComparisonRespVO } from '@/api/mall/statistics/common'
export interface ProductStatisticsVO {
id: number
day: string
spuId: number
spuName: string
spuPicUrl: string
browseCount: number
browseUserCount: number
favoriteCount: number
cartCount: number
orderCount: number
orderPayCount: number
orderPayPrice: number
afterSaleCount: number
afterSaleRefundPrice: number
browseConvertPercent: number
}
// 商品统计 API
export const ProductStatisticsApi = {
// 获得商品统计分析
getProductStatisticsAnalyse: (params: any) => {
return request.get<DataComparisonRespVO<ProductStatisticsVO>>({
url: '/statistics/product/analyse',
params
})
},
// 获得商品状况明细
getProductStatisticsList: (params: any) => {
return request.get<ProductStatisticsVO[]>({
url: '/statistics/product/list',
params
})
},
// 导出获得商品状况明细 Excel
exportProductStatisticsExcel: (params: any) => {
return request.download({
url: '/statistics/product/export-excel',
params
})
},
// 获得商品排行榜分页
getProductStatisticsRankPage: async (params: any) => {
return await request.get({
url: `/statistics/product/rank-page`,
params
})
}
}
+2 -2
View File
@@ -66,9 +66,9 @@ export const getTradeStatisticsSummary = () => {
}
// 获得交易状况统计
export const getTradeTrendSummary = (params: TradeTrendReqVO) => {
export const getTradeStatisticsAnalyse = (params: TradeTrendReqVO) => {
return request.get<DataComparisonRespVO<TradeTrendSummaryRespVO>>({
url: '/statistics/trade/trend/summary',
url: '/statistics/trade/analyse',
params: formatDateParam(params)
})
}
-1
View File
@@ -8,7 +8,6 @@ export interface ConfigVO {
brokerageFirstPercent: number
brokerageSecondPercent: number
brokerageWithdrawMinPrice: number
brokerageBankNames: string
brokerageFrozenDays: number
brokerageWithdrawTypes: string
}
+39
View File
@@ -0,0 +1,39 @@
import request from '@/config/axios'
export interface UReportDataVO {
id: number
name: string
status: number
content: string
remark: string
}
// 查询Ureport2报表分页
export const getUReportDataPage = async (params) => {
return await request.get({ url: `/report/ureport-data/page`, params })
}
// 查询Ureport2报表详情
export const getUReportData = async (id: number) => {
return await request.get({ url: `/report/ureport-data/get?id=` + id })
}
// 新增Ureport2报表
export const createUReportData = async (data: UReportDataVO) => {
return await request.post({ url: `/report/ureport-data/create`, data })
}
// 修改Ureport2报表
export const updateUReportData = async (data: UReportDataVO) => {
return await request.put({ url: `/report/ureport-data/update`, data })
}
// 删除Ureport2报表
export const deleteUReportData = async (id: number) => {
return await request.delete({ url: `/report/ureport-data/delete?id=` + id })
}
// 导出Ureport2报表 Excel
export const exportUReportData = async (params) => {
return await request.download({ url: `/report/ureport-data/export-excel`, params })
}
+1 -1
View File
@@ -14,7 +14,7 @@ export interface DeptVO {
// 查询部门(精简)列表
export const getSimpleDeptList = async (): Promise<DeptVO[]> => {
return await request.get({ url: '/system/dept/list-all-simple' })
return await request.get({ url: '/system/dept/simple-list' })
}
// 查询部门列表
+3 -3
View File
@@ -14,8 +14,8 @@ export type DictDataVO = {
}
// 查询字典数据(精简)列表
export const listSimpleDictData = () => {
return request.get({ url: '/system/dict-data/list-all-simple' })
export const getSimpleDictDataList = () => {
return request.get({ url: '/system/dict-data/simple-list' })
}
// 查询字典数据列表
@@ -45,5 +45,5 @@ export const deleteDictData = (id: number) => {
// 导出字典类型数据
export const exportDictData = (params) => {
return request.get({ url: '/system/dict-data/export', params })
return request.download({ url: '/system/dict-data/export', params })
}
+1 -1
View File
@@ -40,5 +40,5 @@ export const deleteDictType = (id: number) => {
}
// 导出字典类型
export const exportDictType = (params) => {
return request.get({ url: '/system/dict-type/export', params })
return request.download({ url: '/system/dict-type/export', params })
}
+1
View File
@@ -7,6 +7,7 @@ export interface LoginLogVO {
userId: number
userType: number
username: string
result: number
status: number
userIp: string
userAgent: string
+1 -1
View File
@@ -37,5 +37,5 @@ export const deleteMailAccount = async (id: number) => {
// 获得邮箱账号精简列表
export const getSimpleMailAccountList = async () => {
return request.get({ url: '/system/mail-account/list-all-simple' })
return request.get({ url: '/system/mail-account/simple-list' })
}
+1 -1
View File
@@ -20,7 +20,7 @@ export interface MenuVO {
// 查询菜单(精简)列表
export const getSimpleMenusList = () => {
return request.get({ url: '/system/menu/list-all-simple' })
return request.get({ url: '/system/menu/simple-list' })
}
// 查询菜单列表
+1
View File
@@ -13,6 +13,7 @@ export interface NotifyMessageVO {
templateParams: string
readStatus: boolean
readTime: Date
createTime: Date
}
// 查询站内信消息列表
+1 -1
View File
@@ -6,7 +6,7 @@ export interface NotifyTemplateVO {
nickname: string
code: string
content: string
type: number
type?: number
params: string
status: number
remark: string
+26
View File
@@ -23,6 +23,32 @@ export type OperateLogVO = {
resultData: string
}
export type OperateLogV2VO = {
id: number
userNickname: string
traceId: string
userType: number
userId: number
userName: string
type: string
subType: string
bizId: number
action: string
extra: string
requestMethod: string
requestUrl: string
userIp: string
userAgent: string
creator: string
creatorName: string
createTime: Date
// 数据扩展,渲染时使用
title: string // 操作标题(如果为空则取 name 值)
colSize: number // 变更记录行数
contentStrList: string[]
tagsContentList: string[]
}
// 查询操作日志列表
export const getOperateLogPage = (params: PageParam) => {
return request.get({ url: '/system/operate-log/page', params })
+1 -1
View File
@@ -17,7 +17,7 @@ export const getPostPage = async (params: PageParam) => {
// 获取岗位精简信息列表
export const getSimplePostList = async (): Promise<PostVO[]> => {
return await request.get({ url: '/system/post/list-all-simple' })
return await request.get({ url: '/system/post/simple-list' })
}
// 查询岗位详情
+1 -1
View File
@@ -24,7 +24,7 @@ export const getRolePage = async (params: PageParam) => {
// 查询角色(精简)列表
export const getSimpleRoleList = async (): Promise<RoleVO[]> => {
return await request.get({ url: '/system/role/list-all-simple' })
return await request.get({ url: '/system/role/simple-list' })
}
// 查询角色详情
+1 -1
View File
@@ -19,7 +19,7 @@ export const getSmsChannelPage = (params: PageParam) => {
// 获得短信渠道精简列表
export function getSimpleSmsChannelList() {
return request.get({ url: '/system/sms-channel/list-all-simple' })
return request.get({ url: '/system/sms-channel/simple-list' })
}
// 查询短信渠道详情
+3 -3
View File
@@ -1,15 +1,15 @@
import request from '@/config/axios'
export interface SmsTemplateVO {
id: number | null
type: number | null
id?: number
type?: number
status: number
code: string
name: string
content: string
remark: string
apiTemplateId: string
channelId: number | null
channelId?: number
channelCode?: string
params?: string[]
createTime?: Date
+1 -1
View File
@@ -38,5 +38,5 @@ export const deleteTenantPackage = (id: number) => {
}
// 获取租户套餐精简信息列表
export const getTenantPackageList = () => {
return request.get({ url: '/system/tenant-package/get-simple-list' })
return request.get({ url: '/system/tenant-package/simple-list' })
}
+1 -1
View File
@@ -77,5 +77,5 @@ export const updateUserStatus = (id: number, status: number) => {
// 获取用户精简信息列表
export const getSimpleUserList = (): Promise<UserVO[]> => {
return request.get({ url: '/system/user/list-all-simple' })
return request.get({ url: '/system/user/simple-list' })
}
+16 -28
View File
@@ -1,37 +1,25 @@
import request from '@/config/axios'
export interface ProfileDept {
id: number
name: string
}
export interface ProfileRole {
id: number
name: string
}
export interface ProfilePost {
id: number
name: string
}
export interface SocialUser {
id: number
type: number
openid: string
token: string
rawTokenInfo: string
nickname: string
avatar: string
rawUserInfo: string
code: string
state: string
}
export interface ProfileVO {
id: number
username: string
nickname: string
dept: ProfileDept
roles: ProfileRole[]
posts: ProfilePost[]
socialUsers: SocialUser[]
dept: {
id: number
name: string
}
roles: {
id: number
name: string
}[]
posts: {
id: number
name: string
}[]
socialUsers: {
type: number
openid: string
}[]
email: string
mobile: string
sex: number
@@ -0,0 +1,207 @@
<template>
<Dialog v-model="dialogVisible" title="选择链接" width="65%">
<div class="h-500px flex gap-8px">
<!-- 左侧分组列表 -->
<el-scrollbar wrap-class="h-full" ref="groupScrollbar" view-class="flex flex-col">
<el-button
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
:key="groupIndex"
:class="[
'm-r-16px m-l-0px! justify-start! w-90px',
{ active: activeGroup === group.name }
]"
ref="groupBtnRefs"
:text="activeGroup !== group.name"
:type="activeGroup === group.name ? 'primary' : 'default'"
@click="handleGroupSelected(group.name)"
>
{{ group.name }}
</el-button>
</el-scrollbar>
<!-- 右侧链接列表 -->
<el-scrollbar class="h-full flex-1" @scroll="handleScroll" ref="linkScrollbar">
<div v-for="(group, groupIndex) in APP_LINK_GROUP_LIST" :key="groupIndex">
<!-- 分组标题 -->
<div class="font-bold" ref="groupTitleRefs">{{ group.name }}</div>
<!-- 链接列表 -->
<el-tooltip
v-for="(appLink, appLinkIndex) in group.links"
:key="appLinkIndex"
:content="appLink.path"
placement="bottom"
:show-after="300"
>
<el-button
class="m-b-8px m-r-8px m-l-0px!"
:type="isSameLink(appLink.path, activeAppLink.path) ? 'primary' : 'default'"
@click="handleAppLinkSelected(appLink)"
>
{{ appLink.name }}
</el-button>
</el-tooltip>
</div>
</el-scrollbar>
</div>
<!-- 底部对话框操作按钮 -->
<template #footer>
<el-button type="primary" @click="handleSubmit">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
<Dialog v-model="detailSelectDialog.visible" title="" width="50%">
<el-form class="min-h-200px">
<el-form-item
label="选择分类"
v-if="detailSelectDialog.type === APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST"
>
<ProductCategorySelect
v-model="detailSelectDialog.id"
:parent-id="0"
@update:model-value="handleProductCategorySelected"
/>
</el-form-item>
</el-form>
</Dialog>
</template>
<script lang="ts" setup>
import { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM, AppLink } from './data'
import { ButtonInstance, ScrollbarInstance } from 'element-plus'
import { split } from 'lodash-es'
import ProductCategorySelect from '@/views/mall/product/category/components/ProductCategorySelect.vue'
import { getUrlNumberValue } from '@/utils'
// APP 链接选择弹框
defineOptions({ name: 'AppLinkSelectDialog' })
// 选中的分组,默认选中第一个
const activeGroup = ref(APP_LINK_GROUP_LIST[0].name)
// 选中的 APP 链接
const activeAppLink = ref({} as AppLink)
/** 打开弹窗 */
const dialogVisible = ref(false)
const open = (link: string) => {
activeAppLink.value.path = link
dialogVisible.value = true
// 滚动到当前的链接
const group = APP_LINK_GROUP_LIST.find((group) =>
group.links.some((linkItem) => {
const sameLink = isSameLink(linkItem.path, link)
if (sameLink) {
activeAppLink.value = { ...linkItem, path: link }
}
return sameLink
})
)
if (group) {
// 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
nextTick(() => handleGroupSelected(group.name))
}
}
defineExpose({ open })
// 处理 APP 链接选中
const handleAppLinkSelected = (appLink: AppLink) => {
if (!isSameLink(appLink.path, activeAppLink.value.path)) {
activeAppLink.value = appLink
}
switch (appLink.type) {
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:
detailSelectDialog.value.visible = true
detailSelectDialog.value.type = appLink.type
// 返显
detailSelectDialog.value.id =
getUrlNumberValue('id', 'http://127.0.0.1' + activeAppLink.value.path) || undefined
break
default:
break
}
}
// 处理绑定值更新
const emit = defineEmits<{
change: [link: string]
appLinkChange: [appLink: AppLink]
}>()
const handleSubmit = () => {
dialogVisible.value = false
emit('change', activeAppLink.value.path)
emit('appLinkChange', activeAppLink.value)
}
// 分组标题引用列表
const groupTitleRefs = ref<HTMLInputElement[]>([])
/**
* 处理右侧链接列表滚动
* @param scrollTop 滚动条的位置
*/
const handleScroll = ({ scrollTop }: { scrollTop: number }) => {
const titleEl = groupTitleRefs.value.find((titleEl: HTMLInputElement) => {
// 获取标题的位置信息
const { offsetHeight, offsetTop } = titleEl
// 判断标题是否在可视范围内
return scrollTop >= offsetTop && scrollTop < offsetTop + offsetHeight
})
// 只需处理一次
if (titleEl && activeGroup.value !== titleEl.textContent) {
activeGroup.value = titleEl.textContent || ''
// 同步左侧的滚动条位置
scrollToGroupBtn(activeGroup.value)
}
}
// 右侧滚动条
const linkScrollbar = ref<ScrollbarInstance>()
// 处理分组选中
const handleGroupSelected = (group: string) => {
activeGroup.value = group
const titleRef = groupTitleRefs.value.find((item: HTMLInputElement) => item.textContent === group)
if (titleRef) {
// 滚动分组标题
linkScrollbar.value?.setScrollTop(titleRef.offsetTop)
}
}
// 分组滚动条
const groupScrollbar = ref<ScrollbarInstance>()
// 分组引用列表
const groupBtnRefs = ref<ButtonInstance[]>([])
// 自动滚动分组按钮,确保分组按钮保持在可视区域内
const scrollToGroupBtn = (group: string) => {
const groupBtn = groupBtnRefs.value
.map((btn: ButtonInstance) => btn['ref'])
.find((ref: Node) => ref.textContent === group)
if (groupBtn) {
groupScrollbar.value?.setScrollTop(groupBtn.offsetTop)
}
}
// 是否为相同的链接(不比较参数,只比较链接)
const isSameLink = (link1: string, link2: string) => {
return split(link1, '?', 1)[0] === split(link2, '?', 1)[0]
}
// 详情选择对话框
const detailSelectDialog = ref<{
visible: boolean
id?: number
type?: APP_LINK_TYPE_ENUM
}>({
visible: false,
id: undefined,
type: undefined
})
// 处理详情选择
const handleProductCategorySelected = (id: number) => {
const url = new URL(activeAppLink.value.path, 'http://127.0.0.1')
// 修改 id 参数
url.searchParams.set('id', `${id}`)
// 排除域名
activeAppLink.value.path = `${url.pathname}${url.search}`
// 关闭对话框
detailSelectDialog.value.visible = false
// 重置 id
detailSelectDialog.value.id = undefined
}
</script>
<style lang="scss" scoped></style>
+228
View File
@@ -0,0 +1,228 @@
// APP 链接分组
export interface AppLinkGroup {
// 分组名称
name: string
// 链接列表
links: AppLink[]
}
// APP 链接
export interface AppLink {
// 链接名称
name: string
// 链接地址
path: string
// 链接的类型
type?: APP_LINK_TYPE_ENUM
}
// APP 链接类型(需要特殊处理,例如商品详情)
export const enum APP_LINK_TYPE_ENUM {
// 拼团活动
ACTIVITY_COMBINATION,
// 秒杀活动
ACTIVITY_SECKILL,
// 文章详情
ARTICLE_DETAIL,
// 优惠券详情
COUPON_DETAIL,
// 自定义页面详情
DIY_PAGE_DETAIL,
// 品类列表
PRODUCT_CATEGORY_LIST,
// 商品列表
PRODUCT_LIST,
// 商品详情
PRODUCT_DETAIL_NORMAL,
// 拼团商品详情
PRODUCT_DETAIL_COMBINATION,
// 秒杀商品详情
PRODUCT_DETAIL_SECKILL
}
// APP 链接列表(做一下持久化?)
export const APP_LINK_GROUP_LIST = [
{
name: '商城',
links: [
{
name: '首页',
path: '/pages/index/index'
},
{
name: '商品分类',
path: '/pages/index/category',
type: APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST
},
{
name: '购物车',
path: '/pages/index/cart'
},
{
name: '个人中心',
path: '/pages/index/user'
},
{
name: '商品搜索',
path: '/pages/index/search'
},
{
name: '自定义页面',
path: '/pages/index/page',
type: APP_LINK_TYPE_ENUM.DIY_PAGE_DETAIL
},
{
name: '客服',
path: '/pages/chat/index'
},
{
name: '系统设置',
path: '/pages/public/setting'
},
{
name: '常见问题',
path: '/pages/public/faq'
}
]
},
{
name: '商品',
links: [
{
name: '商品列表',
path: '/pages/goods/list',
type: APP_LINK_TYPE_ENUM.PRODUCT_LIST
},
{
name: '商品详情',
path: '/pages/goods/index',
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_NORMAL
},
{
name: '拼团商品详情',
path: '/pages/goods/groupon',
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_COMBINATION
},
{
name: '秒杀商品详情',
path: '/pages/goods/seckill',
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_SECKILL
}
]
},
{
name: '营销活动',
links: [
{
name: '拼团订单',
path: '/pages/activity/groupon/order'
},
{
name: '营销商品',
path: '/pages/activity/index'
},
{
name: '拼团活动',
path: '/pages/activity/groupon/list',
type: APP_LINK_TYPE_ENUM.ACTIVITY_COMBINATION
},
{
name: '秒杀活动',
path: '/pages/activity/seckill/list',
type: APP_LINK_TYPE_ENUM.ACTIVITY_SECKILL
},
{
name: '签到中心',
path: '/pages/app/sign'
},
{
name: '优惠券中心',
path: '/pages/coupon/list'
},
{
name: '优惠券详情',
path: '/pages/coupon/detail',
type: APP_LINK_TYPE_ENUM.COUPON_DETAIL
},
{
name: '文章详情',
path: '/pages/public/richtext',
type: APP_LINK_TYPE_ENUM.ARTICLE_DETAIL
}
]
},
{
name: '分销商城',
links: [
{
name: '分销中心',
path: '/pages/commission/index'
},
{
name: '推广商品',
path: '/pages/commission/goods'
},
{
name: '分销订单',
path: '/pages/commission/order'
},
{
name: '我的团队',
path: '/pages/commission/team'
}
]
},
{
name: '支付',
links: [
{
name: '充值余额',
path: '/pages/pay/recharge'
},
{
name: '充值记录',
path: '/pages/pay/recharge-log'
}
]
},
{
name: '用户中心',
links: [
{
name: '用户信息',
path: '/pages/user/info'
},
{
name: '用户订单',
path: '/pages/order/list'
},
{
name: '售后订单',
path: '/pages/order/aftersale/list'
},
{
name: '商品收藏',
path: '/pages/user/goods-collect'
},
{
name: '浏览记录',
path: '/pages/user/goods-log'
},
{
name: '地址管理',
path: '/pages/user/address/list'
},
{
name: '用户佣金',
path: '/pages/user/wallet/commission'
},
{
name: '用户余额',
path: '/pages/user/wallet/money'
},
{
name: '用户积分',
path: '/pages/user/wallet/score'
}
]
}
] as AppLinkGroup[]
+43
View File
@@ -0,0 +1,43 @@
<template>
<el-input v-model="appLink" placeholder="输入或选择链接">
<template #append>
<el-button @click="handleOpenDialog">选择</el-button>
</template>
</el-input>
<AppLinkSelectDialog ref="dialogRef" @change="handleLinkSelected" />
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
// APP 链接输入框
defineOptions({ name: 'AppLinkInput' })
// 定义属性
const props = defineProps({
// 当前选中的链接
modelValue: propTypes.string.def('')
})
// 当前的链接
const appLink = ref('')
// 选择对话框
const dialogRef = ref()
// 处理打开对话框
const handleOpenDialog = () => dialogRef.value?.open(appLink.value)
// 处理 APP 链接选中
const handleLinkSelected = (link: string) => (appLink.value = link)
// getter
watch(
() => props.modelValue,
() => (appLink.value = props.modelValue),
{ immediate: true }
)
// setter
const emit = defineEmits<{
'update:modelValue': [link: string]
}>()
watch(
() => appLink.value,
() => emit('update:modelValue', appLink.value)
)
</script>
@@ -8,11 +8,11 @@
<component :is="component.id" :property="component.property" />
</div>
<div class="component-wrap">
<!-- 左侧组件名 -->
<!-- 左侧:组件名(悬浮的小贴条) -->
<div class="component-name" v-if="component.name">
{{ component.name }}
</div>
<!-- 左侧:组件操作工具栏 -->
<!-- 右侧:组件操作工具栏 -->
<div class="component-toolbar" v-if="showToolbar && component.name && active">
<VerticalButtonGroup type="primary">
<el-tooltip content="上移" placement="right">
@@ -54,7 +54,7 @@ import { propTypes } from '@/utils/propTypes'
import { object } from 'vue-types'
/**
* 组件容器
* 组件容器:目前在中间部分
* 用于包裹组件,为组件提供 背景、外边距、内边距、边框等样式
*/
defineOptions({ name: 'ComponentContainer' })
@@ -100,6 +100,7 @@ const emits = defineEmits<{
(e: 'copy'): void
(e: 'delete'): void
}>()
/**
* 移动组件
* @param direction 移动方向
@@ -107,12 +108,14 @@ const emits = defineEmits<{
const handleMoveComponent = (direction: number) => {
emits('move', direction)
}
/**
* 复制组件
*/
const handleCopyComponent = () => {
emits('copy')
}
/**
* 删除组件
*/
@@ -1,8 +1,11 @@
<template>
<el-tabs stretch>
<el-tab-pane label="内容">
<!-- 每个组件的自定义内容 -->
<el-tab-pane label="内容" v-if="$slots.default">
<slot></slot>
</el-tab-pane>
<!-- 每个组件的通用内容 -->
<el-tab-pane label="样式" lazy>
<el-card header="组件样式" class="property-group">
<el-form :model="formData" label-width="80px">
@@ -20,7 +23,7 @@
<template #tip>建议宽度 750px</template>
</UploadImg>
</el-form-item>
<el-tree :data="treeData" :expand-on-click-node="false">
<el-tree :data="treeData" :expand-on-click-node="false" default-expand-all>
<template #default="{ node, data }">
<el-form-item
:label="data.label"
@@ -40,7 +43,7 @@
</el-form-item>
</template>
</el-tree>
<slot name="style" :formData="formData"></slot>
<slot name="style" :style="formData"></slot>
</el-form>
</el-card>
</el-tab-pane>
@@ -51,7 +54,7 @@
import { ComponentStyle, usePropertyForm } from '@/components/DiyEditor/util'
/**
* 组件容器属性
* 组件容器属性:目前右边部分
* 用于包裹组件,为组件提供 背景、外边距、内边距、边框等样式
*/
defineOptions({ name: 'ComponentContainer' })
@@ -41,16 +41,19 @@ import { componentConfigs } from '../components/mobile/index'
import { cloneDeep } from 'lodash-es'
import { DiyComponent, DiyComponentLibrary } from '@/components/DiyEditor/util'
/** 组件库 */
/** 组件库:目前左侧的【基础组件】、【图文组件】部分 */
defineOptions({ name: 'ComponentLibrary' })
// 组件列表
const props = defineProps<{
list: DiyComponentLibrary[]
}>()
// 组件分组
const groups = reactive<any[]>([])
// 展开的折叠面板
const extendGroups = reactive<string[]>([])
// 监听 list 属性,按照 DiyComponentLibrary 的 name 分组
watch(
() => props.list,
() => {
@@ -82,7 +85,9 @@ watch(
// 克隆组件
const handleCloneComponent = (component: DiyComponent<any>) => {
return cloneDeep(component)
const instance = cloneDeep(component)
instance.uid = new Date().getTime()
return instance
}
</script>
@@ -39,87 +39,60 @@
</el-form-item>
</el-card>
<el-card header="内容设置" class="property-group" shadow="never">
<el-text type="info" size="small"> 拖动左上角的小圆点可对其排序 </el-text>
<template v-if="formData.items[0]">
<draggable
:list="formData.items"
:force-fallback="true"
:animation="200"
handle=".drag-icon"
class="m-t-8px"
item-key="index"
>
<template #item="{ element, index }">
<div class="content mb-4px flex flex-col gap-4px rounded bg-gray-50 p-8px">
<div
class="m--8px m-b-8px flex flex-row items-center justify-between bg-gray-100 p-8px"
>
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
<Icon
icon="ep:delete"
class="cursor-pointer text-red-5"
@click="handleDeleteImage(index)"
v-if="formData.items.length > 1"
/>
</div>
<el-form-item label="类型" prop="type" class="m-b-8px!" label-width="50px">
<el-radio-group v-model="element.type">
<el-radio label="img">图片</el-radio>
<el-radio label="video">视频</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label="图片"
class="m-b-8px!"
label-width="50px"
v-if="element.type === 'img'"
>
<UploadImg
v-model="element.imgUrl"
draggable="false"
height="80px"
width="100%"
class="min-w-80px"
/>
</el-form-item>
<template v-else>
<el-form-item label="封面" class="m-b-8px!" label-width="50px">
<UploadImg
v-model="element.imgUrl"
draggable="false"
height="80px"
width="100%"
class="min-w-80px"
/>
</el-form-item>
<el-form-item label="视频" class="m-b-8px!" label-width="50px">
<UploadFile
v-model="element.videoUrl"
:file-type="['mp4']"
:limit="1"
:file-size="100"
class="min-w-80px"
/>
</el-form-item>
</template>
<el-form-item label="链接" class="m-b-8px!" label-width="50px">
<el-input placeholder="链接" v-model="element.url" />
</el-form-item>
</div>
<Draggable v-model="formData.items" :empty-item="{ type: 'img' }">
<template #default="{ element }">
<el-form-item label="类型" prop="type" class="m-b-8px!" label-width="40px">
<el-radio-group v-model="element.type">
<el-radio label="img">图片</el-radio>
<el-radio label="video">视频</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label="图片"
class="m-b-8px!"
label-width="40px"
v-if="element.type === 'img'"
>
<UploadImg
v-model="element.imgUrl"
draggable="false"
height="80px"
width="100%"
class="min-w-80px"
/>
</el-form-item>
<template v-else>
<el-form-item label="封面" class="m-b-8px!" label-width="40px">
<UploadImg
v-model="element.imgUrl"
draggable="false"
height="80px"
width="100%"
class="min-w-80px"
/>
</el-form-item>
<el-form-item label="视频" class="m-b-8px!" label-width="40px">
<UploadFile
v-model="element.videoUrl"
:file-type="['mp4']"
:limit="1"
:file-size="100"
class="min-w-80px"
/>
</el-form-item>
</template>
</draggable>
</template>
<el-button @click="handleAddImage" type="primary" plain class="w-full">
添加图片
</el-button>
<el-form-item label="链接" class="m-b-8px!" label-width="40px">
<AppLinkInput v-model="element.url" />
</el-form-item>
</template>
</Draggable>
</el-card>
</el-form>
</ComponentContainerProperty>
</template>
<script setup lang="ts">
import draggable from 'vuedraggable' //拖拽组件
import { CarouselItemProperty, CarouselProperty } from './config'
import { CarouselProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
// 轮播图属性面板
@@ -128,15 +101,6 @@ defineOptions({ name: 'CarouselProperty' })
const props = defineProps<{ modelValue: CarouselProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
// 添加图片
const handleAddImage = () => {
formData.value.items.push({} as CarouselItemProperty)
}
// 删除图片
const handleDeleteImage = (index: number) => {
formData.value.items.splice(index, 1)
}
</script>
<style scoped lang="scss"></style>
@@ -2,14 +2,13 @@ import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
import { CouponTemplateValidityTypeEnum, PromotionDiscountTypeEnum } from '@/utils/constants'
import { floatToFixed2 } from '@/utils'
import { formatDate } from '@/utils/formatTime'
import { object } from 'vue-types'
// 优惠值
export const CouponDiscount = defineComponent({
name: 'CouponDiscount',
props: {
coupon: {
type: CouponTemplateApi.CouponTemplateVO
}
coupon: object<CouponTemplateApi.CouponTemplateVO>()
},
setup(props) {
const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
@@ -34,9 +33,7 @@ export const CouponDiscount = defineComponent({
export const CouponDiscountDesc = defineComponent({
name: 'CouponDiscountDesc',
props: {
coupon: {
type: CouponTemplateApi.CouponTemplateVO
}
coupon: object<CouponTemplateApi.CouponTemplateVO>()
},
setup(props) {
const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
@@ -60,9 +57,7 @@ export const CouponDiscountDesc = defineComponent({
export const CouponValidTerm = defineComponent({
name: 'CouponValidTerm',
props: {
coupon: {
type: CouponTemplateApi.CouponTemplateVO
}
coupon: object<CouponTemplateApi.CouponTemplateVO>()
},
setup(props) {
const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
@@ -0,0 +1,36 @@
import { DiyComponent } from '@/components/DiyEditor/util'
// 悬浮按钮属性
export interface FloatingActionButtonProperty {
// 展开方向
direction: 'horizontal' | 'vertical'
// 是否显示文字
showText: boolean
// 按钮列表
list: FloatingActionButtonItemProperty[]
}
// 悬浮按钮项属性
export interface FloatingActionButtonItemProperty {
// 图片地址
imgUrl: string
// 跳转连接
url: string
// 文字
text: string
// 文字颜色
textColor: string
}
// 定义组件
export const component = {
id: 'FloatingActionButton',
name: '悬浮按钮',
icon: 'tabler:float-right',
position: 'fixed',
property: {
direction: 'vertical',
showText: true,
list: [{ textColor: '#fff' }]
}
} as DiyComponent<FloatingActionButtonProperty>
@@ -0,0 +1,74 @@
<template>
<div
:class="[
'absolute bottom-32px right-[calc(50%-375px/2+32px)] flex z-12 gap-12px items-center',
{
'flex-row': property.direction === 'horizontal',
'flex-col': property.direction === 'vertical'
}
]"
>
<template v-if="expanded">
<div
v-for="(item, index) in property.list"
:key="index"
class="flex flex-col items-center"
@click="handleActive(index)"
>
<el-image :src="item.imgUrl" fit="contain" class="h-27px w-27px">
<template #error>
<div class="h-full w-full flex items-center justify-center">
<Icon icon="ep:picture" :color="item.textColor" />
</div>
</template>
</el-image>
<span v-if="property.showText" class="mt-4px text-12px" :style="{ color: item.textColor }">
{{ item.text }}
</span>
</div>
</template>
<!-- todo: @owen 使用APP主题色 -->
<el-button type="primary" size="large" circle @click="handleToggleFab">
<Icon icon="ep:plus" :class="['fab-icon', { active: expanded }]" />
</el-button>
</div>
<!-- 模态背景:展开时显示,点击后折叠 -->
<div v-if="expanded" class="modal-bg" @click="handleToggleFab"></div>
</template>
<script setup lang="ts">
import { FloatingActionButtonProperty } from './config'
/** 悬浮按钮 */
defineOptions({ name: 'FloatingActionButton' })
// 定义属性
defineProps<{ property: FloatingActionButtonProperty }>()
// 是否展开
const expanded = ref(true)
// 处理展开/折叠
const handleToggleFab = () => {
expanded.value = !expanded.value
}
</script>
<style scoped lang="scss">
/* 模态背景 */
.modal-bg {
position: absolute;
left: calc(50% - 375px / 2);
top: 0;
z-index: 11;
width: 375px;
height: 100%;
background-color: rgba(#000000, 0.4);
}
.fab-icon {
transform: rotate(0deg);
transition: transform 0.3s;
&.active {
transform: rotate(135deg);
}
}
</style>
@@ -0,0 +1,44 @@
<template>
<el-form label-width="80px" :model="formData">
<el-card header="按钮配置" class="property-group" shadow="never">
<el-form-item label="展开方向" prop="direction">
<el-radio-group v-model="formData.direction">
<el-radio label="vertical">垂直</el-radio>
<el-radio label="horizontal">水平</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="显示文字" prop="showText">
<el-switch v-model="formData.showText" />
</el-form-item>
</el-card>
<el-card header="按钮列表" class="property-group" shadow="never">
<Draggable v-model="formData.list" :empty-item="{ textColor: '#fff' }">
<template #default="{ element, index }">
<el-form-item label="图标" :prop="`list[${index}].imgUrl`">
<UploadImg v-model="element.imgUrl" height="56px" width="56px" />
</el-form-item>
<el-form-item label="文字" :prop="`list[${index}].text`">
<InputWithColor v-model="element.text" v-model:color="element.textColor" />
</el-form-item>
<el-form-item label="跳转链接" :prop="`list[${index}].url`">
<AppLinkInput v-model="element.url" />
</el-form-item>
</template>
</Draggable>
</el-card>
</el-form>
</template>
<script setup lang="ts">
import { FloatingActionButtonProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
// 悬浮按钮属性面板
defineOptions({ name: 'FloatingActionButtonProperty' })
const props = defineProps<{ modelValue: FloatingActionButtonProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
</script>
<style scoped lang="scss"></style>
@@ -0,0 +1,143 @@
import { HotZoneItemProperty } from '@/components/DiyEditor/components/mobile/HotZone/config'
import { StyleValue } from 'vue'
// 热区的最小宽高
export const HOT_ZONE_MIN_SIZE = 100
// 控制的类型
export enum CONTROL_TYPE_ENUM {
LEFT,
TOP,
WIDTH,
HEIGHT
}
// 定义热区的控制点
export interface ControlDot {
position: string
types: CONTROL_TYPE_ENUM[]
style: StyleValue
}
// 热区的8个控制点
export const CONTROL_DOT_LIST = [
{
position: '左上角',
types: [
CONTROL_TYPE_ENUM.LEFT,
CONTROL_TYPE_ENUM.TOP,
CONTROL_TYPE_ENUM.WIDTH,
CONTROL_TYPE_ENUM.HEIGHT
],
style: { left: '-5px', top: '-5px', cursor: 'nwse-resize' }
},
{
position: '上方中间',
types: [CONTROL_TYPE_ENUM.TOP, CONTROL_TYPE_ENUM.HEIGHT],
style: { left: '50%', top: '-5px', cursor: 'n-resize', transform: 'translateX(-50%)' }
},
{
position: '右上角',
types: [CONTROL_TYPE_ENUM.TOP, CONTROL_TYPE_ENUM.WIDTH, CONTROL_TYPE_ENUM.HEIGHT],
style: { right: '-5px', top: '-5px', cursor: 'nesw-resize' }
},
{
position: '右侧中间',
types: [CONTROL_TYPE_ENUM.WIDTH],
style: { right: '-5px', top: '50%', cursor: 'e-resize', transform: 'translateX(-50%)' }
},
{
position: '右下角',
types: [CONTROL_TYPE_ENUM.WIDTH, CONTROL_TYPE_ENUM.HEIGHT],
style: { right: '-5px', bottom: '-5px', cursor: 'nwse-resize' }
},
{
position: '下方中间',
types: [CONTROL_TYPE_ENUM.HEIGHT],
style: { left: '50%', bottom: '-5px', cursor: 's-resize', transform: 'translateX(-50%)' }
},
{
position: '左下角',
types: [CONTROL_TYPE_ENUM.LEFT, CONTROL_TYPE_ENUM.WIDTH, CONTROL_TYPE_ENUM.HEIGHT],
style: { left: '-5px', bottom: '-5px', cursor: 'nesw-resize' }
},
{
position: '左侧中间',
types: [CONTROL_TYPE_ENUM.LEFT, CONTROL_TYPE_ENUM.WIDTH],
style: { left: '-5px', top: '50%', cursor: 'w-resize', transform: 'translateX(-50%)' }
}
] as ControlDot[]
//region 热区的缩放
// 热区的缩放比例
export const HOT_ZONE_SCALE_RATE = 2
// 缩小:缩回适合手机屏幕的大小
export const zoomOut = (list?: HotZoneItemProperty[]) => {
return (
list?.map((hotZone) => ({
...hotZone,
left: (hotZone.left /= HOT_ZONE_SCALE_RATE),
top: (hotZone.top /= HOT_ZONE_SCALE_RATE),
width: (hotZone.width /= HOT_ZONE_SCALE_RATE),
height: (hotZone.height /= HOT_ZONE_SCALE_RATE)
})) || []
)
}
// 放大:作用是为了方便在电脑屏幕上编辑
export const zoomIn = (list?: HotZoneItemProperty[]) => {
return (
list?.map((hotZone) => ({
...hotZone,
left: (hotZone.left *= HOT_ZONE_SCALE_RATE),
top: (hotZone.top *= HOT_ZONE_SCALE_RATE),
width: (hotZone.width *= HOT_ZONE_SCALE_RATE),
height: (hotZone.height *= HOT_ZONE_SCALE_RATE)
})) || []
)
}
//endregion
/**
* 封装热区拖拽
*
* 注:为什么不使用vueuse的useDraggable。在本场景下,其使用方式比较复杂
* @param hotZone 热区
* @param downEvent 鼠标按下事件
* @param callback 回调函数
*/
export const useDraggable = (
hotZone: HotZoneItemProperty,
downEvent: MouseEvent,
callback: (
left: number,
top: number,
width: number,
height: number,
moveWidth: number,
moveHeight: number
) => void
) => {
// 阻止事件冒泡
downEvent.stopPropagation()
// 移动前的鼠标坐标
const { clientX: startX, clientY: startY } = downEvent
// 移动前的热区坐标、大小
const { left, top, width, height } = hotZone
// 监听鼠标移动
document.onmousemove = (e) => {
// 移动宽度
const moveWidth = e.clientX - startX
// 移动高度
const moveHeight = e.clientY - startY
// 移动回调
callback(left, top, width, height, moveWidth, moveHeight)
}
// 松开鼠标后,结束拖拽
document.onmouseup = () => {
document.onmousemove = null
document.onmouseup = null
}
}
@@ -0,0 +1,236 @@
<template>
<Dialog v-model="dialogVisible" title="设置热区" width="780" @close="handleClose">
<div ref="container" class="relative h-full w-750px">
<el-image :src="imgUrl" class="pointer-events-none h-full w-750px select-none" />
<div
v-for="(item, hotZoneIndex) in formData"
:key="hotZoneIndex"
class="hot-zone"
:style="{
width: `${item.width}px`,
height: `${item.height}px`,
top: `${item.top}px`,
left: `${item.left}px`
}"
@mousedown="handleMove(item, $event)"
@dblclick="handleShowAppLinkDialog(item)"
>
<span class="pointer-events-none select-none">{{ item.name || '双击选择链接' }}</span>
<Icon icon="ep:close" class="delete" :size="14" @click="handleRemove(item)" />
<!-- 8个控制点 -->
<span
class="ctrl-dot"
v-for="(dot, dotIndex) in CONTROL_DOT_LIST"
:key="dotIndex"
:style="dot.style"
@mousedown="handleResize(item, dot, $event)"
></span>
</div>
</div>
<template #footer>
<el-button @click="handleAdd" type="primary" plain>
<Icon icon="ep:plus" class="mr-5px" />
添加热区
</el-button>
<el-button @click="handleSubmit" type="primary" plain>
<Icon icon="ep:check" class="mr-5px" />
确定
</el-button>
</template>
</Dialog>
<AppLinkSelectDialog ref="appLinkDialogRef" @app-link-change="handleAppLinkChange" />
</template>
<script setup lang="ts">
import { HotZoneItemProperty } from '@/components/DiyEditor/components/mobile/HotZone/config'
import { array, string } from 'vue-types'
import {
CONTROL_DOT_LIST,
CONTROL_TYPE_ENUM,
ControlDot,
HOT_ZONE_MIN_SIZE,
useDraggable,
zoomIn,
zoomOut
} from './controller'
import { AppLink } from '@/components/AppLinkInput/data'
import { remove } from 'lodash-es'
/** 热区编辑对话框 */
defineOptions({ name: 'HotZoneEditDialog' })
// 定义属性
const props = defineProps({
modelValue: array<HotZoneItemProperty>(),
imgUrl: string().def('')
})
const emit = defineEmits(['update:modelValue'])
const formData = ref<HotZoneItemProperty[]>([])
// 弹窗的是否显示
const dialogVisible = ref(false)
// 打开弹窗
const open = () => {
// 放大
formData.value = zoomIn(props.modelValue)
dialogVisible.value = true
}
// 提供 open 方法,用于打开弹窗
defineExpose({ open })
// 热区容器
const container = ref<HTMLDivElement>()
// 增加热区
const handleAdd = () => {
formData.value.push({
width: HOT_ZONE_MIN_SIZE,
height: HOT_ZONE_MIN_SIZE,
top: 0,
left: 0
} as HotZoneItemProperty)
}
// 删除热区
const handleRemove = (hotZone: HotZoneItemProperty) => {
remove(formData.value, hotZone)
}
// 移动热区
const handleMove = (item: HotZoneItemProperty, e: MouseEvent) => {
useDraggable(item, e, (left, top, _, __, moveWidth, moveHeight) => {
setLeft(item, left + moveWidth)
setTop(item, top + moveHeight)
})
}
// 调整热区大小、位置
const handleResize = (item: HotZoneItemProperty, ctrlDot: ControlDot, e: MouseEvent) => {
useDraggable(item, e, (left, top, width, height, moveWidth, moveHeight) => {
ctrlDot.types.forEach((type) => {
switch (type) {
case CONTROL_TYPE_ENUM.LEFT:
setLeft(item, left + moveWidth)
break
case CONTROL_TYPE_ENUM.TOP:
setTop(item, top + moveHeight)
break
case CONTROL_TYPE_ENUM.WIDTH:
{
// 上移时,高度为减少
const direction = ctrlDot.types.includes(CONTROL_TYPE_ENUM.LEFT) ? -1 : 1
setWidth(item, width + moveWidth * direction)
}
break
case CONTROL_TYPE_ENUM.HEIGHT:
{
// 左移时,宽度为减少
const direction = ctrlDot.types.includes(CONTROL_TYPE_ENUM.TOP) ? -1 : 1
setHeight(item, height + moveHeight * direction)
}
break
}
})
})
}
// 设置X轴坐标
const setLeft = (item: HotZoneItemProperty, left: number) => {
// 不能超出容器
if (left >= 0 && left <= container.value!.offsetWidth - item.width) {
item.left = left
}
}
// 设置Y轴坐标
const setTop = (item: HotZoneItemProperty, top: number) => {
// 不能超出容器
if (top >= 0 && top <= container.value!.offsetHeight - item.height) {
item.top = top
}
}
// 设置宽度
const setWidth = (item: HotZoneItemProperty, width: number) => {
// 不能小于最小宽度 && 不能超出容器右边
if (width >= HOT_ZONE_MIN_SIZE && item.left + width <= container.value!.offsetWidth) {
item.width = width
}
}
// 设置高度
const setHeight = (item: HotZoneItemProperty, height: number) => {
// 不能小于最小高度 && 不能超出容器底部
if (height >= HOT_ZONE_MIN_SIZE && item.top + height <= container.value!.offsetHeight) {
item.height = height
}
}
// 处理对话框关闭
const handleSubmit = () => {
// 会自动触发handleClose
dialogVisible.value = false
}
// 处理对话框关闭
const handleClose = () => {
// 缩小
const list = zoomOut(formData.value)
emit('update:modelValue', list)
}
const activeHotZone = ref<HotZoneItemProperty>()
const appLinkDialogRef = ref()
const handleShowAppLinkDialog = (hotZone: HotZoneItemProperty) => {
activeHotZone.value = hotZone
appLinkDialogRef.value.open(hotZone.url)
}
const handleAppLinkChange = (appLink: AppLink) => {
if (!appLink || !activeHotZone.value) return
activeHotZone.value.name = appLink.name
activeHotZone.value.url = appLink.path
}
</script>
<style scoped lang="scss">
.hot-zone {
position: absolute;
background: var(--el-color-primary-light-7);
opacity: 0.8;
border: 1px solid var(--el-color-primary);
color: var(--el-color-primary);
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
cursor: move;
z-index: 10;
/* 控制点 */
.ctrl-dot {
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
border: inherit;
background-color: #fff;
z-index: 11;
}
.delete {
display: none;
position: absolute;
top: 0;
right: 0;
padding: 2px 2px 6px 6px;
background-color: var(--el-color-primary);
border-radius: 0 0 0 80%;
cursor: pointer;
color: #fff;
text-align: right;
}
&:hover {
.delete {
display: block;
}
}
}
</style>
@@ -0,0 +1,43 @@
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 热区属性 */
export interface HotZoneProperty {
// 图片地址
imgUrl: string
// 导航菜单列表
list: HotZoneItemProperty[]
// 组件样式
style: ComponentStyle
}
/** 热区项目属性 */
export interface HotZoneItemProperty {
// 链接的名称
name: string
// 链接
url: string
// 宽
width: number
// 高
height: number
// 上
top: number
// 左
left: number
}
// 定义组件
export const component = {
id: 'HotZone',
name: '热区',
icon: 'tabler:hand-click',
property: {
imgUrl: '',
list: [] as HotZoneItemProperty[],
style: {
bgType: 'color',
bgColor: '#fff',
marginBottom: 8
} as ComponentStyle
}
} as DiyComponent<HotZoneProperty>
@@ -0,0 +1,42 @@
<template>
<div class="relative h-full min-h-30px w-full">
<el-image :src="property.imgUrl" class="pointer-events-none h-full w-full select-none" />
<div
v-for="(item, index) in property.list"
:key="index"
class="hot-zone"
:style="{
width: `${item.width}px`,
height: `${item.height}px`,
top: `${item.top}px`,
left: `${item.left}px`
}"
>
{{ item.name }}
</div>
</div>
</template>
<script setup lang="ts">
import { HotZoneProperty } from './config'
/** 热区 */
defineOptions({ name: 'HotZone' })
const props = defineProps<{ property: HotZoneProperty }>()
</script>
<style scoped lang="scss">
.hot-zone {
position: absolute;
background: var(--el-color-primary-light-7);
opacity: 0.8;
border: 1px solid var(--el-color-primary);
color: var(--el-color-primary);
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
cursor: move;
z-index: 10;
}
</style>
@@ -0,0 +1,63 @@
<template>
<ComponentContainerProperty v-model="formData.style">
<!-- 表单 -->
<el-form label-width="80px" :model="formData" class="m-t-8px">
<el-form-item label="上传图片" prop="imgUrl">
<UploadImg v-model="formData.imgUrl" height="50px" width="auto" class="min-w-80px">
<template #tip>
<el-text type="info" size="small"> 推荐宽度 750</el-text>
</template>
</UploadImg>
</el-form-item>
</el-form>
<el-button type="primary" plain class="w-full" @click="handleOpenEditDialog">
设置热区
</el-button>
</ComponentContainerProperty>
<!-- 热区编辑对话框 -->
<HotZoneEditDialog ref="editDialogRef" v-model="formData.list" :img-url="formData.imgUrl" />
</template>
<script setup lang="ts">
import { usePropertyForm } from '@/components/DiyEditor/util'
import { HotZoneProperty } from '@/components/DiyEditor/components/mobile/HotZone/config'
import HotZoneEditDialog from './components/HotZoneEditDialog/index.vue'
/** 热区属性面板 */
defineOptions({ name: 'HotZoneProperty' })
const props = defineProps<{ modelValue: HotZoneProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
// 热区编辑对话框
const editDialogRef = ref()
// 打开热区编辑对话框
const handleOpenEditDialog = () => {
editDialogRef.value.open()
}
</script>
<style scoped lang="scss">
.hot-zone {
position: absolute;
background: #409effbf;
border: 1px solid var(--el-color-primary);
color: #fff;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
cursor: move;
/* 控制点 */
.ctrl-dot {
position: absolute;
width: 4px;
height: 4px;
border-radius: 50%;
background-color: #fff;
}
}
</style>
@@ -13,7 +13,7 @@
</UploadImg>
</el-form-item>
<el-form-item label="链接" prop="url">
<el-input placeholder="链接" v-model="formData.url" />
<AppLinkInput v-model="formData.url" />
</el-form-item>
</el-form>
</ComponentContainerProperty>
@@ -13,6 +13,7 @@ export interface MagicCubeProperty {
// 组件样式
style: ComponentStyle
}
/** 广告魔方项目属性 */
export interface MagicCubeItemProperty {
// 图标链接
@@ -17,7 +17,7 @@
<UploadImg v-model="hotArea.imgUrl" height="80px" width="80px" />
</el-form-item>
<el-form-item label="链接" :prop="`list[${index}].url`">
<el-input v-model="hotArea.url" placeholder="请输入链接" />
<AppLinkInput v-model="hotArea.url" />
</el-form-item>
</template>
</template>
@@ -10,6 +10,7 @@ export interface MenuGridProperty {
// 组件样式
style: ComponentStyle
}
/** 宫格导航项目属性 */
export interface MenuGridItemProperty {
// 图标链接
@@ -9,72 +9,50 @@
</el-radio-group>
</el-form-item>
<el-text tag="p"> 菜单设置 </el-text>
<el-text type="info" size="small"> 拖动左侧的小圆点可以调整顺序 </el-text>
<template v-if="formData.list.length">
<VueDraggable
class="m-t-8px"
:list="formData.list"
item-key="index"
handle=".drag-icon"
:forceFallback="true"
:animation="200"
>
<template #item="{ element, index }">
<div class="mb-4px flex flex-col gap-4px rounded bg-gray-100 p-8px">
<div class="flex flex-row justify-between">
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
<Icon icon="ep:delete" class="text-red-500" @click="handleDeleteMenu(index)" />
</div>
<el-form-item label="图标" prop="iconUrl">
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
<template #tip> 建议尺寸:44 * 44 </template>
</UploadImg>
<el-card header="菜单设置" class="property-group" shadow="never">
<Draggable v-model="formData.list" :empty-item="EMPTY_MENU_GRID_ITEM_PROPERTY">
<template #default="{ element }">
<el-form-item label="图标" prop="iconUrl">
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
<template #tip> 建议尺寸:44 * 44 </template>
</UploadImg>
</el-form-item>
<el-form-item label="标题" prop="title">
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
</el-form-item>
<el-form-item label="副标题" prop="subtitle">
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
</el-form-item>
<el-form-item label="链接" prop="url">
<AppLinkInput v-model="element.url" />
</el-form-item>
<el-form-item label="显示角标" prop="badge.show">
<el-switch v-model="element.badge.show" />
</el-form-item>
<template v-if="element.badge.show">
<el-form-item label="角标内容" prop="badge.text">
<InputWithColor
v-model="element.badge.text"
v-model:color="element.badge.textColor"
/>
</el-form-item>
<el-form-item label="标题" prop="title">
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
<el-form-item label="背景颜色" prop="badge.bgColor">
<ColorInput v-model="element.badge.bgColor" />
</el-form-item>
<el-form-item label="副标题" prop="subtitle">
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
</el-form-item>
<el-form-item label="链接" prop="url">
<el-input v-model="element.url" />
</el-form-item>
<el-form-item label="显示角标" prop="badge.show">
<el-switch v-model="element.badge.show" />
</el-form-item>
<template v-if="element.badge.show">
<el-form-item label="角标内容" prop="badge.text">
<InputWithColor
v-model="element.badge.text"
v-model:color="element.badge.textColor"
/>
</el-form-item>
<el-form-item label="背景颜色" prop="badge.bgColor">
<ColorInput v-model="element.badge.bgColor" />
</el-form-item>
</template>
</div>
</template>
</template>
</VueDraggable>
</template>
<el-form-item label-width="0">
<el-button @click="handleAddMenu" type="primary" plain class="m-t-8px w-full">
<Icon icon="ep:plus" class="mr-5px" /> 添加菜单
</el-button>
</el-form-item>
</Draggable>
</el-card>
</el-form>
</ComponentContainerProperty>
</template>
<script setup lang="ts">
import VueDraggable from 'vuedraggable'
import { usePropertyForm } from '@/components/DiyEditor/util'
import {
EMPTY_MENU_GRID_ITEM_PROPERTY,
MenuGridProperty
} from '@/components/DiyEditor/components/mobile/MenuGrid/config'
import { cloneDeep } from 'lodash-es'
/** 宫格导航属性面板 */
defineOptions({ name: 'MenuGridProperty' })
@@ -82,15 +60,6 @@ defineOptions({ name: 'MenuGridProperty' })
const props = defineProps<{ modelValue: MenuGridProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
/* 添加菜单 */
const handleAddMenu = () => {
formData.value.list.push(cloneDeep(EMPTY_MENU_GRID_ITEM_PROPERTY))
}
/* 删除菜单 */
const handleDeleteMenu = (index: number) => {
formData.value.list.splice(index, 1)
}
</script>
<style scoped lang="scss"></style>
@@ -8,6 +8,7 @@ export interface MenuListProperty {
// 组件样式
style: ComponentStyle
}
/** 列表导航项目属性 */
export interface MenuListItemProperty {
// 图标链接
@@ -5,55 +5,34 @@
<!-- 表单 -->
<el-form label-width="60px" :model="formData" class="m-t-8px">
<div v-if="formData.list.length">
<VueDraggable
:list="formData.list"
item-key="index"
handle=".drag-icon"
:forceFallback="true"
:animation="200"
>
<template #item="{ element, index }">
<div class="mb-4px flex flex-col gap-4px rounded bg-gray-100 p-8px">
<div class="flex flex-row justify-between">
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
<Icon icon="ep:delete" class="text-red-500" @click="handleDeleteMenu(index)" />
</div>
<el-form-item label="图标" prop="iconUrl">
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
<template #tip> 建议尺寸:44 * 44 </template>
</UploadImg>
</el-form-item>
<el-form-item label="标题" prop="title">
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
</el-form-item>
<el-form-item label="副标题" prop="subtitle">
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
</el-form-item>
<el-form-item label="链接" prop="url">
<el-input v-model="element.url" />
</el-form-item>
</div>
</template>
</VueDraggable>
</div>
<el-form-item label-width="0">
<el-button @click="handleAddMenu" type="primary" plain class="m-t-8px w-full">
<Icon icon="ep:plus" class="mr-5px" /> 添加菜单
</el-button>
</el-form-item>
<Draggable v-model="formData.list" :empty-item="EMPTY_MENU_LIST_ITEM_PROPERTY">
<template #default="{ element }">
<el-form-item label="图标" prop="iconUrl">
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
<template #tip> 建议尺寸:44 * 44 </template>
</UploadImg>
</el-form-item>
<el-form-item label="标题" prop="title">
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
</el-form-item>
<el-form-item label="副标题" prop="subtitle">
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
</el-form-item>
<el-form-item label="链接" prop="url">
<AppLinkInput v-model="element.url" />
</el-form-item>
</template>
</Draggable>
</el-form>
</ComponentContainerProperty>
</template>
<script setup lang="ts">
import VueDraggable from 'vuedraggable'
import { usePropertyForm } from '@/components/DiyEditor/util'
import {
EMPTY_MENU_LIST_ITEM_PROPERTY,
MenuListProperty
} from '@/components/DiyEditor/components/mobile/MenuList/config'
import { cloneDeep } from 'lodash-es'
/** 列表导航属性面板 */
defineOptions({ name: 'MenuListProperty' })
@@ -61,15 +40,6 @@ defineOptions({ name: 'MenuListProperty' })
const props = defineProps<{ modelValue: MenuListProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
/* 添加菜单 */
const handleAddMenu = () => {
formData.value.list.push(cloneDeep(EMPTY_MENU_LIST_ITEM_PROPERTY))
}
/* 删除菜单 */
const handleDeleteMenu = (index: number) => {
formData.value.list.splice(index, 1)
}
</script>
<style scoped lang="scss"></style>
@@ -28,7 +28,7 @@
<!-- 标题 -->
<span
v-if="property.layout === 'iconText'"
class="text-14px"
class="text-12px"
:style="{
color: item.titleColor,
height: `${TITLE_HEIGHT}px`,
@@ -51,7 +51,7 @@ const props = defineProps<{ property: MenuSwiperProperty }>()
// 标题的高度
const TITLE_HEIGHT = 20
// 图标的高度
const ICON_SIZE = 50
const ICON_SIZE = 42
// 垂直间距:一行上下的间距
const SPACE_Y = 16
@@ -22,63 +22,42 @@
</el-radio-group>
</el-form-item>
<el-text tag="p"> 菜单设置 </el-text>
<el-text type="info" size="small"> 拖动左侧的小圆点可以调整顺序 </el-text>
<template v-if="formData.list.length">
<VueDraggable
class="m-t-8px"
:list="formData.list"
item-key="index"
handle=".drag-icon"
:forceFallback="true"
:animation="200"
>
<template #item="{ element, index }">
<div class="mb-4px flex flex-col gap-4px rounded bg-gray-100 p-8px">
<div class="flex flex-row justify-between">
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
<Icon icon="ep:delete" class="text-red-500" @click="handleDeleteMenu(index)" />
</div>
<el-form-item label="图标" prop="iconUrl">
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
<template #tip> 建议尺寸:98 * 98 </template>
</UploadImg>
<el-card header="菜单设置" class="property-group" shadow="never">
<Draggable v-model="formData.list" :empty-item="cloneDeep(EMPTY_MENU_SWIPER_ITEM_PROPERTY)">
<template #default="{ element }">
<el-form-item label="图标" prop="iconUrl">
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
<template #tip> 建议尺寸:98 * 98 </template>
</UploadImg>
</el-form-item>
<el-form-item label="标题" prop="title">
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
</el-form-item>
<el-form-item label="链接" prop="url">
<AppLinkInput v-model="element.url" />
</el-form-item>
<el-form-item label="显示角标" prop="badge.show">
<el-switch v-model="element.badge.show" />
</el-form-item>
<template v-if="element.badge.show">
<el-form-item label="角标内容" prop="badge.text">
<InputWithColor
v-model="element.badge.text"
v-model:color="element.badge.textColor"
/>
</el-form-item>
<el-form-item label="标题" prop="title">
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
<el-form-item label="背景颜色" prop="badge.bgColor">
<ColorInput v-model="element.badge.bgColor" />
</el-form-item>
<el-form-item label="链接" prop="url">
<el-input v-model="element.url" />
</el-form-item>
<el-form-item label="显示角标" prop="badge.show">
<el-switch v-model="element.badge.show" />
</el-form-item>
<template v-if="element.badge.show">
<el-form-item label="角标内容" prop="badge.text">
<InputWithColor
v-model="element.badge.text"
v-model:color="element.badge.textColor"
/>
</el-form-item>
<el-form-item label="背景颜色" prop="badge.bgColor">
<ColorInput v-model="element.badge.bgColor" />
</el-form-item>
</template>
</div>
</template>
</template>
</VueDraggable>
</template>
<el-form-item label-width="0">
<el-button @click="handleAddMenu" type="primary" plain class="m-t-8px w-full">
<Icon icon="ep:plus" class="mr-5px" /> 添加菜单
</el-button>
</el-form-item>
</Draggable>
</el-card>
</el-form>
</ComponentContainerProperty>
</template>
<script setup lang="ts">
import VueDraggable from 'vuedraggable'
import { usePropertyForm } from '@/components/DiyEditor/util'
import {
EMPTY_MENU_SWIPER_ITEM_PROPERTY,
@@ -92,15 +71,6 @@ defineOptions({ name: 'MenuSwiperProperty' })
const props = defineProps<{ modelValue: MenuSwiperProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
/* 添加菜单 */
const handleAddMenu = () => {
formData.value.list.push(cloneDeep(EMPTY_MENU_SWIPER_ITEM_PROPERTY))
}
/* 删除菜单 */
const handleDeleteMenu = (index: number) => {
formData.value.list.splice(index, 1)
}
</script>
<style scoped lang="scss"></style>
@@ -1,4 +1,4 @@
import { DiyComponent } from '@/components/DiyEditor/util'
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 公告栏属性 */
export interface NoticeBarProperty {
@@ -10,6 +10,8 @@ export interface NoticeBarProperty {
backgroundColor: string
// 文字颜色
textColor: string
// 组件样式
style: ComponentStyle
}
/** 内容属性 */
@@ -34,6 +36,11 @@ export const component = {
}
],
backgroundColor: '#fff',
textColor: '#333'
textColor: '#333',
style: {
bgType: 'color',
bgColor: '#fff',
marginBottom: 8
} as ComponentStyle
}
} as DiyComponent<NoticeBarProperty>
@@ -1,58 +1,36 @@
<template>
<el-form label-width="80px" :model="formData" :rules="rules">
<el-form-item label="公告图标" prop="iconUrl">
<UploadImg v-model="formData.iconUrl" height="48px">
<template #tip>建议尺寸:24 * 24</template>
</UploadImg>
</el-form-item>
<el-form-item label="背景颜色" prop="backgroundColor">
<ColorInput v-model="formData.backgroundColor" />
</el-form-item>
<el-form-item label="文字颜色" prop="文字颜色">
<ColorInput v-model="formData.textColor" />
</el-form-item>
<el-text tag="p"> 公告内容 </el-text>
<el-text type="info" size="small"> 拖动左上角的小圆点可以调整热词顺序 </el-text>
<template v-if="formData.contents.length">
<VueDraggable
:list="formData.contents"
item-key="index"
handle=".drag-icon"
:forceFallback="true"
:animation="200"
class="m-t-8px"
>
<template #item="{ element, index }">
<div class="mb-4px flex flex-row gap-4px rounded bg-gray-100 p-8px">
<div class="flex flex-col items-start justify-between">
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
<Icon
icon="ep:delete"
class="cursor-pointer text-red-5"
@click="handleDeleteContent(index)"
v-if="formData.contents.length > 1"
/>
</div>
<div class="w-full flex flex-col gap-8px">
<ComponentContainerProperty v-model="formData.style">
<el-form label-width="80px" :model="formData" :rules="rules">
<el-form-item label="公告图标" prop="iconUrl">
<UploadImg v-model="formData.iconUrl" height="48px">
<template #tip>建议尺寸:24 * 24</template>
</UploadImg>
</el-form-item>
<el-form-item label="背景颜色" prop="backgroundColor">
<ColorInput v-model="formData.backgroundColor" />
</el-form-item>
<el-form-item label="文字颜色" prop="文字颜色">
<ColorInput v-model="formData.textColor" />
</el-form-item>
<el-card header="公告内容" class="property-group" shadow="never">
<Draggable v-model="formData.contents">
<template #default="{ element }">
<el-form-item label="公告" prop="text" label-width="40px">
<el-input v-model="element.text" placeholder="请输入公告" />
<el-input v-model="element.url" placeholder="请输入链接" />
</div>
</div>
</template>
</VueDraggable>
</template>
<el-form-item label-width="0">
<el-button @click="handleAddContent" type="primary" plain class="m-t-8px w-full">
添加内容
</el-button>
</el-form-item>
</el-form>
</el-form-item>
<el-form-item label="链接" prop="url" label-width="40px">
<AppLinkInput v-model="element.url" />
</el-form-item>
</template>
</Draggable>
</el-card>
</el-form>
</ComponentContainerProperty>
</template>
<script setup lang="ts">
import { NoticeBarProperty, NoticeContentProperty } from './config'
import { NoticeBarProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
import VueDraggable from 'vuedraggable'
// 通知栏属性面板
defineOptions({ name: 'NoticeBarProperty' })
// 表单校验
@@ -63,15 +41,6 @@ const rules = {
const props = defineProps<{ modelValue: NoticeBarProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
/* 添加公告 */
const handleAddContent = () => {
formData.value.contents.push({} as NoticeContentProperty)
}
/* 删除公告 */
const handleDeleteContent = (index: number) => {
formData.value.contents.splice(index, 1)
}
</script>
<style scoped lang="scss"></style>
@@ -0,0 +1,26 @@
import { DiyComponent } from '@/components/DiyEditor/util'
/** 弹窗广告属性 */
export interface PopoverProperty {
list: PopoverItemProperty[]
}
export interface PopoverItemProperty {
// 图片地址
imgUrl: string
// 跳转连接
url: string
// 显示类型:仅显示一次、每次启动都会显示
showType: 'once' | 'always'
}
// 定义组件
export const component = {
id: 'Popover',
name: '弹窗广告',
icon: 'carbon:popup',
position: 'fixed',
property: {
list: [{ showType: 'once' }]
}
} as DiyComponent<PopoverProperty>
@@ -0,0 +1,38 @@
<template>
<div
v-for="(item, index) in property.list"
:key="index"
class="absolute bottom-50% right-50% h-454px w-292px border-1px border-gray border-rounded-4px border-solid bg-white p-1px"
:style="{
zIndex: 100 + index + (activeIndex === index ? 100 : 0),
marginRight: `${-146 - index * 20}px`,
marginBottom: `${-227 - index * 20}px`
}"
@click="handleActive(index)"
>
<el-image :src="item.imgUrl" fit="contain" class="h-full w-full">
<template #error>
<div class="h-full w-full flex items-center justify-center">
<Icon icon="ep:picture" />
</div>
</template>
</el-image>
<div class="absolute right-1 top-1 text-12px">{{ index + 1 }}</div>
</div>
</template>
<script setup lang="ts">
import { PopoverProperty } from './config'
/** 弹窗广告 */
defineOptions({ name: 'Popover' })
// 定义属性
defineProps<{ property: PopoverProperty }>()
// 处理选中
const activeIndex = ref(0)
const handleActive = (index: number) => {
activeIndex.value = index
}
</script>
<style scoped lang="scss"></style>
@@ -0,0 +1,38 @@
<template>
<el-form label-width="80px" :model="formData">
<Draggable v-model="formData.list" :empty-item="{ showType: 'once' }">
<template #default="{ element, index }">
<el-form-item label="图片" :prop="`list[${index}].imgUrl`">
<UploadImg v-model="element.imgUrl" height="56px" width="56px" />
</el-form-item>
<el-form-item label="跳转链接" :prop="`list[${index}].url`">
<AppLinkInput v-model="element.url" />
</el-form-item>
<el-form-item label="显示次数" :prop="`list[${index}].showType`">
<el-radio-group v-model="element.showType">
<el-tooltip content="只显示一次,下次打开时不显示" placement="bottom">
<el-radio label="once">一次</el-radio>
</el-tooltip>
<el-tooltip content="每次打开时都会显示" placement="bottom">
<el-radio label="always">不限</el-radio>
</el-tooltip>
</el-radio-group>
</el-form-item>
</template>
</Draggable>
</el-form>
</template>
<script setup lang="ts">
import { PopoverProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
// 弹窗广告属性面板
defineOptions({ name: 'PopoverProperty' })
const props = defineProps<{ modelValue: PopoverProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
</script>
<style scoped lang="scss"></style>
@@ -134,6 +134,7 @@ watch(
deep: true
}
)
/**
* 计算商品的间距
* @param index 商品索引
@@ -1,6 +1,6 @@
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 商品卡片属性 */
/** 商品栏属性 */
export interface ProductListProperty {
// 布局类型:双列 | 三列 | 水平滑动
layoutType: 'twoCol' | 'threeCol' | 'horizSwiper'
@@ -66,7 +66,7 @@
import { ProductListProperty } from './config'
import * as ProductSpuApi from '@/api/mall/product/spu'
/** 商品卡片 */
/** 商品栏 */
defineOptions({ name: 'ProductList' })
// 定义属性
const props = defineProps<{ property: ProductListProperty }>()
@@ -88,7 +88,7 @@ import { ProductListProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
// 商品卡片属性面板
// 商品栏属性面板
defineOptions({ name: 'ProductListProperty' })
const props = defineProps<{ modelValue: ProductListProperty }>()
@@ -0,0 +1,25 @@
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 营销文章属性 */
export interface PromotionArticleProperty {
// 文章编号
id: number
// 组件样式
style: ComponentStyle
}
// 定义组件
export const component = {
id: 'PromotionArticle',
name: '营销文章',
icon: 'ph:article-medium',
property: {
style: {
bgType: 'color',
bgColor: '',
marginLeft: 8,
marginRight: 8,
marginBottom: 8
} as ComponentStyle
}
} as DiyComponent<PromotionArticleProperty>
@@ -0,0 +1,27 @@
<template>
<div class="min-h-30px" v-html="article?.content"></div>
</template>
<script setup lang="ts">
import { PromotionArticleProperty } from './config'
import * as ArticleApi from '@/api/mall/promotion/article/index'
/** 营销文章 */
defineOptions({ name: 'PromotionArticle' })
// 定义属性
const props = defineProps<{ property: PromotionArticleProperty }>()
// 商品列表
const article = ref<ArticleApi.ArticleVO>()
watch(
() => props.property.id,
async () => {
if (props.property.id) {
article.value = await ArticleApi.getArticle(props.property.id)
}
},
{
immediate: true
}
)
</script>
<style scoped lang="scss"></style>
@@ -0,0 +1,56 @@
<template>
<ComponentContainerProperty v-model="formData.style">
<el-form label-width="40px" :model="formData">
<el-form-item label="文章" prop="id">
<el-select
v-model="formData.id"
placeholder="请选择文章"
class="w-full"
filterable
remote
:remote-method="queryArticleList"
:loading="loading"
>
<el-option
v-for="article in articles"
:key="article.id"
:label="article.title"
:value="article.id"
/>
</el-select>
</el-form-item>
</el-form>
</ComponentContainerProperty>
</template>
<script setup lang="ts">
import { PromotionArticleProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
import * as ArticleApi from '@/api/mall/promotion/article/index'
// 营销文章属性面板
defineOptions({ name: 'PromotionArticleProperty' })
const props = defineProps<{ modelValue: PromotionArticleProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
// 文章列表
const articles = ref<ArticleApi.ArticleVO>([])
// 加载中
const loading = ref(false)
// 查询文章列表
const queryArticleList = async (title?: string) => {
loading.value = true
const { list } = await ArticleApi.getArticlePage({ title, pageSize: 10 })
articles.value = list
loading.value = false
}
// 初始化
onMounted(() => {
queryArticleList()
})
</script>
<style scoped lang="scss"></style>
@@ -0,0 +1,64 @@
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 拼团属性 */
export interface PromotionCombinationProperty {
// 布局类型:单列 | 三列
layoutType: 'oneCol' | 'threeCol'
// 商品字段
fields: {
// 商品名称
name: PromotionCombinationFieldProperty
// 商品价格
price: PromotionCombinationFieldProperty
}
// 角标
badge: {
// 是否显示
show: boolean
// 角标图片
imgUrl: string
}
// 上圆角
borderRadiusTop: number
// 下圆角
borderRadiusBottom: number
// 间距
space: number
// 拼团活动编号
activityId: number
// 组件样式
style: ComponentStyle
}
// 商品字段
export interface PromotionCombinationFieldProperty {
// 是否显示
show: boolean
// 颜色
color: string
}
// 定义组件
export const component = {
id: 'PromotionCombination',
name: '拼团',
icon: 'mdi:account-group',
property: {
layoutType: 'oneCol',
fields: {
name: { show: true, color: '#000' },
price: { show: true, color: '#ff3000' }
},
badge: { show: false, imgUrl: '' },
borderRadiusTop: 8,
borderRadiusBottom: 8,
space: 8,
style: {
bgType: 'color',
bgColor: '',
marginLeft: 8,
marginRight: 8,
marginBottom: 8
} as ComponentStyle
}
} as DiyComponent<PromotionCombinationProperty>
@@ -0,0 +1,125 @@
<template>
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
<!-- 商品网格 -->
<div
class="grid overflow-x-auto"
:style="{
gridGap: `${property.space}px`,
gridTemplateColumns,
width: scrollbarWidth
}"
>
<!-- 商品 -->
<div
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
:style="{
borderTopLeftRadius: `${property.borderRadiusTop}px`,
borderTopRightRadius: `${property.borderRadiusTop}px`,
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
borderBottomRightRadius: `${property.borderRadiusBottom}px`
}"
v-for="(spu, index) in spuList"
:key="index"
>
<!-- 角标 -->
<div
v-if="property.badge.show"
class="absolute left-0 top-0 z-1 items-center justify-center"
>
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
</div>
<!-- 商品封面图 -->
<el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" />
<div
:class="[
'flex flex-col gap-8px p-8px box-border',
{
'w-[calc(100%-64px)]': columns === 2,
'w-full': columns === 3
}
]"
>
<!-- 商品名称 -->
<div
v-if="property.fields.name.show"
class="truncate text-12px"
:style="{ color: property.fields.name.color }"
>
{{ spu.name }}
</div>
<div>
<!-- 商品价格 -->
<span
v-if="property.fields.price.show"
class="text-12px"
:style="{ color: property.fields.price.color }"
>
¥{{ spu.price }}
</span>
</div>
</div>
</div>
</div>
</el-scrollbar>
</template>
<script setup lang="ts">
import { PromotionCombinationProperty } from './config'
import * as ProductSpuApi from '@/api/mall/product/spu'
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
/** 拼团 */
defineOptions({ name: 'PromotionCombination' })
// 定义属性
const props = defineProps<{ property: PromotionCombinationProperty }>()
// 商品列表
const spuList = ref<ProductSpuApi.Spu[]>([])
watch(
() => props.property.activityId,
async () => {
if (!props.property.activityId) return
const activity = await CombinationActivityApi.getCombinationActivity(props.property.activityId)
if (!activity?.spuId) return
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
},
{
immediate: true,
deep: true
}
)
// 手机宽度
const phoneWidth = ref(375)
// 容器
const containerRef = ref()
// 商品的列数
const columns = ref(2)
// 滚动条宽度
const scrollbarWidth = ref('100%')
// 商品图大小
const imageSize = ref('0')
// 商品网络列数
const gridTemplateColumns = ref('')
// 计算布局参数
watch(
() => [props.property, phoneWidth, spuList.value.length],
() => {
// 计算列数
columns.value = props.property.layoutType === 'oneCol' ? 1 : 3
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
const productWidth =
(phoneWidth.value - props.property.space * (columns.value - 1)) / columns.value
// 商品图布局:2列时,左右布局 3列时,上下布局
imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`
// 指定列数
gridTemplateColumns.value = `repeat(${columns.value}, auto)`
// 不滚动
scrollbarWidth.value = '100%'
},
{ immediate: true, deep: true }
)
onMounted(() => {
// 提取手机宽度
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375
})
</script>
<style scoped lang="scss"></style>
@@ -0,0 +1,112 @@
<template>
<ComponentContainerProperty v-model="formData.style">
<el-form label-width="80px" :model="formData">
<el-card header="拼团活动" class="property-group" shadow="never">
<el-form-item label="拼团活动" prop="activityId">
<el-select v-model="formData.activityId">
<el-option
v-for="activity in activityList"
:key="activity.id"
:label="activity.name"
:value="activity.id"
/>
</el-select>
</el-form-item>
</el-card>
<el-card header="商品样式" class="property-group" shadow="never">
<el-form-item label="布局" prop="type">
<el-radio-group v-model="formData.layoutType">
<el-tooltip class="item" content="单列" placement="bottom">
<el-radio-button label="oneCol">
<Icon icon="fluent:text-column-one-24-filled" />
</el-radio-button>
</el-tooltip>
<el-tooltip class="item" content="三列" placement="bottom">
<el-radio-button label="threeCol">
<Icon icon="fluent:text-column-three-24-filled" />
</el-radio-button>
</el-tooltip>
</el-radio-group>
</el-form-item>
<el-form-item label="商品名称" prop="fields.name.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.name.color" />
<el-checkbox v-model="formData.fields.name.show" />
</div>
</el-form-item>
<el-form-item label="商品价格" prop="fields.price.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.price.color" />
<el-checkbox v-model="formData.fields.price.show" />
</div>
</el-form-item>
</el-card>
<el-card header="角标" class="property-group" shadow="never">
<el-form-item label="角标" prop="badge.show">
<el-switch v-model="formData.badge.show" />
</el-form-item>
<el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
<template #tip> 建议尺寸:36 * 22 </template>
</UploadImg>
</el-form-item>
</el-card>
<el-card header="商品样式" class="property-group" shadow="never">
<el-form-item label="上圆角" prop="borderRadiusTop">
<el-slider
v-model="formData.borderRadiusTop"
:max="100"
:min="0"
show-input
input-size="small"
:show-input-controls="false"
/>
</el-form-item>
<el-form-item label="下圆角" prop="borderRadiusBottom">
<el-slider
v-model="formData.borderRadiusBottom"
:max="100"
:min="0"
show-input
input-size="small"
:show-input-controls="false"
/>
</el-form-item>
<el-form-item label="间隔" prop="space">
<el-slider
v-model="formData.space"
:max="100"
:min="0"
show-input
input-size="small"
:show-input-controls="false"
/>
</el-form-item>
</el-card>
</el-form>
</ComponentContainerProperty>
</template>
<script setup lang="ts">
import { PromotionCombinationProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
import { CommonStatusEnum } from '@/utils/constants'
// 拼团属性面板
defineOptions({ name: 'PromotionCombinationProperty' })
const props = defineProps<{ modelValue: PromotionCombinationProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
// 活动列表
const activityList = ref<CombinationActivityApi.CombinationActivityVO>([])
onMounted(async () => {
const { list } = await CombinationActivityApi.getCombinationActivityPage({
status: CommonStatusEnum.ENABLE
})
activityList.value = list
})
</script>
<style scoped lang="scss"></style>
Loaded 100 of 297 files, more files were not shown because too many files have changed in this diff. Show more