37 Commits

Author SHA1 Message Date
1f5867b568 feat: 🚀 简介 2025-11-25 14:30:19 +08:00
3aadf27102 feat: 🚀 角色权限-首页默认选择-父级半选中传给后台 2025-10-27 15:35:16 +08:00
5f423ba282 feat: 🚀 省略 2025-09-20 14:22:09 +08:00
f97dc8fd2e fix: 🧩 文章列表查询功能文章分类2级查询ID未传 2025-09-19 17:59:00 +08:00
f7979b4e9b feat: 🚀 生产环境更新 2025-09-18 11:58:42 +08:00
9110df9711 feat: 🚀 图片增加删除功能 2025-09-17 14:37:55 +08:00
1090351df7 feat: 🚀 图片排序 2025-09-17 13:54:30 +08:00
0dab4cc524 fix: 🧩 修复富文本编辑器视频上传限制(150M) 2025-08-26 17:27:49 +08:00
29d6ba59c9 feat: 🚀 切换站点清空语言 2025-08-07 14:51:54 +08:00
1566a72cb6 fix: 🧩 修復不能換行 2025-07-31 12:36:42 +08:00
4e8f3e6564 feat: 🚀 div标签解析 2025-07-30 16:33:56 +08:00
5da9c11771 feat: 🚀 生产环境 2025-07-29 12:01:11 +08:00
41da6b1914 feat: 🚀 tabs 2025-07-29 11:41:09 +08:00
1a97f75546 feat: 🚀 tabs增加滚动条 2025-07-28 10:03:07 +08:00
b3bffac35e feat: 🚀 富文本自定义tabs功能 2025-07-24 15:21:08 +08:00
da149760cb feat: 🚀 tabs功能基本完成 2025-07-23 15:42:28 +08:00
d79c3f8191 feat: 🚀 优化编辑器自定义图片和视频命名 2025-07-22 16:13:26 +08:00
2347bc6f0c feat: 🚀 不知道修复了没有 2025-07-22 14:26:12 +08:00
f867f50114 feat: 🚀 文章分类添加图片 2025-07-18 15:05:07 +08:00
fd0aaee998 feat: 🚀 图片排序 2025-07-17 15:51:07 +08:00
e2261c5fc4 feat: 🚀 图片排序 2025-07-17 15:20:57 +08:00
cc894333bf feat: 🚀 批量图片排序 2025-07-17 10:48:59 +08:00
e24a4adec6 feat: 🚀 图片上传按顺序回显 2025-07-16 15:36:48 +08:00
ebef3963f8 feat: 🚀 优化富文本多张图片上传 2025-07-14 17:53:37 +08:00
0ecb7691da feat: 🚀 登出地址 2025-07-14 10:57:34 +08:00
9c2253a91e feat: 🚀 生产环境登录地址修改 2025-07-14 10:30:19 +08:00
b302b3af2b feat: 🚀 优化树状结构 2025-07-11 16:34:41 +08:00
38b57adb5e fix: 🧩 修复产品详情传值 2025-07-08 17:10:10 +08:00
776de99441 fix: 🧩 修复bug 2025-07-05 13:37:06 +08:00
8524d2112e fix: 🧩 bug修复 2025-07-01 16:00:05 +08:00
fd35dc532b feat: 🚀 添加悬浮图上传 2025-06-30 09:35:47 +08:00
cc5d380551 fix: 🧩 label文字显示不全 2025-06-12 11:53:01 +08:00
6f8a3e72a5 fix: 🧩 树状结构支持多选 2025-06-06 14:05:40 +08:00
f2ee833412 feat: 🚀 addLabelValue方法抽离 2025-05-09 16:40:41 +08:00
ab003714bf feat: 🚀 切换富文本编辑器 2025-05-07 17:55:49 +08:00
4bd5797772 feat: 🚀 视频上传可以覆盖上传 2025-04-28 15:36:15 +08:00
927e2ebc4c feat: 🚀 banner添加相关分类 2025-04-23 17:21:46 +08:00
89 changed files with 6466 additions and 637 deletions

View File

@@ -2,7 +2,7 @@ ENV = 'dev'
VITE_APP_API_BASEURL =https://dev.ow.f2b211.com/admapi/v1 VITE_APP_API_BASEURL =https://dev.ow.f2b211.com/admapi/v1
#前端回显图片之类的地址 #前端回显图片之类的地址
VITE_APP_API_BASE_UPLOAD_URL =https://dev.ow.f2b211.com VITE_APP_API_BASE_UPLOAD_URL =https://dev.ow.f2b211.com/
# 本地环境 # 本地环境
VITE_USER_NODE_ENV = development VITE_USER_NODE_ENV = development

View File

@@ -1,11 +1,10 @@
ENV = 'production' ENV = 'production'
VITE_APP_API_BASEURL = https://opsscmapi.f2b211.com/api/ #请求地址 VITE_APP_API_BASEURL =https://orico.com.cn/admapi/v1
#前端回显图片之类的地址
# 线上环境 # 线上环境
VITE_USER_NODE_ENV = production VITE_USER_NODE_ENV = production
#前端回显图片之类的地址 #前端回显图片之类的地址
VITE_APP_API_BASE_UPLOAD_URL =https://dev.ow.f2b211.com/ VITE_APP_API_BASE_UPLOAD_URL =https://orico.com.cn/
# 是否启用 gzip 或 brotli 压缩打包,如果需要多个压缩规则,可以使用 “,” 分隔 # 是否启用 gzip 或 brotli 压缩打包,如果需要多个压缩规则,可以使用 “,” 分隔
# Optional: gzip | brotli | none # Optional: gzip | brotli | none

BIN
dist.zip Normal file

Binary file not shown.

600
package-lock.json generated
View File

@@ -10,9 +10,11 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.1.0", "@element-plus/icons-vue": "^2.1.0",
"@types/decimal.js": "^7.4.0", "@types/decimal.js": "^7.4.0",
"@vueup/vue-quill": "^1.2.0",
"@vueuse/core": "^10.1.2", "@vueuse/core": "^10.1.2",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
"@zhj-target/vue3-kind-editor": "^0.1.3",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"axios": "^1.4.0", "axios": "^1.4.0",
"bwip-js": "^4.3.2", "bwip-js": "^4.3.2",
@@ -27,12 +29,14 @@
"jsbarcode": "^3.11.6", "jsbarcode": "^3.11.6",
"jsonpack": "^1.1.5", "jsonpack": "^1.1.5",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mavon-editor": "^3.0.2",
"mitt": "^3.0.0", "mitt": "^3.0.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.1.3", "pinia": "^2.1.3",
"pinia-plugin-persistedstate": "^3.1.0", "pinia-plugin-persistedstate": "^3.1.0",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"qs": "^6.13.1", "qs": "^6.13.1",
"quill": "^2.0.3",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-router": "^4.2.2", "vue-router": "^4.2.2",
@@ -3607,6 +3611,74 @@
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.4.tgz", "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.4.tgz",
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
}, },
"node_modules/@vueup/vue-quill": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/@vueup/vue-quill/-/vue-quill-1.2.0.tgz",
"integrity": "sha512-kd5QPSHMDpycklojPXno2Kw2JSiKMYduKYQckTm1RJoVDA557MnyUXgcuuDpry4HY/Rny9nGNcK+m3AHk94wag==",
"dependencies": {
"quill": "^1.3.7",
"quill-delta": "^4.2.2"
},
"peerDependencies": {
"vue": "^3.2.41"
}
},
"node_modules/@vueup/vue-quill/node_modules/eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
},
"node_modules/@vueup/vue-quill/node_modules/fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w=="
},
"node_modules/@vueup/vue-quill/node_modules/parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg=="
},
"node_modules/@vueup/vue-quill/node_modules/quill": {
"version": "1.3.7",
"resolved": "https://registry.npmmirror.com/quill/-/quill-1.3.7.tgz",
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
"dependencies": {
"clone": "^2.1.1",
"deep-equal": "^1.0.1",
"eventemitter3": "^2.0.3",
"extend": "^3.0.2",
"parchment": "^1.1.4",
"quill-delta": "^3.6.2"
}
},
"node_modules/@vueup/vue-quill/node_modules/quill-delta": {
"version": "4.2.2",
"resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-4.2.2.tgz",
"integrity": "sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==",
"dependencies": {
"fast-diff": "1.2.0",
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0"
}
},
"node_modules/@vueup/vue-quill/node_modules/quill/node_modules/fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
},
"node_modules/@vueup/vue-quill/node_modules/quill/node_modules/quill-delta": {
"version": "3.6.3",
"resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-3.6.3.tgz",
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
"dependencies": {
"deep-equal": "^1.0.1",
"extend": "^3.0.2",
"fast-diff": "1.1.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/@vueuse/core": { "node_modules/@vueuse/core": {
"version": "10.1.2", "version": "10.1.2",
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.1.2.tgz", "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.1.2.tgz",
@@ -3825,6 +3897,11 @@
"snabbdom": "^3.1.0" "snabbdom": "^3.1.0"
} }
}, },
"node_modules/@zhj-target/vue3-kind-editor": {
"version": "0.1.3",
"resolved": "https://registry.npmmirror.com/@zhj-target/vue3-kind-editor/-/vue3-kind-editor-0.1.3.tgz",
"integrity": "sha512-tzSutZUBmGBRFBW8UMu2w5qmFMzp0RbaER8YQnMytq3COc04BHETiQkMOQ2PCHkrUXitzjBOWNSquW/AibizQA=="
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.8.2", "version": "8.8.2",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.2.tgz", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.2.tgz",
@@ -4323,13 +4400,20 @@
} }
}, },
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.2", "version": "1.0.8",
"resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
"dev": true,
"dependencies": { "dependencies": {
"function-bind": "^1.1.1", "call-bind-apply-helpers": "^1.0.0",
"get-intrinsic": "^1.0.2" "es-define-property": "^1.0.0",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/call-bind-apply-helpers": { "node_modules/call-bind-apply-helpers": {
@@ -4683,7 +4767,6 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz", "resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"dev": true,
"engines": { "engines": {
"node": ">=0.8" "node": ">=0.8"
} }
@@ -5448,6 +5531,11 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmmirror.com/cssfilter/-/cssfilter-0.0.10.tgz",
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
},
"node_modules/csso": { "node_modules/csso": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz", "resolved": "https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz",
@@ -5617,6 +5705,25 @@
"node": ">=0.10" "node": ">=0.10"
} }
}, },
"node_modules/deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.1.2.tgz",
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
"dependencies": {
"is-arguments": "^1.1.1",
"is-date-object": "^1.0.5",
"is-regex": "^1.1.4",
"object-is": "^1.1.5",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.5.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-is": { "node_modules/deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
@@ -5637,6 +5744,22 @@
"resolved": "https://registry.npmmirror.com/default-passive-events/-/default-passive-events-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/default-passive-events/-/default-passive-events-2.0.0.tgz",
"integrity": "sha512-eMtt76GpDVngZQ3ocgvRcNCklUMwID1PaNbCNxfpDXuiOXttSh0HzBbda1HU9SIUsDc02vb7g9+3I5tlqe/qMQ==" "integrity": "sha512-eMtt76GpDVngZQ3ocgvRcNCklUMwID1PaNbCNxfpDXuiOXttSh0HzBbda1HU9SIUsDc02vb7g9+3I5tlqe/qMQ=="
}, },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-lazy-prop": { "node_modules/define-lazy-prop": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -5647,16 +5770,19 @@
} }
}, },
"node_modules/define-properties": { "node_modules/define-properties": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.0.tgz", "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"dependencies": { "dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0", "has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1" "object-keys": "^1.1.1"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/define-property": { "node_modules/define-property": {
@@ -6566,6 +6692,11 @@
"es5-ext": "~0.10.14" "es5-ext": "~0.10.14"
} }
}, },
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
},
"node_modules/execa": { "node_modules/execa": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz", "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz",
@@ -6715,6 +6846,11 @@
"resolved": "https://registry.npmmirror.com/type/-/type-2.7.2.tgz", "resolved": "https://registry.npmmirror.com/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
}, },
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"node_modules/extend-shallow": { "node_modules/extend-shallow": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz",
@@ -6755,8 +6891,7 @@
"node_modules/fast-diff": { "node_modules/fast-diff": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.3.0.tgz", "resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="
"dev": true
}, },
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.2.12", "version": "3.2.12",
@@ -7061,8 +7196,7 @@
"node_modules/functions-have-names": { "node_modules/functions-have-names": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
"dev": true
}, },
"node_modules/gensync": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
@@ -7576,12 +7710,14 @@
} }
}, },
"node_modules/has-property-descriptors": { "node_modules/has-property-descriptors": {
"version": "1.0.0", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"dependencies": { "dependencies": {
"get-intrinsic": "^1.1.1" "es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/has-proto": { "node_modules/has-proto": {
@@ -7605,15 +7741,17 @@
} }
}, },
"node_modules/has-tostringtag": { "node_modules/has-tostringtag": {
"version": "1.0.0", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"dependencies": { "dependencies": {
"has-symbols": "^1.0.2" "has-symbols": "^1.0.3"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/has-value": { "node_modules/has-value": {
@@ -7958,6 +8096,21 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/is-arguments": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz",
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
"dependencies": {
"call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-array-buffer": { "node_modules/is-array-buffer": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -8049,7 +8202,6 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz", "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dev": true,
"dependencies": { "dependencies": {
"has-tostringtag": "^1.0.0" "has-tostringtag": "^1.0.0"
}, },
@@ -8202,7 +8354,6 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz", "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"dev": true,
"dependencies": { "dependencies": {
"call-bind": "^1.0.2", "call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0" "has-tostringtag": "^1.0.0"
@@ -9161,6 +9312,14 @@
"integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==",
"dev": true "dev": true
}, },
"node_modules/mavon-editor": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/mavon-editor/-/mavon-editor-3.0.2.tgz",
"integrity": "sha512-QmmsypznEkru3Gj9u/i3OLs7uWOwrAUDyLAiQnl7bj2lvZZ++J7MPA+Al0G26cQZ8GwzyC69T5l6NQjZ2z1Nuw==",
"dependencies": {
"xss": "^1.0.10"
}
},
"node_modules/mdn-data": { "node_modules/mdn-data": {
"version": "2.0.30", "version": "2.0.30",
"resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.30.tgz", "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.30.tgz",
@@ -9725,11 +9884,25 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": { "node_modules/object-keys": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@@ -9898,6 +10071,11 @@
"tslib": "^2.0.3" "tslib": "^2.0.3"
} }
}, },
"node_modules/parchment": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/parchment/-/parchment-3.0.0.tgz",
"integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A=="
},
"node_modules/parent-module": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
@@ -10653,6 +10831,33 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/quill": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/quill/-/quill-2.0.3.tgz",
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
"dependencies": {
"eventemitter3": "^5.0.1",
"lodash-es": "^4.17.21",
"parchment": "^3.0.0",
"quill-delta": "^5.1.0"
},
"engines": {
"npm": ">=8.2.3"
}
},
"node_modules/quill-delta": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-5.1.0.tgz",
"integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
"dependencies": {
"fast-diff": "^1.3.0",
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/randombytes": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz",
@@ -10925,17 +11130,22 @@
} }
}, },
"node_modules/regexp.prototype.flags": { "node_modules/regexp.prototype.flags": {
"version": "1.5.0", "version": "1.5.4",
"resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
"integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
"dev": true,
"dependencies": { "dependencies": {
"call-bind": "^1.0.2", "call-bind": "^1.0.8",
"define-properties": "^1.2.0", "define-properties": "^1.2.1",
"functions-have-names": "^1.2.3" "es-errors": "^1.3.0",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"set-function-name": "^2.0.2"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/regexpu-core": { "node_modules/regexpu-core": {
@@ -11284,6 +11494,36 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"dev": true "dev": true
}, },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/set-function-name": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz",
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"functions-have-names": "^1.2.3",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/set-value": { "node_modules/set-value": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/set-value/-/set-value-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/set-value/-/set-value-2.0.1.tgz",
@@ -14839,6 +15079,26 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/xss": {
"version": "1.0.15",
"resolved": "https://registry.npmmirror.com/xss/-/xss-1.0.15.tgz",
"integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==",
"dependencies": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"
},
"bin": {
"xss": "bin/xss"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/xss/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"node_modules/xtend": { "node_modules/xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
@@ -17502,6 +17762,72 @@
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.4.tgz", "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.4.tgz",
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
}, },
"@vueup/vue-quill": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/@vueup/vue-quill/-/vue-quill-1.2.0.tgz",
"integrity": "sha512-kd5QPSHMDpycklojPXno2Kw2JSiKMYduKYQckTm1RJoVDA557MnyUXgcuuDpry4HY/Rny9nGNcK+m3AHk94wag==",
"requires": {
"quill": "^1.3.7",
"quill-delta": "^4.2.2"
},
"dependencies": {
"eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
},
"fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w=="
},
"parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg=="
},
"quill": {
"version": "1.3.7",
"resolved": "https://registry.npmmirror.com/quill/-/quill-1.3.7.tgz",
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
"requires": {
"clone": "^2.1.1",
"deep-equal": "^1.0.1",
"eventemitter3": "^2.0.3",
"extend": "^3.0.2",
"parchment": "^1.1.4",
"quill-delta": "^3.6.2"
},
"dependencies": {
"fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
},
"quill-delta": {
"version": "3.6.3",
"resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-3.6.3.tgz",
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
"requires": {
"deep-equal": "^1.0.1",
"extend": "^3.0.2",
"fast-diff": "1.1.2"
}
}
}
},
"quill-delta": {
"version": "4.2.2",
"resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-4.2.2.tgz",
"integrity": "sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==",
"requires": {
"fast-diff": "1.2.0",
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0"
}
}
}
},
"@vueuse/core": { "@vueuse/core": {
"version": "10.1.2", "version": "10.1.2",
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.1.2.tgz", "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.1.2.tgz",
@@ -17629,6 +17955,11 @@
"integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==", "integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==",
"requires": {} "requires": {}
}, },
"@zhj-target/vue3-kind-editor": {
"version": "0.1.3",
"resolved": "https://registry.npmmirror.com/@zhj-target/vue3-kind-editor/-/vue3-kind-editor-0.1.3.tgz",
"integrity": "sha512-tzSutZUBmGBRFBW8UMu2w5qmFMzp0RbaER8YQnMytq3COc04BHETiQkMOQ2PCHkrUXitzjBOWNSquW/AibizQA=="
},
"acorn": { "acorn": {
"version": "8.8.2", "version": "8.8.2",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.2.tgz", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.2.tgz",
@@ -18014,13 +18345,14 @@
} }
}, },
"call-bind": { "call-bind": {
"version": "1.0.2", "version": "1.0.8",
"resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
"dev": true,
"requires": { "requires": {
"function-bind": "^1.1.1", "call-bind-apply-helpers": "^1.0.0",
"get-intrinsic": "^1.0.2" "es-define-property": "^1.0.0",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.2"
} }
}, },
"call-bind-apply-helpers": { "call-bind-apply-helpers": {
@@ -18301,8 +18633,7 @@
"clone": { "clone": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz", "resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
"dev": true
}, },
"codepage": { "codepage": {
"version": "1.15.0", "version": "1.15.0",
@@ -18909,6 +19240,11 @@
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true "dev": true
}, },
"cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmmirror.com/cssfilter/-/cssfilter-0.0.10.tgz",
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
},
"csso": { "csso": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz", "resolved": "https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz",
@@ -19040,6 +19376,19 @@
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"dev": true "dev": true
}, },
"deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.1.2.tgz",
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
"requires": {
"is-arguments": "^1.1.1",
"is-date-object": "^1.0.5",
"is-regex": "^1.1.4",
"object-is": "^1.1.5",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.5.1"
}
},
"deep-is": { "deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
@@ -19057,6 +19406,16 @@
"resolved": "https://registry.npmmirror.com/default-passive-events/-/default-passive-events-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/default-passive-events/-/default-passive-events-2.0.0.tgz",
"integrity": "sha512-eMtt76GpDVngZQ3ocgvRcNCklUMwID1PaNbCNxfpDXuiOXttSh0HzBbda1HU9SIUsDc02vb7g9+3I5tlqe/qMQ==" "integrity": "sha512-eMtt76GpDVngZQ3ocgvRcNCklUMwID1PaNbCNxfpDXuiOXttSh0HzBbda1HU9SIUsDc02vb7g9+3I5tlqe/qMQ=="
}, },
"define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"requires": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
}
},
"define-lazy-prop": { "define-lazy-prop": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -19064,11 +19423,11 @@
"dev": true "dev": true
}, },
"define-properties": { "define-properties": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.0.tgz", "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"requires": { "requires": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0", "has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1" "object-keys": "^1.1.1"
} }
@@ -19784,6 +20143,11 @@
"es5-ext": "~0.10.14" "es5-ext": "~0.10.14"
} }
}, },
"eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
},
"execa": { "execa": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz", "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz",
@@ -19914,6 +20278,11 @@
} }
} }
}, },
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"extend-shallow": { "extend-shallow": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz",
@@ -19948,8 +20317,7 @@
"fast-diff": { "fast-diff": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.3.0.tgz", "resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="
"dev": true
}, },
"fast-glob": { "fast-glob": {
"version": "3.2.12", "version": "3.2.12",
@@ -20191,8 +20559,7 @@
"functions-have-names": { "functions-have-names": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
"dev": true
}, },
"gensync": { "gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
@@ -20600,12 +20967,11 @@
"dev": true "dev": true
}, },
"has-property-descriptors": { "has-property-descriptors": {
"version": "1.0.0", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"requires": { "requires": {
"get-intrinsic": "^1.1.1" "es-define-property": "^1.0.0"
} }
}, },
"has-proto": { "has-proto": {
@@ -20620,12 +20986,11 @@
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
}, },
"has-tostringtag": { "has-tostringtag": {
"version": "1.0.0", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"requires": { "requires": {
"has-symbols": "^1.0.2" "has-symbols": "^1.0.3"
} }
}, },
"has-value": { "has-value": {
@@ -20901,6 +21266,15 @@
"kind-of": "^6.0.0" "kind-of": "^6.0.0"
} }
}, },
"is-arguments": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz",
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
"requires": {
"call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2"
}
},
"is-array-buffer": { "is-array-buffer": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -20980,7 +21354,6 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz", "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dev": true,
"requires": { "requires": {
"has-tostringtag": "^1.0.0" "has-tostringtag": "^1.0.0"
} }
@@ -21088,7 +21461,6 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz", "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"dev": true,
"requires": { "requires": {
"call-bind": "^1.0.2", "call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0" "has-tostringtag": "^1.0.0"
@@ -21866,6 +22238,14 @@
"integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==",
"dev": true "dev": true
}, },
"mavon-editor": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/mavon-editor/-/mavon-editor-3.0.2.tgz",
"integrity": "sha512-QmmsypznEkru3Gj9u/i3OLs7uWOwrAUDyLAiQnl7bj2lvZZ++J7MPA+Al0G26cQZ8GwzyC69T5l6NQjZ2z1Nuw==",
"requires": {
"xss": "^1.0.10"
}
},
"mdn-data": { "mdn-data": {
"version": "2.0.30", "version": "2.0.30",
"resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.30.tgz", "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.30.tgz",
@@ -22328,11 +22708,19 @@
"resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.3.tgz", "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.3.tgz",
"integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==" "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="
}, },
"object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"requires": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
}
},
"object-keys": { "object-keys": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
"dev": true
}, },
"object-visit": { "object-visit": {
"version": "1.0.1", "version": "1.0.1",
@@ -22466,6 +22854,11 @@
"tslib": "^2.0.3" "tslib": "^2.0.3"
} }
}, },
"parchment": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/parchment/-/parchment-3.0.0.tgz",
"integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A=="
},
"parent-module": { "parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
@@ -23059,6 +23452,27 @@
"integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
"dev": true "dev": true
}, },
"quill": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/quill/-/quill-2.0.3.tgz",
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
"requires": {
"eventemitter3": "^5.0.1",
"lodash-es": "^4.17.21",
"parchment": "^3.0.0",
"quill-delta": "^5.1.0"
}
},
"quill-delta": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-5.1.0.tgz",
"integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
"requires": {
"fast-diff": "^1.3.0",
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0"
}
},
"randombytes": { "randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz",
@@ -23280,14 +23694,16 @@
} }
}, },
"regexp.prototype.flags": { "regexp.prototype.flags": {
"version": "1.5.0", "version": "1.5.4",
"resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
"integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
"dev": true,
"requires": { "requires": {
"call-bind": "^1.0.2", "call-bind": "^1.0.8",
"define-properties": "^1.2.0", "define-properties": "^1.2.1",
"functions-have-names": "^1.2.3" "es-errors": "^1.3.0",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"set-function-name": "^2.0.2"
} }
}, },
"regexpu-core": { "regexpu-core": {
@@ -23557,6 +23973,30 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"dev": true "dev": true
}, },
"set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"requires": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
}
},
"set-function-name": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz",
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
"requires": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"functions-have-names": "^1.2.3",
"has-property-descriptors": "^1.0.2"
}
},
"set-value": { "set-value": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/set-value/-/set-value-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/set-value/-/set-value-2.0.1.tgz",
@@ -26355,6 +26795,22 @@
"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
"dev": true "dev": true
}, },
"xss": {
"version": "1.0.15",
"resolved": "https://registry.npmmirror.com/xss/-/xss-1.0.15.tgz",
"integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==",
"requires": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
}
}
},
"xtend": { "xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",

View File

@@ -22,9 +22,11 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.1.0", "@element-plus/icons-vue": "^2.1.0",
"@types/decimal.js": "^7.4.0", "@types/decimal.js": "^7.4.0",
"@vueup/vue-quill": "^1.2.0",
"@vueuse/core": "^10.1.2", "@vueuse/core": "^10.1.2",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
"@zhj-target/vue3-kind-editor": "^0.1.3",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"axios": "^1.4.0", "axios": "^1.4.0",
"bwip-js": "^4.3.2", "bwip-js": "^4.3.2",
@@ -39,12 +41,14 @@
"jsbarcode": "^3.11.6", "jsbarcode": "^3.11.6",
"jsonpack": "^1.1.5", "jsonpack": "^1.1.5",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mavon-editor": "^3.0.2",
"mitt": "^3.0.0", "mitt": "^3.0.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.1.3", "pinia": "^2.1.3",
"pinia-plugin-persistedstate": "^3.1.0", "pinia-plugin-persistedstate": "^3.1.0",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"qs": "^6.13.1", "qs": "^6.13.1",
"quill": "^2.0.3",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-router": "^4.2.2", "vue-router": "^4.2.2",

View File

@@ -71,12 +71,20 @@ class RequestHttp {
*/ */
this.service.interceptors.response.use( this.service.interceptors.response.use(
(response: AxiosResponse) => { (response: AxiosResponse) => {
const { data } = response; const { data, request } = response;
tryHideFullScreenLoading(); tryHideFullScreenLoading();
const imgId = request?.responseURL?.split("imgId=")[1];
//获取导出表格名称 //获取导出表格名称
getDispositionName(response); getDispositionName(response);
// 获取响应头中的 Authorization 信息 // 获取响应头中的 Authorization 信息
const authorization = response.headers["authorization"]; const authorization = response.headers["authorization"];
if (imgId) {
return {
imgId,
data
};
}
if (authorization) { if (authorization) {
// 可以在这里更新用户的 token 信息 // 可以在这里更新用户的 token 信息
const userStore = useUserStore(); const userStore = useUserStore();

View File

@@ -19,7 +19,7 @@ export const getArticleClassDelApi = (params: any) => {
}; };
//文章分类更新(用于编辑后) //文章分类更新(用于编辑后)
export const getArticleClassEditUpApi = (params: any) => { export const getArticleClassEditUpApi = (params: any) => {
const { id, name, sort, is_show, pid, seo_title, seo_keywords, seo_desc } = params; const { id, name, sort, is_show, pid, seo_title, seo_keywords, seo_desc, icon } = params;
return http.put<any>(`/article/category/update/${id}`, { return http.put<any>(`/article/category/update/${id}`, {
name, name,
@@ -28,7 +28,8 @@ export const getArticleClassEditUpApi = (params: any) => {
pid, pid,
seo_title, seo_title,
seo_keywords, seo_keywords,
seo_desc seo_desc,
icon
}); });
}; };
//文章分类详情(用于编辑) //文章分类详情(用于编辑)

View File

@@ -11,8 +11,8 @@ import http from "@/api";
* @name 文件上传模块 * @name 文件上传模块
*/ */
// 图片上传 // 图片上传
export const uploadImg = (params: any, name?: any) => { export const uploadImg = (params: any, name?: any, id?: any) => {
return http.post<any>(`/images/${name}/upload`, params); return http.post<any>(`/images/${name}/upload?imgId=${id}`, params);
}; };
// 视频上传 // 视频上传

119
src/auto-import.d.ts vendored
View File

@@ -4,67 +4,66 @@
// Generated by unplugin-auto-import // Generated by unplugin-auto-import
export {} export {}
declare global { declare global {
const EffectScope: typeof import('vue')['EffectScope'] const EffectScope: typeof import("vue")["EffectScope"];
const ElNotification: typeof import('element-plus/es')['ElNotification'] const computed: typeof import("vue")["computed"];
const computed: typeof import('vue')['computed'] const createApp: typeof import("vue")["createApp"];
const createApp: typeof import('vue')['createApp'] const customRef: typeof import("vue")["customRef"];
const customRef: typeof import('vue')['customRef'] const defineAsyncComponent: typeof import("vue")["defineAsyncComponent"];
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] const defineComponent: typeof import("vue")["defineComponent"];
const defineComponent: typeof import('vue')['defineComponent'] const effectScope: typeof import("vue")["effectScope"];
const effectScope: typeof import('vue')['effectScope'] const getCurrentInstance: typeof import("vue")["getCurrentInstance"];
const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getCurrentScope: typeof import("vue")["getCurrentScope"];
const getCurrentScope: typeof import('vue')['getCurrentScope'] const h: typeof import("vue")["h"];
const h: typeof import('vue')['h'] const inject: typeof import("vue")["inject"];
const inject: typeof import('vue')['inject'] const isProxy: typeof import("vue")["isProxy"];
const isProxy: typeof import('vue')['isProxy'] const isReactive: typeof import("vue")["isReactive"];
const isReactive: typeof import('vue')['isReactive'] const isReadonly: typeof import("vue")["isReadonly"];
const isReadonly: typeof import('vue')['isReadonly'] const isRef: typeof import("vue")["isRef"];
const isRef: typeof import('vue')['isRef'] const markRaw: typeof import("vue")["markRaw"];
const markRaw: typeof import('vue')['markRaw'] const nextTick: typeof import("vue")["nextTick"];
const nextTick: typeof import('vue')['nextTick'] const onActivated: typeof import("vue")["onActivated"];
const onActivated: typeof import('vue')['onActivated'] const onBeforeMount: typeof import("vue")["onBeforeMount"];
const onBeforeMount: typeof import('vue')['onBeforeMount'] const onBeforeRouteLeave: typeof import("vue-router")["onBeforeRouteLeave"];
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] const onBeforeRouteUpdate: typeof import("vue-router")["onBeforeRouteUpdate"];
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] const onBeforeUnmount: typeof import("vue")["onBeforeUnmount"];
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] const onBeforeUpdate: typeof import("vue")["onBeforeUpdate"];
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] const onDeactivated: typeof import("vue")["onDeactivated"];
const onDeactivated: typeof import('vue')['onDeactivated'] const onErrorCaptured: typeof import("vue")["onErrorCaptured"];
const onErrorCaptured: typeof import('vue')['onErrorCaptured'] const onMounted: typeof import("vue")["onMounted"];
const onMounted: typeof import('vue')['onMounted'] const onRenderTracked: typeof import("vue")["onRenderTracked"];
const onRenderTracked: typeof import('vue')['onRenderTracked'] const onRenderTriggered: typeof import("vue")["onRenderTriggered"];
const onRenderTriggered: typeof import('vue')['onRenderTriggered'] const onScopeDispose: typeof import("vue")["onScopeDispose"];
const onScopeDispose: typeof import('vue')['onScopeDispose'] const onServerPrefetch: typeof import("vue")["onServerPrefetch"];
const onServerPrefetch: typeof import('vue')['onServerPrefetch'] const onUnmounted: typeof import("vue")["onUnmounted"];
const onUnmounted: typeof import('vue')['onUnmounted'] const onUpdated: typeof import("vue")["onUpdated"];
const onUpdated: typeof import('vue')['onUpdated'] const provide: typeof import("vue")["provide"];
const provide: typeof import('vue')['provide'] const reactive: typeof import("vue")["reactive"];
const reactive: typeof import('vue')['reactive'] const readonly: typeof import("vue")["readonly"];
const readonly: typeof import('vue')['readonly'] const ref: typeof import("vue")["ref"];
const ref: typeof import('vue')['ref'] const resolveComponent: typeof import("vue")["resolveComponent"];
const resolveComponent: typeof import('vue')['resolveComponent'] const shallowReactive: typeof import("vue")["shallowReactive"];
const shallowReactive: typeof import('vue')['shallowReactive'] const shallowReadonly: typeof import("vue")["shallowReadonly"];
const shallowReadonly: typeof import('vue')['shallowReadonly'] const shallowRef: typeof import("vue")["shallowRef"];
const shallowRef: typeof import('vue')['shallowRef'] const toRaw: typeof import("vue")["toRaw"];
const toRaw: typeof import('vue')['toRaw'] const toRef: typeof import("vue")["toRef"];
const toRef: typeof import('vue')['toRef'] const toRefs: typeof import("vue")["toRefs"];
const toRefs: typeof import('vue')['toRefs'] const toValue: typeof import("vue")["toValue"];
const toValue: typeof import('vue')['toValue'] const triggerRef: typeof import("vue")["triggerRef"];
const triggerRef: typeof import('vue')['triggerRef'] const unref: typeof import("vue")["unref"];
const unref: typeof import('vue')['unref'] const useAttrs: typeof import("vue")["useAttrs"];
const useAttrs: typeof import('vue')['useAttrs'] const useCssModule: typeof import("vue")["useCssModule"];
const useCssModule: typeof import('vue')['useCssModule'] const useCssVars: typeof import("vue")["useCssVars"];
const useCssVars: typeof import('vue')['useCssVars'] const useLink: typeof import("vue-router")["useLink"];
const useLink: typeof import('vue-router')['useLink'] const useRoute: typeof import("vue-router")["useRoute"];
const useRoute: typeof import('vue-router')['useRoute'] const useRouter: typeof import("vue-router")["useRouter"];
const useRouter: typeof import('vue-router')['useRouter'] const useSlots: typeof import("vue")["useSlots"];
const useSlots: typeof import('vue')['useSlots'] const watch: typeof import("vue")["watch"];
const watch: typeof import('vue')['watch'] const watchEffect: typeof import("vue")["watchEffect"];
const watchEffect: typeof import('vue')['watchEffect'] const watchPostEffect: typeof import("vue")["watchPostEffect"];
const watchPostEffect: typeof import('vue')['watchPostEffect'] const watchSyncEffect: typeof import("vue")["watchSyncEffect"];
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
} }
// for type re-export // for type re-export
declare global { declare global {
// @ts-ignore // @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue' export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from "vue";
} }

20
src/components.d.ts vendored
View File

@@ -7,10 +7,6 @@ export {}
declare module "vue" { declare module "vue" {
export interface GlobalComponents { export interface GlobalComponents {
403: typeof import("./components/ErrorMessage/403.vue")["default"];
404: typeof import("./components/ErrorMessage/404.vue")["default"];
500: typeof import("./components/ErrorMessage/500.vue")["default"];
ColSetting: typeof import("./components/ProTable/components/ColSetting.vue")["default"];
ElAside: typeof import("element-plus/es")["ElAside"]; ElAside: typeof import("element-plus/es")["ElAside"];
ElAutocomplete: typeof import("element-plus/es")["ElAutocomplete"]; ElAutocomplete: typeof import("element-plus/es")["ElAutocomplete"];
ElBreadcrumb: typeof import("element-plus/es")["ElBreadcrumb"]; ElBreadcrumb: typeof import("element-plus/es")["ElBreadcrumb"];
@@ -57,10 +53,6 @@ declare module "vue" {
ElTree: typeof import("element-plus/es")["ElTree"]; ElTree: typeof import("element-plus/es")["ElTree"];
ElTreeSelect: typeof import("element-plus/es")["ElTreeSelect"]; ElTreeSelect: typeof import("element-plus/es")["ElTreeSelect"];
ElUpload: typeof import("element-plus/es")["ElUpload"]; ElUpload: typeof import("element-plus/es")["ElUpload"];
Empty: typeof import("./components/ProTable/components/Empty.vue")["default"];
FormTable: typeof import("./components/FormTable/index.vue")["default"];
Grid: typeof import("./components/Grid/index.vue")["default"];
GridItem: typeof import("./components/Grid/components/GridItem.vue")["default"];
IEpArrowDown: typeof import("~icons/ep/arrow-down")["default"]; IEpArrowDown: typeof import("~icons/ep/arrow-down")["default"];
IEpCircleClose: typeof import("~icons/ep/circle-close")["default"]; IEpCircleClose: typeof import("~icons/ep/circle-close")["default"];
IEpFolderDelete: typeof import("~icons/ep/folder-delete")["default"]; IEpFolderDelete: typeof import("~icons/ep/folder-delete")["default"];
@@ -68,19 +60,7 @@ declare module "vue" {
IEpRemove: typeof import("~icons/ep/remove")["default"]; IEpRemove: typeof import("~icons/ep/remove")["default"];
IEpSearch: typeof import("~icons/ep/search")["default"]; IEpSearch: typeof import("~icons/ep/search")["default"];
IEpSwitchButton: typeof import("~icons/ep/switch-button")["default"]; IEpSwitchButton: typeof import("~icons/ep/switch-button")["default"];
ImportExcel: typeof import("./components/ImportExcel/index.vue")["default"];
Loading: typeof import("./components/Loading/index.vue")["default"];
Pagination: typeof import("./components/ProTable/components/Pagination.vue")["default"];
ProTable: typeof import("./components/ProTable/index.vue")["default"];
RouterLink: typeof import("vue-router")["RouterLink"]; RouterLink: typeof import("vue-router")["RouterLink"];
RouterView: typeof import("vue-router")["RouterView"]; RouterView: typeof import("vue-router")["RouterView"];
RulesForm: typeof import("./components/rulesForm/index.vue")["default"];
SearchForm: typeof import("./components/SearchForm/index.vue")["default"];
SearchFormItem: typeof import("./components/SearchForm/components/SearchFormItem.vue")["default"];
TableColumn: typeof import("./components/ProTable/components/TableColumn.vue")["default"];
UploadImg: typeof import("./components/Upload/UploadImg.vue")["default"];
UploadImgs: typeof import("./components/Upload/UploadImgs.vue")["default"];
UploadVideo: typeof import("./components/Upload/UploadVideo.vue")["default"];
WangEditor: typeof import("./components/WangEditor/index.vue")["default"];
} }
} }

View File

@@ -0,0 +1,227 @@
.editor,
.ql-toolbar {
line-height: normal !important;
white-space: pre-wrap !important;
}
.editor-img-uploader {
display: none;
}
.ql-editor {
min-height: 600px;
max-height: 600px;
overflow: auto;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
padding-right: 0;
content: "保存";
border-right: 0;
}
.ql-snow .ql-tooltip[data-mode="video"]::before {
content: "请输入视频地址:";
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: "12px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
content: "10px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
content: "32px";
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: "文本";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: "标题1";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: "标题2";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: "标题3";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: "标题4";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: "标题5";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: "标题6";
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: "标准字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
content: "衬线字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
content: "等宽字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="SimSun"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="SimSun"]::before {
font-family: SimSun, sans-serif;
content: "宋体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="SimHei"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="SimHei"]::before {
font-family: SimHei, sans-serif;
content: "黑体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="Microsoft-YaHei"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="Microsoft-YaHei"]::before {
font-family: "Microsoft YaHei", sans-serif;
content: "微软雅黑";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="KaiTi"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="KaiTi"]::before {
font-family: KaiTi, sans-serif;
content: "楷体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="FangSong"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="FangSong"]::before {
font-family: FangSong, sans-serif;
content: "仿宋";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="Arial"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="Arial"]::before {
font-family: Arial, sans-serif;
content: "Arial";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="Times-New-Roman"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="Times-New-Roman"]::before {
font-family: "Times New Roman", sans-serif;
content: "Times New Roman";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="sans-serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="sans-serif"]::before {
font-family: sans-serif;
content: "sans-serif";
}
.ql-font-SimSun {
font-family: SimSun, sans-serif;
}
.ql-font-SimHei {
font-family: SimHei, sans-serif;
}
.ql-font-Microsoft-YaHei {
font-family: "Microsoft YaHei", sans-serif;
}
.ql-font-KaiTi {
font-family: KaiTi, sans-serif;
}
.ql-font-FangSong {
font-family: FangSong, sans-serif;
}
.ql-font-Arial {
font-family: Arial, sans-serif;
}
.ql-font-Times-New-Roman {
font-family: "Times New Roman", sans-serif;
}
.ql-font-sans-serif {
font-family: sans-serif;
}
// .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="10px"]::before,
// .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="10px"]::before {
// content: "10px";
// }
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="12px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="12px"]::before {
content: "12px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="14px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="14px"]::before {
content: "14px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="16px"]::before {
content: "16px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="18px"]::before {
content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before {
content: "20px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="22px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="22px"]::before {
content: "22px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="24px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="24px"]::before {
content: "24px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="26px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="26px"]::before {
content: "26px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="28px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="28px"]::before {
content: "28px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="30px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="30px"]::before {
content: "30px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="32px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="32px"]::before {
content: "32px";
}
// .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="32px"]::before,
// .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="32px"]::before {
// content: "32px";
// }
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="36px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="36px"]::before {
content: "36px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="38px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="38px"]::before {
content: "38px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="40px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="40px"]::before {
content: "40px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="42px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="42px"]::before {
content: "44px";
}
// .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="44px"]::before,
// .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="44px"]::before {
// content: "44px";
// }
// .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="45px"]::before,
// .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="45px"]::before {
// content: "45px";
// }
// .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="50px"]::before,
// .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="50px"]::before {
// content: "50px";
// }

View File

@@ -0,0 +1,941 @@
<template>
<!-- 图片上传组件 -->
<el-upload
:id="uuid"
action="#"
:multiple="true"
:show-file-list="false"
:http-request="handleHttpUpload"
:before-upload="handleBeforeUpload"
class="editor-img-uploader"
accept=".jpeg,.jpg,.png,.gif"
>
<i ref="uploadRef" class="Plus editor-img-uploader"></i>
</el-upload>
<!-- 视频上传组件 -->
<input
type="file"
accept="video/*"
name="file"
ref="uploadFileVideo"
id="uploadFileVideo"
@change="handleVideoUpload"
style="width: 0; height: 0; cursor: pointer; opacity: 0"
/>
<!-- 主富文本编辑器 -->
<div class="editor">
<QuillEditor
id="mainEditor"
ref="myQuillEditor"
v-model:content="editorContent"
contentType="html"
@update:content="onContentChange"
:options="options"
/>
</div>
<!-- 标签页配置弹窗 -->
<div>
<el-dialog
v-model="outerVisible"
title="标签页配置"
style="width: 1200px; height: 900px"
close-on-click-modal
close-on-press-escape
:before-close="handleBeforeClose"
>
<el-tabs
v-model="activeName"
type="card"
class="demo-tabs"
editable
@edit="handleTabsEdit"
@tab-change="handleTabChange"
>
<el-tab-pane
:label="item.title"
:name="item.key"
v-for="(item, index) in tabsData"
:key="item.key"
@keydown.delete.stop
@keydown.backspace.stop
>
<template #label>
<div class="tab-title-edit">
<span v-if="!item.isEditing" @click="startEditTitle(index)" class="title-text">
{{ item.title }}
</span>
<el-input
@click.stop
@keydown.delete.stop
@keydown.backspace.stop
v-else
v-model="item.title"
max-length=""
:ref="el => (editInputRefs[index] = el)"
size="small"
class="title-input"
@blur="finishEditTitle(index)"
/>
</div>
</template>
<QuillEditor
:id="`tabEditor_${item.key}`"
:ref="
el => {
if (el) tabEditors[index] = el;
}
"
v-model:content="item.content"
contentType="html"
:options="options1"
/>
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleQX">取消</el-button>
<el-button type="primary" @click="handleQR"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
<!-- 图片顺序调整弹窗 -->
<el-dialog v-model="showImageSortDialog" title="调整图片顺序" width="800px" :before-close="handleImageDialogClose">
<div class="image-sort-container">
<draggable
v-model="sortedImageList"
:animation="200"
class="image-grid"
ghost-class="ghost"
item-key="customUid"
@update:modelValue="updateSortOrder"
>
<template #item="{ element: img }">
<div class="image-item">
<div class="image-preview-container">
<div class="image-order-badge">{{ img.sortOrder + 1 }}</div>
<img :src="img.tempUrl" :alt="`图片 ${img.sortOrder + 1}`" class="preview-img" />
<div style="display: flex; justify-content: flex-end; padding: 6px">
<el-button size="small" type="default" @click.stop="removeImage(img.customUid)">删除</el-button>
</div>
</div>
<div class="image-info">
<span class="image-name">{{ img.name }}</span>
</div>
</div>
</template>
</draggable>
<div v-if="sortedImageList.length === 0" class="empty-state">暂无图片请先上传</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="showImageSortDialog = false">取消</el-button>
<el-button type="primary" @click="confirmInsertImages" :disabled="sortedImageList.length === 0">
确认插入
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup name="Editor">
// import { Delete } from "@element-plus/icons-vue";
import { QuillEditor, Quill } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { getCurrentInstance, reactive, ref, toRaw, computed, onMounted, nextTick } from "vue";
import { generateUUID } from "@/utils";
import { routerObj } from "./utils.js";
import { titleConfig } from "./titleConfig.js";
import { uploadVideo, uploadImg } from "@/api/modules/upload";
import { ElNotification } from "element-plus";
import { useRouter } from "vue-router";
import { useMsg } from "@/hooks/useMsg";
import draggable from "vuedraggable";
// 字体配置
let fontSizeStyle = Quill.import("attributors/style/size");
fontSizeStyle.whitelist = [
"12px",
"14px",
"16px",
"18px",
"20px",
"22px",
"24px",
"26px",
"28px",
"30px",
"32px",
"34px",
"36px",
"38px",
"40px",
"42px"
];
Quill.register(fontSizeStyle, true);
// 自定义Blot
import ImageBlot from "./quill-image";
import Video from "./quill-video";
import TabsBlot from "./quill-tabs";
import DynamicDivBlot from "./quill-detail-div";
Quill.register(Video);
Quill.register(ImageBlot);
Quill.register(TabsBlot);
Quill.register(DynamicDivBlot);
// 基础变量
const { proxy } = getCurrentInstance();
const emit = defineEmits(["update:content", "handleRichTextContentChange"]);
const uuid = ref("id-" + generateUUID());
const $router = useRouter();
const routerValueName = $router.currentRoute.value.name;
const routerName = ref(routerObj[routerValueName]);
const uploadFileVideo = ref(null);
const outerVisible = ref(false);
const imageList = ref([]);
const imageListDb = ref([]);
const activeName = ref(null);
const activeEditor = ref("main");
// 标签页数据
const tabsData = ref([]);
const tabEditors = ref([]);
const editInputRefs = ref([]);
const currentEditingTabsRef = ref(null);
// 图片排序相关变量
const showImageSortDialog = ref(false);
const sortedImageList = ref([]);
const uploadingCount = ref(0);
// Props
const props = defineProps({
content: { type: String, default: "" },
readOnly: { type: Boolean, default: false },
fileSizeLimit: { type: Number, default: 5 }
});
// 主编辑器内容双向绑定
const editorContent = computed({
get: () => {
if (!props.content) return "";
return props.content;
},
set: val => {
emit("update:content", val);
}
});
const myQuillEditor = ref(null);
// 主编辑器配置
const options = reactive({
theme: "snow",
debug: "warn",
strict: false,
modules: {
toolbar: {
container: [
["bold", "italic", "underline", "strike"],
["blockquote", "code-block"],
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }],
[{ size: fontSizeStyle.whitelist }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ align: [] }],
["clean"],
["link", "image", "video", "tabs"]
],
handlers: {
image: function (value) {
if (value) {
activeEditor.value = "main";
sortedImageList.value = [];
proxy.$refs.uploadRef.click();
} else Quill.format("customImage", true);
},
video: function (value) {
if (value) {
activeEditor.value = "main";
document.querySelector("#uploadFileVideo")?.click();
} else Quill.format("customVideo", true);
},
tabs: function (value) {
outerVisible.value = value;
}
}
}
},
placeholder: "请输入内容...",
readOnly: props.readOnly
});
// 标签页编辑器配置
const options1 = reactive({
theme: "snow",
debug: "warn",
strict: false,
modules: {
toolbar: {
container: [
["bold", "italic", "underline", "strike"],
["blockquote", "code-block"],
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }],
[{ size: fontSizeStyle.whitelist }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ align: [] }],
["clean"],
["link", "image", "video"]
],
handlers: {
image: function (value) {
if (value) {
const currentIndex = tabsData.value.findIndex(item => item.key === activeName.value);
activeEditor.value = `tab-${currentIndex}`;
sortedImageList.value = [];
proxy.$refs.uploadRef.click();
} else Quill.format("customImage", true);
},
video: function (value) {
if (value) {
const currentIndex = tabsData.value.findIndex(item => item.key === activeName.value);
activeEditor.value = `tab-${currentIndex}`;
document.querySelector("#uploadFileVideo")?.click();
} else Quill.format("customVideo", true);
}
}
}
},
placeholder: "请输入内容...",
readOnly: props.readOnly
});
// 上传前校验
const handleBeforeUpload = file => {
const fileType = file.type;
file.customUid = generateUUID();
const validTypes = ["image/jpeg", "image/png", "image/gif", "image/jpg", "image/bmp", "image/webp"];
if (!validTypes.includes(fileType)) {
ElNotification({ title: "格式错误", message: "仅支持图片格式", type: "warning" });
return false;
}
const isLt = file.size / 1024 / 1024 < props.fileSizeLimit;
if (!isLt) {
ElNotification({ title: "大小超限", message: `不能超过 ${props.fileSizeLimit} MB`, type: "warning" });
return false;
}
// 生成临时URL用于预览
const tempUrl = URL.createObjectURL(file);
imageListDb.value.push({
...file,
tempUrl,
sortOrder: imageListDb.value.length, // 初始化排序索引
serverImgId: "",
path: ""
});
uploadingCount.value++;
return true;
};
// 图片上传处理
const handleHttpUpload = async options => {
let formData = new FormData();
formData.append("image", options.file);
try {
const result = await uploadImg(formData, routerName.value, options.file.customUid);
if (result?.data?.code === 0) {
const { data } = result.data;
const { imgId } = result;
const fileItem = imageListDb.value.find(item => item.customUid === options.file.customUid);
if (fileItem) {
fileItem.serverImgId = imgId;
fileItem.path = data.path;
}
uploadingCount.value--;
// 所有图片上传完成后显示排序弹窗
if (uploadingCount.value === 0) {
sortedImageList.value = [...imageListDb.value];
updateSortOrder(); // 确保排序索引正确
nextTick(() => {
showImageSortDialog.value = true;
});
}
}
} catch (error) {
console.error("图片上传失败:", error);
const failUid = options.file.customUid;
imageListDb.value = imageListDb.value.filter(item => item.customUid !== failUid);
uploadingCount.value = Math.max(0, uploadingCount.value - 1);
ElNotification({
title: "上传失败",
message: `图片 ${options.file.name} 上传失败`,
type: "error"
});
}
};
// 更新排序索引
const updateSortOrder = () => {
sortedImageList.value.forEach((item, index) => {
item.sortOrder = index;
});
};
// 从排序列表移除图片
const removeImage = customUid => {
// 释放临时URL
const removedImg = sortedImageList.value.find(img => img.customUid === customUid);
if (removedImg?.tempUrl) {
URL.revokeObjectURL(removedImg.tempUrl);
}
// 从列表中移除
sortedImageList.value = sortedImageList.value.filter(img => img.customUid !== customUid);
// 重新计算排序索引
updateSortOrder();
};
// 确认插入图片到编辑器
const confirmInsertImages = () => {
let quill;
if (activeEditor.value === "main") {
const rawQuillEditor = toRaw(myQuillEditor.value);
quill = rawQuillEditor.getQuill();
} else {
const tabIndex = parseInt(activeEditor.value.split("-")[1]);
const rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
quill = rawQuillEditor.getQuill();
}
if (!quill) {
ElNotification({ title: "错误", message: "编辑器未加载完成", type: "error" });
return;
}
// 获取光标位置
const range = quill.getSelection() || { index: 0 };
let currentInsertIndex = range.index;
// 按排序索引升序插入
const sortedByOrder = [...sortedImageList.value].sort((a, b) => a.sortOrder - b.sortOrder);
//
sortedByOrder.forEach(item => {
quill.insertEmbed(currentInsertIndex, "customImage", {
url: item.path,
id: item.serverImgId || generateUUID()
});
currentInsertIndex++;
});
// 调整光标位置
quill.setSelection(currentInsertIndex);
// 清理资源
sortedImageList.value.forEach(img => {
if (img.tempUrl) {
URL.revokeObjectURL(img.tempUrl);
}
});
// 关闭弹窗并重置
showImageSortDialog.value = false;
imageList.value = [];
imageListDb.value = [];
sortedImageList.value = [];
};
// 关闭图片排序弹窗时清理资源
const handleImageDialogClose = () => {
// 释放所有临时URL
sortedImageList.value.forEach(img => {
if (img.tempUrl) {
URL.revokeObjectURL(img.tempUrl);
}
});
// 重置状态
showImageSortDialog.value = false;
imageList.value = [];
imageListDb.value = [];
sortedImageList.value = [];
};
// 视频上传
const handleVideoUpload = async evt => {
if (evt.target.files.length === 0) return;
const file = evt.target.files[0];
// 校验视频文件
const maxSize = 150 * 1024 * 1024;
if (file.size > maxSize) {
ElNotification({
title: "文件过大",
message: `视频大小不能超过 ${150}MB`,
type: "warning"
});
evt.target.value = "";
return;
}
// 生成视频本地URL
const localVideoUrl = URL.createObjectURL(file);
try {
// 上传视频
const videoFormData = new FormData();
videoFormData.append("video", file);
const videoRes = await uploadVideo(videoFormData);
if (videoRes?.code !== 0) {
throw new Error(`视频上传失败: ${videoRes?.message || "未知错误"}`);
}
const videoUrl = videoRes.data.path;
// 生成封面图并上传
const frameBlob = await Video.captureVideoFrame(localVideoUrl);
let coverUrl = "";
if (frameBlob) {
const coverFormData = new FormData();
const coverUid = generateUUID();
coverFormData.append("image", frameBlob, `cover-${coverUid}.jpg`);
const coverRes = await uploadImg(coverFormData, routerName.value, coverUid);
if (coverRes?.data?.code === 0) {
coverUrl = coverRes.data.data.path;
}
}
// 插入视频到编辑器
insertVideoToEditor(videoUrl, coverUrl);
} catch (error) {
console.log(error);
} finally {
URL.revokeObjectURL(localVideoUrl);
evt.target.value = "";
}
};
// 插入视频到编辑器
const insertVideoToEditor = (videoUrl, coverUrl) => {
let quill;
if (activeEditor.value === "main") {
quill = toRaw(myQuillEditor.value)?.getQuill();
} else {
const tabIndex = parseInt(activeEditor.value.split("-")[1]);
quill = toRaw(tabEditors.value[tabIndex])?.getQuill();
}
if (quill) {
const range = quill.getSelection() || { index: 0 };
quill.insertEmbed(range.index, "customVideo", {
url: videoUrl,
poster: coverUrl
});
quill.setSelection(range.index + 1);
}
};
// 标签页切换事件
const handleTabChange = key => {
const tabIndex = tabsData.value.findIndex(item => item.key === key);
activeName.value = key;
activeEditor.value = `tab-${tabIndex}`;
};
// 标签页增删事件
const handleTabsEdit = (targetKey, action) => {
if (action === "add") {
if (tabsData.value.length > 5) {
return useMsg("error", "标签页已达上限 !");
}
const newKey = `tab_${generateUUID()}`;
const newIndex = tabsData.value.length;
tabsData.value.push({
key: newKey,
title: `标签${newIndex + 1}`,
content: "",
isEditing: false
});
nextTick(() => {
activeName.value = newKey;
activeEditor.value = `tab-${newIndex}`;
setTimeout(() => {
startEditTitle(newIndex);
}, 100);
});
} else if (action === "remove") {
const index = tabsData.value.findIndex(item => item.key === targetKey);
tabsData.value.splice(index, 1);
tabEditors.value.splice(index, 1);
editInputRefs.value.splice(index, 1);
if (activeEditor.value.startsWith("tab-")) {
const currentTabIndex = parseInt(activeEditor.value.split("-")[1]);
if (currentTabIndex > index) {
activeEditor.value = `tab-${currentTabIndex - 1}`;
} else if (currentTabIndex === index) {
activeEditor.value = tabsData.value.length > 0 ? "tab-0" : "main";
activeName.value = tabsData.value.length > 0 ? tabsData.value[0].key : null;
}
}
}
};
// 开始编辑标签页标题
const startEditTitle = index => {
const tab = tabsData.value[index];
if (!tab) return;
tab.originalTitle = tab.title;
tab.isEditing = true;
nextTick(() => {
editInputRefs.value[index]?.focus();
});
};
// 完成编辑标签页标题
const finishEditTitle = index => {
const tab = tabsData.value[index];
if (!tab) return;
if (!tab.title.trim()) {
tab.title = tab.originalTitle || `标签${index + 1}`;
ElNotification({ title: "提示", message: "标签标题不能为空", type: "info" });
}
tab.isEditing = false;
if (tab.key === activeName.value) {
activeName.value = tab.key;
}
};
// 其他方法
const onContentChange = content => {
emit("handleRichTextContentChange", content);
emit("update:content", content);
};
const setTabsInfo = () => {
outerVisible.value = false;
tabsData.value = [];
activeName.value = null;
activeEditor.value = "main";
};
const handleBeforeClose = () => {
setTabsInfo();
};
const handleQR = () => {
const quill = toRaw(myQuillEditor.value)?.getQuill();
if (!quill) return;
if (!tabsData.value.length) {
return useMsg("error", "标签页内容为空 !");
}
const range = quill.getSelection(true);
if (currentEditingTabsRef.value) {
currentEditingTabsRef.value.updateContents(tabsData.value);
currentEditingTabsRef.value = null;
} else {
quill.insertEmbed(range.index, "tabs", tabsData.value);
quill.setSelection(range.index + 1);
quill.insertText(range.index, "\n");
}
setTabsInfo();
};
const handleQX = () => {
setTabsInfo();
};
const initTitle = () => {
const editor = document.querySelector(".ql-editor");
if (editor) editor.dataset.placeholder = "";
titleConfig.value.forEach(item => {
const tip = document.querySelector(`.ql-toolbar ${item.Choice}`);
if (tip) tip.setAttribute("title", item.title);
});
};
const loadTabsDataToEditor = tabs => {
tabsData.value = [];
tabs.forEach((tab, index) => {
tabsData.value.push({
key: `tab_${generateUUID()}`,
title: tab.title || `标签${index + 1}`,
content: tab.content || "",
isEditing: false
});
});
nextTick(() => {
if (tabsData.value.length > 0) {
activeName.value = tabsData.value[0].key;
activeEditor.value = "tab-0";
}
});
};
onMounted(() => {
initTitle();
const editorEl = document.querySelector(".ql-editor");
if (editorEl) {
editorEl.addEventListener("edit-tabs", e => {
const tabsData = TabsBlot.value(e.detail.blot.domNode);
if (tabsData.length > 0) {
currentEditingTabsRef.value = e.detail.blot;
loadTabsDataToEditor(tabsData);
outerVisible.value = true;
}
});
}
});
defineExpose({
clearEditor: () => {
const quill = toRaw(myQuillEditor.value)?.getQuill();
if (quill) {
quill.setText("");
editorContent.value = "";
}
}
});
</script>
<style lang="scss">
@import "./index.scss";
// 编辑器基础样式
.ql-editor {
min-height: 600px;
cursor: text !important;
user-select: text !important;
}
// 图片排序弹窗样式
.image-sort-container {
margin-top: 15px;
}
.image-grid {
display: flex;
flex-wrap: wrap;
gap: 15px;
padding: 10px;
}
.image-item {
width: 150px;
padding: 8px;
cursor: move;
border: 1px solid #e5e7eb;
border-radius: 8px;
transition: all 0.2s ease;
&:hover {
box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
}
}
// 图片预览容器
.image-preview-container {
position: relative;
overflow: hidden;
border-radius: 8px;
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
}
.preview-img {
display: block;
width: 100%;
height: 120px;
object-fit: cover;
}
// 排序序号徽章
.image-order-badge {
position: absolute;
top: 5px;
left: 5px;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
font-size: 12px;
color: white;
background: rgb(0 0 0 / 50%);
border-radius: 50%;
}
// 圆形删除按钮
// .circle-delete-btn {
// position: absolute;
// top: -8px;
// right: -8px;
// z-index: 500;
// display: flex;
// align-items: center;
// justify-content: center;
// width: 28px;
// height: 28px;
// padding: 0;
// margin: 0;
// color: white;
// background-color: #ff4d4f;
// border: none;
// border-radius: 50%;
// box-shadow: 0 2px 4px rgb(0 0 0 / 20%);
// transition: all 0.2s;
// &:hover {
// color: white;
// background-color: #d93025;
// transform: scale(1.1);
// }
// .el-icon {
// font-size: 14px;
// }
// }
.image-info {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8px;
font-size: 12px;
}
.image-name {
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.empty-state {
padding: 30px 0;
font-size: 14px;
color: #666666;
text-align: center;
}
.ghost {
background-color: #e9ecef;
opacity: 0.5;
}
// 标签页样式
.quill-tabs {
margin: 15px 0;
overflow: hidden;
border-radius: 4px;
}
.ql-tabs::before {
font-size: 16px;
content: "T";
}
.title-input {
width: 100px;
margin: -2px 0;
}
// 图片样式
.ql-editor .quill-image {
max-width: 100%;
height: auto;
margin: 5px 0;
&:focus {
outline: 2px solid #4285f4;
outline-offset: 2px;
}
}
// 详情样式
.o_detail_all {
overflow: hidden;
text-align: center;
background-color: #ffffff;
}
.o_detail_title {
margin-top: 2vw;
margin-bottom: 1.25vw;
overflow: hidden;
font-size: 2.25em;
font-weight: 600;
line-height: 1.2em;
color: #101010;
text-align: center;
background-color: #ffffff;
}
.o_detail_small {
margin-bottom: 0.7vw;
font-size: 1.5em;
color: #333333;
}
.o_detail_text {
width: 80%;
margin-right: auto;
margin-bottom: 0.7vw;
margin-left: auto;
font-size: 1.125em;
line-height: 1.5em;
color: #737373;
}
.products_des {
width: 100%;
margin-bottom: 50px;
}
.products_des img {
width: 100%;
}
.de_t_n {
font-size: 1.5em;
color: #333333;
}
.detail_title {
padding: 2% 0;
text-align: center;
}
.detail_title p {
line-height: 2em;
}
.detail_con_a {
margin: auto;
overflow: hidden;
}
.lj_detail_text,
.lj_detail_texts {
font-size: 0.875em;
}
.lj_detail_text p {
padding: 0.5% 0;
line-height: 1.6em;
}
/* seo-pro */
.seo-pro h3 {
margin: 2% 0 1%;
font-size: 1.5em;
font-weight: 400;
line-height: 1.2;
color: #333333;
text-align: center;
}
.seo-pro p {
margin: 0 0 11px;
text-align: center;
}
.seo-pro a {
color: #333333;
text-decoration: none;
}
.sa_blue,
.sa_blue a,
.seo-pro a:hover {
color: #009fdf;
}
</style>

View File

@@ -0,0 +1,960 @@
<template>
<!-- 图片上传组件 -->
<el-upload
:id="uuid"
action="#"
:multiple="true"
:show-file-list="false"
:http-request="handleHttpUpload"
:before-upload="handleBeforeUpload"
class="editor-img-uploader"
accept=".jpeg,.jpg,.png,.gif"
>
<i ref="uploadRef" class="Plus editor-img-uploader"></i>
</el-upload>
<!-- 视频上传组件 -->
<input
type="file"
accept="video/*"
name="file"
ref="uploadFileVideo"
id="uploadFileVideo"
@change="handleVideoUpload"
style="width: 0; height: 0; cursor: pointer; opacity: 0"
/>
<!-- 主富文本编辑器 -->
<div class="editor">
<QuillEditor
id="mainEditor"
ref="myQuillEditor"
v-model:content="editorContent"
contentType="html"
@update:content="onContentChange"
:options="options"
/>
</div>
<!-- 标签页配置弹窗 -->
<div>
<el-dialog
v-model="outerVisible"
title="标签页配置"
style="width: 1200px; height: 900px"
close-on-click-modal
close-on-press-escape
:before-close="handleBeforeClose"
>
<el-tabs
v-model="activeName"
type="card"
class="demo-tabs"
editable
@edit="handleTabsEdit"
@tab-change="handleTabChange"
>
<!-- 标签页标题支持编辑 -->
<el-tab-pane
:label="item.title"
:name="item.key"
v-for="(item, index) in tabsData"
:key="item.key"
@keydown.delete.stop
@keydown.backspace.stop
>
<template #label>
<div class="tab-title-edit">
<!-- 文字显示状态 -->
<span v-if="!item.isEditing" @click="startEditTitle(index)" class="title-text">
{{ item.title }}
</span>
<!-- 输入框编辑状态 -->
<el-input
@click.stop
@keydown.delete.stop
@keydown.backspace.stop
v-else
v-model="item.title"
max-length=""
:ref="el => (editInputRefs[index] = el)"
size="small"
class="title-input"
@blur="finishEditTitle(index)"
/>
</div>
</template>
<!-- 标签页编辑器内容 -->
<QuillEditor
:id="`tabEditor_${item.key}`"
:ref="
el => {
if (el) tabEditors[index] = el;
}
"
v-model:content="item.content"
contentType="html"
:options="options1"
/>
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleQX">取消</el-button>
<el-button type="primary" @click="handleQR"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Editor">
import { QuillEditor, Quill } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { getCurrentInstance, reactive, ref, toRaw, computed, onMounted, nextTick } from "vue";
import { generateUUID } from "@/utils";
// import { h } from "@/utils/url";
import { routerObj } from "./utils.js";
import { titleConfig } from "./titleConfig.js";
import { uploadVideo, uploadImg } from "@/api/modules/upload";
import { ElNotification } from "element-plus";
import { useRouter } from "vue-router";
import { useMsg } from "@/hooks/useMsg";
// 字体配置
let fontSizeStyle = Quill.import("attributors/style/size");
fontSizeStyle.whitelist = [
"12px",
"14px",
"16px",
"18px",
"20px",
"22px",
"24px",
"26px",
"28px",
"30px",
"32px",
"34px",
"36px",
"38px",
"40px",
"42px"
];
Quill.register(fontSizeStyle, true);
// 自定义Blot
import ImageBlot from "./quill-image";
import Video from "./quill-video";
import TabsBlot from "./quill-tabs";
import DynamicDivBlot from "./quill-detail-div";
Quill.register(Video);
Quill.register(ImageBlot);
Quill.register(TabsBlot);
Quill.register(DynamicDivBlot);
// 基础变量
const { proxy } = getCurrentInstance();
const emit = defineEmits(["update:content", "handleRichTextContentChange"]);
const uuid = ref("id-" + generateUUID());
const $router = useRouter();
const routerValueName = $router.currentRoute.value.name;
const routerName = ref(routerObj[routerValueName]);
const uploadFileVideo = ref(null);
const outerVisible = ref(false);
const imageList = ref([]);
const imageListDb = ref([]);
const activeName = ref(null); // 跟踪当前激活的标签页key
const activeEditor = ref("main"); // 跟踪当前活跃编辑器main/tab-索引
// 标签页数据新增key作为唯一标识isEditing控制编辑状态
const tabsData = ref([]);
// 标签页编辑器ref数组
const tabEditors = ref([]);
// 标题编辑输入框的ref
const editInputRefs = ref([]);
const currentEditingTabsRef = ref(null);
// Props
const props = defineProps({
content: { type: String, default: "" },
readOnly: { type: Boolean, default: false },
fileSizeLimit: { type: Number, default: 5 }
});
// 主编辑器内容双向绑定
const editorContent = computed({
get: () => {
if (!props.content) return "";
return props.content;
},
set: val => {
emit("update:content", val);
}
});
const myQuillEditor = ref(null); // 主编辑器ref
// 主编辑器配置(保持不变)
const options = reactive({
theme: "snow",
debug: "warn",
strict: false,
modules: {
toolbar: {
container: [
["bold", "italic", "underline", "strike"],
["blockquote", "code-block"],
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }],
[{ size: fontSizeStyle.whitelist }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ align: [] }],
["clean"],
["link", "image", "video", "tabs"]
],
handlers: {
image: function (value) {
if (value) {
activeEditor.value = "main";
proxy.$refs.uploadRef.click();
} else Quill.format("customImage", true);
},
video: function (value) {
if (value) {
activeEditor.value = "main";
document.querySelector("#uploadFileVideo")?.click();
} else Quill.format("customVideo", true);
},
tabs: function (value) {
outerVisible.value = value;
}
}
}
},
placeholder: "请输入内容...",
readOnly: props.readOnly
});
// 标签页编辑器配置(保持不变)
const options1 = reactive({
theme: "snow",
debug: "warn",
strict: false,
modules: {
toolbar: {
container: [
["bold", "italic", "underline", "strike"],
["blockquote", "code-block"],
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }],
[{ size: fontSizeStyle.whitelist }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ align: [] }],
["clean"],
["link", "image", "video"]
],
handlers: {
image: function (value) {
if (value) {
const currentIndex = tabsData.value.findIndex(item => item.key === activeName.value);
activeEditor.value = `tab-${currentIndex}`;
proxy.$refs.uploadRef.click();
} else Quill.format("customImage", true);
},
video: function (value) {
if (value) {
const currentIndex = tabsData.value.findIndex(item => item.key === activeName.value);
activeEditor.value = `tab-${currentIndex}`;
document.querySelector("#uploadFileVideo")?.click();
} else Quill.format("customVideo", true);
}
}
}
},
placeholder: "请输入内容...",
readOnly: props.readOnly
});
// 上传前校验(保持不变)
const handleBeforeUpload = file => {
const fileType = file.type;
file.customUid = generateUUID();
const validTypes = ["image/jpeg", "image/png", "image/gif", "image/jpg", "image/bmp", "image/webp"];
if (!validTypes.includes(fileType)) {
ElNotification({ title: "格式错误", message: "仅支持图片格式", type: "warning" });
imageListDb.value = imageListDb.value.filter(item => item.customUid !== file.customUid);
return false;
}
const isLt = file.size / 1024 / 1024 < props.fileSizeLimit;
if (!isLt) {
ElNotification({ title: "大小超限", message: `不能超过 ${props.fileSizeLimit} MB`, type: "warning" });
imageListDb.value = imageListDb.value.filter(item => item.customUid !== file.customUid);
return false;
}
imageListDb.value.push({
...file,
order: imageListDb.value.length
});
imageListDb.value = [...imageListDb.value].sort((a, b) => a.order - b.order);
console.log(imageListDb.value, "=================value==================");
return true;
};
const handleHttpUpload = async options => {
let formData = new FormData();
formData.append("image", options.file);
try {
const result = await uploadImg(formData, routerName.value, options.file.customUid);
if (result?.data?.code === 0) {
const { data } = result.data;
const { imgId } = result;
const fileItem = imageListDb.value.find(item => item.customUid === imgId);
console.log(fileItem, "=fileItem=");
if (fileItem) {
fileItem.serverImgId = imgId;
fileItem.path = data.path;
// 记录完成时间,用于排序
fileItem.completeTime = Date.now();
}
const allFilesUploaded = imageListDb.value.every(item => item.path);
console.log(allFilesUploaded, "=allFilesUploaded=");
if (allFilesUploaded) {
let rawQuillEditor = "";
let quill = "";
if (activeEditor.value === "main") {
rawQuillEditor = toRaw(myQuillEditor.value);
quill = rawQuillEditor.getQuill();
} else {
const tabIndex = parseInt(activeEditor.value.split("-")[1]);
rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
quill = rawQuillEditor.getQuill();
}
// 获取当前光标位置
const selection = quill.getSelection();
const insertPosition = selection ? selection.index : quill.getLength();
// 关键修改:按照原始上传顺序排序
// 假设imageListDb中的顺序就是上传顺序
// 或者如果有order属性可以按照order排序
const sortedImages = [...imageListDb.value];
// 按顺序插入图片
sortedImages.forEach((item, index) => {
quill.insertEmbed(insertPosition + index, "customImage", {
url: "https://dev.ow.f2b211.com" + item.path,
id: item.serverImgId || generateUUID()
});
});
// 最终光标定位到最后一张图片后面
const finalPosition = insertPosition + sortedImages.length;
quill.setSelection(finalPosition);
imageList.value = [];
imageListDb.value = [];
}
}
} catch (error) {
console.error("图片上传失败:", error);
imageList.value = imageList.value.filter(item => item.customUid !== options.file.customUid);
imageListDb.value = imageListDb.value.filter(item => item.customUid !== options.file.customUid);
}
};
// // 图片上传(保持不变)
// const handleHttpUpload = async options => {
// let formData = new FormData();
// formData.append("image", options.file);
// imageList.value.push(options.file);
// try {
// const result = await uploadImg(formData, routerName.value, options.file.customUid);
// if (result?.data?.code === 0) {
// const { data } = result.data;
// const { imgId } = result;
// const fileItem = imageListDb.value.find(item => item.customUid === imgId);
// if (fileItem) {
// fileItem.serverImgId = imgId;
// fileItem.path = data.path;
// }
// const allFilesUploaded = imageListDb.value.every(item => item.path);
// if (allFilesUploaded) {
// let rawQuillEditor = "";
// let quill = "";
// if (activeEditor.value === "main") {
// rawQuillEditor = toRaw(myQuillEditor.value);
// quill = rawQuillEditor.getQuill();
// } else {
// const tabIndex = parseInt(activeEditor.value.split("-")[1]);
// rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
// quill = rawQuillEditor.getQuill();
// }
// imageListDb.value.forEach(item => {
// const length = quill.getLength() - 1;
// quill.insertEmbed(length, "customImage", {
// url: item.path,
// id: item.serverImgId || generateUUID()
// });
// quill.setSelection(length + 1);
// });
// const finalLength = quill.getLength();
// quill.setSelection(finalLength);
// imageList.value = [];
// imageListDb.value = [];
// }
// }
// } catch (error) {
// console.error("图片上传失败:", error);
// imageList.value = imageList.value.filter(item => item.customUid !== options.file.customUid);
// imageListDb.value = imageListDb.value.filter(item => item.customUid !== options.file.customUid);
// }
// };
// // // 图片上传(修改插入位置逻辑)
// const handleHttpUpload = async options => {
// let formData = new FormData();
// formData.append("image", options.file);
// // imageList.value.push(options.file);
// try {
// const result = await uploadImg(formData, routerName.value, options.file.customUid);
// if (result?.data?.code === 0) {
// const { data } = result.data;
// const { imgId } = result;
// // imageListDb.value.forEach(item => {
// // if (item.customUid === imgId) {
// // item.serverImgId = imgId;
// // item.path = data.path;
// // console.log(item.path, "================>");
// // }
// // });
// const fileItem = imageListDb.value.find(item => item.customUid === imgId);
// console.log(fileItem, "=fileItem=");
// if (fileItem) {
// fileItem.serverImgId = imgId;
// fileItem.path = data.path;
// }
// const allFilesUploaded = imageListDb.value.every(item => item.path);
// console.log(allFilesUploaded, "=allFilesUploaded=");
// if (allFilesUploaded) {
// let rawQuillEditor = "";
// let quill = "";
// if (activeEditor.value === "main") {
// rawQuillEditor = toRaw(myQuillEditor.value);
// quill = rawQuillEditor.getQuill();
// } else {
// const tabIndex = parseInt(activeEditor.value.split("-")[1]);
// rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
// quill = rawQuillEditor.getQuill();
// }
// // 关键修改:获取当前光标位置(选区起始索引)
// const selection = quill.getSelection();
// // 如果没有选区(光标未激活),默认插入到末尾
// const insertPosition = selection ? selection.index : quill.getLength();
// console.log(imageListDb, "=imageListDb=");
// imageListDb?.value?.forEach(item => {
// // 使用光标位置插入图片
// setTimeout(() => {
// quill.insertEmbed(insertPosition, "customImage", {
// url: "https://dev.ow.f2b211.com" + item.path,
// id: item.serverImgId || generateUUID()
// });
// }, 100);
// // 插入后光标后移一位(避免多张图片重叠插入)
// quill.setSelection(insertPosition + 1);
// });
// // 最终光标定位到最后一张图片后面
// const finalPosition = insertPosition + imageListDb.value.length;
// quill.setSelection(finalPosition);
// imageList.value = [];
// imageListDb.value = [];
// }
// }
// } catch (error) {
// console.error("图片上传失败:", error);
// imageList.value = imageList.value.filter(item => item.customUid !== options.file.customUid);
// imageListDb.value = imageListDb.value.filter(item => item.customUid !== options.file.customUid);
// }
// };
// 视频上传(保持不变)
// const handleVideoUpload = async evt => {
// if (evt.target.files.length === 0) return;
// const formData = new FormData();
// formData.append("video", evt.target.files[0]);
// try {
// let rawQuillEditor = "";
// let quill = "";
// if (activeEditor.value === "main") {
// rawQuillEditor = toRaw(myQuillEditor.value);
// quill = rawQuillEditor.getQuill();
// } else {
// const tabIndex = parseInt(activeEditor.value.split("-")[1]);
// rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
// quill = rawQuillEditor.getQuill();
// }
// let length = quill.selection.savedRange.index;
// const { data } = await uploadVideo(formData);
// quill.insertEmbed(length, "customVideo", {
// url: data.path,
// id: generateUUID()
// });
// uploadFileVideo.value.value = "";
// } catch (error) {
// console.log(error);
// }
// };
// 在<script setup>中替换handleVideoUpload方法
const handleVideoUpload = async evt => {
if (evt.target.files.length === 0) return;
const file = evt.target.files[0];
// 1. 校验视频文件
const maxSize = 150 * 1024 * 1024;
if (file.size > maxSize) {
ElNotification({
title: "文件过大",
message: `视频大小不能超过 ${150}MB`,
type: "warning"
});
evt.target.value = "";
return;
}
// 2. 生成视频本地URL用于生成封面不上传
const localVideoUrl = URL.createObjectURL(file);
try {
// 4. 并行处理:上传视频 + 生成并上传封面
// 4.1 上传视频到视频服务器
const videoFormData = new FormData();
videoFormData.append("video", file);
const videoRes = await uploadVideo(videoFormData); // 视频上传接口
// 校验视频上传结果(根据你的接口返回格式调整)
if (videoRes?.code !== 0) {
throw new Error(`视频上传失败: ${videoRes?.message || "未知错误"}`);
}
const videoUrl = videoRes.data.path; // 服务器返回的视频URL
console.log(localVideoUrl, "=localVideoUrl=");
// 4.2 生成封面图并上传到图片服务器
const frameBlob = await Video.captureVideoFrame(localVideoUrl);
console.log(frameBlob, "============frameBlob===========");
let coverUrl = "";
if (!frameBlob) return;
// 复用图片上传接口(与图片上传逻辑一致)
const coverFormData = new FormData();
const coverUid = generateUUID(); // 生成唯一ID
// formData.append("image", options.file);
coverFormData.append("image", frameBlob, `cover-${coverUid}.jpg`);
console.log(coverFormData, "=coverFormData=");
// 调用图片上传接口(和普通图片上传用同一个接口)
const coverRes = await uploadImg(coverFormData, routerName.value, coverUid);
// 校验封面上传结果
if (coverRes?.data?.code === 0) {
coverUrl = coverRes.data.data.path; // 服务器返回的封面URL
} else {
console.warn("封面上传失败,使用默认封面");
}
// 5. 将带封面的视频插入编辑器
insertVideoToEditor(videoUrl, coverUrl);
// 6. 上传成功提示
// loading.close();
// ElNotification({
// title: "上传成功",
// message: "视频已添加到编辑器",
// type: "success"
// });
} catch (error) {
console.log(error, "==============");
} finally {
console.log("======12323232========");
// 清理资源
URL.revokeObjectURL(localVideoUrl); // 释放本地视频URL
evt.target.value = ""; // 重置文件输入框
}
};
// 辅助方法:插入视频到编辑器
const insertVideoToEditor = (videoUrl, coverUrl) => {
// 获取当前活跃的编辑器(主编辑器或标签页编辑器)
let quill;
if (activeEditor.value === "main") {
quill = toRaw(myQuillEditor.value)?.getQuill();
} else {
const tabIndex = parseInt(activeEditor.value.split("-")[1]);
quill = toRaw(tabEditors.value[tabIndex])?.getQuill();
}
if (quill) {
const range = quill.getSelection() || { index: 0 };
// 插入自定义视频组件携带服务器返回的视频URL和封面URL
quill.insertEmbed(range.index, "customVideo", {
url: videoUrl,
poster: coverUrl // 这里使用图片服务器返回的封面URL
});
quill.setSelection(range.index + 1); // 移动光标到视频后
}
};
// 标签页切换事件基于key切换
const handleTabChange = key => {
const tabIndex = tabsData.value.findIndex(item => item.key === key);
activeName.value = key;
activeEditor.value = `tab-${tabIndex}`;
};
// 标签页增删事件
const handleTabsEdit = (targetKey, action) => {
if (action === "add") {
if (tabsData.value.length > 5) {
return useMsg("error", "标签页已达上限 !");
}
// 新增标签页生成唯一key默认标题初始不处于编辑状态
const newKey = `tab_${generateUUID()}`;
const newIndex = tabsData.value.length;
tabsData.value.push({
key: newKey,
title: `标签${newIndex + 1}`,
content: "",
isEditing: false // 新增时默认不编辑
});
nextTick(() => {
activeName.value = newKey;
activeEditor.value = `tab-${newIndex}`;
// 新增后自动进入编辑状态
setTimeout(() => {
startEditTitle(newIndex);
}, 100);
});
} else if (action === "remove") {
// 删除标签页
const index = tabsData.value.findIndex(item => item.key === targetKey);
tabsData.value.splice(index, 1);
tabEditors.value.splice(index, 1);
editInputRefs.value.splice(index, 1);
// 调整活跃编辑器索引
if (activeEditor.value.startsWith("tab-")) {
const currentTabIndex = parseInt(activeEditor.value.split("-")[1]);
if (currentTabIndex > index) {
activeEditor.value = `tab-${currentTabIndex - 1}`;
} else if (currentTabIndex === index) {
// 若删除当前活跃标签,切换到第一个或主编辑器
activeEditor.value = tabsData.value.length > 0 ? "tab-0" : "main";
activeName.value = tabsData.value.length > 0 ? tabsData.value[0].key : null;
}
}
}
};
// 开始编辑标签页标题
const startEditTitle = index => {
const tab = tabsData.value[index];
if (!tab) return;
// 记录原始标题(用于取消编辑时恢复)
tab.originalTitle = tab.title;
tab.isEditing = true;
// 延迟获取焦点,确保输入框已渲染
nextTick(() => {
editInputRefs.value[index]?.focus();
});
};
// 完成编辑(失去焦点或回车)
const finishEditTitle = index => {
const tab = tabsData.value[index];
if (!tab) return;
// 校验标题(不能为空)
if (!tab.title.trim()) {
tab.title = tab.originalTitle || `标签${index + 1}`;
ElNotification({ title: "提示", message: "标签标题不能为空", type: "info" });
}
tab.isEditing = false;
// 更新activeName如果当前编辑的是活跃标签
if (tab.key === activeName.value) {
activeName.value = tab.key; // 触发重绘
}
};
// 其他方法(保持不变)
const onContentChange = content => {
emit("handleRichTextContentChange", content);
emit("update:content", content);
};
const setTabsInfo = () => {
outerVisible.value = false;
//清空
tabsData.value = [];
activeName.value = null;
activeEditor.value = "main";
};
//弹窗关闭前的钩子
const handleBeforeClose = () => {
setTabsInfo();
};
// 确认按钮点击事件(修改后)
const handleQR = () => {
const quill = toRaw(myQuillEditor.value)?.getQuill();
if (!quill) return;
if (!tabsData.value.length) {
return useMsg("error", "标签页内容为空 !");
}
const range = quill.getSelection(true);
// 判断是否是编辑已有标签页(通过 currentEditingTabsRef 是否有值)
if (currentEditingTabsRef.value) {
// 1. 编辑模式:更新原有标签页组件
const blot = currentEditingTabsRef.value;
// 更新 blot 的数据(触发 DOM 更新)
blot.updateContents(tabsData.value); // 需要在 TabsBlot 中添加 updateContents 方法
// 清除编辑状态标记
currentEditingTabsRef.value = null;
} else {
// 2. 新增模式:插入新的标签页组件
quill.insertEmbed(range.index, "tabs", tabsData.value);
// 关键:在标签页前方插入一个空段落(确保顶部有空间)
// quill.insertText(range.index, "\n"); // 插入换行
quill.setSelection(range.index + 1);
quill.insertText(range.index, "\n"); // 插入换行
}
// 关闭弹窗并清空临时数据
setTabsInfo();
};
//取消
const handleQX = () => {
setTabsInfo();
};
const initTitle = () => {
const editor = document.querySelector(".ql-editor");
if (editor) editor.dataset.placeholder = "";
titleConfig.value.forEach(item => {
const tip = document.querySelector(`.ql-toolbar ${item.Choice}`);
if (tip) tip.setAttribute("title", item.title);
});
};
// 定义 loadTabsDataToEditor 函数
const loadTabsDataToEditor = tabs => {
// 清空现有数据
tabsData.value = [];
// 转换原始标签数据为编辑所需格式添加key和编辑状态
tabs.forEach((tab, index) => {
tabsData.value.push({
key: `tab_${generateUUID()}`, // 生成唯一key
title: tab.title || `标签${index + 1}`, // 避免空标题
content: tab.content || "", // 标签页内容
isEditing: false // 编辑状态标记
});
});
// 激活第一个标签页(如果有数据)
nextTick(() => {
if (tabsData.value.length > 0) {
activeName.value = tabsData.value[0].key;
activeEditor.value = "tab-0";
}
});
};
// const cleanEmptyTags = container => {
// if (!container) return;
// // 获取所有 <p> 标签
// const pTags = container.querySelectorAll("p");
// // 只清理真正的空标签(不含任何内容,包括<br>
// Array.from(pTags).forEach(pTag => {
// // 判断标准:
// // 1. 完全没有子节点
// // 2. 或innerHTML为空字符串
// const isCompletelyEmpty = pTag.childNodes.length === 0 || pTag.innerHTML.trim() === "";
// if (isCompletelyEmpty) {
// // 特殊处理:保留首尾空标签,避免编辑器异常
// const isFirstOrLast = pTag === container.firstElementChild || pTag === container.lastElementChild;
// if (isFirstOrLast) {
// pTag.innerHTML = ""; // 清空内容
// } else {
// pTag.remove(); // 移除中间的纯空标签
// }
// }
// });
// };
// // 触发清理的函数(针对所有编辑器)
// const handleCleanEmptyTags = () => {
// // 处理主编辑器
// const mainEditor = document.querySelector("#mainEditor .ql-editor");
// console.log(mainEditor, "=mainEditor=");
// if (mainEditor) cleanEmptyTags(mainEditor);
// };
onMounted(() => {
initTitle();
// 监听编辑按钮点击事件
const editorEl = document.querySelector(".ql-editor");
if (editorEl) {
editorEl.addEventListener("edit-tabs", e => {
const tabsData = TabsBlot.value(e.detail.blot.domNode);
if (tabsData.length > 0) {
// 保存当前编辑的标签页引用
currentEditingTabsRef.value = e.detail.blot;
// 加载数据到弹窗
loadTabsDataToEditor(tabsData);
// 显示弹窗
outerVisible.value = true;
}
});
}
// 等待编辑器首次渲染完成
// setTimeout(handleCleanEmptyTags, 300);
});
// 监听内容变化,重新清理空标签
// watch(editorContent, () => {
// nextTick(() => {
// setTimeout(handleCleanEmptyTags, 100); // 延迟确保 Quill 已重新渲染
// });
// });
defineExpose({
clearEditor: () => {
const quill = toRaw(myQuillEditor.value)?.getQuill();
if (quill) {
quill.setText("");
editorContent.value = "";
}
}
});
</script>
<style lang="scss">
@import "./index.scss";
// 增加编辑器内容区交互性确保删除可用
.ql-editor {
min-height: 600px; // 确保空编辑器也有点击区域
cursor: text !important;
user-select: text !important;
}
// /* 标签页样式 */
.quill-tabs {
margin: 15px 0;
overflow: hidden;
border-radius: 4px;
}
// /* 用伪元素添加图标(可替换为自己的图标) */
.ql-tabs::before {
font-size: 16px;
content: "T"; /* 用 emoji 或字体图标 */
}
.title-input {
width: 100px;
margin: -2px 0; /* 与标签对齐 */
}
/* 详情样式 */
.o_detail_all {
overflow: hidden;
text-align: center;
background-color: #ffffff;
}
.o_detail_title {
margin-top: 2vw;
margin-bottom: 1.25vw;
overflow: hidden;
font-size: 2.25em;
font-weight: 600;
line-height: 1.2em;
color: #101010;
text-align: center;
background-color: #ffffff;
}
.o_detail_small {
margin-bottom: 0.7vw;
font-size: 1.5em;
color: #333333;
}
.o_detail_text {
width: 80%;
margin-right: auto;
margin-bottom: 0.7vw;
margin-left: auto;
font-size: 1.125em;
line-height: 1.5em;
color: #737373;
}
.products_des {
width: 100%;
margin-bottom: 50px;
}
.products_des img {
width: 100%;
}
.de_t_n {
font-size: 1.5em;
color: #333333;
}
.detail_title {
padding: 2% 0;
text-align: center;
}
.detail_title p {
line-height: 2em;
}
.detail_con_a {
margin: auto;
overflow: hidden;
}
.lj_detail_text,
.lj_detail_texts {
font-size: 0.875em;
}
.lj_detail_text p {
padding: 0.5% 0;
line-height: 1.6em;
}
/* seo-pro */
.seo-pro h3 {
margin: 2% 0 1%;
font-size: 1.5em;
font-weight: 400;
line-height: 1.2;
color: #333333;
text-align: center;
}
.seo-pro p {
margin: 0 0 11px;
text-align: center;
}
.seo-pro a {
color: #333333;
text-decoration: none;
}
.sa_blue,
.sa_blue a,
.seo-pro a:hover {
color: #009fdf;
}
</style>

View File

@@ -0,0 +1,49 @@
// quill-dynamic-div.js
import { Quill } from "@vueup/vue-quill";
const Block = Quill.import("blots/block");
class DynamicDivBlot extends Block {
// 从 DOM 节点创建 Blot 时,保留所有属性和类名
static create(value) {
const node = super.create();
// 如果是从数据恢复value 存在),还原类名
if (value?.className) {
node.className = value.className; // 直接设置完整类名字符串(支持多类名)
}
// 保留其他属性(如 id、data-* 等,按需添加)
if (value?.attrs) {
Object.keys(value.attrs).forEach(key => {
node.setAttribute(key, value.attrs[key]);
});
}
return node;
}
// 从 Blot 提取值(序列化时用),保留所有类名和属性
static value(node) {
return {
className: node.className, // 保留完整类名(如 "o_detail_all big"
attrs: Array.from(node.attributes).reduce((attrs, attr) => {
attrs[attr.name] = attr.value;
return attrs;
}, {}) // 保留所有属性
};
}
// 格式化时识别所有 div 标签
static formats(domNode) {
// 只要是 div 标签,就返回其类名和属性(确保 Quill 不清理)
return {
className: domNode.className,
attrs: Array.from(domNode.attributes).reduce((attrs, attr) => {
attrs[attr.name] = attr.value;
return attrs;
}, {})
};
}
}
DynamicDivBlot.blotName = "dynamicDiv"; // 唯一标识
DynamicDivBlot.tagName = "div"; // 处理所有 div 标签
export default DynamicDivBlot;

View File

@@ -0,0 +1,25 @@
import { Quill } from "@vueup/vue-quill";
let BlockEmbed = Quill.import("blots/block/embed");
class ImageBlot extends BlockEmbed {
static create(value) {
let node = super.create();
node.setAttribute("src", value.url);
node.setAttribute("id", value.id);
console.log("图片信息", node);
return node;
}
// 允许通过键盘删除
deleteAt(index, length) {
super.deleteAt(index, length);
}
static value(node) {
return {
url: node.getAttribute("src"),
id: node.getAttribute("id")
};
}
}
ImageBlot.blotName = "customImage";
ImageBlot.tagName = "img";
export default ImageBlot;

View File

@@ -0,0 +1,25 @@
import { Quill } from "@vueup/vue-quill";
let BlockEmbed = Quill.import("blots/block/embed");
class ImageBlot extends BlockEmbed {
static create(value) {
let node = super.create();
node.setAttribute("src", value.url);
node.setAttribute("id", value.id);
console.log("图片信息", node);
return node;
}
// 允许通过键盘删除
deleteAt(index, length) {
super.deleteAt(index, length);
}
static value(node) {
return {
url: node.getAttribute("src"),
id: node.getAttribute("id")
};
}
}
ImageBlot.blotName = "customImage";
ImageBlot.tagName = "img";
export default ImageBlot;

View File

@@ -0,0 +1,407 @@
import { Quill } from "@vueup/vue-quill";
const BlockEmbed = Quill.import("blots/block/embed");
class TabsBlot extends BlockEmbed {
static blotName = "tabs";
static tagName = "div";
static className = "m-quill-tabs";
constructor(domNode) {
super(domNode);
this.bindEvents();
this.bindDeleteKeyEvent(); // 绑定删除键事件
}
static create(value) {
const node = super.create(value);
const tabs = value || [];
// 主容器样式
node.setAttribute(
"style",
`
margin: 15px 0;
overflow: hidden;
border-radius: 4px;
position: relative;
`
);
// 标签栏滚动容器 - 优化高度计算
const tabScrollContainer = document.createElement("div");
tabScrollContainer.className = "m-quill-tab-scroll-container";
tabScrollContainer.setAttribute(
"style",
`
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
-ms-overflow-style: none;
overflow-y: hidden;
overflow-x: auto;
height: auto;
`
);
// height: auto; /* 自动高度 */
// Chrome, Safari 隐藏滚动条
tabScrollContainer.style.overflow = "auto";
tabScrollContainer.style.webkitOverflowScrolling = "touch";
tabScrollContainer.style.scrollbarWidth = "none";
tabScrollContainer.style.msOverflowStyle = "none";
// 标签栏 - 保持原有样式不变
const tabList = document.createElement("div");
tabList.className = "m-quill-tab-list";
tabList.setAttribute(
"style",
`
display: flex;
border-bottom: 2px solid #dddddd;
min-width: max-content; /* 确保内容撑开容器 */
`
);
// 生成标签按钮 - 优化内边距计算
tabs.forEach((tab, index) => {
const btn = document.createElement("button");
btn.className = `m-quill-tab-button`;
btn.setAttribute("data-index", index);
btn.textContent = tab.title;
btn.setAttribute(
"style",
`
font-weight: 900;
color: #8f9099;
cursor: pointer;
background: transparent;
border: none;
padding-bottom:14px;
margin-right:3%; /* 增大间距 */
cursor: pointer;
white-space:nowrap;
font-size:16px;
${index === 0 ? "color: #1f2635; border-bottom: 3px solid #537CD8;font-size:16px;" : ""}
`
);
tabList.appendChild(btn);
});
// 编辑按钮 - 保持原有样式不变
const editBtn = document.createElement("button");
editBtn.className = "m-quill-tab-edit-btn";
editBtn.innerHTML = "编辑";
editBtn.setAttribute("data-action", "edit");
editBtn.setAttribute(
"style",
`
padding: 10px;
margin-left: auto;
color: #606266;
cursor: pointer;
width:60px;
padding-left:20px;
background: transparent;
border: none;
display:block;
`
);
tabList.appendChild(editBtn);
// 内容区 - 保持原有样式不变
const contentList = document.createElement("div");
contentList.className = "m-quill-tab-content-list";
contentList.setAttribute(
"style",
`
padding: 15px;
`
);
// 生成内容面板 - 保持原有样式不变
tabs.forEach((tab, index) => {
const panel = document.createElement("div");
panel.className = `m-quill-tab-content`;
panel.setAttribute("data-index", index);
panel.innerHTML = tab.content;
panel.setAttribute(
"style",
`
display: ${index === 0 ? "block" : "none"};
min-height: 50px;
`
);
panel.contentEditable = "false";
contentList.appendChild(panel);
});
// 组装结构
tabScrollContainer.appendChild(tabList);
node.appendChild(tabScrollContainer); // 滚动容器添加到主节点
node.appendChild(contentList); // 内容区
// 标签页切换逻辑 - 保持原有逻辑不变
const scriptTag = document.createElement("script");
scriptTag.textContent = `
(function() {
const container = document.currentScript.parentElement;
const isAdmin = window.location.pathname.includes('/admin');
const editBtn1 = container.querySelector('.m-quill-tab-edit-btn');
// 仅在非管理系统(文章网站)隐藏编辑按钮,管理系统保持显示
if (!isAdmin && editBtn1) {
editBtn1.style.display = 'none'; // 文章网站隐藏按钮
} else if (isAdmin && editBtn1) {
editBtn1.style.display = 'block'; // 管理系统强制显示按钮
editBtn1.style.width='60px';
editBtn1.style.minWidth='60px';
}
// 非管理系统才执行标签切换逻辑(管理系统不执行)
if (!isAdmin) {
const tabButtons = container.querySelectorAll('.m-quill-tab-button:not([data-action])');
const contentPanels = container.querySelectorAll('.m-quill-tab-content');
tabButtons.forEach(btn => {
btn.addEventListener('click', function() {
const index = parseInt(this.dataset.index);
tabButtons.forEach((b, i) => {
b.setAttribute('style', \`
font-weight: 900;
cursor: pointer;
background: transparent;
font-size:16px;
white-space:nowrap;
padding-bottom:14px;
margin-right:3%;
color: #8f9099;
border: none;
\${i === index ?
'color: #1f2635;border-bottom: 3px solid #537CD8;font-size:16px;' :
''
}
\`);
});
contentPanels.forEach((panel, i) => {
panel.style.display = i === index ? 'block' : 'none';
});
// 添加滚动逻辑到自执行函数中
const scrollContainer = container.querySelector(".m-quill-tab-scroll-container");
const activeBtn = tabButtons[index];
if (scrollContainer && activeBtn) {
activeBtn.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "center"
});
}
});
});
}
})();
`;
node.appendChild(scriptTag);
node.setAttribute("contenteditable", "false");
return node;
}
bindEvents() {
if (!this.eventBoundElements) {
this.eventBoundElements = new WeakMap();
}
// 编辑按钮事件 - 保持原有逻辑不变
const editBtn = this.domNode.querySelector(".m-quill-tab-edit-btn");
if (editBtn) {
editBtn.removeEventListener("click", this.handleEditClick);
this.handleEditClick = e => {
e.stopPropagation();
this.domNode.dispatchEvent(
new CustomEvent("edit-tabs", {
bubbles: true,
detail: { blot: this }
})
);
};
editBtn.addEventListener("click", this.handleEditClick);
}
// 标签切换事件 - 保持原有逻辑不变
const tabButtons = this.domNode.querySelectorAll(".m-quill-tab-button:not([data-action])");
tabButtons.forEach(btn => {
if (!this.eventBoundElements.has(btn)) {
btn.addEventListener("click", () => {
const index = parseInt(btn.dataset.index, 10);
this.selectTab(index);
});
this.eventBoundElements.set(btn, true);
}
});
}
// 增强版删除键处理 - 保持原有逻辑不变
bindDeleteKeyEvent() {
this.domNode.addEventListener(
"keydown",
e => {
if (e.key === "Backspace" || e.key === "Delete") {
const selection = window.getSelection();
if (!selection.rangeCount) return;
const range = selection.getRangeAt(0);
const parentBlock = this.domNode;
const isInside = parentBlock.contains(range.commonAncestorContainer);
if (!isInside) {
e.preventDefault();
return;
}
if (
range.startContainer === parentBlock &&
range.endContainer === parentBlock &&
range.startOffset === 0 &&
range.endOffset >= parentBlock.childNodes.length
) {
e.preventDefault();
}
}
},
true
);
const tabList = this.domNode.querySelector(".m-quill-tab-list");
if (tabList) {
tabList.querySelectorAll("*").forEach(el => {
el.contentEditable = "false";
});
}
}
selectTab(index) {
const buttons = this.domNode.querySelectorAll(".m-quill-tab-button:not([data-action])");
const panels = this.domNode.querySelectorAll(".m-quill-tab-content");
// 保持原有样式逻辑不变
buttons.forEach((btn, i) => {
btn.setAttribute(
"style",
`
font-weight: 900;
cursor: pointer;
background: transparent;
border: none;
font-size:16px;
padding-bottom:14px;
margin-right:3%;
white-space:nowrap;
color: #8f9099;
border-bottom: 3px solid transparent;
${i === index ? "color: #1f2635;border-bottom: 3px solid #537CD8;font-size:16px;" : ""}
`
);
});
panels.forEach((panel, i) => {
panel.style.display = i === index ? "block" : "none";
});
// 滚动到当前选中的标签
const scrollContainer = this.domNode.querySelector(".m-quill-tab-scroll-container");
const activeBtn = buttons[index];
if (scrollContainer && activeBtn) {
activeBtn.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "center"
});
}
}
static value(node) {
const tabs = [];
const buttons = node.querySelectorAll(".m-quill-tab-button:not([data-action])");
const panels = node.querySelectorAll(".m-quill-tab-content");
buttons.forEach((btn, i) => {
tabs.push({
title: btn.textContent,
content: panels[i]?.innerHTML || ""
});
});
return tabs;
}
update(mutations, context) {
super.update(mutations, context);
const scriptTag = this.domNode.querySelector("script");
if (scriptTag) {
const newScript = document.createElement("script");
newScript.textContent = scriptTag.textContent;
scriptTag.parentNode.replaceChild(newScript, scriptTag);
}
this.bindEvents();
this.bindDeleteKeyEvent();
}
getValue() {
return TabsBlot.value(this.domNode);
}
// 更新标签页数据 - 保持原有逻辑不变
updateContents(tabs) {
const contentList = this.domNode.querySelector(".m-quill-tab-content-list");
const tabList = this.domNode.querySelector(".m-quill-tab-list");
const editBtn = this.domNode.querySelector(".m-quill-tab-edit-btn");
Array.from(tabList.children).forEach(child => {
if (!child.classList.contains("m-quill-tab-edit-btn")) {
child.remove();
}
});
contentList.innerHTML = "";
tabs.forEach((tab, index) => {
const btn = document.createElement("button");
btn.className = "m-quill-tab-button";
btn.setAttribute("data-index", index);
btn.textContent = tab.title;
btn.setAttribute(
"style",
`
font-weight: 900;
color: #8f9099;
cursor: pointer;
background: transparent;
border: none;
padding-bottom:14px;
margin-right:3%;
white-space:nowrap;
font-size:16px;
${index === 0 ? "color: #1f2635; border-bottom: 3px solid #537CD8;font-size:16px;" : ""}
`
);
tabList.insertBefore(btn, editBtn);
const panel = document.createElement("div");
panel.className = "m-quill-tab-content";
panel.setAttribute("data-index", index);
panel.innerHTML = tab.content;
panel.setAttribute(
"style",
`
display: ${index === 0 ? "block" : "none"};
min-height: 50px;
`
);
panel.contentEditable = "false";
contentList.appendChild(panel);
});
this.bindEvents();
}
}
export default TabsBlot;

View File

@@ -0,0 +1,398 @@
import { Quill } from "@vueup/vue-quill";
const BlockEmbed = Quill.import("blots/block/embed");
class TabsBlot extends BlockEmbed {
static blotName = "tabs";
static tagName = "div";
static className = "m-quill-tabs";
constructor(domNode) {
super(domNode);
this.bindEvents();
this.bindDeleteKeyEvent(); // 绑定删除键事件
}
static create(value) {
const node = super.create(value);
const tabs = value || [];
// 主容器样式
node.setAttribute(
"style",
`
margin: 15px 0;
overflow: hidden;
border-radius: 4px;
position: relative;
`
);
// 标签栏滚动容器 - 添加隐藏滚动条样式
const tabScrollContainer = document.createElement("div");
tabScrollContainer.className = "m-quill-tab-scroll-container";
tabScrollContainer.setAttribute(
"style",
`
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch; /* 增强移动端滚动体验 */
scrollbar-width: none; /* Firefox 隐藏滚动条 */
-ms-overflow-style: none; /* IE 10+ 隐藏滚动条 */
`
);
// Chrome, Safari 隐藏滚动条
tabScrollContainer.style.overflow = "auto";
tabScrollContainer.style.webkitOverflowScrolling = "touch";
tabScrollContainer.style.scrollbarWidth = "none";
tabScrollContainer.style.msOverflowStyle = "none";
// 关键:隐藏滚动条但保留功能
tabScrollContainer.innerHTML = `
<style>
.m-quill-tab-scroll-container::-webkit-scrollbar {
display: none; /* Chrome, Safari 隐藏滚动条 */
}
</style>
`;
// 标签栏 - 保持原有样式不变
const tabList = document.createElement("div");
tabList.className = "m-quill-tab-list";
tabList.setAttribute(
"style",
`
display: flex;
border-bottom: 1px solid #dddddd;
min-width: max-content; /* 确保内容撑开容器 */
`
);
// 生成标签按钮 - 保持原有样式不变
tabs.forEach((tab, index) => {
const btn = document.createElement("button");
btn.className = `m-quill-tab-button`;
btn.setAttribute("data-index", index);
btn.textContent = tab.title;
btn.setAttribute(
"style",
`
padding: 1%;
font-weight: 900;
color: #8f9099;
cursor: pointer;
background: transparent;
border: none;
margin-right: 1%;
cursor: pointer;
font-size:16px;
${index === 0 ? "color: #1f2635; border-bottom: 3px solid #537CD8;font-size:16px;" : ""}
`
);
tabList.appendChild(btn);
});
// 编辑按钮 - 保持原有样式不变
const editBtn = document.createElement("button");
editBtn.className = "m-quill-tab-edit-btn";
editBtn.innerHTML = "编辑";
editBtn.setAttribute("data-action", "edit");
editBtn.setAttribute(
"style",
`
padding: 10px;
margin-left: auto;
color: #606266;
cursor: pointer;
width:50px;
background: transparent;
border: none;
display:block;
`
);
tabList.appendChild(editBtn);
// 内容区 - 保持原有样式不变
const contentList = document.createElement("div");
contentList.className = "m-quill-tab-content-list";
contentList.setAttribute(
"style",
`
padding: 15px;
`
);
// 生成内容面板 - 保持原有样式不变
tabs.forEach((tab, index) => {
const panel = document.createElement("div");
panel.className = `m-quill-tab-content`;
panel.setAttribute("data-index", index);
panel.innerHTML = tab.content;
panel.setAttribute(
"style",
`
display: ${index === 0 ? "block" : "none"};
min-height: 50px;
`
);
panel.contentEditable = "false";
contentList.appendChild(panel);
});
// 组装结构
tabScrollContainer.appendChild(tabList);
node.appendChild(tabScrollContainer); // 滚动容器添加到主节点
node.appendChild(contentList); // 内容区
// 标签页切换逻辑 - 保持原有逻辑不变
const scriptTag = document.createElement("script");
scriptTag.textContent = `
(function() {
const container = document.currentScript.parentElement;
const isAdmin = window.location.pathname.includes('/admin');
const editBtn1 = container.querySelector('.m-quill-tab-edit-btn');
// 仅在非管理系统(文章网站)隐藏编辑按钮,管理系统保持显示
if (!isAdmin && editBtn1) {
editBtn1.style.display = 'none'; // 文章网站隐藏按钮
} else if (isAdmin && editBtn1) {
editBtn1.style.display = 'block'; // 管理系统强制显示按钮
}
// 非管理系统才执行标签切换逻辑(管理系统不执行)
if (!isAdmin) {
const tabButtons = container.querySelectorAll('.m-quill-tab-button:not([data-action])');
const contentPanels = container.querySelectorAll('.m-quill-tab-content');
tabButtons.forEach(btn => {
btn.addEventListener('click', function() {
const index = parseInt(this.dataset.index);
tabButtons.forEach((b, i) => {
b.setAttribute('style', \`
padding: 1%;
font-weight: 900;
cursor: pointer;
background: transparent;
font-size:16px;
margin-right: 1%;
color: #8f9099;
border: none;
\${i === index ?
'color: #1f2635;border-bottom: 3px solid #537CD8;font-size:16px;' :
''
}
\`);
});
contentPanels.forEach((panel, i) => {
panel.style.display = i === index ? 'block' : 'none';
});
});
});
}
})();
`;
node.appendChild(scriptTag);
node.setAttribute("contenteditable", "false");
return node;
}
bindEvents() {
if (!this.eventBoundElements) {
this.eventBoundElements = new WeakMap();
}
// 编辑按钮事件 - 保持原有逻辑不变
const editBtn = this.domNode.querySelector(".m-quill-tab-edit-btn");
if (editBtn) {
editBtn.removeEventListener("click", this.handleEditClick);
this.handleEditClick = e => {
e.stopPropagation();
this.domNode.dispatchEvent(
new CustomEvent("edit-tabs", {
bubbles: true,
detail: { blot: this }
})
);
};
editBtn.addEventListener("click", this.handleEditClick);
}
// 标签切换事件 - 保持原有逻辑不变
const tabButtons = this.domNode.querySelectorAll(".m-quill-tab-button:not([data-action])");
tabButtons.forEach(btn => {
if (!this.eventBoundElements.has(btn)) {
btn.addEventListener("click", () => {
const index = parseInt(btn.dataset.index, 10);
this.selectTab(index);
});
this.eventBoundElements.set(btn, true);
}
});
}
// 增强版删除键处理 - 保持原有逻辑不变
bindDeleteKeyEvent() {
this.domNode.addEventListener(
"keydown",
e => {
if (e.key === "Backspace" || e.key === "Delete") {
const selection = window.getSelection();
if (!selection.rangeCount) return;
const range = selection.getRangeAt(0);
const parentBlock = this.domNode;
const isInside = parentBlock.contains(range.commonAncestorContainer);
if (!isInside) {
e.preventDefault();
return;
}
if (
range.startContainer === parentBlock &&
range.endContainer === parentBlock &&
range.startOffset === 0 &&
range.endOffset >= parentBlock.childNodes.length
) {
e.preventDefault();
}
}
},
true
);
const tabList = this.domNode.querySelector(".m-quill-tab-list");
if (tabList) {
tabList.querySelectorAll("*").forEach(el => {
el.contentEditable = "false";
});
}
}
selectTab(index) {
const buttons = this.domNode.querySelectorAll(".m-quill-tab-button:not([data-action])");
const panels = this.domNode.querySelectorAll(".m-quill-tab-content");
// 保持原有样式逻辑不变
buttons.forEach((btn, i) => {
btn.setAttribute(
"style",
`
padding: 1%;
font-weight: 900;
cursor: pointer;
background: transparent;
border: none;
font-size:16px;
margin-right: 1%;
color: #8f9099;
border-bottom: 3px solid transparent;
${i === index ? "color: #1f2635;border-bottom: 3px solid #537CD8;font-size:16px;" : ""}
`
);
});
panels.forEach((panel, i) => {
panel.style.display = i === index ? "block" : "none";
});
// 滚动到当前选中的标签
const scrollContainer = this.domNode.querySelector(".m-quill-tab-scroll-container");
const activeBtn = buttons[index];
if (scrollContainer && activeBtn) {
activeBtn.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "center"
});
}
}
static value(node) {
const tabs = [];
const buttons = node.querySelectorAll(".m-quill-tab-button:not([data-action])");
const panels = node.querySelectorAll(".m-quill-tab-content");
buttons.forEach((btn, i) => {
tabs.push({
title: btn.textContent,
content: panels[i]?.innerHTML || ""
});
});
return tabs;
}
update(mutations, context) {
super.update(mutations, context);
const scriptTag = this.domNode.querySelector("script");
if (scriptTag) {
const newScript = document.createElement("script");
newScript.textContent = scriptTag.textContent;
scriptTag.parentNode.replaceChild(newScript, scriptTag);
}
this.bindEvents();
this.bindDeleteKeyEvent();
}
getValue() {
return TabsBlot.value(this.domNode);
}
// 更新标签页数据 - 保持原有逻辑不变
updateContents(tabs) {
const contentList = this.domNode.querySelector(".m-quill-tab-content-list");
const tabList = this.domNode.querySelector(".m-quill-tab-list");
const editBtn = this.domNode.querySelector(".m-quill-tab-edit-btn");
Array.from(tabList.children).forEach(child => {
if (!child.classList.contains("m-quill-tab-edit-btn")) {
child.remove();
}
});
contentList.innerHTML = "";
tabs.forEach((tab, index) => {
const btn = document.createElement("button");
btn.className = "m-quill-tab-button";
btn.setAttribute("data-index", index);
btn.textContent = tab.title;
btn.setAttribute(
"style",
`
padding: 1%;
font-weight: 900;
color: #8f9099;
cursor: pointer;
background: transparent;
border: none;
margin-right: 1%;
font-size:16px;
${index === 0 ? "color: #1f2635; border-bottom: 3px solid #537CD8;font-size:16px;" : ""}
`
);
tabList.insertBefore(btn, editBtn);
const panel = document.createElement("div");
panel.className = "m-quill-tab-content";
panel.setAttribute("data-index", index);
panel.innerHTML = tab.content;
panel.setAttribute(
"style",
`
display: ${index === 0 ? "block" : "none"};
min-height: 50px;
`
);
panel.contentEditable = "false";
contentList.appendChild(panel);
});
this.bindEvents();
}
}
export default TabsBlot;

View File

@@ -0,0 +1,139 @@
import { Quill } from "@vueup/vue-quill";
const BlockEmbed = Quill.import("blots/block/embed");
const Link = Quill.import("formats/link");
const ATTRIBUTES = ["height", "width", "poster"];
class Video extends BlockEmbed {
static create(value) {
let node = super.create();
// 基础视频属性
node.setAttribute("controls", "controls");
node.setAttribute("playsinline", "true");
node.setAttribute("webkit-playsinline", "true");
node.setAttribute("type", "video/mp4");
// 处理视频URL添加时间片段定位到0.001秒(避开黑屏)
const baseUrl = this.sanitize(value.url);
const videoUrl = baseUrl.includes("#") ? `${baseUrl}&t=0.001` : `${baseUrl}#t=0.001`;
node.setAttribute("src", videoUrl);
// 临时封面(加载中显示)
node.setAttribute(
"poster",
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='300' viewBox='0 0 600 300'%3E%3Crect width='100%25' height='100%25' fill='%23f0f0f0'/%3E%3Ccircle cx='300' cy='150' r='40' fill='%23ccc'/%3E%3Cpolygon points='300,130 330,160 270,160' fill='white'/%3E%3C/svg%3E"
);
// 自动截取封面未提供poster时
if (!value.poster) {
this.captureFrameAsBlob(videoUrl, node);
} else {
node.setAttribute("poster", this.sanitize(value.poster));
}
// width: 600px;
// height: 300px;
// object-fit: contain;
// 视频样式
node.setAttribute(
"style",
`
`
);
return node;
}
/**
* 生成视频帧Blob核心方法返回Promise
* @param {string} videoUrl - 视频的本地Blob URL
* @returns {Promise<Blob|null>} - 视频帧Blob或null
*/
/**
* 将视频Blob URL转换为JPG图片Blob
* @param {string} videoBlobUrl - 本地视频Blob URLblob:xxx
* @returns {Promise<Blob|null>} - JPG格式图片Blob失败返回null
*/
static async captureVideoFrame(videoBlobUrl) {
return new Promise(resolve => {
// 1. 创建视频元素加载Blob
const video = document.createElement("video");
video.src = videoBlobUrl;
video.muted = true;
video.playsInline = true;
video.preload = "auto";
// 2. 视频可播放时开始转换
video.oncanplay = () => {
// 3. 创建Canvas绘制视频帧
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth || 640; // 使用视频实际宽度
canvas.height = video.videoHeight || 360; // 使用视频实际高度
const ctx = canvas.getContext("2d");
// 绘制视频当前帧到Canvas
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
// 4. 将Canvas转换为JPG Blob
canvas.toBlob(
blob => {
console.log(blob, "================>");
video.remove(); // 清理视频元素
resolve(blob || null); // 返回JPG Blob
},
"image/jpeg", // 强制JPG格式
0.8 // 图片质量0-1
);
};
// 错误处理
video.onerror = () => {
video.remove();
resolve(null);
};
});
}
// 以下方法保持不变
static formats(domNode) {
return ATTRIBUTES.reduce((formats, attribute) => {
if (domNode.hasAttribute(attribute)) {
formats[attribute] = domNode.getAttribute(attribute);
}
return formats;
}, {});
}
static sanitize(url) {
return Link.sanitize(url);
}
static value(domNode) {
return {
url: domNode.getAttribute("src").split("#")[0],
poster: domNode.getAttribute("poster") || ""
};
}
format(name, value) {
if (ATTRIBUTES.includes(name)) {
if (value) {
this.domNode.setAttribute(name, value);
} else {
this.domNode.removeAttribute(name);
}
} else {
super.format(name, value);
}
}
html() {
const { url, poster } = this.value();
return `<video src="${url}" ${poster ? `poster="${poster}"` : ""} controls playsinline webkit-playsinline ></video>`;
}
}
//style="width:600px;height:300px;"
Video.blotName = "customVideo";
// Video.className = "ql-video";
Video.tagName = "video";
export default Video;

View File

@@ -0,0 +1,72 @@
import { Quill } from "@vueup/vue-quill";
// 源码中是import直接倒入这里要用Quill.import引入
const BlockEmbed = Quill.import("blots/block/embed");
const Link = Quill.import("formats/link");
const ATTRIBUTES = ["height", "width"];
class Video extends BlockEmbed {
static create(value) {
let node = super.create();
// 添加video标签所需的属性
node.setAttribute("controls", "controls");
node.setAttribute("playsinline", "true");
node.setAttribute("webkit-playsinline", "true");
node.setAttribute("type", "video/mp4");
// poster 属性指定视频下载时显示的图像,或者在用户点击播放按钮前显示的图像。
// console.log(value.url, "= value.poster=");
// node.setAttribute("poster", this.sanitize(value.url));
node.setAttribute("src", this.sanitize(value.url));
node.setAttribute(
"style",
`
width: 600px;
height: 300px;
`
);
return node;
}
static formats(domNode) {
return ATTRIBUTES.reduce((formats, attribute) => {
if (domNode.hasAttribute(attribute)) {
formats[attribute] = domNode.getAttribute(attribute);
}
return formats;
}, {});
}
static sanitize(url) {
return Link.sanitize(url);
}
static value(domNode) {
// 设置自定义的属性值
return {
url: domNode.getAttribute("src")
// poster: domNode.getAttribute("src")
};
}
format(name, value) {
if (ATTRIBUTES.indexOf(name) > -1) {
if (value) {
this.domNode.setAttribute(name, value);
} else {
this.domNode.removeAttribute(name);
}
} else {
super.format(name, value);
}
}
html() {
const { video } = this.value();
return `<a href="${video}">${video}</a>`;
}
}
Video.blotName = "customVideo"; // 这里不用改不用iframe直接替换掉原来如果需要也可以保留原来的这里用个新的blot
Video.className = "ql-video"; // 可添加样式,看实际使用需要
Video.tagName = "video"; // 用video标签替换iframe
export default Video;

View File

@@ -0,0 +1,47 @@
// toolbar标题此项是用来增加hover标题
export const titleConfig = ref([
{ Choice: ".ql-insertMetric", title: "跳转配置" },
{ Choice: ".ql-bold", title: "加粗" },
{ Choice: ".ql-italic", title: "斜体" },
{ Choice: ".ql-underline", title: "下划线" },
{ Choice: ".ql-header", title: "段落格式" },
{ Choice: ".ql-strike", title: "删除线" },
{ Choice: ".ql-blockquote", title: "块引用" },
{ Choice: ".ql-code", title: "插入代码" },
{ Choice: ".ql-code-block", title: "插入代码段" },
{ Choice: ".ql-font", title: "字体" },
{ Choice: ".ql-size", title: "字体大小" },
{ Choice: '.ql-list[value="ordered"]', title: "编号列表" },
{ Choice: '.ql-list[value="bullet"]', title: "项目列表" },
{ Choice: ".ql-direction", title: "文本方向" },
{ Choice: '.ql-header[value="1"]', title: "h1" },
{ Choice: '.ql-header[value="2"]', title: "h2" },
{ Choice: ".ql-align", title: "对齐方式" },
{ Choice: ".ql-color", title: "字体颜色" },
{ Choice: ".ql-background", title: "背景颜色" },
{ Choice: ".ql-image", title: "图像" },
{ Choice: ".ql-video", title: "视频" },
{ Choice: ".ql-link", title: "添加链接" },
{ Choice: ".ql-formula", title: "插入公式" },
{ Choice: ".ql-clean", title: "清除字体格式" },
{ Choice: '.ql-script[value="sub"]', title: "下标" },
{ Choice: '.ql-script[value="super"]', title: "上标" },
{ Choice: '.ql-indent[value="-1"]', title: "向左缩进" },
{ Choice: '.ql-indent[value="+1"]', title: "向右缩进" },
{ Choice: ".ql-header .ql-picker-label", title: "标题大小" },
{ Choice: '.ql-header .ql-picker-item[data-value="1"]', title: "标题一" },
{ Choice: '.ql-header .ql-picker-item[data-value="2"]', title: "标题二" },
{ Choice: '.ql-header .ql-picker-item[data-value="3"]', title: "标题三" },
{ Choice: '.ql-header .ql-picker-item[data-value="4"]', title: "标题四" },
{ Choice: '.ql-header .ql-picker-item[data-value="5"]', title: "标题五" },
{ Choice: '.ql-header .ql-picker-item[data-value="6"]', title: "标题六" },
{ Choice: ".ql-header .ql-picker-item:last-child", title: "标准" },
{ Choice: '.ql-size .ql-picker-item[data-value="small"]', title: "小号" },
{ Choice: '.ql-size .ql-picker-item[data-value="large"]', title: "大号" },
{ Choice: '.ql-size .ql-picker-item[data-value="huge"]', title: "超大号" },
{ Choice: ".ql-size .ql-picker-item:nth-child(2)", title: "标准" },
{ Choice: ".ql-align .ql-picker-item:first-child", title: "居左对齐" },
{ Choice: '.ql-align .ql-picker-item[data-value="center"]', title: "居中对齐" },
{ Choice: '.ql-align .ql-picker-item[data-value="right"]', title: "居右对齐" },
{ Choice: '.ql-align .ql-picker-item[data-value="justify"]', title: "两端对齐" }
]);

View File

@@ -0,0 +1,8 @@
export const routerObj = {
articleEditIndex: "article",
productEditIndex: "product",
bannerListIndex: "banner",
downloadListIndex: "download",
videoListIndex: "video",
QAListIndex: "QA"
};

View File

@@ -28,7 +28,7 @@
</template> </template>
<template #default="scope" v-if="item.formType === 'inputNumber'"> <template #default="scope" v-if="item.formType === 'inputNumber'">
<el-input-number <el-input-number
:min="1" :min="0"
:max="9999" :max="9999"
:controls="true" :controls="true"
style="width: 125px" style="width: 125px"

View File

@@ -52,13 +52,13 @@
class="m-2 select" class="m-2 select"
remote-show-suffix remote-show-suffix
:remote-method=" :remote-method="
(query:any) => { (query:any) => {
remoteMethod( remoteMethod(
query, query,
item item
); );
} }
" "
:disabled="item.disabled" :disabled="item.disabled"
style="width: 224px" style="width: 224px"
> >
@@ -72,10 +72,14 @@
v-model="_searchParam[`${item.prop}`]" v-model="_searchParam[`${item.prop}`]"
:data="item.options" :data="item.options"
:placeholder="item.placeholder" :placeholder="item.placeholder"
multiple
:render-after-expand="false" :render-after-expand="false"
:check-strictly="false"
show-checkbox show-checkbox
style="width: 224px" style="width: 224px"
clearable ref="treeSelectRef"
@change="handleTreeSelectChange(item)"
@remove-tag="handleRemoveTag(item, $event)"
/> />
</template> </template>
<!-- 双 --> <!-- 双 -->
@@ -112,7 +116,7 @@
/> />
</template> </template>
<template v-if="item.type === 'selectInputs'"> <!-- <template v-if="item.type === 'selectInputs'">
<div></div> <div></div>
<el-select <el-select
v-model="selectInputValue" v-model="selectInputValue"
@@ -147,19 +151,19 @@
style="width: 106px !important" style="width: 106px !important"
@input="handleInput(item)" @input="handleInput(item)"
/> />
</template> </template> -->
</div> </div>
</template> </template>
<script lang="ts" setup name="SearchFormItem"> <script lang="ts" setup name="SearchFormItem">
// import { verificationInput } from "./utils/verificationInput"; // import { verificationInput } from "./utils/verificationInput";
import { getCountryListApi } from "@/api/modules/global"; import { getCountryListApi } from "@/api/modules/global";
import $Bus from "@/utils/mittBus";
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
import { ref } from "vue"; // import { ref } from "vue";
const $router = useRouter(); const $router = useRouter();
const routeName: any = ref($router.currentRoute.value.name); const routeName: any = ref($router.currentRoute.value.name);
const treeSelectRef = ref<any>(null);
// const userStore: any = useUserStore(); // const userStore: any = useUserStore();
interface SearchFormItem { interface SearchFormItem {
item: { [key: string]: any }; item: { [key: string]: any };
@@ -167,18 +171,9 @@ interface SearchFormItem {
search: (params: any) => void; // 搜索方法 search: (params: any) => void; // 搜索方法
handleEmitClear?: (item: any) => void; handleEmitClear?: (item: any) => void;
} }
let selectInputValue = ref(1); // let selectInputValue = ref();
const options = [
{
value: 1,
label: "序号"
},
{
value: 2,
label: "数字序列号"
}
];
// const treeSelectValue = ref(null);
const props = defineProps<SearchFormItem>(); const props = defineProps<SearchFormItem>();
const _searchParam = computed(() => props.searchParam); const _searchParam = computed(() => props.searchParam);
@@ -217,6 +212,125 @@ const remoteMethod = async (query: any, item: any) => {
loading.value = false; loading.value = false;
}; };
// 关闭标签时同步 - 修复版
const handleRemoveTag = (item: any, removedValue: any) => {
nextTick(() => {
// 从item.options获取树形数据这是我们传递给组件的数据源
const allNodes = Array.isArray(item.options) ? item.options : [];
console.log(allNodes, "=allNodes=");
// 找到被删除的节点
const removedNode = findNode(allNodes, removedValue);
console.log(removedNode, "=removedNode=");
let ids: any = [];
if (removedNode) {
// 判断被删除的是父节点还是子节点
if (removedNode.children && Array.isArray(removedNode.children) && removedNode.children.length > 0) {
// 是父节点,需要删除所有子节点
const childIds = getAllChildIds(removedNode);
// 从选中值中移除父节点和所有子节点
ids = (_searchParam.value[item.prop] || []).filter((id: any) => !childIds.includes(id) && id !== removedNode.id);
nextTick(() => {
_searchParam.value[item.prop] = ids;
_searchParam.value[item.prop1] = ids.join(",");
});
} else {
// 是子节点,需要找到其父节点并删除
const parentNode = findParentNode(allNodes, removedValue);
if (parentNode) {
// 从选中值中移除子节点和父节点
ids = (_searchParam.value[item.prop] || []).filter((id: any) => id !== removedValue && id !== parentNode.id);
nextTick(() => {
_searchParam.value[item.prop] = ids;
_searchParam.value[item.prop1] = ids.join(",");
});
} else {
ids = (_searchParam.value[item.prop] || []).filter((id: any) => id !== removedValue && id !== parentNode.id);
nextTick(() => {
_searchParam.value[item.prop] = ids;
_searchParam.value[item.prop1] = ids.join(",");
});
}
}
}
syncCheckedIds(item);
});
};
// 辅助方法:查找节点
const findNode = (nodes: any[], value: any): any => {
if (!Array.isArray(nodes)) return null;
for (const node of nodes) {
if (node?.id === value) {
return node;
}
if (node?.children && Array.isArray(node.children) && node.children.length > 0) {
const found = findNode(node.children, value);
if (found) return found;
}
}
return null;
};
// 辅助方法:查找父节点
const findParentNode = (nodes: any[], value: any, parent: any = null): any => {
if (!Array.isArray(nodes)) return null;
for (const node of nodes) {
if (node?.id === value) {
return parent;
}
if (node?.children && Array.isArray(node.children) && node.children.length > 0) {
const foundParent = findParentNode(node.children, value, node);
if (foundParent) return foundParent;
}
}
return null;
};
// 辅助方法获取所有子节点ID
const getAllChildIds = (node: any): any[] => {
let ids: any[] = [];
if (node?.children && Array.isArray(node.children) && node.children.length > 0) {
for (const child of node.children) {
if (child?.id) {
ids.push(child.id);
ids = [...ids, ...getAllChildIds(child)];
}
}
}
return ids;
};
// 统一同步选中ID的方法
const syncCheckedIds = (item: any) => {
// 获取所有全选中的节点(包括父节点)
const allCheckedNodes = treeSelectRef.value.getCheckedNodes(false, false);
const allCheckedIds = allCheckedNodes.map((node: any) => node.id);
_searchParam.value[item.prop] = allCheckedIds;
// 同步到搜索参数
_searchParam.value[item.prop1] = allCheckedIds.length ? allCheckedIds.join(",") : null;
};
const handleTreeSelectChange = (item: any) => {
console.log(routeName.value);
if (routeName.value === "articleListIndex") {
// 通过ref获取组件实例
if (treeSelectRef.value) {
syncCheckedIds(item);
return;
}
}
if (_searchParam.value[item.prop].length) {
let values = cloneDeep(_searchParam.value[item.prop]);
_searchParam.value[item.prop1] = values.join(",");
} else {
_searchParam.value[item.prop1] = "";
}
};
const handleClear = (item: any) => { const handleClear = (item: any) => {
item.options = []; item.options = [];
}; };
@@ -227,27 +341,17 @@ const handleInput = (item: any) => {
//验证 //验证
// verificationInput(item, _searchParam, selectInputValue.value); // verificationInput(item, _searchParam, selectInputValue.value);
}; };
const handleChange = (item: any) => { // const handleChange = (item: any) => {
_searchParam.value[item.endProp] = ""; // _searchParam.value[item.endProp] = "";
_searchParam.value[item.startProp] = ""; // _searchParam.value[item.startProp] = "";
_searchParam.value["serialNumberBegin"] = ""; // _searchParam.value["serialNumberBegin"] = "";
_searchParam.value["numberCodeBegin"] = ""; // _searchParam.value["numberCodeBegin"] = "";
_searchParam.value["serialNumberEnd"] = ""; // _searchParam.value["serialNumberEnd"] = "";
_searchParam.value["numberCodeEnd"] = ""; // _searchParam.value["numberCodeEnd"] = "";
}; // };
const handleEmitClear = (item: any) => { const handleEmitClear = (item: any) => {
console.log(item); console.log(item);
if (routeName.value === "barCode") {
$Bus.emit("clearBarCodeCreateUser");
}
if (routeName.value === "boxCode") {
$Bus.emit("clearBoxCodeCreateUser");
}
if (routeName.value === "boxMarkIndex") {
$Bus.emit("clearBoxMarkIndexCreator");
}
}; };
</script> </script>
<style lang="scss" scope> <style lang="scss" scope>

View File

@@ -12,19 +12,19 @@ export const verificationInput = (item: any, _searchParam: any, selectInputValue
//序列号区间值开始 限制只能输入正整数 //序列号区间值开始 限制只能输入正整数
if (startProp === "startNumber") { if (startProp === "startNumber") {
_searchParam.value[startProp] = integerNumber(_searchParam.value[startProp]); _searchParam.value[startProp] = integerNumber(_searchParam.value[startProp]);
if (selectInputValue === 1) { if (selectInputValue == 1) {
_searchParam.value["serialNumberBegin"] = _searchParam.value[startProp]; _searchParam.value["serialNumberBegin"] = _searchParam.value[startProp];
} }
if (selectInputValue === 2) { if (selectInputValue == 2) {
_searchParam.value["numberCodeBegin"] = _searchParam.value[startProp]; _searchParam.value["numberCodeBegin"] = _searchParam.value[startProp];
} }
} }
if (endProp === "endNumber") { if (endProp === "endNumber") {
_searchParam.value[endProp] = integerNumber(_searchParam.value[endProp]); _searchParam.value[endProp] = integerNumber(_searchParam.value[endProp]);
if (selectInputValue === 1) { if (selectInputValue == 1) {
_searchParam.value["serialNumberEnd"] = _searchParam.value[endProp]; _searchParam.value["serialNumberEnd"] = _searchParam.value[endProp];
} }
if (selectInputValue === 2) { if (selectInputValue == 2) {
_searchParam.value["numberCodeEnd"] = _searchParam.value[endProp]; _searchParam.value["numberCodeEnd"] = _searchParam.value[endProp];
} }
} }

View File

@@ -1,12 +1,15 @@
.el-form-item--default { .el-form-item--default {
height: auto !important;
margin-bottom: 8px; margin-bottom: 8px;
} }
.form-item { .form-item {
display: flex;
align-items: center;
width: 344px !important; width: 344px !important;
height: 32px; height: 32px;
margin-right: 12px !important; margin-right: 12px !important;
.form-item-select { .form-item-select {
width: 224px !important; width: 254px !important;
} }
} }
.form-box1 { .form-box1 {

View File

@@ -73,7 +73,7 @@ const props = withDefaults(defineProps<UploadFileProps>(), {
drag: true, drag: true,
disabled: false, disabled: false,
fileSize: 5, fileSize: 5,
fileType: () => ["image/jpeg", "image/png", "image/gif"], fileType: () => ["image/jpeg", "image/png", "image/gif", "image/webp"],
height: "150px", height: "150px",
width: "150px", width: "150px",
borderRadius: "8px" borderRadius: "8px"
@@ -119,9 +119,9 @@ const handleHttpUpload = async (options: UploadRequestOptions) => {
const api = props.api ?? uploadImg; const api = props.api ?? uploadImg;
const result = await api(formData, routerName.value); const result = await api(formData, routerName.value);
if (result?.code === 0) { console.log(result, "============>>>");
const { data } = result; if (result?.data?.code === 0) {
emit("update:imageUrl", data.path); emit("update:imageUrl", result?.data?.data.path);
} }
// 调用 el-form 内部的校验方法(可自动校验) // 调用 el-form 内部的校验方法(可自动校验)
@@ -151,7 +151,9 @@ const editImg = () => {
* @param rawFile 选择的文件 * @param rawFile 选择的文件
* */ * */
const beforeUpload: UploadProps["beforeUpload"] = rawFile => { const beforeUpload: UploadProps["beforeUpload"] = rawFile => {
// console.log(rowFile.type, "=============1111111111");
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize; const imgSize = rawFile.size / 1024 / 1024 < props.fileSize;
const imgType = props.fileType.includes(rawFile.type as File.ImageMimeType); const imgType = props.fileType.includes(rawFile.type as File.ImageMimeType);
if (!imgType) if (!imgType)

View File

@@ -43,10 +43,9 @@ import { ref, computed, inject, watch } from "vue";
import { Plus, Delete } from "@element-plus/icons-vue"; import { Plus, Delete } from "@element-plus/icons-vue";
import { uploadImg } from "@/api/modules/upload"; import { uploadImg } from "@/api/modules/upload";
import type { UploadProps, UploadFile, UploadUserFile, UploadRequestOptions } from "element-plus"; import type { UploadProps, UploadFile, UploadUserFile, UploadRequestOptions } from "element-plus";
import { generateUUID } from "@/utils"; // import { generateUUID } from "@/utils";
import { ElNotification, formContextKey, formItemContextKey } from "element-plus"; import { ElNotification, formContextKey, formItemContextKey } from "element-plus";
let uid = generateUUID(); // let uid = generateUUID();
console.log("uid:", uid);
interface UploadFileProps { interface UploadFileProps {
fileList: UploadUserFile[]; fileList: UploadUserFile[];
api?: (params: any) => Promise<any>; // 上传图片的 api 方法,一般项目上传都是同一个 api 方法,在组件里直接引入即可 ==> 非必传 api?: (params: any) => Promise<any>; // 上传图片的 api 方法,一般项目上传都是同一个 api 方法,在组件里直接引入即可 ==> 非必传
@@ -66,7 +65,7 @@ const props = withDefaults(defineProps<UploadFileProps>(), {
disabled: false, disabled: false,
limit: 9, limit: 9,
fileSize: 5, fileSize: 5,
fileType: () => ["image/jpeg", "image/png", "image/gif"], fileType: () => ["image/jpeg", "image/png", "image/gif", "image/webp"],
height: "150px", height: "150px",
width: "150px", width: "150px",
borderRadius: "8px" borderRadius: "8px"
@@ -134,9 +133,10 @@ const handleHttpUpload = async (options: UploadRequestOptions) => {
formData.append("image", options.file); formData.append("image", options.file);
try { try {
const api = props.api ?? uploadImg; const api = props.api ?? uploadImg;
const { data } = await api(formData, routerName.value); const { data } = await api(formData, routerName.value);
console.log(data.path, "========data=========="); console.log(data.data.path, "========data==========");
options.onSuccess(data.path); options.onSuccess(data.data.path);
} catch (error) { } catch (error) {
options.onError(error as any); options.onError(error as any);
} }

View File

@@ -9,11 +9,13 @@
:show-file-list="false" :show-file-list="false"
:http-request="handleHttpUpload" :http-request="handleHttpUpload"
:before-upload="beforeUpload" :before-upload="beforeUpload"
:on-success="uploadSuccess"
:on-error="uploadError" :on-error="uploadError"
:on-exceed="handleExceed"
:limit="1" :limit="1"
:accept="fileType.join(',')" :accept="fileType.join(',')"
ref="upload"
> >
<!-- :on-success="uploadSuccess" -->
<!-- disabled --> <!-- disabled -->
<el-input v-model="videoShowUrl" style="width: 414px" @click.stop @input="handleInput"> <el-input v-model="videoShowUrl" style="width: 414px" @click.stop @input="handleInput">
<template #prepend <template #prepend
@@ -33,9 +35,10 @@ import { FolderAdd } from "@element-plus/icons-vue";
import { ref, computed, inject } from "vue"; import { ref, computed, inject } from "vue";
import { generateUUID } from "@/utils"; import { generateUUID } from "@/utils";
import { uploadVideo } from "@/api/modules/upload"; import { uploadVideo } from "@/api/modules/upload";
import { genFileId } from "element-plus";
import { ElNotification, formContextKey, formItemContextKey } from "element-plus"; import { ElNotification, formContextKey, formItemContextKey } from "element-plus";
import type { UploadProps, UploadRequestOptions } from "element-plus"; import type { UploadProps, UploadRawFile, UploadInstance } from "element-plus";
//UploadRequestOptions
interface UploadFileProps { interface UploadFileProps {
videoUrl: any; // 图片地址 ==> 必传 videoUrl: any; // 图片地址 ==> 必传
api?: (params: any) => Promise<any>; // 上传图片的 api 方法,一般项目上传都是同一个 api 方法,在组件里直接引入即可 ==> 非必传 api?: (params: any) => Promise<any>; // 上传图片的 api 方法,一般项目上传都是同一个 api 方法,在组件里直接引入即可 ==> 非必传
@@ -50,7 +53,7 @@ const videoShowUrl = ref<any>(null);
const props = withDefaults(defineProps<UploadFileProps>(), { const props = withDefaults(defineProps<UploadFileProps>(), {
videoUrl: "", videoUrl: "",
disabled: false, disabled: false,
fileSize: 200, fileSize: 150,
width: "400px", width: "400px",
fileType: () => [".mp4", ".avi", ".mov", "video/mp4", "video/mov", "video/avi"], fileType: () => [".mp4", ".avi", ".mov", "video/mp4", "video/mov", "video/avi"],
borderRadius: "8px" borderRadius: "8px"
@@ -69,7 +72,7 @@ const routerName = ref(routerObj[routerValueName]);
// 生成组件唯一id // 生成组件唯一id
const uuid = ref("id-" + generateUUID()); const uuid = ref("id-" + generateUUID());
const upload = ref<UploadInstance>();
// 获取 el-form 组件上下文 // 获取 el-form 组件上下文
const formContext = inject(formContextKey, void 0); const formContext = inject(formContextKey, void 0);
// 获取 el-form-item 组件上下文 // 获取 el-form-item 组件上下文
@@ -86,23 +89,45 @@ interface UploadEmits {
(e: "update:videoUrl", value: string): void; (e: "update:videoUrl", value: string): void;
} }
const emit = defineEmits<UploadEmits>(); const emit = defineEmits<UploadEmits>();
const handleHttpUpload = async (options: UploadRequestOptions) => { const handleHttpUpload = async (options: any) => {
let formData = new FormData(); let formData = new FormData();
formData.append("video", options.file); formData.append("video", options.file);
try { try {
const api = props.api ?? uploadVideo; const api = props.api ?? uploadVideo;
const { data } = await api(formData, routerName.value); const { data } = await api(formData, routerName.value);
emit("update:videoUrl", data.path); if (data.path) {
// 调用 el-form 内部的校验方法(可自动校验) ElNotification({
formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]); title: "温馨提示",
message: "视频上传成功!",
type: "success"
});
emit("update:videoUrl", data.path);
// 调用 el-form 内部的校验方法(可自动校验)
formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]);
} else {
videoShowUrl.value = "";
emit("update:videoUrl", "");
}
} catch (error) { } catch (error) {
videoShowUrl.value = "";
emit("update:videoUrl", "");
if (!error) {
return;
}
options.onError(error as any); options.onError(error as any);
} }
}; };
const handleExceed: UploadProps["onExceed"] = files => {
upload.value!.clearFiles();
const file = files[0] as UploadRawFile;
file.uid = genFileId();
let options: any = {
file
};
handleHttpUpload(options);
};
const handleInput = () => { const handleInput = () => {
emit("update:videoUrl", videoShowUrl.value); emit("update:videoUrl", videoShowUrl.value);
console.log("会触发吗");
}; };
/** /**
@@ -132,13 +157,13 @@ const beforeUpload: UploadProps["beforeUpload"] = rawFile => {
/** /**
* @description 视频上传成功 * @description 视频上传成功
* */ * */
const uploadSuccess = () => { // const uploadSuccess = () => {
ElNotification({ // ElNotification({
title: "温馨提示", // title: "温馨提示",
message: "视频上传成功!", // message: "视频上传成功!",
type: "success" // type: "success"
}); // });
}; // };
/** /**
* @description 视频上传错误 * @description 视频上传错误
@@ -153,7 +178,9 @@ const uploadError = () => {
watch( watch(
() => props.videoUrl, () => props.videoUrl,
(newVal: any) => { (newVal: any) => {
videoShowUrl.value = newVal; if (newVal) {
videoShowUrl.value = newVal;
}
} }
); );
</script> </script>

View File

@@ -26,3 +26,6 @@
overflow-y: hidden; overflow-y: hidden;
} }
} }
span img {
display: block !important;
}

View File

@@ -20,7 +20,7 @@ import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { uploadImg, uploadVideo } from "@/api/modules/upload"; import { uploadImg, uploadVideo } from "@/api/modules/upload";
import "@wangeditor/editor/dist/css/style.css"; import "@wangeditor/editor/dist/css/style.css";
import { formContextKey, formItemContextKey } from "element-plus"; import { formContextKey, formItemContextKey } from "element-plus";
// import { cloneDeep } from "lodash-es";
// 富文本 DOM 元素 // 富文本 DOM 元素
const editorRef = shallowRef(); const editorRef = shallowRef();
@@ -28,6 +28,7 @@ const editorRef = shallowRef();
const handleCreated = (editor: any) => { const handleCreated = (editor: any) => {
editorRef.value = editor; editorRef.value = editor;
}; };
const $router = useRouter(); const $router = useRouter();
const routerValueName: string = $router.currentRoute.value.name as string; const routerValueName: string = $router.currentRoute.value.name as string;
const routerObj: any = { const routerObj: any = {
@@ -55,12 +56,23 @@ const props = withDefaults(defineProps<RichEditorProps>(), {
excludeKeys: [] excludeKeys: []
}; };
}, },
editorConfig: () => { editorConfig: () => {
return { return {
placeholder: "请输入内容...", placeholder: "请输入内容...",
MENU_CONF: {} MENU_CONF: {}
// parseElemHtml: (elemHtml: string, elem: HTMLElement, editor: any) => {
// console.log(editor, "========editor=========");
// console.log(elem.tagName, "=elem.tagName=");
// if (elem.tagName === "SPAN" && elem.querySelector("img")) {
// // 确保 span 元素可以正确显示 img
// elem.style.display = "inline-block";
// }
// return elemHtml;
// }
}; };
}, },
height: "500px", height: "500px",
mode: "default", mode: "default",
hideToolBar: false, hideToolBar: false,
@@ -84,7 +96,9 @@ type EmitProps = {
(e: "update:value", val: string): void; (e: "update:value", val: string): void;
(e: "check-validate"): void; (e: "check-validate"): void;
}; };
const emit = defineEmits<EmitProps>(); const emit = defineEmits<EmitProps>();
const valueHtml = computed({ const valueHtml = computed({
get() { get() {
return props.value; return props.value;
@@ -112,7 +126,6 @@ props.editorConfig.MENU_CONF!["uploadImage"] = {
const result = await uploadImg(formData, routerName.value); const result = await uploadImg(formData, routerName.value);
if (result?.code === 0) { if (result?.code === 0) {
const { data } = result; const { data } = result;
console.log(data, "==============data====================");
insertFn(import.meta.env.VITE_APP_API_BASE_UPLOAD_URL + data.path); insertFn(import.meta.env.VITE_APP_API_BASE_UPLOAD_URL + data.path);
} }
} catch (error) { } catch (error) {

File diff suppressed because one or more lines are too long

View File

@@ -53,7 +53,7 @@
</template> </template>
<template v-if="item.type === 'inputNumber'"> <template v-if="item.type === 'inputNumber'">
<el-input-number <el-input-number
:min="1" :min="0"
:max="9999" :max="9999"
:controls="true" :controls="true"
v-model="_ruleForm[`${item.prop}`]" v-model="_ruleForm[`${item.prop}`]"
@@ -159,6 +159,7 @@
show-checkbox show-checkbox
check-strictly check-strictly
@change="handleSelectChange(_ruleForm[`${item.prop}`], item.prop)" @change="handleSelectChange(_ruleForm[`${item.prop}`], item.prop)"
style="max-width: 340px"
/> />
</template> </template>
<template v-if="item.type === 'treeSelects'"> <template v-if="item.type === 'treeSelects'">
@@ -170,14 +171,14 @@
show-checkbox show-checkbox
check-strictly check-strictly
check-on-click-node check-on-click-node
style="width: 240px" style="width: 340px"
/> />
</template> </template>
<template v-if="item.type === 'treeSelectInput'"> <template v-if="item.type === 'treeSelectInput'">
<slot /> <slot />
</template> </template>
<template v-if="item.type === 'WangEditor'"> <template v-if="item.type === 'WangEditor'">
<WangEditor v-model:value="_ruleForm[`${item.prop}`]" /> <Editor v-model:content="_ruleForm[`${item.prop}`]" ref="editorRef"></Editor>
</template> </template>
</el-form-item> </el-form-item>
</template> </template>
@@ -188,6 +189,7 @@
import type { FormInstance, FormRules } from "element-plus"; import type { FormInstance, FormRules } from "element-plus";
import UploadVideo from "@/components/Upload/UploadVideo.vue"; import UploadVideo from "@/components/Upload/UploadVideo.vue";
import UploadImg from "@/components/Upload/UploadImg.vue"; import UploadImg from "@/components/Upload/UploadImg.vue";
import Editor from "@/components/Editor/index.vue";
interface IProps { interface IProps {
ruleForm: { [key: string]: any }; ruleForm: { [key: string]: any };
formData: any[]; formData: any[];
@@ -207,7 +209,7 @@ const emits = defineEmits<{
(e: "handleRadioGroupEmits", result?: any): void; (e: "handleRadioGroupEmits", result?: any): void;
(e: "handleTreesSelectChangeEmits", result?: any): void; (e: "handleTreesSelectChangeEmits", result?: any): void;
}>(); }>();
const editorRef = ref<any>(null);
const ruleFormRef = ref<FormInstance>(); const ruleFormRef = ref<FormInstance>();
const props = defineProps<IProps>(); const props = defineProps<IProps>();
//本地化处理,props是单向的,通过本地化就可以改变父组件传过来的值了 //本地化处理,props是单向的,通过本地化就可以改变父组件传过来的值了
@@ -233,7 +235,8 @@ const handleRadioGroup = (value: any) => {
// 暴露给父组件的参数和方法(外部需要什么,都可以从这里暴露出去) // 暴露给父组件的参数和方法(外部需要什么,都可以从这里暴露出去)
defineExpose({ defineExpose({
ruleForm: _ruleForm, ruleForm: _ruleForm,
ruleFormRef: ruleFormRef ruleFormRef: ruleFormRef,
editorRef: editorRef
}); });
</script> </script>

View File

@@ -87,9 +87,10 @@ export const useTable = (
// 处理查询参数,可以给查询参数加自定义前缀操作 // 处理查询参数,可以给查询参数加自定义前缀操作
let nowSearchParam: Table.StateProps["searchParam"] = {}; let nowSearchParam: Table.StateProps["searchParam"] = {};
// 防止手动清空输入框携带参数(这里可以自定义查询参数前缀) // 防止手动清空输入框携带参数(这里可以自定义查询参数前缀)
console.log(state.searchParam, "=state.searchParam=");
for (let key in state.searchParam) { for (let key in state.searchParam) {
//剔除日期参数 //剔除日期参数
if (key !== "Time" && key !== "Time1") { if (key !== "Time" && key !== "Time1" && key !== "treeIds") {
// * 某些情况下参数为 false/0 也应该携带参数 // * 某些情况下参数为 false/0 也应该携带参数
if (state.searchParam[key] || state.searchParam[key] === false || state.searchParam[key] === 0) { if (state.searchParam[key] || state.searchParam[key] === false || state.searchParam[key] === 0) {
nowSearchParam[key] = state.searchParam[key]; nowSearchParam[key] = state.searchParam[key];

View File

@@ -46,7 +46,6 @@ const authStore = useAuthStore();
const globalStore = useGlobalStore(); const globalStore = useGlobalStore();
const isCollapse = computed(() => globalStore.isCollapse); const isCollapse = computed(() => globalStore.isCollapse);
const menuList = computed(() => authStore.showMenuListGet); const menuList = computed(() => authStore.showMenuListGet);
console.log(menuList, "=menuList=");
const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string); const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string);
</script> </script>

View File

@@ -44,9 +44,16 @@ import { useMsg } from "@/hooks/useMsg";
import { useUserStore } from "@/stores/modules/user"; import { useUserStore } from "@/stores/modules/user";
import { ElMessageBox } from "element-plus"; import { ElMessageBox } from "element-plus";
import { outLogin } from "@/utils/outLogin"; import { outLogin } from "@/utils/outLogin";
import { useKeepAliveStore } from "@/stores/modules/keepAlive";
import { getLanguageListApi, getLanguageCutoverApi } from "@/api/modules/global"; import { getLanguageListApi, getLanguageCutoverApi } from "@/api/modules/global";
import { HOME_URL } from "@/config";
import { useRouter } from "vue-router";
import { useTabsStore } from "@/stores/modules/tabs";
const tabStore = useTabsStore();
const userStore = useUserStore(); const userStore = useUserStore();
const router = useRouter();
const keepAliveStore = useKeepAliveStore();
document.cookie = `lang=zh_cn`; document.cookie = `lang=zh_cn`;
const langs = ref<any>([]); const langs = ref<any>([]);
const name = ref(""); const name = ref("");
@@ -58,13 +65,13 @@ const getLanguageList = async () => {
const { data } = result; const { data } = result;
langs.value = data; langs.value = data;
let id = userStore?.languageType ? userStore?.languageType : data[0]?.id; let id = userStore?.languageType ? userStore?.languageType : data[0]?.id;
getLanguageCutover(id); getLanguageCutover(id, "noCLick");
} }
}; };
getLanguageList(); getLanguageList();
// 站点切换接口 // 站点切换接口
const getLanguageCutover = async (id: any) => { const getLanguageCutover = async (id: any, type: any) => {
const result = await getLanguageCutoverApi(id); const result = await getLanguageCutoverApi(id);
if (result?.code === 0) { if (result?.code === 0) {
userStore.setLanguageType(id); userStore.setLanguageType(id);
@@ -72,12 +79,35 @@ const getLanguageCutover = async (id: any) => {
return item.id === id; return item.id === id;
}); });
name.value = names[0]?.country_name; name.value = names[0]?.country_name;
if (type === "click") {
tabStore.setTabs([
{
icon: "",
title: "首页",
path: "/admin/index",
name: "home",
close: true
}
]);
keepAliveStore.setKeepAliveName();
setTimeout(() => {
router.push(HOME_URL);
}, 500);
}
} }
}; };
// 站点切换事件 // 站点切换事件
const handleCommand = (val: string) => { const handleCommand = (val: string) => {
getLanguageCutover(val); //切换语言将清空所有标签页面
ElMessageBox.confirm("切换语言将清空所有标签页面?", "温馨提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
await getLanguageCutover(val, "click");
});
}; };
// 退出登录 // 退出登录

View File

@@ -40,6 +40,7 @@ watch(
); );
const handleOpenPage = (item: any) => { const handleOpenPage = (item: any) => {
console.log(item, "===========item==========");
$router.push({ $router.push({
path: item.path path: item.path
}); });

View File

@@ -86,9 +86,23 @@ const closeOtherTab = () => {
// Close All // Close All
const closeAllTab = () => { const closeAllTab = () => {
tabStore.closeMultipleTab(); tabStore.setTabs([
{
icon: "",
title: "首页",
path: "/admin/index",
name: "home",
close: true
}
]);
keepAliveStore.setKeepAliveName(); keepAliveStore.setKeepAliveName();
router.push(HOME_URL); setTimeout(() => {
router.push(HOME_URL);
}, 500);
// tabStore.closeMultipleTab();
// keepAliveStore.setKeepAliveName();
// router.push(HOME_URL);
}; };
</script> </script>

View File

@@ -36,6 +36,7 @@ import MoreButton from "./components/MoreButton.vue";
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const tabStore = useTabsStore(); const tabStore = useTabsStore();
const authStore = useAuthStore(); const authStore = useAuthStore();
// const globalStore = useGlobalStore(); // const globalStore = useGlobalStore();
@@ -44,7 +45,7 @@ const keepAliveStore = useKeepAliveStore();
const tabsMenuValue = ref(route.fullPath); const tabsMenuValue = ref(route.fullPath);
const tabsMenuList = computed(() => tabStore.tabsMenuList); const tabsMenuList = computed(() => tabStore.tabsMenuList);
console.log(tabStore.tabsMenuList, "===============value================");
onMounted(() => { onMounted(() => {
tabsDrop(); tabsDrop();
initTabs(); initTabs();

View File

@@ -33,8 +33,10 @@ import errorHandler from "@/utils/errorHandler";
import VXETable from "vxe-table"; import VXETable from "vxe-table";
import "vxe-table/lib/style.css"; import "vxe-table/lib/style.css";
import mavonEditor from "mavon-editor";
import "mavon-editor/dist/css/index.css";
const app = createApp(App); const app = createApp(App);
app.config.errorHandler = errorHandler; app.config.errorHandler = errorHandler;
app.use(directives).use(router).use(pinia).use(VXETable).mount("#app"); app.use(directives).use(router).use(pinia).use(VXETable).use(mavonEditor).mount("#app");

View File

@@ -30,12 +30,13 @@ export const useTabsStore = defineStore({
}, },
// Close MultipleTab // Close MultipleTab
async closeMultipleTab(tabsMenuValue?: string) { async closeMultipleTab(tabsMenuValue?: string) {
this.tabsMenuList = this.tabsMenuList.filter(item => { this.tabsMenuList = this.tabsMenuList.filter((item: any) => {
return item.path === tabsMenuValue || !item.close; return item.path === tabsMenuValue || !item.hidden;
}); });
}, },
// Set Tabs // Set Tabs
async setTabs(tabsMenuList: any[]) { async setTabs(tabsMenuList: any[]) {
console.log(tabsMenuList, "=tabsMenuList=");
this.tabsMenuList = tabsMenuList; this.tabsMenuList = tabsMenuList;
}, },
// Set Tabs Title // Set Tabs Title

View File

@@ -97,6 +97,8 @@
// table-search 表格搜索样式 // table-search 表格搜索样式
.table-search { .table-search {
// height: auto;
// max-height: 80px;
margin-bottom: 10px; margin-bottom: 10px;
.el-form { .el-form {
.el-form-item__content > * { .el-form-item__content > * {
@@ -269,5 +271,5 @@
white-space: nowrap; white-space: nowrap;
} }
.el-message__wrapper { .el-message__wrapper {
z-index: 9999; /* 一个较大的值,确保在抽屉之上 */ z-index: 3000 !important; /* 一个较大的值,确保在抽屉之上 */
} }

View File

@@ -0,0 +1,15 @@
export const convertSpanToDiv = (html: any) => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const spans = doc.querySelectorAll("span");
if (!spans.length) {
return;
}
spans.forEach((span: any) => {
if (span.querySelector("img")) {
const img = span.querySelector("img");
span.parentNode.replaceChild(img, span);
}
});
return doc.body.innerHTML;
};

View File

@@ -4,8 +4,8 @@ import { useUserStore } from "@/stores/modules/user";
//不同环境的login地址 //不同环境的login地址
const LOGIN_OBJ: any = { const LOGIN_OBJ: any = {
development: "http://localhost:8080/admin/login", //开发环境 development: "http://localhost:8080/admin/login", //开发环境
test: "https://dev.ow.admin.f2b211.com/", //测试环境 test: "https://dev.orico.com.cn/admin/login", //测试环境
production: "http://localhost:8080/login" //生产环境 production: "https://orico.com.cn/admin/login" //生产环境
}; };
/** /**
/** /**

View File

@@ -0,0 +1,13 @@
export const recursiveCompare = (item: any, targetId: any) => {
if (typeof item === "object" && item !== null) {
if (item.value !== undefined && item.value === targetId) {
return true;
}
for (const key in item) {
if (recursiveCompare(item[key], targetId)) {
return true;
}
}
}
return false;
};

View File

@@ -20,7 +20,7 @@
<el-tab-pane label="问答详细" name="third"> <el-tab-pane label="问答详细" name="third">
<div style="width: 1280px; margin: 0 auto"> <div style="width: 1280px; margin: 0 auto">
<WangEditor v-model:value="dataStore.editRuleForm.answer" /> <Editor v-model:content="dataStore.editRuleForm.answer" ref="editorRef" />
</div> </div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@@ -32,7 +32,8 @@
import rulesForm from "@/components/rulesForm/index.vue"; import rulesForm from "@/components/rulesForm/index.vue";
import { getQAListDetailsApi, getQAListEditUpApi, getQAListSaveApi } from "@/api/modules/QAList"; import { getQAListDetailsApi, getQAListEditUpApi, getQAListSaveApi } from "@/api/modules/QAList";
import { ref, reactive } from "vue"; import { ref, reactive } from "vue";
import WangEditor from "@/components/WangEditor/index.vue"; // import { convertSpanToDiv } from "@/utils/convertSpanToDiv";
import Editor from "@/components/Editor/index.vue";
import { useMsg } from "@/hooks/useMsg"; import { useMsg } from "@/hooks/useMsg";
import { EDIT_FORM_DATA, EDIT_RULE_FORM, RULES } from "./constant/index"; import { EDIT_FORM_DATA, EDIT_RULE_FORM, RULES } from "./constant/index";
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
@@ -46,10 +47,10 @@ const dataStore = reactive<any>({
rules: RULES rules: RULES
}); });
const formRef: any = ref(null); const formRef: any = ref(null);
const editorRef = ref<any>(null);
//详情 //详情
const getQAListDetails = async () => { const getQAListDetails = async () => {
let id = $route.query.id; let id = $route.query.id;
console.log(id, "=========id========");
if (!id) { if (!id) {
return; return;
} }
@@ -58,6 +59,11 @@ const getQAListDetails = async () => {
const { data } = result; const { data } = result;
//这里是传给基本信息组件的表单数据 //这里是传给基本信息组件的表单数据
dataStore.editRuleForm = cloneDeep(data); dataStore.editRuleForm = cloneDeep(data);
if (!data.answer) {
editorRef?.value?.clearEditor();
}
// dataStore.editRuleForm.answer = convertSpanToDiv(dataStore.editRuleForm.answer);
dataStore.value = data.answer; dataStore.value = data.answer;
} }
}; };
@@ -91,9 +97,11 @@ const handleReset = () => {
const resetFields = () => { const resetFields = () => {
if (!formRef.value!.ruleFormRef) return; if (!formRef.value!.ruleFormRef) return;
formRef!.value!.ruleFormRef.resetFields(); formRef!.value!.ruleFormRef.resetFields();
editorRef?.value?.clearEditor();
for (let key in dataStore.editRuleForm) { for (let key in dataStore.editRuleForm) {
dataStore.editRuleForm[key] = ""; dataStore.editRuleForm[key] = "";
} }
dataStore.editRuleForm.sort = 0;
// dataStore.value = ""; // dataStore.value = "";
}; };

View File

@@ -53,13 +53,19 @@ export const EDIT_FORM_DATA: FormItem[] = [
type: "treeSelect", type: "treeSelect",
label: "所属分类: ", label: "所属分类: ",
options: [] options: []
},
{
prop: "icon",
type: "upImg",
label: "图片: "
} }
]; ];
export const EDIT_RULE_FORM = { export const EDIT_RULE_FORM = {
is_show: 1, is_show: 1,
sort: 1, sort: 0,
name: "", name: "",
pid: 0 pid: 0,
icon: ""
}; };
// editRuleForm: {}, // editRuleForm: {},
//editFormData: [], //editFormData: [],

View File

@@ -6,13 +6,7 @@
</div> </div>
<div class="card table-main"> <div class="card table-main">
<SearchForm :search="search" :reset="reset" :formData="dataStore.formData" :search-param="dataStore.ruleForm" /> <SearchForm :search="search" :reset="reset" :formData="dataStore.formData" :search-param="dataStore.ruleForm" />
<el-table <el-table :data="dataStore.tableData" style="width: 100%; margin-bottom: 20px; font-size: 14px" row-key="id" border>
:data="dataStore.tableData"
style="width: 100%; margin-bottom: 20px; font-size: 14px"
row-key="id"
border
default-expand-all
>
<el-table-column prop="id" label="id" /> <el-table-column prop="id" label="id" />
<el-table-column prop="name" label="分类名称" /> <el-table-column prop="name" label="分类名称" />
<el-table-column prop="sort" label="分类排序"> <el-table-column prop="sort" label="分类排序">
@@ -24,9 +18,15 @@
</el-table-column> </el-table-column>
<el-table-column prop="is_show" label="是否显示"> <el-table-column prop="is_show" label="是否显示">
<template #default="{ row }"> <template #default="{ row }">
{{ row.is_show === 1 ? "✔️" : "❌" }} {{ row.is_show == 1 ? "✔️" : "❌" }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="icon" label="图片">
<template #default="{ row }">
<el-image :src="row.icon ? h + row.icon : ''" style="width: 60px; height: 60px" />
</template>
</el-table-column>
<!-- <template #default="scope"> --> <!-- <template #default="scope"> -->
<el-table-column label="操作" :width="240"> <el-table-column label="操作" :width="240">
<template #default="scope"> <template #default="scope">
@@ -76,7 +76,10 @@
<script setup lang="ts" name="articleClassListIndex"> <script setup lang="ts" name="articleClassListIndex">
import rulesForm from "@/components/rulesForm/index.vue"; import rulesForm from "@/components/rulesForm/index.vue";
import SearchForm from "@/components/SearchForm/index.vue";
import { integerRexg } from "@/utils/regexp/index"; import { integerRexg } from "@/utils/regexp/index";
import { addLabelValue } from "@/utils/addLabelValue";
import { h } from "@/utils/url";
// import ProTable from "@/components/ProTable/index.vue"; // import ProTable from "@/components/ProTable/index.vue";
import { messageBox } from "@/utils/messageBox"; import { messageBox } from "@/utils/messageBox";
import { useMsg } from "@/hooks/useMsg"; import { useMsg } from "@/hooks/useMsg";
@@ -138,21 +141,6 @@ const reset = () => {
getArticleClassList(); getArticleClassList();
}; };
const addLabelValue = (arr: any) => {
return arr.map((item: any) => {
// 为当前对象添加 label 和 value 属性
const newItem = { ...item };
newItem.label = newItem.name;
newItem.value = newItem.id;
// 如果有子对象,递归调用 addLabelValue 处理子对象
if (newItem.children && Array.isArray(newItem.children)) {
newItem.children = addLabelValue(newItem.children);
}
return newItem;
});
};
const handleSelectChangeEmits = (params: any) => { const handleSelectChangeEmits = (params: any) => {
const { id } = params; const { id } = params;
dataStore.editRuleForm.pid = id; dataStore.editRuleForm.pid = id;
@@ -187,7 +175,6 @@ const getArticleClassAddSave = async () => {
}; };
//文章编辑 //文章编辑
const getArticleClassUpEdit = async () => { const getArticleClassUpEdit = async () => {
console.log("编辑");
const result = await getArticleClassEditUpApi(dataStore.editRuleForm); const result = await getArticleClassEditUpApi(dataStore.editRuleForm);
const { msg, code } = result; const { msg, code } = result;

View File

@@ -29,7 +29,7 @@ export const EDIT_FORM_DATA: FormItem[] = [
{ {
prop: "category_id1", prop: "category_id1",
placeholder: "请选择", placeholder: "请选择",
type: "select", type: "treeSelect",
label: "文章分类: ", label: "文章分类: ",
options: [] options: []
}, },

View File

@@ -1,5 +1,6 @@
interface FormItem { interface FormItem {
prop: string; prop: string;
prop1?: string;
label?: string; label?: string;
placeholder?: string; placeholder?: string;
type: string; type: string;
@@ -25,9 +26,10 @@ export const FORM_DATA: FormItem[] = [
}, },
{ {
prop: "category_id", prop: "treeIds",
prop1: "category_id",
placeholder: "请选择", placeholder: "请选择",
type: "select", type: "treeSelect",
isArray: true, isArray: true,
label: "文章分类: ", label: "文章分类: ",
options: [] options: []

View File

@@ -49,12 +49,12 @@ export const COLUMNS = [
width: 160 width: 160
}, },
{ // {
align: "center", // align: "center",
label: "状态", // label: "状态",
prop: "enabled", // prop: "enabled",
width: 80 // width: 80
}, // },
{ prop: "operation", label: "操作", fixed: "right", width: 200 } { prop: "operation", label: "操作", fixed: "right", width: 200 }
]; ];

View File

@@ -20,7 +20,7 @@
<el-tab-pane label="详细内容" name="third"> <el-tab-pane label="详细内容" name="third">
<div style="width: 1280px; margin: 0 auto"> <div style="width: 1280px; margin: 0 auto">
<WangEditor v-model:value="dataStore.editRuleForm.content" /> <Editor v-model:content="dataStore.editRuleForm.content" ref="editorRef"></Editor>
</div> </div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@@ -30,25 +30,24 @@
<script setup lang="ts" name="articleEditIndex"> <script setup lang="ts" name="articleEditIndex">
import rulesForm from "@/components/rulesForm/index.vue"; import rulesForm from "@/components/rulesForm/index.vue";
import Editor from "@/components/Editor/index.vue";
import { import {
getArticleClassDataApi, getArticleClassDataApi,
getArticleListAddSaveApi, getArticleListAddSaveApi,
getArticleListDetailsApi, getArticleListDetailsApi,
getArticleListUpApi getArticleListUpApi
} from "@/api/modules/articleList"; } from "@/api/modules/articleList";
// /handleSubmit, import { recursiveCompare } from "@/utils/recursiveCompare";
import { EDIT_FORM_DATA, EDIT_RULE_FORM, RULES } from "./constant/index"; import { EDIT_FORM_DATA, EDIT_RULE_FORM, RULES } from "./constant/index";
import { useSearchInfoArray } from "@/hooks/useSearch";
import { ref, reactive } from "vue";
import WangEditor from "@/components/WangEditor/index.vue";
// import rulesForm from "@/components/rulesForm/index.vue";
import { ref, reactive } from "vue";
import { addLabelValue } from "@/utils/addLabelValue";
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
import { useMsg } from "@/hooks/useMsg"; import { useMsg } from "@/hooks/useMsg";
const formRef = ref<any>(null); const formRef = ref<any>(null);
const activeName = ref("basicInfo"); const activeName = ref("basicInfo");
const $route = useRoute(); const $route = useRoute();
const editorRef = ref<any>(null);
//数据集合 //数据集合
const dataStore = reactive<any>({ const dataStore = reactive<any>({
rules: cloneDeep(RULES), //抽屉表单验证 rules: cloneDeep(RULES), //抽屉表单验证
@@ -61,7 +60,7 @@ const getArticleClassData = async () => {
const result = await getArticleClassDataApi({ is_show: 1 }); const result = await getArticleClassDataApi({ is_show: 1 });
if (result?.code === 0) { if (result?.code === 0) {
const { data } = result; const { data } = result;
dataStore.editFormData[1].options = useSearchInfoArray(data); dataStore.editFormData[1].options = addLabelValue(data);
//如果有category_id就代表是从文章分类跳转过来的 //如果有category_id就代表是从文章分类跳转过来的
if ($route.query.category_id) { if ($route.query.category_id) {
dataStore.editRuleForm.category_id = Number($route.query.category_id); dataStore.editRuleForm.category_id = Number($route.query.category_id);
@@ -80,18 +79,17 @@ const getArticleListDetails = async () => {
if (result?.code === 0) { if (result?.code === 0) {
const { data } = result; const { data } = result;
dataStore.editRuleForm = data; dataStore.editRuleForm = data;
console.log(data); if (!data.content) {
editorRef?.value?.clearEditor(); // 调用子组件的清空方法
let is = dataStore.editFormData[1].options.some((item: any) => { }
console.log(item.id); // dataStore.editRuleForm.content = convertSpanToDiv(dataStore.editRuleForm.content);
console.log(dataStore.editRuleForm.category_id); let is = dataStore.editFormData[1].options.some((item: any) =>
return item.value == dataStore.editRuleForm.category_id; recursiveCompare(item, dataStore.editRuleForm.category_id)
}); );
dataStore.editRuleForm.category_id1 = is ? dataStore.editRuleForm.category_id : dataStore.editRuleForm.category_name; dataStore.editRuleForm.category_id1 = is ? dataStore.editRuleForm.category_id : dataStore.editRuleForm.category_name;
} }
}; };
const handleSelectChangeEmits = (value: any) => { const handleSelectChangeEmits = (value: any) => {
console.log(value, "=======value========");
if (value?.prop === "category_id1") { if (value?.prop === "category_id1") {
dataStore.editRuleForm.category_id = value.id; dataStore.editRuleForm.category_id = value.id;
} }
@@ -153,6 +151,13 @@ const handleConfirmClick = () => {
const handleReset = () => { const handleReset = () => {
if ($route.query.type === "add") { if ($route.query.type === "add") {
dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM); dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM);
editorRef?.value?.clearEditor(); // 调用子组件的清空方法
// if (!data.detail) {
// dataStore.detail = "";
// editorRef.value.clearEditor(); // 调用子组件的清空方法
// } else {
// dataStore.detail = cloneDeep(data.detail);
// }
} else { } else {
getArticleListDetails(); getArticleListDetails();
} }

View File

@@ -15,9 +15,9 @@
<template #image="scope"> <template #image="scope">
<el-image :src="scope.row.image ? h + scope.row.image : '--'" style="width: 60px; height: 60px" /> <el-image :src="scope.row.image ? h + scope.row.image : '--'" style="width: 60px; height: 60px" />
</template> </template>
<template #enabled="scope"> <!-- <template #enabled="scope">
<el-tag type="success" effect="dark">{{ scope.row.enabled === 1 ? "启用" : "禁用" }}</el-tag> <el-tag type="success" effect="dark">{{ scope.row.enabled == 1 ? "启用" : "禁用" }}</el-tag>
</template> </template> -->
<template #operation="scope"> <template #operation="scope">
<el-button size="small" type="primary" @click="handleBtnClick('edit', scope.row)">编辑</el-button> <el-button size="small" type="primary" @click="handleBtnClick('edit', scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleBtnClick('del', scope.row)">删除</el-button> <el-button size="small" type="danger" @click="handleBtnClick('del', scope.row)">删除</el-button>
@@ -38,11 +38,14 @@ import {
getArticleListExportApi, getArticleListExportApi,
getArticleListDelApi getArticleListDelApi
} from "@/api/modules/articleList"; } from "@/api/modules/articleList";
import { useSearchInfoArray } from "@/hooks/useSearch"; // import { useSearchInfoArray } from "@/hooks/useSearch";
//深拷贝方法 //深拷贝方法
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
//表格和搜索條件 //表格和搜索條件
import { RULE_FORM, FORM_DATA, COLUMNS } from "./constant/index"; import { RULE_FORM, FORM_DATA, COLUMNS } from "./constant/index";
import { addLabelValue } from "@/utils/addLabelValue";
//图片地址 //图片地址
import { h } from "@/utils/url"; import { h } from "@/utils/url";
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) // 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
@@ -63,7 +66,8 @@ const getArticleClassData = async () => {
const result = await getArticleClassDataApi(); const result = await getArticleClassDataApi();
if (result?.code === 0) { if (result?.code === 0) {
const { data } = result; const { data } = result;
dataStore.formData[1].options = useSearchInfoArray(data); console.log(data, "============>>>>>");
dataStore.formData[1].options = addLabelValue(data);
} }
}; };
getArticleClassData(); getArticleClassData();

View File

@@ -27,7 +27,7 @@ export const FORM_DATA: FormItem[] = [
{ {
prop: "category_id", prop: "category_id",
placeholder: "请选择", placeholder: "请选择",
type: "select", type: "treeSelect",
isArray: true, isArray: true,
label: "文章分类: ", label: "文章分类: ",
options: [] options: []

View File

@@ -27,7 +27,7 @@
import ProTable from "@/components/ProTable/index.vue"; import ProTable from "@/components/ProTable/index.vue";
import { messageBox } from "@/utils/messageBox"; import { messageBox } from "@/utils/messageBox";
import { useMsg } from "@/hooks/useMsg"; import { useMsg } from "@/hooks/useMsg";
import { useSearchInfoArray } from "@/hooks/useSearch"; import { addLabelValue } from "@/utils/addLabelValue";
import { h } from "@/utils/url"; import { h } from "@/utils/url";
//列表接口 //列表接口
import { getArticleTrashListApi, getArticleTrashDelApi, getArticleTrashRestoreApi } from "@/api/modules/articleRecycle"; import { getArticleTrashListApi, getArticleTrashDelApi, getArticleTrashRestoreApi } from "@/api/modules/articleRecycle";
@@ -66,12 +66,13 @@ const getArticleTrashDel = (id: any) => {
} }
}); });
}; };
//文章分类(搜索条件) //文章分类(搜索条件)
const getArticleClassData = async () => { const getArticleClassData = async () => {
const result = await getArticleClassDataApi(); const result = await getArticleClassDataApi();
if (result?.code === 0) { if (result?.code === 0) {
const { data } = result; const { data } = result;
dataStore.formData[1].options = useSearchInfoArray(data); dataStore.formData[1].options = addLabelValue(data);
} }
}; };
getArticleClassData(); getArticleClassData();

View File

@@ -27,6 +27,12 @@ export const EDIT_FORM_DATA: FormItem[] = [
type: "input", type: "input",
label: "Banner名称: " label: "Banner名称: "
}, },
{
prop: "short_title",
placeholder: "请输入",
type: "input",
label: "Banner简称: "
},
{ {
prop: "title_txt_color", prop: "title_txt_color",
placeholder: "填写RGB值", placeholder: "填写RGB值",
@@ -70,14 +76,26 @@ export const EDIT_FORM_DATA: FormItem[] = [
type: "upImg", type: "upImg",
label: "Banner图片: " label: "Banner图片: "
}, },
{
prop: "extra_image",
type: "upImg",
label: "悬浮图: "
},
{ {
prop: "banner_id", prop: "banner_id",
placeholder: "请选择", placeholder: "请选择",
type: "select", type: "treeSelect",
label: "Banner分类: ", label: "Banner分类: ",
options: [] options: []
}, },
{
prop: "rel_prod_cate_id",
placeholder: "请选择",
type: "treeSelect",
label: "相关分类: ",
options: []
},
{ {
prop: "sort", prop: "sort",
placeholder: "请输入", placeholder: "请输入",
@@ -115,6 +133,12 @@ export const EDIT_FORM_DATA1: FormItem[] = [
type: "input", type: "input",
label: "Banner名称: " label: "Banner名称: "
}, },
{
prop: "short_title",
placeholder: "请输入",
type: "input",
label: "Banner简称: "
},
{ {
prop: "title_txt_color", prop: "title_txt_color",
placeholder: "填写RGB值", placeholder: "填写RGB值",
@@ -149,7 +173,7 @@ export const EDIT_FORM_DATA1: FormItem[] = [
}, },
{ {
label: "否", label: "否",
value: 0 value: -1
} }
] ]
}, },
@@ -158,11 +182,15 @@ export const EDIT_FORM_DATA1: FormItem[] = [
type: "upImg", type: "upImg",
label: "Banner图片: " label: "Banner图片: "
}, },
// {
// prop: "image1",
// type: "upImg",
// label: "Banner图片1: "
// },
{ {
prop: "banner_id", prop: "banner_id",
placeholder: "请选择", placeholder: "请选择",
type: "select", type: "treeSelect",
label: "Banner分类: ", label: "Banner分类: ",
options: [] options: []
}, },
@@ -203,7 +231,8 @@ export const EDIT_FORM_DATA1: FormItem[] = [
export const EDIT_RULE_FORM = { export const EDIT_RULE_FORM = {
type: "image", type: "image",
sort: 1 sort: 1,
status: 1
}; };
// editRuleForm: {}, // editRuleForm: {},
//editFormData: [], //editFormData: [],

View File

@@ -1,5 +1,6 @@
interface FormItem { interface FormItem {
prop: string; prop: string;
prop1?: string;
label?: string; label?: string;
placeholder?: string; placeholder?: string;
type: string; type: string;
@@ -25,9 +26,10 @@ export const FORM_DATA: FormItem[] = [
}, },
{ {
prop: "banner_id", prop: "treeIds",
prop1: "banner_id",
placeholder: "请选择", placeholder: "请选择",
type: "select", type: "treeSelect",
isArray: true, isArray: true,
label: "Banner分类: ", label: "Banner分类: ",
options: [] options: []

View File

@@ -18,8 +18,8 @@
<el-input v-model="scope.row.sort" @blur="handleBlur(scope.row)" @input="handleInput(scope.row)"></el-input> <el-input v-model="scope.row.sort" @blur="handleBlur(scope.row)" @input="handleInput(scope.row)"></el-input>
</template> </template>
<template #status="scope"> <template #status="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'" effect="dark">{{ <el-tag :type="scope.row.status == 1 ? 'success' : 'danger'" effect="dark">{{
scope.row.status === 1 ? "启用" : "禁用" scope.row.status == 1 ? "启用" : "禁用"
}}</el-tag> }}</el-tag>
</template> </template>
<template #operation="scope"> <template #operation="scope">
@@ -84,6 +84,8 @@ import { messageBox } from "@/utils/messageBox";
import { useMsg } from "@/hooks/useMsg"; import { useMsg } from "@/hooks/useMsg";
import { useExport } from "@/hooks/useExport"; import { useExport } from "@/hooks/useExport";
import { integerRexg } from "@/utils/regexp/index"; import { integerRexg } from "@/utils/regexp/index";
import { getProductCategoryListApi } from "@/api/modules/productList";
// import { addLabelValue } from "@/utils/addLabelValue";
// 图片地址 // 图片地址
import { h } from "@/utils/url"; import { h } from "@/utils/url";
// 列表接口 // 列表接口
@@ -97,7 +99,8 @@ import {
getBannerListExportApi, getBannerListExportApi,
getBannerClassListApi getBannerClassListApi
} from "@/api/modules/banner"; } from "@/api/modules/banner";
// import { getBannerClassListApi } from "@/api/modules/bannerClass"; import { addLabelValue } from "@/utils/addLabelValue";
import { getSystemUrlsApi } from "@/api/modules/home"; import { getSystemUrlsApi } from "@/api/modules/home";
// 深拷贝方法 // 深拷贝方法
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
@@ -118,10 +121,11 @@ const dataStore = reactive<any>({
formData: FORM_DATA, // 搜索配置项 dataStore.formData formData: FORM_DATA, // 搜索配置项 dataStore.formData
visible: false, visible: false,
data: [], data: [],
type: "image",
isFirstRequest: true isFirstRequest: true
}); });
const selectedNodes = ref(null); const selectedNodes = ref<any>(null);
const treeRef = ref(null); const treeRef = ref(null);
@@ -132,6 +136,16 @@ const treeProps = {
value: "value" value: "value"
}; };
//产品分类(后端大佬说直接掉列表接口)
const getProductCategoryList = async () => {
const result = await getProductCategoryListApi();
if (result?.code === 0) {
let dataClone: any = cloneDeep(result?.data);
console.log(dataClone, "=dataClone=");
dataStore.editFormData[7].options = addLabelValue(dataClone);
}
};
const buildTree = (data: any, outerLinkTo: any = "") => { const buildTree = (data: any, outerLinkTo: any = "") => {
return data.map((item: any) => { return data.map((item: any) => {
const { name, id, url, data: childData = [], children: nestedChildren = [] } = item; const { name, id, url, data: childData = [], children: nestedChildren = [] } = item;
@@ -152,24 +166,19 @@ const buildTree = (data: any, outerLinkTo: any = "") => {
}; };
// let isFirstRequest = true; // let isFirstRequest = true;
const handleRadioGroupEmits = (value: any) => { const handleRadioGroupEmits = (value: any) => {
// if (value !== "video" || value !== "image") { dataStore.type = value;
// return;
// }
if (value === "video") { if (value === "video") {
dataStore.editFormData = EDIT_FORM_DATA1; dataStore.editFormData = EDIT_FORM_DATA1;
dataStore.rules = RULES1; dataStore.rules = RULES1;
getBannerClassEditList(); getBannerClassEditList();
} }
// getBannerClassList();
if (value === "image") { if (value === "image") {
dataStore.isFirstRequest = true; dataStore.isFirstRequest = true;
dataStore.editFormData = EDIT_FORM_DATA; dataStore.editFormData = EDIT_FORM_DATA;
dataStore.rules = RULES; dataStore.rules = RULES;
getBannerClassEditList(); getBannerClassEditList();
} }
// console.log(value, "==========value==========");
// getBannerClassEditList();
}; };
const getSystemUrls = async (node: any, resolve: any) => { const getSystemUrls = async (node: any, resolve: any) => {
@@ -183,10 +192,10 @@ const getSystemUrls = async (node: any, resolve: any) => {
} }
} else { } else {
//第二次请求 //第二次请求
if (node.data.children) { if (node?.data?.children) {
resolve(node.data.children); resolve(node?.data?.children);
} }
if (!node.data.children.length && !node.data.url && node.level > 1) { if (!node?.data?.children?.length && !node?.data?.url && node?.level > 1) {
const [link_to, id] = node?.data?.value?.split("/"); const [link_to, id] = node?.data?.value?.split("/");
const result = await getSystemUrlsApi({ link_to, id }); const result = await getSystemUrlsApi({ link_to, id });
if (result?.code === 0) { if (result?.code === 0) {
@@ -198,43 +207,55 @@ const getSystemUrls = async (node: any, resolve: any) => {
} }
} }
}; };
const setImgOrVideo = () => { const setImgOrVideo = (result: any) => {
if (dataStore.editRuleForm.type === "image") { dataStore.type = result?.data?.type;
dataStore.editFormData = EDIT_FORM_DATA; if (result?.data?.type === "image") {
} else { dataStore.editFormData = cloneDeep(EDIT_FORM_DATA);
dataStore.editFormData = EDIT_FORM_DATA1;
} }
if (result?.data?.type === "video") {
dataStore.editFormData = cloneDeep(EDIT_FORM_DATA1);
}
getBannerClassEditList(); getBannerClassEditList();
}; };
// 详情 // 详情
const getBannerRead = async (id: any) => { const getBannerRead = async (id: any) => {
selectedNodes.value = "";
dataStore.data = [];
dataStore.title = "编辑Banner"; dataStore.title = "编辑Banner";
dataStore.visible = true; dataStore.visible = true;
const result = await getBannerReadApi(id); const result = await getBannerReadApi(id);
if (result?.code === 0) { if (result?.code === 0) {
dataStore.editRuleForm = result?.data; setImgOrVideo(result);
setImgOrVideo(); getProductCategoryList();
if (dataStore.editRuleForm.link && dataStore.editRuleForm.link_to) { if (result?.data?.type === "image" && !result?.data?.desc) {
let { id, name, link } = dataStore.editRuleForm.link_echo_data; formRef?.value?.editorRef[0]?.clearEditor();
if (!id || !name || !link) {
return;
}
let obj: any = {
label: name, // 确保这里的name是你想要显示的文本
value: `${dataStore.editRuleForm.link_to}` + "/" + `${id}` + "/" + `${name}`,
url: link,
link_to: dataStore.editRuleForm.link_to,
children: []
};
let data: any = [];
data.push(obj);
selectedNodes.value = obj.value;
dataStore.data = data;
} }
nextTick(() => {
dataStore.editRuleForm = result?.data;
if (dataStore.editRuleForm.link && dataStore.editRuleForm.link_to) {
let { id, name, link } = dataStore.editRuleForm.link_echo_data;
if (!id || !name || !link) {
return;
}
let obj: any = {
label: name, // 确保这里的name是你想要显示的文本
value: `${dataStore.editRuleForm.link_to}` + "/" + `${id}` + "/" + `${name}`,
url: link,
link_to: dataStore.editRuleForm.link_to,
children: []
};
let data: any = [];
data.push(obj);
selectedNodes.value = obj.value;
dataStore.data = data;
}
});
} }
}; };
const handleCheck = (checkedNodes: any, values: any) => { const handleCheck = (checkedNodes: any, values: any) => {
const { checkedKeys } = values; const { checkedKeys } = values;
console.log(checkedKeys, "=checkedKeys=");
if (checkedKeys.length) { if (checkedKeys.length) {
dataStore.editRuleForm.link = checkedNodes.url; dataStore.editRuleForm.link = checkedNodes.url;
dataStore.editRuleForm.link_to = checkedNodes.link_to; dataStore.editRuleForm.link_to = checkedNodes.link_to;
@@ -260,34 +281,22 @@ const getBannerUp = async () => {
formRef!.value!.ruleFormRef.resetFields(); formRef!.value!.ruleFormRef.resetFields();
proTableRef?.value?.getTableList(); proTableRef?.value?.getTableList();
dataStore.isFirstRequest = true; dataStore.isFirstRequest = true;
useMsg("success", result?.msg);
} }
}; };
// 分类
const getBannerClassList = async () => {
const result = await getBannerClassListApi();
if (result?.code === 0) {
let arr: any = [];
result?.data?.forEach((item: any) => {
arr.push({ value: item.id, label: item.name });
});
dataStore.formData[1].options = arr;
// dataStore.editFormData[4].options = arr;
}
};
getBannerClassList();
//详情里的分类 //详情里的分类
const getBannerClassEditList = async () => { const getBannerClassEditList = async () => {
const result = await getBannerClassListApi(); const result = await getBannerClassListApi();
if (result?.code === 0) { if (result?.code === 0) {
let arr: any = []; dataStore.formData[1].options = addLabelValue(result?.data);
result?.data?.forEach((item: any) => { dataStore.editFormData.forEach((item: any) => {
arr.push({ value: item.id, label: item.name }); if (item.prop === "banner_id") {
item.options = addLabelValue(result?.data);
}
}); });
dataStore.editFormData[5].options = arr;
} }
}; };
getBannerClassEditList();
// 新增 // 新增
const getBannerListSave = async () => { const getBannerListSave = async () => {
const result = await getBannerListSaveApi(dataStore.editRuleForm); const result = await getBannerListSaveApi(dataStore.editRuleForm);
@@ -329,6 +338,9 @@ const handleConfirmClick = () => {
const resetFields = () => { const resetFields = () => {
if (!formRef.value!.ruleFormRef) return; if (!formRef.value!.ruleFormRef) return;
formRef!.value!.ruleFormRef.resetFields(); formRef!.value!.ruleFormRef.resetFields();
if (dataStore.type === "image") {
formRef?.value?.editorRef[0]?.clearEditor();
}
}; };
// 抽屉重置 // 抽屉重置
const handleResetClick = () => { const handleResetClick = () => {
@@ -342,11 +354,19 @@ const handleResetClick = () => {
const handleAdd = () => { const handleAdd = () => {
dataStore.title = "添加Banner"; dataStore.title = "添加Banner";
dataStore.visible = true; dataStore.visible = true;
selectedNodes.value = "";
dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM);
dataStore.editFormData = cloneDeep(EDIT_FORM_DATA); // 抽屉表单配置项
dataStore.rules = cloneDeep(RULES);
getBannerClassEditList(); getBannerClassEditList();
// getBannerClassList();
getProductCategoryList();
}; };
// 抽屉关闭前的钩子 // 抽屉关闭前的钩子
const handleBeforeClone = () => { const handleBeforeClone = () => {
dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM); dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM);
dataStore.editFormData = cloneDeep(EDIT_FORM_DATA); // 抽屉表单配置项
dataStore.rules = cloneDeep(RULES);
resetFields(); resetFields();
dataStore.visible = false; dataStore.visible = false;
dataStore.isFirstRequest = true; dataStore.isFirstRequest = true;

View File

@@ -7,13 +7,7 @@
<div class="card table-main"> <div class="card table-main">
<SearchForm :search="search" :reset="reset" :formData="dataStore.formData" :search-param="dataStore.ruleForm" /> <SearchForm :search="search" :reset="reset" :formData="dataStore.formData" :search-param="dataStore.ruleForm" />
<el-table <el-table :data="dataStore.tableData" style="width: 100%; margin-bottom: 20px; font-size: 14px" row-key="id" border>
:data="dataStore.tableData"
style="width: 100%; margin-bottom: 20px; font-size: 14px"
row-key="id"
border
default-expand-all
>
<el-table-column prop="id" label="id" /> <el-table-column prop="id" label="id" />
<el-table-column prop="name" label="下载分类名称" /> <el-table-column prop="name" label="下载分类名称" />
<el-table-column prop="sort" label="下载分类排序"> <el-table-column prop="sort" label="下载分类排序">
@@ -25,7 +19,7 @@
</el-table-column> </el-table-column>
<el-table-column prop="is_show" label="是否显示"> <el-table-column prop="is_show" label="是否显示">
<template #default="{ row }"> <template #default="{ row }">
{{ row.is_show === 1 ? "✔️" : "❌" }} {{ row.is_show == 1 ? "✔️" : "❌" }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" :width="350"> <el-table-column label="操作" :width="350">
@@ -88,6 +82,8 @@ import {
} from "@/api/modules/downloadClass"; } from "@/api/modules/downloadClass";
//深拷贝方法 //深拷贝方法
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
import { addLabelValue } from "@/utils/addLabelValue";
//表格和搜索條件 //表格和搜索條件
import { RULE_FORM, FORM_DATA, COLUMNS, EDIT_FORM_DATA, EDIT_RULE_FORM, RULES } from "./constant/index"; import { RULE_FORM, FORM_DATA, COLUMNS, EDIT_FORM_DATA, EDIT_RULE_FORM, RULES } from "./constant/index";
@@ -130,20 +126,6 @@ const handleSelectChangeEmits = (params: any) => {
dataStore.editRuleForm.pid = id; dataStore.editRuleForm.pid = id;
}; };
const addLabelValue = (arr: any) => {
return arr.map((item: any) => {
// 为当前对象添加 label 和 value 属性
const newItem = { ...item };
newItem.label = newItem.name;
newItem.value = newItem.id;
// 如果有子对象,递归调用 addLabelValue 处理子对象
if (newItem.children && Array.isArray(newItem.children)) {
newItem.children = addLabelValue(newItem.children);
}
return newItem;
});
};
//分类下拉 //分类下拉
const getCategorys = async () => { const getCategorys = async () => {
const result = await getCategorysApi(); const result = await getCategorysApi();

View File

@@ -1,5 +1,6 @@
interface FormItem { interface FormItem {
prop: string; prop: string;
prop1?: string;
label?: string; label?: string;
placeholder?: string; placeholder?: string;
type: string; type: string;
@@ -25,7 +26,8 @@ export const FORM_DATA: FormItem[] = [
}, },
{ {
prop: "category_id", prop: "treeIds",
prop1: "category_id",
placeholder: "请选择", placeholder: "请选择",
type: "treeSelect", type: "treeSelect",
isArray: true, isArray: true,

View File

@@ -59,6 +59,9 @@ import { useMsg } from "@/hooks/useMsg";
import { Delete } from "@element-plus/icons-vue"; import { Delete } from "@element-plus/icons-vue";
const $route = useRoute(); const $route = useRoute();
import { getCategorysApi } from "@/api/modules/downloadClass"; import { getCategorysApi } from "@/api/modules/downloadClass";
import { recursiveCompare } from "@/utils/recursiveCompare";
import { addLabelValue } from "@/utils/addLabelValue";
import { ElNotification } from "element-plus";
//深拷贝方法 //深拷贝方法
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
import { import {
@@ -153,7 +156,6 @@ const excelUploadSuccess = (response: any, row: any) => {
if (response?.code === 0) { if (response?.code === 0) {
row.file_path = response.data.path; // 假设后端返回的文件路径在 data.path 中 row.file_path = response.data.path; // 假设后端返回的文件路径在 data.path 中
} }
console.log(row, "====row========");
ElNotification({ ElNotification({
title: "温馨提示", title: "温馨提示",
message: `文件上传成功!`, message: `文件上传成功!`,
@@ -219,6 +221,7 @@ const handleEditAdd = () => {
file_ext: "" //文件格式 file_ext: "" //文件格式
}); });
}; };
//详情 //详情
const getAttachmentRead = async () => { const getAttachmentRead = async () => {
let id = $route.query.id; let id = $route.query.id;
@@ -229,12 +232,16 @@ const getAttachmentRead = async () => {
if (result?.code === 0) { if (result?.code === 0) {
dataStore.editRuleForm = result?.data; dataStore.editRuleForm = result?.data;
dataStore.editTableData = result?.data.attach; dataStore.editTableData = result?.data.attach;
//recursiveCompare
let is = dataStore.editFormData[1].options.some((item: any) => { let is = dataStore.editFormData[1].options.some((item: any) =>
console.log(item.id); recursiveCompare(item, dataStore.editRuleForm.category_id)
console.log(dataStore.editRuleForm.category_id); );
return item.value == dataStore.editRuleForm.category_id; console.log(is, "========is==========");
}); // let is = dataStore.editFormData[1].options.some((item: any) => {
// console.log(item.id);
// console.log(dataStore.editRuleForm.category_id);
// return item.value == dataStore.editRuleForm.category_id;
// });
dataStore.editRuleForm.category_id1 = is ? dataStore.editRuleForm.category_id : dataStore.editRuleForm.category_name; dataStore.editRuleForm.category_id1 = is ? dataStore.editRuleForm.category_id : dataStore.editRuleForm.category_name;
} }
}; };
@@ -245,21 +252,6 @@ const handleSelectChangeEmits = (value: any) => {
} }
}; };
const addLabelValue = (arr: any) => {
return arr.map((item: any) => {
// 为当前对象添加 label 和 value 属性
const newItem = { ...item };
newItem.label = newItem.name;
newItem.value = newItem.id;
// 如果有子对象,递归调用 addLabelValue 处理子对象
if (newItem.children && Array.isArray(newItem.children)) {
newItem.children = addLabelValue(newItem.children);
}
return newItem;
});
};
//分类 //分类
const getCategorys = async () => { const getCategorys = async () => {
const result = await getCategorysApi({ is_show: 1 }); const result = await getCategorysApi({ is_show: 1 });

View File

@@ -15,8 +15,8 @@
<el-image :src="scope.row.image ? h + scope.row.image : ''" style="width: 60px; height: 60px" /> <el-image :src="scope.row.image ? h + scope.row.image : ''" style="width: 60px; height: 60px" />
</template> </template>
<template #status="scope"> <template #status="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'" effect="dark">{{ <el-tag :type="scope.row.status == 1 ? 'success' : 'danger'" effect="dark">{{
scope.row.status === 1 ? "启用" : "禁用" scope.row.status == 1 ? "启用" : "禁用"
}}</el-tag> }}</el-tag>
</template> </template>
<template #sort="scope"> <template #sort="scope">
@@ -40,6 +40,8 @@ import { getCategorysApi } from "@/api/modules/downloadClass";
import { integerRexg } from "@/utils/regexp/index"; import { integerRexg } from "@/utils/regexp/index";
import { messageBox } from "@/utils/messageBox"; import { messageBox } from "@/utils/messageBox";
import { useMsg } from "@/hooks/useMsg"; import { useMsg } from "@/hooks/useMsg";
import { addLabelValue } from "@/utils/addLabelValue";
//深拷贝方法 //深拷贝方法
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
//表格和搜索條件 FORM_DATA //表格和搜索條件 FORM_DATA
@@ -151,21 +153,6 @@ const handleInput = (row: any) => {
row.sort = integerRexg(row.sort); row.sort = integerRexg(row.sort);
}; };
const addLabelValue = (arr: any) => {
return arr.map((item: any) => {
// 为当前对象添加 label 和 value 属性
const newItem = { ...item };
newItem.label = newItem.name;
newItem.value = newItem.id;
// 如果有子对象,递归调用 addLabelValue 处理子对象
if (newItem.children && Array.isArray(newItem.children)) {
newItem.children = addLabelValue(newItem.children);
}
return newItem;
});
};
const getCategorys = async () => { const getCategorys = async () => {
const result = await getCategorysApi(); const result = await getCategorysApi();

View File

@@ -36,25 +36,25 @@
autocomplete="off" autocomplete="off"
/> />
<!-- <div <!-- <div
style=" style="
width: 350px; width: 350px;
font-size: 12px; font-size: 12px;
line-height: 1; line-height: 1;
color: #4178d5; color: #4178d5;
text-align: right; text-align: right;
cursor: pointer; cursor: pointer;
" "
@click="handleTabClick(2)" @click="handleTabClick(2)"
> >
忘记密码? 忘记密码?
</div> --> </div> -->
</el-form-item> </el-form-item>
<!-- <el-form-item prop="captcha"> <!-- <el-form-item prop="captcha">
<span style="margin-right: 10px; color: #606266">验证码</span> <span style="margin-right: 10px; color: #606266">验证码</span>
<el-input v-model="dataStore.ruleForm.captcha" style="width: 80px" autocomplete="off" /> <el-input v-model="dataStore.ruleForm.captcha" style="width: 80px" autocomplete="off" />
<img :src="dataStore.base64" style="width: 150px; height: 60px; margin: 0 6px" /> <img :src="dataStore.base64" style="width: 150px; height: 60px; margin: 0 6px" />
<el-button type="primary" v-debounce="getLoginCodeImg">刷新</el-button> <el-button type="primary" v-debounce="getLoginCodeImg">刷新</el-button>
</el-form-item> --> </el-form-item> -->
<el-form-item> <el-form-item>
<el-button <el-button
size="large" size="large"

291
src/views/login/index2.vue Normal file
View File

@@ -0,0 +1,291 @@
<template>
<div class="main">
<div class="container">
<div class="left">
<div class="left-logo-box"></div>
</div>
<!-- 登录 -->
<div class="right" v-if="dataStore.type === 1">
<div class="right-logo-box">
<div>
<div class="right-logo-font">登录</div>
<div class="sign-in">Sign in</div>
</div>
</div>
<div style="margin-left: 100px">
<el-form
ref="ruleFormRef"
style="width: 300px"
:model="dataStore.ruleForm"
:rules="dataStore.rules"
label-width="auto"
label-position="top"
class="demo-ruleForm"
hide-required-asterisk
>
<el-form-item label="账号" prop="username">
<el-input v-model="dataStore.ruleForm.username" :prefix-icon="Iphone" autocomplete="off" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="dataStore.ruleForm.password"
type="password"
show-password
:prefix-icon="Lock"
autocomplete="off"
/>
<!-- <div
style="
width: 350px;
font-size: 12px;
line-height: 1;
color: #4178d5;
text-align: right;
cursor: pointer;
"
@click="handleTabClick(2)"
>
忘记密码?
</div> -->
</el-form-item>
<!-- <el-form-item prop="captcha">
<span style="margin-right: 10px; color: #606266">验证码</span>
<el-input v-model="dataStore.ruleForm.captcha" style="width: 80px" autocomplete="off" />
<img :src="dataStore.base64" style="width: 150px; height: 60px; margin: 0 6px" />
<el-button type="primary" v-debounce="getLoginCodeImg">刷新</el-button>
</el-form-item> -->
<el-form-item>
<el-button
size="large"
type="primary"
style="width: 100px; height: 36px; border-radius: 2px; box-shadow: 2px 5px 16px #4178d5"
v-debounce="submitForm"
>登录</el-button
>
</el-form-item>
</el-form>
</div>
</div>
<!--忘记密码 -->
<div class="right" v-if="dataStore.type === 2">
<div style="margin-left: 100px">
<el-button
type="primary"
:icon="ArrowLeftBold"
circle
style="margin: 60px 0 20px"
@click="handleTabClick(1)"
/>
<el-form
ref="resetRuleFormRef"
:model="dataStore.resetRuleForm"
:rules="dataStore.resetRules"
label-width="auto"
label-position="top"
class="demo-ruleForm"
hide-required-asterisk
>
<el-form-item label="手机号" prop="iphone">
<el-input v-model="dataStore.resetRuleForm.iphone" :prefix-icon="Iphone" autocomplete="off" />
</el-form-item>
<el-form-item prop="code" label="验证码">
<el-input v-model="dataStore.resetRuleForm.code" autocomplete="off">
<template #append>
<div style="font-size: 14px; color: #4178d5; cursor: pointer" v-debounce="handleGetCode">
{{ dataStore.codeFont }}
</div>
<div v-if="dataStore.isShowTime" style="margin: 0 5px; font-size: 14px; color: #4178d5">
{{ dataStore.timeCount }}s
</div>
</template>
</el-input>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input
v-model="dataStore.resetRuleForm.newPassword"
type="password"
show-password
:prefix-icon="Lock"
autocomplete="off"
/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input
v-model="dataStore.resetRuleForm.confirmPassword"
type="password"
show-password
:prefix-icon="Lock"
autocomplete="off"
/>
</el-form-item>
<el-form-item>
<el-button
size="large"
type="primary"
style="width: 100px; height: 36px; border-radius: 2px; box-shadow: 2px 5px 16px #4178d5"
v-debounce="resetConfirmForm"
>提交</el-button
>
</el-form-item>
</el-form>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import CryptoJS from "crypto-js";
//loginCodeImgApi
import { loginApi } from "@/api/modules/login";
import { Iphone, Lock, ArrowLeftBold } from "@element-plus/icons-vue";
import { RULES, RULE_FORM, RESET_RULES, RESET_RULE_FORM } from "./constant/index";
import { cloneDeep } from "lodash-es";
//用户信息存储
import { useUserStore } from "@/stores/modules/user";
const userStore = useUserStore();
const $router = useRouter();
//登录表单Ref
const ruleFormRef: any = ref(null);
//修改密码表单Ref
const resetRuleFormRef: any = ref(null);
//数据源
const dataStore = reactive<any>({
timeCount: 120, //修改密码里验证码倒计时
isShowTime: false,
base64: "", //验证码图片
type: 1, //1:登录,2:修改密码
codeFont: "获取验证码", //修改密码里的验证码按钮文字
ruleForm: cloneDeep(RULE_FORM), //登录表单数据
rules: cloneDeep(RULES), //登录表单验证
resetRuleForm: cloneDeep(RESET_RULE_FORM), //修改密码表单数据
resetRules: cloneDeep(RESET_RULES) //修改密码表单验证
});
//计时器
let intervalId: any = null;
//登录
const submitForm = () => {
console.log(ruleFormRef);
if (!ruleFormRef) return;
ruleFormRef?.value?.validate((valid: any) => {
if (valid) {
login();
} else {
console.log("error submit!");
}
});
};
//忘记密码提交事件
const resetConfirmForm = () => {
if (!resetRuleFormRef) return;
resetRuleFormRef?.value?.validate((valid: any) => {
if (valid) {
let password = CryptoJS.MD5(dataStore.ruleForm.password)?.toString();
console.log(password, "==== dataStore.ruleForm.password====");
console.log("submit!");
} else {
console.log("error submit!");
}
});
};
//登录接口
const login = async () => {
const result: any = await loginApi({
...dataStore.ruleForm,
password: CryptoJS?.MD5(dataStore.ruleForm.password)?.toString()
});
if (result?.code === 0) {
const { data } = result;
setUserData(data);
}
// else {
// getLoginCodeImg();
// }
};
//获取验证码图片接口
// const getLoginCodeImg = async () => {
// const result: any = await loginCodeImgApi();
// if (result?.code === 0) {
// const { data } = result;
// const { captcha, token } = data;
// dataStore.base64 = captcha;
// dataStore.ruleForm.token = token;
// }
// };
// getLoginCodeImg();
//登录和密码重置切换
const handleTabClick = (type: any) => {
dataStore.type = type;
for (let key in dataStore.ruleForm) {
dataStore.ruleForm[key] = "";
}
for (let key in dataStore.resetRuleForm) {
dataStore.resetRuleForm[key] = "";
}
if (intervalId) {
handleClearInterval();
dataStore.timeCount = 120;
dataStore.isShowTime = false;
}
};
//120秒验证时间
const updateCountdown = () => {
intervalId = setInterval(() => {
if (dataStore.timeCount > 0) {
dataStore.isShowTime = true;
dataStore.timeCount--;
} else {
handleClearInterval();
dataStore.timeCount = 120;
dataStore.isShowTime = false;
}
}, 1000);
};
//获取验证码
const handleGetCode = () => {
//如果计时器已经启动了,就不要再去触发了
if (intervalId) {
return;
}
resetRuleFormRef.value.validateField("iphone", (valid: any) => {
if (valid) {
updateCountdown();
} else {
}
});
};
//清理定时器
const handleClearInterval = () => {
clearInterval(intervalId);
intervalId = null;
};
// 设置用户数据
const setUserData = (data: any) => {
const { username, uid, token, avatar } = data;
console.log();
// 设置token
userStore.setToken("Bearer" + " " + token);
userStore.setUid(uid);
userStore.setNickname(username);
userStore.setAvatar(avatar);
// await userStore.getAuthMenuList();
//跳转到首页
setTimeout(() => {
$router.push("/admin/index");
}, 500);
};
onUnmounted(() => {
//页面卸载的时候清空定时器
if (intervalId) {
handleClearInterval();
}
});
</script>
<style lang="scss">
@import "./index.scss";
</style>

View File

@@ -8,13 +8,8 @@
:formData="dataStore.searchFormData" :formData="dataStore.searchFormData"
:search-param="dataStore.searchParam" :search-param="dataStore.searchParam"
/> />
<el-table <!-- default-expand-all -->
:data="dataStore.tableData" <el-table :data="dataStore.tableData" style="width: 100%; margin-bottom: 20px; font-size: 14px" row-key="id" border>
style="width: 100%; margin-bottom: 20px; font-size: 14px"
row-key="id"
border
default-expand-all
>
<el-table-column prop="id" label="id" /> <el-table-column prop="id" label="id" />
<el-table-column prop="name" label="分类名称" /> <el-table-column prop="name" label="分类名称" />
<el-table-column prop="sort" label="分类排序"> <el-table-column prop="sort" label="分类排序">
@@ -26,7 +21,7 @@
</el-table-column> </el-table-column>
<el-table-column prop="is_show" label="是否显示"> <el-table-column prop="is_show" label="是否显示">
<template #default="{ row }"> <template #default="{ row }">
{{ row.is_show === 1 ? "✔️" : "❌" }} {{ row.is_show == 1 ? "✔️" : "❌" }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" :width="350"> <el-table-column label="操作" :width="350">
@@ -100,7 +95,7 @@ import {
getArticleCategorySortApi, getArticleCategorySortApi,
getProductClassTcoTreeApi getProductClassTcoTreeApi
} from "@/api/modules/productClass"; } from "@/api/modules/productClass";
import { addLabelValue } from "../list/utils/common/addLabelValue"; import { addLabelValue } from "@/utils/addLabelValue";
import { useMsg } from "@/hooks/useMsg"; import { useMsg } from "@/hooks/useMsg";
@@ -154,7 +149,6 @@ const getArticleCategorySave = async (params: any) => {
}; };
const handleSelectChangeEmits = (value: any) => { const handleSelectChangeEmits = (value: any) => {
console.log(value, "===============value1111111============");
if (value.prop === "pid") { if (value.prop === "pid") {
dataStore.ruleForm.pid = value.id; dataStore.ruleForm.pid = value.id;
} }
@@ -207,12 +201,12 @@ const reset = () => {
}; };
//重置表单配置 //重置表单配置
const resetFrom = () => { const resetFrom = () => {
if (dataStore.selectLevel === 1 || dataStore.selectLevel === 2) { if (dataStore.selectLevel == 1 || dataStore.selectLevel == 2) {
dataStore.ruleForm = cloneDeep(RULE_FORM_LV1); dataStore.ruleForm = cloneDeep(RULE_FORM_LV1);
dataStore.formData = cloneDeep(FORM_DATA_LV1); dataStore.formData = cloneDeep(FORM_DATA_LV1);
dataStore.rules = RULES_LV1; dataStore.rules = RULES_LV1;
} }
if (dataStore.selectLevel === 3) { if (dataStore.selectLevel == 3) {
dataStore.ruleForm = cloneDeep(RULE_FORM_LV2); dataStore.ruleForm = cloneDeep(RULE_FORM_LV2);
dataStore.formData = cloneDeep(FORM_DATA_LV2); dataStore.formData = cloneDeep(FORM_DATA_LV2);
dataStore.rules = RULES_LV2; dataStore.rules = RULES_LV2;

View File

@@ -9,7 +9,7 @@
<el-input v-model="_ruleFormParam.short_name" style="width: 440px" /> <el-input v-model="_ruleFormParam.short_name" style="width: 440px" />
</el-form-item> </el-form-item>
<el-form-item label="型号" required> <el-form-item label="型号" required>
<el-input v-model="_ruleFormParam.spu" style="width: 440px" /> <el-input v-model="_ruleFormParam.spu" style="width: 440px" disabled />
</el-form-item> </el-form-item>
<el-form-item label="产品分类" style="width: 440px" required> <el-form-item label="产品分类" style="width: 440px" required>
<!-- <el-input v-model="_ruleFormParam.category_id" /> --> <!-- <el-input v-model="_ruleFormParam.category_id" /> -->
@@ -26,14 +26,19 @@
</el-form-item> </el-form-item>
<el-form-item label="产品参数"> <el-form-item label="产品参数">
<el-input v-model="_ruleFormParam.params" style="width: 440px" /> <el-input
v-model="_ruleFormParam.params"
style="width: 440px"
type="textarea"
:autosize="{ minRows: 10, maxRows: 20 }"
/>
</el-form-item> </el-form-item>
<el-form-item label="产品排序" style="width: 440px" required> <el-form-item label="产品排序" style="width: 440px" required>
<!-- <el-input v-model="_ruleFormParam.sort" /> --> <!-- <el-input v-model="_ruleFormParam.sort" /> -->
<el-input-number <el-input-number
:min="1" :min="0"
:max="9999" :max="9999"
:controls="true" :controls="true"
v-model="_ruleFormParam.sort" v-model="_ruleFormParam.sort"

View File

@@ -3,11 +3,23 @@
<!-- 封面图 --> <!-- 封面图 -->
<div> <div>
<h5 style="margin: 0; margin-bottom: 16px; font-size: 14px">封面图</h5> <h5 style="margin: 0; margin-bottom: 16px; font-size: 14px">封面图</h5>
<UploadImg v-model:image-url="imgInfoDataStore.cover_image">
<template #tip> <div style="display: flex">
<div style="width: 150px; text-align: center">图片尺寸800x800</div> <div>
</template> <UploadImg v-model:image-url="imgInfoDataStore.cover_image">
</UploadImg> <template #tip>
<div style="width: 150px; text-align: center">图片尺寸800x800</div>
</template>
</UploadImg>
</div>
<!-- <div style="margin-left: 20px">
<UploadImg v-model:image-url="imgInfoDataStore.cover_image">
<template #tip>
<div style="width: 150px; text-align: center">图片尺寸800x800</div>
</template>
</UploadImg>
</div> -->
</div>
</div> </div>
<el-divider /> <el-divider />
<!-- 属性 --> <!-- 属性 -->
@@ -44,7 +56,7 @@
<el-table-column v-for="attrId in selectedAttrIds" :key="attrId" :label="findAttrById(attrId)?.attr_name"> <el-table-column v-for="attrId in selectedAttrIds" :key="attrId" :label="findAttrById(attrId)?.attr_name">
<template #default="{ row }"> <template #default="{ row }">
<el-form :model="row"> <el-form :model="row">
<el-form-item v-if="findAttrById(attrId)?.attr_type === 1" style="margin: 0"> <el-form-item v-if="findAttrById(attrId)?.attr_type == 1" style="margin: 0">
<el-select v-model="findAttrObjInRow(row, attrId).attr_value" placeholder="请选择"> <el-select v-model="findAttrObjInRow(row, attrId).attr_value" placeholder="请选择">
<el-option <el-option
v-for="prop in findAttrById(attrId)?.props" v-for="prop in findAttrById(attrId)?.props"
@@ -116,7 +128,6 @@ const findAttrById = (id: any) => {
// 在 row 的 attrs 数组中查找对应 attrId 的对象 // 在 row 的 attrs 数组中查找对应 attrId 的对象
const findAttrObjInRow = (row: any, attrId: any) => { const findAttrObjInRow = (row: any, attrId: any) => {
console.log(row.attrs, "=======row===========");
let obj = row.attrs.find((item: any) => item.attr_id === attrId.toString()); let obj = row.attrs.find((item: any) => item.attr_id === attrId.toString());
if (!obj) { if (!obj) {
obj = { attr_id: attrId.toString(), attr_value: "" }; obj = { attr_id: attrId.toString(), attr_value: "" };
@@ -190,7 +201,6 @@ const echoData = () => {
}); });
imgInfoDataStore.skus = newSkus; imgInfoDataStore.skus = newSkus;
console.log(imgInfoDataStore, "=imgInfoDataStore=");
handleCheckboxChange(); handleCheckboxChange();
}; };
const callEchoDataIfHasValue = () => { const callEchoDataIfHasValue = () => {

View File

@@ -1,5 +1,6 @@
interface FormItem { interface FormItem {
prop: string; prop: string;
prop1?: string;
label?: string; label?: string;
placeholder?: string; placeholder?: string;
type: string; type: string;
@@ -31,29 +32,13 @@ export const FORM_DATA: FormItem[] = [
label: "型号: " label: "型号: "
}, },
{ {
prop: "category_id", prop: "treeIds",
prop1: "category_id",
placeholder: "请选择", placeholder: "请选择",
type: "treeSelect", type: "treeSelect", //treeSelect
isArray: true, isArray: true,
label: "产品分类: ", label: "产品分类: ",
options: [ options: []
{
value: "1",
label: "Level one 1",
children: [
{
value: "1-1",
label: "Level two 1-1",
children: [
{
value: "1-1-1",
label: "Level three 1-1-1"
}
]
}
]
}
]
}, },
{ {
prop: "Time", prop: "Time",

View File

@@ -3,7 +3,7 @@
<div class="table-box"> <div class="table-box">
<div style="padding-bottom: 16px"> <div style="padding-bottom: 16px">
<el-button @click="handleReset(dataStore)"> 重置 </el-button> <el-button @click="handleReset(dataStore, editorRef)"> 重置 </el-button>
<el-button type="primary" @click="handleSubmit(infoRef, imgInfoRef, dataStore)"> 提交 </el-button> <el-button type="primary" @click="handleSubmit(infoRef, imgInfoRef, dataStore)"> 提交 </el-button>
</div> </div>
<div class="card table-main"> <div class="card table-main">
@@ -16,7 +16,7 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="产品详情" name="third"> <el-tab-pane label="产品详情" name="third">
<div style="width: 1280px; margin: 0 auto"> <div style="width: 1280px; margin: 0 auto">
<WangEditor v-model:value="dataStore.detail" /> <Editor v-model:content="dataStore.detail" ref="editorRef" />
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="相关信息" name="related"> <el-tab-pane label="相关信息" name="related">
@@ -42,7 +42,7 @@
<script setup lang="ts" name="productEditIndex"> <script setup lang="ts" name="productEditIndex">
import { ref, reactive } from "vue"; import { ref, reactive } from "vue";
//getProductAttrsApi import Editor from "@/components/Editor/index.vue";
import { import {
getProductDetailsApi, getProductDetailsApi,
getProductListApi, getProductListApi,
@@ -56,14 +56,18 @@ import { messageBox } from "@/utils/messageBox";
import { useMsg } from "@/hooks/useMsg"; import { useMsg } from "@/hooks/useMsg";
import { handleSubmit, handleReset, initDetailParams } from "./utils/edit/index"; import { handleSubmit, handleReset, initDetailParams } from "./utils/edit/index";
import { addLabelValue } from "./utils/common/addLabelValue"; import { addLabelValue } from "@/utils/addLabelValue";
//组件引入 //组件引入
import basicInfo from "./components/basicInfo.vue"; import basicInfo from "./components/basicInfo.vue";
import imgInfo from "./components/imgInfo.vue"; import imgInfo from "./components/imgInfo.vue";
import WangEditor from "@/components/WangEditor/index.vue"; //import WangEditor from "@/components/WangEditor/index.vue";
import FormTable from "@/components/FormTable/index.vue"; import FormTable from "@/components/FormTable/index.vue";
const $route = useRoute(); const $route = useRoute();
const editorRef = ref<any>(null);
//数据集合 //数据集合
const dataStore = reactive<any>({ const dataStore = reactive<any>({
relatedColumns: cloneDeep(RELATED_INFO_COLUMNS), //相关信息及下载表格配置 relatedColumns: cloneDeep(RELATED_INFO_COLUMNS), //相关信息及下载表格配置
@@ -93,7 +97,7 @@ const getProductDetails = async () => {
if (result?.code === 0) { if (result?.code === 0) {
const { data } = result; const { data } = result;
//参数初始化(将参数按照不同的tab区分出来) //参数初始化(将参数按照不同的tab区分出来)
initDetailParams(dataStore, data); initDetailParams(dataStore, data, editorRef);
} }
}; };
getProductDetails(); getProductDetails();
@@ -109,6 +113,7 @@ getProductAttrsList();
const getProductList = async (query: any) => { const getProductList = async (query: any) => {
const result: any = await getProductListApi({ const result: any = await getProductListApi({
spu: query, spu: query,
is_show: 1,
page: 1, page: 1,
size: 1000 size: 1000
}); });
@@ -164,6 +169,7 @@ const handleRelatedAdd = () => {
}; };
//相关信息及下载远程搜索 //相关信息及下载远程搜索
const handleRemote = debounce((params: any) => { const handleRemote = debounce((params: any) => {
console.log(params, "============>>>>");
getProductList(params.query); getProductList(params.query);
}, 800); }, 800);
//产品属性接口 //产品属性接口

View File

@@ -15,13 +15,13 @@
<el-image :src="scope.row.cover_image ? h + scope.row.cover_image : ''" style="width: 60px; height: 60px" /> <el-image :src="scope.row.cover_image ? h + scope.row.cover_image : ''" style="width: 60px; height: 60px" />
</template> </template>
<template #status="scope"> <template #status="scope">
<el-tag effect="dark" :type="scope.row.status === 1 ? 'success' : 'danger'">{{ <el-tag effect="dark" :type="scope.row.status == 1 ? 'success' : 'danger'">{{
scope.row.status === 1 ? "启用" : "禁用" scope.row.status == 1 ? "启用" : "禁用"
}}</el-tag> }}</el-tag>
</template> </template>
<template #is_show="scope"> <template #is_show="scope">
<el-tag :type="scope.row.is_show === 1 ? 'success' : 'danger'" effect="dark">{{ <el-tag :type="scope.row.is_show == 1 ? 'success' : 'danger'" effect="dark">{{
scope.row.is_show === 1 ? "已上架" : "未上架" scope.row.is_show == 1 ? "已上架" : "未上架"
}}</el-tag> }}</el-tag>
</template> </template>
<!-- sort --> <!-- sort -->
@@ -31,9 +31,9 @@
<template #operation="scope"> <template #operation="scope">
<el-button <el-button
size="small" size="small"
:type="scope.row.is_show === 1 ? 'danger' : 'primary'" :type="scope.row.is_show == 1 ? 'danger' : 'primary'"
@click="handleBtnClick('上下架', scope.row)" @click="handleBtnClick('上下架', scope.row)"
>{{ scope.row.is_show === 1 ? "下架" : "上架" }}</el-button >{{ scope.row.is_show == 1 ? "下架" : "上架" }}</el-button
> >
<el-button size="small" type="info" style="cursor: not-allowed" @click="handleBtnClick('添加SKU', scope.row)" <el-button size="small" type="info" style="cursor: not-allowed" @click="handleBtnClick('添加SKU', scope.row)"
>添加SKU</el-button >添加SKU</el-button
@@ -63,7 +63,7 @@ import {
getProductCategoryListApi getProductCategoryListApi
} from "@/api/modules/productList"; } from "@/api/modules/productList";
// import { getProductCategoryListApi } from "@/api/modules/productClass"; // import { getProductCategoryListApi } from "@/api/modules/productClass";
import { addLabelValue } from "./utils/common/addLabelValue"; import { addLabelValue } from "@/utils/addLabelValue";
//深拷贝方法 //深拷贝方法
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
@@ -153,4 +153,3 @@ const handleBtnClick = (type: any, row: any) => {
</script> </script>
<style scoped></style> <style scoped></style>
./utils/common/addLabelValue

View File

@@ -1,5 +1,5 @@
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
// import { convertSpanToDiv } from "@/utils/convertSpanToDiv";
const hasIdRecursive = (data: any, targetId: any) => { const hasIdRecursive = (data: any, targetId: any) => {
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
const item = data[i]; const item = data[i];
@@ -15,8 +15,37 @@ const hasIdRecursive = (data: any, targetId: any) => {
return false; return false;
}; };
const htmlDecode = (html: any) => {
let e: any = document.createElement("div");
e.innerHTML = html;
// 关键:在解码后添加样式处理
const detailAllElements = e.querySelectorAll(".o_detail_all");
detailAllElements.forEach((detailAll: HTMLElement) => {
// 为文字类子元素添加居中样式
let textElements: any = [
...Array.from(detailAll.querySelectorAll<HTMLElement>(".o_detail_text")),
...Array.from(detailAll.querySelectorAll<HTMLElement>(".o_detail_small")),
...Array.from(detailAll.querySelectorAll<HTMLElement>(".o_detail_title"))
];
textElements.forEach((el: any) => {
// 保留原有样式,追加居中样式(避免覆盖已有样式)
el.style.textAlign = "center";
// 如果需要强制覆盖,可添加 !important
// el.style.textAlign = 'center !important';
});
});
if (e.childNodes.length > 1) {
return e.innerHTML;
} else {
return e.childNodes[0].innerHTML;
}
};
//将参数分离 //将参数分离
export const initDetailParams = (dataStore: any, data: any) => { export const initDetailParams = (dataStore: any, data: any, editorRef: any) => {
let is = hasIdRecursive(dataStore.options, data.category_id); let is = hasIdRecursive(dataStore.options, data.category_id);
//基本信息 //基本信息
dataStore.basicInfoRuleForm = cloneDeep({ dataStore.basicInfoRuleForm = cloneDeep({
@@ -40,8 +69,15 @@ export const initDetailParams = (dataStore: any, data: any) => {
stock_qty: data.stock_qty, stock_qty: data.stock_qty,
id: data.id id: data.id
}); });
//详情 //详情
dataStore.detail = cloneDeep(data.detail); if (!data.detail) {
dataStore.detail = "";
editorRef?.value?.clearEditor(); // 调用子组件的清空方法
} else {
dataStore.detail = htmlDecode(data.detail); //htmlDecode(data.detail);
}
//图片 //图片
dataStore.imgInfoData.cover_image = data.cover_image; dataStore.imgInfoData.cover_image = data.cover_image;
dataStore.imgInfoData.video_url = data.video_url; dataStore.imgInfoData.video_url = data.video_url;

View File

@@ -4,19 +4,19 @@ import { messageBox } from "@/utils/messageBox";
// import { cloneDeep } from "lodash-es"; // import { cloneDeep } from "lodash-es";
import { initDetailParams } from "./initDetailParams"; import { initDetailParams } from "./initDetailParams";
//详情(重置,重新获取一下详情) //详情(重置,重新获取一下详情)
const getProductDetails = async (dataStore: any) => { const getProductDetails = async (dataStore: any, editorRef: any) => {
const { id } = dataStore.basicInfoRuleForm; const { id } = dataStore.basicInfoRuleForm;
const result = await getProductDetailsApi(id); const result = await getProductDetailsApi(id);
if (result?.code === 0) { if (result?.code === 0) {
const { data } = result; const { data } = result;
initDetailParams(dataStore, data); initDetailParams(dataStore, data, editorRef);
useMsg("success", "重置成功 !"); useMsg("success", "重置成功 !");
} }
}; };
export const handleReset = (dataStore: any) => { export const handleReset = (dataStore: any, editorRef?: any) => {
messageBox("该操作会将数据重置为初始状态", () => { messageBox("该操作会将数据重置为初始状态", () => {
getProductDetails(dataStore); getProductDetails(dataStore, editorRef);
}); });
}; };

View File

@@ -69,7 +69,7 @@ export const handleSubmit = async (infoRef: any, imgInfoRef: any, dataStore: any
video_url, video_url,
video_img, video_img,
skus: skusCloneStr, skus: skusCloneStr,
detail: dataStore.details, detail: dataStore.detail,
related: JSON.stringify(relatedData) || [] related: JSON.stringify(relatedData) || []
}; };

View File

@@ -1,5 +1,6 @@
interface FormItem { interface FormItem {
prop: string; prop: string;
prop1?: string;
label?: string; label?: string;
placeholder?: string; placeholder?: string;
type: string; type: string;
@@ -31,7 +32,8 @@ export const FORM_DATA: FormItem[] = [
label: "型号: " label: "型号: "
}, },
{ {
prop: "category_id", prop: "treeIds",
prop1: "category_id",
placeholder: "请选择", placeholder: "请选择",
type: "treeSelect", type: "treeSelect",
isArray: true, isArray: true,

View File

@@ -8,11 +8,11 @@
:request-api="getProductTrashListApi" :request-api="getProductTrashListApi"
:init-param="dataStore.initParam" :init-param="dataStore.initParam"
> >
<template #image="scope"> <template #cover_image="scope">
<el-image :src="scope.row.image ? h + scope.row.image : ''" style="width: 60px; height: 60px" /> <el-image :src="scope.row.cover_image ? h + scope.row.cover_image : ''" style="width: 60px; height: 60px" />
</template> </template>
<template #status="scope"> <template #status="scope">
<el-tag type="danger" effect="dark"> {{ scope.row.status === 1 ? "删除" : "删除" }}</el-tag> <el-tag type="danger" effect="dark"> {{ scope.row.status == 1 ? "删除" : "删除" }}</el-tag>
</template> </template>
<template #operation="scope"> <template #operation="scope">
@@ -37,6 +37,7 @@ import { useMsg } from "@/hooks/useMsg";
import { getProductCategoryListApi } from "@/api/modules/productClass"; import { getProductCategoryListApi } from "@/api/modules/productClass";
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) // 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTableRef = ref<any>(null); const proTableRef = ref<any>(null);
import { addLabelValue } from "@/utils/addLabelValue";
// 数据源 // 数据源
const dataStore = reactive<any>({ const dataStore = reactive<any>({
@@ -75,20 +76,6 @@ const handleBtnClick = (type: any, row: any) => {
} }
}; };
const addLabelValue = (arr: any) => {
return arr.map((item: any) => {
// 为当前对象添加 label 和 value 属性
const newItem = { ...item };
newItem.label = newItem.name;
newItem.value = newItem.id;
// 如果有子对象,递归调用 addLabelValue 处理子对象
if (newItem.children && Array.isArray(newItem.children)) {
newItem.children = addLabelValue(newItem.children);
}
return newItem;
});
};
//产品分类(后端大佬说直接掉列表接口) //产品分类(后端大佬说直接掉列表接口)
const getProductCategoryList = async () => { const getProductCategoryList = async () => {
const result = await getProductCategoryListApi({ page: 1, size: 500 }); const result = await getProductCategoryListApi({ page: 1, size: 500 });

View File

@@ -12,8 +12,8 @@
:init-param="dataStore.initParam" :init-param="dataStore.initParam"
> >
<template #status="scope"> <template #status="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'" effect="dark">{{ <el-tag :type="scope.row.status == 1 ? 'success' : 'danger'" effect="dark">{{
scope.row.status === 1 ? "启用" : "禁用" scope.row.status == 1 ? "启用" : "禁用"
}}</el-tag> }}</el-tag>
</template> </template>
<template #operation="scope"> <template #operation="scope">
@@ -22,7 +22,7 @@
size="small" size="small"
type="danger" type="danger"
@click="handleBtnClick('删除', scope.row)" @click="handleBtnClick('删除', scope.row)"
v-if="scope.row.delete_disable === 0" v-if="scope.row.delete_disable == 0"
>删除</el-button >删除</el-button
> >
</template> </template>

View File

@@ -50,6 +50,7 @@
highlight-current highlight-current
:props="defaultProps" :props="defaultProps"
@check-change="handleTreeCheckChange" @check-change="handleTreeCheckChange"
:check-strictly="false"
/> />
</div> </div>
</div> </div>
@@ -77,49 +78,54 @@ import {
getRoleListEditUpApi, getRoleListEditUpApi,
getRoleListSaveApi getRoleListSaveApi
} from "@/api/modules/roleList"; } from "@/api/modules/roleList";
//getMenusListApi //权限列表接口
import { getRoleMenusListApi } from "@/api/modules/webMenusList"; import { getRoleMenusListApi } from "@/api/modules/webMenusList";
//深拷贝方法 //深拷贝方法
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
//表格和搜索 //表格和搜索
import { RULE_FORM, FORM_DATA, COLUMNS, EDIT_FORM_DATA, EDIT_RULE_FORM, RULES } from "./constant/index"; import { RULE_FORM, FORM_DATA, COLUMNS, EDIT_FORM_DATA, EDIT_RULE_FORM, RULES } from "./constant/index";
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
// 组件引用
const proTableRef = ref<any>(null); const proTableRef = ref<any>(null);
const formRef: any = ref(null); const formRef: any = ref(null);
const treeRef: any = ref(null); const treeRef: any = ref(null);
const defaultProps = { const defaultProps = {
children: "children", children: "children",
label: "title" label: "title",
disabled: (data: any) => data.id === 7 // 首页节点ID为7禁止选择
}; };
// 数据源 // 数据源
const dataStore = reactive<any>({ const dataStore = reactive<any>({
treeData: [], //权限 treeData: [], //权限树数据
allCheck: false, allCheck: false, //全选框状态
title: "添加角色", //抽屉标题 title: "添加角色", //抽屉标题
columns: COLUMNS, //列表配置项 columns: COLUMNS, //列表配置项
rules: cloneDeep(RULES), //抽屉表单验证 rules: cloneDeep(RULES), //表单验证规则
editRuleForm: cloneDeep(EDIT_RULE_FORM), editRuleForm: cloneDeep(EDIT_RULE_FORM), //编辑表单数据
editFormData: cloneDeep(EDIT_FORM_DATA), //抽屉表单配置项 editFormData: cloneDeep(EDIT_FORM_DATA), //表单配置项
initParam: cloneDeep(RULE_FORM), // 初始化搜索条件|重置搜索条件 initParam: cloneDeep(RULE_FORM), //初始化搜索条件
ruleForm: cloneDeep(RULE_FORM), // 搜索參數 ruleForm: cloneDeep(RULE_FORM), //搜索参数
formData: FORM_DATA, //搜索配置项 formData: FORM_DATA, //搜索配置项
selectedMenuIds: [], // 新增属性,用于保存选中的菜单 id selectedMenuIds: [], //选中的菜单ID含全选和半选
isIndeterminate: false, //全选框样式控制 isIndeterminate: false, //全选框半选样式
visible: false, //抽屉控制 visible: false, //抽屉显示状态
selectRow: {} //当前选择的row selectRow: {} //当前选中行数据
}); });
// 处理全选框状态变化 // 处理全选框状态变化(排除首页节点)
const handleAllCheckChange = (checked: any) => { const handleAllCheckChange = (checked: any) => {
const allNodeKeys = getAllNodeKeys(dataStore.treeData); const allNodeKeys = getAllNodeKeys(dataStore.treeData).filter(id => id !== 7); // 排除首页ID
if (checked) { if (checked) {
treeRef.value.setCheckedKeys(allNodeKeys); // 全选时选中所有节点(包括父节点,但首页已禁用)
treeRef.value.setCheckedKeys([7, ...allNodeKeys]); // 强制包含首页
} else { } else {
treeRef.value.setCheckedKeys([]); // 取消全选时保留首页选中状态
treeRef.value.setCheckedKeys([7]);
} }
}; };
// 获取所有节点的 key // 获取所有节点的ID用于全选计算
const getAllNodeKeys = (data: any[]) => { const getAllNodeKeys = (data: any[]) => {
let keys: number[] = []; let keys: number[] = [];
data.forEach(item => { data.forEach(item => {
@@ -131,139 +137,187 @@ const getAllNodeKeys = (data: any[]) => {
return keys; return keys;
}; };
// 处理树节点选中状态变化 // 处理树节点选中状态变化(确保首页始终选中)
const handleTreeCheckChange = () => { const handleTreeCheckChange = () => {
const allNodeKeys = getAllNodeKeys(dataStore.treeData); // 移除 (data: any) 参数
// 防止手动取消首页选中(虽然已禁用,但做双重保险)
if (!dataStore.selectedMenuIds.some((item: any) => item.menu_id === 7)) {
dataStore.selectedMenuIds.unshift({ menu_id: 7 });
}
const allNodeKeys = getAllNodeKeys(dataStore.treeData).filter(id => id !== 7); // 排除首页
// 获取全选节点ID包括父节点全选和叶子节点选中
const checkedKeys = treeRef.value.getCheckedKeys(); const checkedKeys = treeRef.value.getCheckedKeys();
if (checkedKeys.length === allNodeKeys.length) { // 获取半选节点ID父节点部分子节点选中
const halfCheckedKeys = treeRef.value.getHalfCheckedKeys();
// 合并全选和半选ID并去重强制包含首页
const allSelectedKeys = [...new Set([7, ...checkedKeys, ...halfCheckedKeys])];
// 更新全选框状态(排除首页计算)
const selectedWithoutHome = allSelectedKeys.filter(id => id !== 7);
if (selectedWithoutHome.length === allNodeKeys.length) {
dataStore.allCheck = true; dataStore.allCheck = true;
dataStore.isIndeterminate = false; dataStore.isIndeterminate = false;
} else if (checkedKeys.length === 0) { } else if (selectedWithoutHome.length === 0) {
dataStore.allCheck = false; dataStore.allCheck = false;
dataStore.isIndeterminate = false; dataStore.isIndeterminate = false;
} else { } else {
dataStore.allCheck = false; dataStore.allCheck = false;
dataStore.isIndeterminate = true; dataStore.isIndeterminate = true;
} }
// 更新选中的菜单 id
dataStore.selectedMenuIds = checkedKeys.map((id: any) => ({ menu_id: id })); // 保存所有选中的菜单ID强制包含首页
dataStore.selectedMenuIds = allSelectedKeys.map((id: number) => ({ menu_id: id }));
}; };
//抽屉确认 // 抽屉确认按钮
const handleConfirmClick = () => { const handleConfirmClick = () => {
if (!formRef.value!.ruleFormRef) return; if (!formRef.value?.ruleFormRef) return;
formRef!.value!.ruleFormRef!.validate((valid: any) => { formRef.value.ruleFormRef.validate((valid: boolean) => {
if (valid) { if (valid) {
console.log("submit!");
dataStore.title === "添加角色" ? getRoleListSave() : getRoleListEditUp(); dataStore.title === "添加角色" ? getRoleListSave() : getRoleListEditUp();
} else { } else {
console.log("error submit!"); console.log("表单验证失败");
return false; return false;
} }
}); });
}; };
//重置验证状态
// 重置表单验证状态
const resetFields = () => { const resetFields = () => {
if (!formRef.value!.ruleFormRef) return; if (formRef.value?.ruleFormRef) {
formRef!.value!.ruleFormRef.resetFields(); formRef.value.ruleFormRef.resetFields();
}
}; };
//抽屉重置
// 抽屉重置按钮
const handleResetClick = () => { const handleResetClick = () => {
if (dataStore.title === "添加角色") { if (dataStore.title === "添加角色") {
resetFields(); resetFields();
// 重置树选择状态(保留首页选中)
treeRef.value?.setCheckedKeys([7]);
dataStore.selectedMenuIds = [{ menu_id: 7 }];
} else { } else {
// 编辑时重新获取详情数据
getRoleListDetails(dataStore.editRuleForm.id); getRoleListDetails(dataStore.editRuleForm.id);
} }
}; };
//添加
// 打开添加抽屉
const handleAdd = () => { const handleAdd = () => {
dataStore.title = "添加角色"; dataStore.title = "添加角色";
dataStore.visible = true; dataStore.visible = true;
// 重置表单和树状态(默认选中首页)
dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM);
resetFields();
nextTick(() => {
treeRef.value?.setCheckedKeys([7]);
});
}; };
//抽屉关闭前的钩子
// 抽屉关闭前处理
const handleBeforeClone = () => { const handleBeforeClone = () => {
dataStore.selectedMenuIds = []; dataStore.selectedMenuIds = [];
treeRef.value.setCheckedKeys([]); treeRef.value?.setCheckedKeys([]);
dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM); dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM);
resetFields(); resetFields();
dataStore.visible = false; dataStore.visible = false;
}; };
//按钮点击事件
const handleBtnClick = (type: any, row: any) => { // 编辑/删除按钮点击
const handleBtnClick = (type: string, row: any) => {
dataStore.selectRow = row; dataStore.selectRow = row;
//编辑
if (type === "编辑") { if (type === "编辑") {
dataStore.visible = true; dataStore.visible = true;
dataStore.title = "编辑角色"; dataStore.title = "编辑角色";
getRoleListDetails(row.id); getRoleListDetails(row.id);
return; } else if (type === "删除") {
}
//删除
if (type === "删除") {
getRoleListDel(row.id); getRoleListDel(row.id);
} }
}; };
//删除 // 删除角色
const getRoleListDel = (id: any) => { const getRoleListDel = (id: number) => {
messageBox("你确定要删除?", async () => { messageBox("你确定要删除?", async () => {
const result = await getRoleListDelApi(id); const result = await getRoleListDelApi(id);
if (result?.code === 0) { if (result?.code === 0) {
const { msg } = result; useMsg("success", result.msg);
useMsg("success", msg); proTableRef.value?.getTableList();
proTableRef?.value?.getTableList();
} }
}); });
}; };
//详情
const getRoleListDetails = async (id: any) => { // 获取角色详情并回显(确保首页始终选中)
const getRoleListDetails = async (id: number) => {
const result = await getRoleListDetailsApi(id); const result = await getRoleListDetailsApi(id);
if (result?.code === 0) { if (result?.code === 0) {
dataStore.editRuleForm = result?.data; dataStore.editRuleForm = result.data;
const menuIds = dataStore.editRuleForm.authorities.map((item: any) => item.menu_id); // 提取后台返回的权限ID列表强制包含首页
// 设置树组件的默认选中节点 let savedMenuIds = dataStore.editRuleForm.authorities.map((item: any) => item.menu_id);
savedMenuIds = [...new Set([7, ...savedMenuIds])]; // 确保首页ID存在
nextTick(() => { nextTick(() => {
if (treeRef.value) { if (treeRef.value) {
treeRef.value.setCheckedKeys(menuIds); // 筛选出所有叶子节点ID用于正确触发父节点半选状态
const leafIds: number[] = [];
const findLeafNodes = (nodes: any[]) => {
nodes.forEach(node => {
if (!node.children || node.children.length === 0) {
// 叶子节点且在保存的ID中才选中
if (savedMenuIds.includes(node.id)) {
leafIds.push(node.id);
}
} else {
findLeafNodes(node.children);
}
});
};
findLeafNodes(dataStore.treeData);
// 设置叶子节点选中状态,强制包含首页
treeRef.value.setCheckedKeys([7, ...leafIds]);
// 触发状态更新
handleTreeCheckChange();
} }
}); });
} }
}; };
//保存
// 保存新角色
const getRoleListSave = async () => { const getRoleListSave = async () => {
const result = await getRoleListSaveApi({ const result = await getRoleListSaveApi({
...dataStore.editRuleForm, ...dataStore.editRuleForm,
menu_permission: JSON.stringify(dataStore.selectedMenuIds) menu_permission: JSON.stringify(dataStore.selectedMenuIds)
}); });
if (result?.code === 0) { if (result?.code === 0) {
const { msg } = result; useMsg("success", result.msg);
useMsg("success", msg);
dataStore.visible = false; dataStore.visible = false;
dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM); proTableRef.value?.getTableList();
proTableRef?.value?.getTableList();
} }
}; };
//更新
// 更新角色信息
const getRoleListEditUp = async () => { const getRoleListEditUp = async () => {
const result = await getRoleListEditUpApi({ const result = await getRoleListEditUpApi({
...dataStore.editRuleForm, ...dataStore.editRuleForm,
menu_permission: JSON.stringify(dataStore.selectedMenuIds) menu_permission: JSON.stringify(dataStore.selectedMenuIds)
}); });
if (result?.code === 0) { if (result?.code === 0) {
const { msg } = result; useMsg("success", result.msg);
useMsg("success", msg);
dataStore.visible = false; dataStore.visible = false;
dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM); proTableRef.value?.getTableList();
proTableRef?.value?.getTableList();
} }
}; };
//权限管理
// 获取权限树数据
const getMenusList = async () => { const getMenusList = async () => {
const result = await getRoleMenusListApi(); const result = await getRoleMenusListApi();
if (result?.code === 0) { if (result?.code === 0) {
dataStore.treeData = result?.data; dataStore.treeData = result.data;
} }
}; };
// 初始化加载权限树
getMenusList(); getMenusList();
</script> </script>

View File

@@ -0,0 +1,316 @@
<template>
<div class="table-box">
<div style="padding-bottom: 16px">
<el-button type="primary" @click="handleAdd"> 添加 </el-button>
</div>
<ProTable
ref="proTableRef"
:formData="dataStore.formData"
:columns="dataStore.columns"
:request-api="getRoleListApi"
:init-param="dataStore.initParam"
>
<template #operation="scope">
<el-button size="small" type="primary" @click="handleBtnClick('编辑', scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleBtnClick('删除', scope.row)">删除</el-button>
</template>
</ProTable>
<el-drawer
v-model="dataStore.visible"
:show-close="true"
:size="600"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="handleBeforeClone"
destroy-on-close
>
<template #header="{ titleId, titleClass }">
<h4 :id="titleId" :class="titleClass">{{ dataStore.title }}</h4>
</template>
<div>
<rulesForm
:ruleForm="dataStore.editRuleForm"
:formData="dataStore.editFormData"
:rules="dataStore.rules"
:indeterminate="dataStore.isIndeterminate"
ref="formRef"
>
</rulesForm>
<div style="margin-left: 65px; font-size: 14px; color: #606266">
<div style="display: flex; align-items: center; margin-bottom: 10px">
<span style="margin-right: 10px">权限分配:</span>
<el-checkbox v-model="dataStore.allCheck" @change="handleAllCheckChange" label="全选" size="large" />
<!-- <el-checkbox v-model="dataStore.allCheck" @change="handleAllCheckChange" label="全选" size="large" /> -->
</div>
<el-tree
ref="treeRef"
style="max-width: 600px"
:data="dataStore.treeData"
show-checkbox
node-key="id"
highlight-current
:props="defaultProps"
@check-change="handleTreeCheckChange"
/>
</div>
</div>
<template #footer>
<div style="flex: auto">
<el-button @click="handleResetClick">重置</el-button>
<el-button type="primary" @click="handleConfirmClick">确认</el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<script setup lang="ts" name="roleListIndex">
import ProTable from "@/components/ProTable/index.vue";
import rulesForm from "@/components/rulesForm/index.vue";
import { messageBox } from "@/utils/messageBox";
import { nextTick } from "vue";
import { useMsg } from "@/hooks/useMsg";
//列表接口
import {
getRoleListApi,
getRoleListDetailsApi,
getRoleListDelApi,
getRoleListEditUpApi,
getRoleListSaveApi
} from "@/api/modules/roleList";
//权限列表接口
import { getRoleMenusListApi } from "@/api/modules/webMenusList";
//深拷贝方法
import { cloneDeep } from "lodash-es";
//表格和搜索条件
import { RULE_FORM, FORM_DATA, COLUMNS, EDIT_FORM_DATA, EDIT_RULE_FORM, RULES } from "./constant/index";
// 组件引用
const proTableRef = ref<any>(null);
const formRef: any = ref(null);
const treeRef: any = ref(null);
const defaultProps = {
children: "children",
label: "title"
};
// 数据源
const dataStore = reactive<any>({
treeData: [], //权限树数据
allCheck: false, //全选框状态
title: "添加角色", //抽屉标题
columns: COLUMNS, //列表配置项
rules: cloneDeep(RULES), //表单验证规则
editRuleForm: cloneDeep(EDIT_RULE_FORM), //编辑表单数据
editFormData: cloneDeep(EDIT_FORM_DATA), //表单配置项
initParam: cloneDeep(RULE_FORM), //初始化搜索条件
ruleForm: cloneDeep(RULE_FORM), //搜索参数
formData: FORM_DATA, //搜索配置项
selectedMenuIds: [], //选中的菜单ID含全选和半选
isIndeterminate: false, //全选框半选样式
visible: false, //抽屉显示状态
selectRow: {} //当前选中行数据
});
// 处理全选框状态变化
const handleAllCheckChange = (checked: any) => {
const allNodeKeys = getAllNodeKeys(dataStore.treeData);
if (checked) {
// 全选时选中所有节点(包括父节点)
treeRef.value.setCheckedKeys(allNodeKeys);
} else {
// 取消全选时清空所有选中
treeRef.value.setCheckedKeys([]);
}
};
// 获取所有节点的ID
const getAllNodeKeys = (data: any[]) => {
let keys: number[] = [];
data.forEach(item => {
keys.push(item.id);
if (item.children && item.children.length > 0) {
keys = keys.concat(getAllNodeKeys(item.children));
}
});
return keys;
};
// 处理树节点选中状态变化
const handleTreeCheckChange = () => {
const allNodeKeys = getAllNodeKeys(dataStore.treeData);
// 获取全选节点ID包括父节点全选和叶子节点选中
const checkedKeys = treeRef.value.getCheckedKeys();
// 获取半选节点ID父节点部分子节点选中
const halfCheckedKeys = treeRef.value.getHalfCheckedKeys();
// 合并全选和半选ID并去重
const allSelectedKeys = [...new Set([...checkedKeys, ...halfCheckedKeys])];
// 更新全选框状态
if (allSelectedKeys.length === allNodeKeys.length) {
dataStore.allCheck = true;
dataStore.isIndeterminate = false;
} else if (allSelectedKeys.length === 0) {
dataStore.allCheck = false;
dataStore.isIndeterminate = false;
} else {
dataStore.allCheck = false;
dataStore.isIndeterminate = true;
}
// 保存所有选中的菜单ID含半选父节点
dataStore.selectedMenuIds = allSelectedKeys.map((id: number) => ({ menu_id: id }));
};
// 抽屉确认按钮
const handleConfirmClick = () => {
if (!formRef.value?.ruleFormRef) return;
formRef.value.ruleFormRef.validate((valid: boolean) => {
if (valid) {
dataStore.title === "添加角色" ? getRoleListSave() : getRoleListEditUp();
} else {
console.log("表单验证失败");
return false;
}
});
};
// 重置表单验证状态
const resetFields = () => {
if (formRef.value?.ruleFormRef) {
formRef.value.ruleFormRef.resetFields();
}
};
// 抽屉重置按钮
const handleResetClick = () => {
if (dataStore.title === "添加角色") {
resetFields();
// 重置树选择状态
treeRef.value?.setCheckedKeys([]);
dataStore.selectedMenuIds = [];
} else {
// 编辑时重新获取详情数据
getRoleListDetails(dataStore.editRuleForm.id);
}
};
// 打开添加抽屉
const handleAdd = () => {
dataStore.title = "添加角色";
dataStore.visible = true;
// 重置表单和树状态
dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM);
resetFields();
nextTick(() => {
treeRef.value?.setCheckedKeys([]);
});
};
// 抽屉关闭前处理
const handleBeforeClone = () => {
dataStore.selectedMenuIds = [];
treeRef.value?.setCheckedKeys([]);
dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM);
resetFields();
dataStore.visible = false;
};
// 编辑/删除按钮点击
const handleBtnClick = (type: string, row: any) => {
dataStore.selectRow = row;
if (type === "编辑") {
dataStore.visible = true;
dataStore.title = "编辑角色";
getRoleListDetails(row.id);
} else if (type === "删除") {
getRoleListDel(row.id);
}
};
// 删除角色
const getRoleListDel = (id: number) => {
messageBox("你确定要删除?", async () => {
const result = await getRoleListDelApi(id);
if (result?.code === 0) {
useMsg("success", result.msg);
proTableRef.value?.getTableList();
}
});
};
// 获取角色详情并回显
const getRoleListDetails = async (id: number) => {
const result = await getRoleListDetailsApi(id);
if (result?.code === 0) {
dataStore.editRuleForm = result.data;
// 提取后台返回的权限ID列表
const savedMenuIds = dataStore.editRuleForm.authorities.map((item: any) => item.menu_id);
nextTick(() => {
if (treeRef.value) {
// 筛选出所有叶子节点ID用于正确触发父节点半选状态
const leafIds: number[] = [];
const findLeafNodes = (nodes: any[]) => {
nodes.forEach(node => {
if (!node.children || node.children.length === 0) {
// 叶子节点且在保存的ID中才选中
if (savedMenuIds.includes(node.id)) {
leafIds.push(node.id);
}
} else {
findLeafNodes(node.children);
}
});
};
findLeafNodes(dataStore.treeData);
// 设置叶子节点选中状态,父节点会自动计算半选/全选
treeRef.value.setCheckedKeys(leafIds);
// 触发状态更新
handleTreeCheckChange();
}
});
}
};
// 保存新角色
const getRoleListSave = async () => {
const result = await getRoleListSaveApi({
...dataStore.editRuleForm,
menu_permission: JSON.stringify(dataStore.selectedMenuIds)
});
if (result?.code === 0) {
useMsg("success", result.msg);
dataStore.visible = false;
proTableRef.value?.getTableList();
}
};
// 更新角色信息
const getRoleListEditUp = async () => {
const result = await getRoleListEditUpApi({
...dataStore.editRuleForm,
menu_permission: JSON.stringify(dataStore.selectedMenuIds)
});
if (result?.code === 0) {
useMsg("success", result.msg);
dataStore.visible = false;
proTableRef.value?.getTableList();
}
};
// 获取权限树数据
const getMenusList = async () => {
const result = await getRoleMenusListApi();
if (result?.code === 0) {
dataStore.treeData = result.data;
console.log(result.data, "==============data===============");
}
};
// 初始化加载权限树
getMenusList();
</script>
<style scoped></style>

View File

@@ -16,8 +16,8 @@
<el-image :src="scope.row.image ? h + scope.row.image : ''" style="width: 60px; height: 60px" /> <el-image :src="scope.row.image ? h + scope.row.image : ''" style="width: 60px; height: 60px" />
</template> </template>
<template #status="scope"> <template #status="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'" effect="dark">{{ <el-tag :type="scope.row.status == 1 ? 'success' : 'danger'" effect="dark">{{
scope.row.status === 1 ? "启用" : "禁用" scope.row.status == 1 ? "启用" : "禁用"
}}</el-tag> }}</el-tag>
</template> </template>
<template #operation="scope"> <template #operation="scope">
@@ -73,6 +73,7 @@ import {
getVideoSaveApi, getVideoSaveApi,
getVideoClassListApi getVideoClassListApi
} from "@/api/modules/videoList"; } from "@/api/modules/videoList";
import { recursiveCompare } from "@/utils/recursiveCompare";
// import { getVideoClassListApi } from "@/api/modules/videoClass"; // import { getVideoClassListApi } from "@/api/modules/videoClass";
//深拷贝方法 //深拷贝方法
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
@@ -177,11 +178,14 @@ const getVideoRead = async (id: any) => {
const result = await getVideoReadApi(id); const result = await getVideoReadApi(id);
if (result?.code === 0) { if (result?.code === 0) {
dataStore.editRuleForm = result?.data; dataStore.editRuleForm = result?.data;
let is = dataStore.editFormData[1].options.some((item: any) => { // let is = dataStore.editFormData[1].options.some((item: any) => {
console.log(item.id); // console.log(item.id);
console.log(dataStore.editRuleForm.category_id); // console.log(dataStore.editRuleForm.category_id);
return item.value == dataStore.editRuleForm.category_id; // return item.value == dataStore.editRuleForm.category_id;
}); // });
let is = dataStore.editFormData[1].options.some((item: any) =>
recursiveCompare(item, dataStore.editRuleForm.category_id)
);
dataStore.editRuleForm.category_id1 = is ? dataStore.editRuleForm.category_id : dataStore.editRuleForm.category_name; dataStore.editRuleForm.category_id1 = is ? dataStore.editRuleForm.category_id : dataStore.editRuleForm.category_name;
} }
}; };

View File

@@ -21,7 +21,7 @@ interface FormItem {
export const EDIT_FORM_DATA: FormItem[] = [ export const EDIT_FORM_DATA: FormItem[] = [
{ {
prop: "name", prop: "name",
placeholder: "请输入", placeholder: "以“.”分隔,可对同分组内配置再次分组,支持多维",
type: "input", type: "input",
label: "配置标识: " label: "配置标识: "
}, },
@@ -60,7 +60,8 @@ export const EDIT_FORM_DATA: FormItem[] = [
{ {
prop: "extra", prop: "extra",
type: "textarea", type: "textarea",
label: "配置项: " label: "配置项: ",
placeholder: "选项型配置时:选项值:选项名['联动配置项的“配置标识”', ......],联动配置项为可选"
}, },
{ {
prop: "remark", prop: "remark",

View File

@@ -34,12 +34,12 @@
</el-table-column> </el-table-column>
<el-table-column prop="hidden" label="是否显示" :width="100"> <el-table-column prop="hidden" label="是否显示" :width="100">
<template #default="{ row }"> <template #default="{ row }">
{{ row.hidden === 0 ? "❌" : "✔️" }} {{ row.hidden == 0 ? "❌" : "✔️" }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="status" label="是否启用" :width="100"> <el-table-column prop="status" label="是否启用" :width="100">
<template #default="{ row }"> <template #default="{ row }">
{{ row.status === 1 ? "✔️" : "❌" }} {{ row.status == 1 ? "✔️" : "❌" }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" :width="160"> <el-table-column label="操作" :width="160">

View File

@@ -132,7 +132,7 @@ const dataStore = reactive<any>({
isFirstRequest: true, isFirstRequest: true,
selectRow: {} //当前选择的row selectRow: {} //当前选择的row
}); });
const selectedNodes = ref(null); const selectedNodes = ref("");
const treeRef = ref(null); const treeRef = ref(null);
// 配置 tree-select 的属性 // 配置 tree-select 的属性
const treeProps = { const treeProps = {
@@ -202,11 +202,17 @@ const getNavClassList = async () => {
getNavClassList(); getNavClassList();
//详情接口 //详情接口
const getItemsRead = async (id: any) => { const getItemsRead = async (id: any) => {
selectedNodes.value = "";
dataStore.data = [];
dataStore.title = "编辑Banner";
dataStore.visible = true;
const result = await getItemsReadApi(id); const result = await getItemsReadApi(id);
dataStore.title = "编辑导航"; dataStore.title = "编辑导航";
dataStore.visible = true; dataStore.visible = true;
if (result?.code === 0) { if (result?.code === 0) {
dataStore.editRuleForm = result?.data; dataStore.editRuleForm = result?.data;
console.log(dataStore.editRuleForm, "===========>>>>");
if (dataStore.editRuleForm.link && dataStore.editRuleForm.link_to) { if (dataStore.editRuleForm.link && dataStore.editRuleForm.link_to) {
let { id, name, link } = dataStore.editRuleForm.link_echo_data; let { id, name, link } = dataStore.editRuleForm.link_echo_data;
if (!id || !name || !link) { if (!id || !name || !link) {
@@ -353,9 +359,12 @@ const handleResetClick = () => {
const handleAdd = () => { const handleAdd = () => {
dataStore.title = "添加导航"; dataStore.title = "添加导航";
dataStore.visible = true; dataStore.visible = true;
selectedNodes.value = "";
}; };
const resetForm = () => { const resetForm = () => {
dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM); dataStore.editRuleForm = cloneDeep(EDIT_RULE_FORM);
// dataStore.editRuleForm.link_to = "";
// dataStore.editRuleForm.link = "";
}; };
//抽屉关闭前的钩子 //抽屉关闭前的钩子
const handleBeforeClone = () => { const handleBeforeClone = () => {

View File

@@ -11,12 +11,13 @@
{{ extraItem.name }} {{ extraItem.name }}
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
<span style="margin-left: 20px; color: #999999" v-if="config.remark">({{ config.remark }})</span>
</el-form-item> </el-form-item>
<el-form-item :label="config.title" v-if="config.type === 'text'"> <el-form-item :label="config.title" v-if="config.type === 'text'">
<el-input v-model="config.value" style="width: 400px"> </el-input> <el-input v-model="config.value" style="width: 400px" :placeholder="config.remark"> </el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="config.type === 'select'" :label="config.title"> <el-form-item v-if="config.type === 'select'" :label="config.title">
<el-select v-model="config.value" placeholder="请选择"> <el-select v-model="config.value" :placeholder="config.remark">
<el-option <el-option
v-for="extraItem in config.extra" v-for="extraItem in config.extra"
:key="extraItem.value" :key="extraItem.value"
@@ -32,12 +33,14 @@
{{ extraItem.name }} {{ extraItem.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
<span style="margin-left: 20px; color: #999999" v-if="config.remark">({{ config.remark }})</span>
</el-form-item> </el-form-item>
<el-form-item v-if="config.type === 'image'" :label="config.title"> <el-form-item v-if="config.type === 'image'" :label="config.title">
<UploadImg v-model:image-url="config.value" /> <UploadImg v-model:image-url="config.value" />
</el-form-item> </el-form-item>
<el-form-item v-if="config.type === 'textarea'" :label="config.title"> <el-form-item v-if="config.type === 'textarea'" :label="config.title">
<el-input v-model="config.value" type="textarea" :rows="8" style="width: 400px"> </el-input> <el-input v-model="config.value" type="textarea" :rows="8" style="width: 400px" :placeholder="config.remark">
</el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="config.type === 'file'" :label="config.title"> <el-form-item v-if="config.type === 'file'" :label="config.title">

View File

@@ -8,7 +8,7 @@
<div class="card table-main"> <div class="card table-main">
<el-tabs v-model="activeName" class="demo-tabs"> <el-tabs v-model="activeName" class="demo-tabs">
<el-tab-pane :label="item.name" :name="item.name" v-for="(item, index) in dataStore.list" :key="index"> <el-tab-pane :label="item.name" :name="item.name" v-for="(item, index) in dataStore.list" :key="index">
<el-form :model="dataStore.formData" label-width="120px"> <el-form :model="dataStore.formData" label-position="top">
<RecursiveForm :configs="item.configs" /> <RecursiveForm :configs="item.configs" />
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
@@ -108,4 +108,9 @@ const handleClick = () => {
}; };
</script> </script>
<style scoped></style> <style scoped lang="scss">
::v-deep(.el-form-item__label) {
font-weight: 900;
color: #333333;
}
</style>

View File

@@ -47,8 +47,8 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
}, },
plugins: createVitePlugins(viteEnv), plugins: createVitePlugins(viteEnv),
esbuild: { esbuild: {
//"console.log", //"console.log","console.log",
pure: viteEnv.VITE_DROP_CONSOLE ? ["console.log", "debugger"] : [] pure: viteEnv.VITE_DROP_CONSOLE ? ["debugger"] : []
}, },
build: { build: {
outDir: "dist", outDir: "dist",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long