mirror of
https://gitee.com/niucloud-team/javashop.git
synced 2026-04-25 03:08:09 +00:00
1.1.0
Niucloud JAVA版框架1.1.0更新内容
新增
* 增加小程序展示线上版本号
* 发布小程序添加自定义版本号
* 添加后台取消小程序绑定微信公众平台授权
* 管理端核销功能
* 用户端导入微信地址功能
优化
* 装修页面时可自定义底部导航
* 小程序添加版权信息展示
* 应用菜单结构优化:取消独立营销菜单,统一在应用菜单下按分类
* 分页列表增加缓存,记录当前页信息,操作完成后,返回当前页
* 开启标签栏时对标签页面进行缓存
* 统一框架装修自定义使用底部导航数据结构
修复
* 删除表单报错
* admin首页点击未安装、已安装应用,页面跳转空白
* 修复插件云安装错误时同时显示安装失败和安装完成
商城1.0.5更新内容
新增
* 用户端添加订单开具发票功能
* 管理后台添加补开发票功能
优化
* 商品列表增加相关活动的关联展示
* 限时折扣、积分商城增加批量操作
* 积分商品,若有规格未参与积分活动,则无法购买
* 复制商品,提示语优化
修复
* 满减送活动赠品设置多件,只展示1件
* 满减送赠品未扣除库存问题
* 商品列表批量设置分类后商品分类查询不到商品的问题
* 商品标签状态已经关闭添加商品时依旧可以选择
* 商家地址库联系方式搜索无效
* 商品分类拖动排序无效
* 参与满减活动再使用优惠券,订单0元申请退款时,退款金额出现负数
* 积分商城规格值设置不参与积分兑换提交订单未拦截
* 虚拟商品核销码设置的永久有效,核销时提示商品已过期
* 订单满减送退款售后点击详情报错
* 按会员标签发放优惠券异常一直未发放
* 微信支付申请退款售后点击转账跳转404
* 订单列表输入会员编号、账号点击搜索筛选不出来内容
* 多商品组商品来源选择分组,只有一个商品分类时多商品组商品展示不正确
* 商品组件排序方式选择为价格时报错
* 图文导航选择积分商品报错
This commit is contained in:
parent
c196a8622d
commit
86198c273f
1
admin/components.d.ts
vendored
1
admin/components.d.ts
vendored
@ -94,6 +94,7 @@ declare module '@vue/runtime-core' {
|
||||
SelectIcon: typeof import('./src/components/select-icon/index.vue')['default']
|
||||
SpreadPopup: typeof import('./src/components/spread-popup/index.vue')['default']
|
||||
UploadAttachment: typeof import('./src/components/upload-attachment/index.vue')['default']
|
||||
UploadAudio: typeof import('./src/components/upload-audio/index.vue')['default']
|
||||
UploadFile: typeof import('./src/components/upload-file/index.vue')['default']
|
||||
UploadImage: typeof import('./src/components/upload-image/index.vue')['default']
|
||||
UploadVideo: typeof import('./src/components/upload-video/index.vue')['default']
|
||||
|
||||
84
admin/package-lock.json
generated
84
admin/package-lock.json
generated
@ -9,12 +9,18 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "2.0.10",
|
||||
"@fullcalendar/core": "^6.1.19",
|
||||
"@fullcalendar/daygrid": "^6.1.19",
|
||||
"@fullcalendar/interaction": "^6.1.19",
|
||||
"@fullcalendar/vue3": "^6.1.19",
|
||||
"@heroicons/vue": "^2.2.0",
|
||||
"@highlightjs/vue-plugin": "2.1.0",
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@vueuse/core": "9.12.0",
|
||||
"axios": "1.4.0",
|
||||
"crypto-js": "4.1.1",
|
||||
"css-color-function": "1.3.3",
|
||||
"date-fns": "^4.1.0",
|
||||
"day": "^0.0.2",
|
||||
"echarts": "5.4.1",
|
||||
"element-plus": "^2.7.4",
|
||||
@ -41,6 +47,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "5.53.0",
|
||||
"@vitejs/plugin-vue": "4.0.0",
|
||||
"autoprefixer": "10.4.13",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "8.34.0",
|
||||
"eslint-config-standard-with-typescript": "34.0.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
@ -963,6 +970,47 @@
|
||||
"resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.1.6.tgz",
|
||||
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
|
||||
},
|
||||
"node_modules/@fullcalendar/core": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/@fullcalendar/core/-/core-6.1.19.tgz",
|
||||
"integrity": "sha512-z0aVlO5e4Wah6p6mouM0UEqtRf1MZZPt4mwzEyU6kusaNL+dlWQgAasF2cK23hwT4cmxkEmr4inULXgpyeExdQ==",
|
||||
"dependencies": {
|
||||
"preact": "~10.12.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/daygrid": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/@fullcalendar/daygrid/-/daygrid-6.1.19.tgz",
|
||||
"integrity": "sha512-IAAfnMICnVWPjpT4zi87i3FEw0xxSza0avqY/HedKEz+l5MTBYvCDPOWDATpzXoLut3aACsjktIyw9thvIcRYQ==",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/interaction": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/@fullcalendar/interaction/-/interaction-6.1.19.tgz",
|
||||
"integrity": "sha512-GOciy79xe8JMVp+1evAU3ytdwN/7tv35t5i1vFkifiuWcQMLC/JnLg/RA2s4sYmQwoYhTw/p4GLcP0gO5B3X5w==",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/vue3": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/@fullcalendar/vue3/-/vue3-6.1.19.tgz",
|
||||
"integrity": "sha512-j5eUSxx0xIy3ADljo0f5B9PhjqXnCQ+7nUMPfsslc2eGVjp4F74YvY3dyd6OBbg13IvpsjowkjncGipYMQWmTA==",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.19",
|
||||
"vue": "^3.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroicons/vue": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/@heroicons/vue/-/vue-2.2.0.tgz",
|
||||
"integrity": "sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==",
|
||||
"peerDependencies": {
|
||||
"vue": ">= 3"
|
||||
}
|
||||
},
|
||||
"node_modules/@highlightjs/vue-plugin": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@highlightjs/vue-plugin/-/vue-plugin-2.1.0.tgz",
|
||||
@ -2569,6 +2617,24 @@
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@ -2624,6 +2690,15 @@
|
||||
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz",
|
||||
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/day": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/day/-/day-0.0.2.tgz",
|
||||
@ -5003,6 +5078,15 @@
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.12.1",
|
||||
"resolved": "https://registry.npmmirror.com/preact/-/preact-10.12.1.tgz",
|
||||
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
|
||||
@ -5,17 +5,23 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build && node publish.cjs",
|
||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 && vite build && node publish.cjs",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "2.0.10",
|
||||
"@fullcalendar/core": "^6.1.19",
|
||||
"@fullcalendar/daygrid": "^6.1.19",
|
||||
"@fullcalendar/interaction": "^6.1.19",
|
||||
"@fullcalendar/vue3": "^6.1.19",
|
||||
"@heroicons/vue": "^2.2.0",
|
||||
"@highlightjs/vue-plugin": "2.1.0",
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@vueuse/core": "9.12.0",
|
||||
"axios": "1.4.0",
|
||||
"crypto-js": "4.1.1",
|
||||
"css-color-function": "1.3.3",
|
||||
"date-fns": "^4.1.0",
|
||||
"day": "^0.0.2",
|
||||
"echarts": "5.4.1",
|
||||
"element-plus": "^2.7.4",
|
||||
@ -54,6 +60,7 @@
|
||||
"unplugin-auto-import": "0.13.0",
|
||||
"unplugin-vue-components": "0.23.0",
|
||||
"vite": "4.1.0",
|
||||
"vue-tsc": "1.0.24"
|
||||
"vue-tsc": "1.0.24",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,7 +157,7 @@ UE.I18N['zh-cn'] = {
|
||||
'elementPathTip': "元素路径",
|
||||
'wordCountTip': "字数统计",
|
||||
'wordCountMsg': '{#count} / {#leave}',
|
||||
'wordOverFlowMsg': '<span style="color:red;">字数超出最大允许值,服务器可能拒绝保存!</span>',
|
||||
'wordOverFlowMsg': '<span style="color:red;">字数超出最大允许值!</span>',
|
||||
'ok': "确认",
|
||||
'cancel': "取消",
|
||||
'closeDialog': "关闭对话框",
|
||||
|
||||
@ -147,7 +147,10 @@ div.edui-box {
|
||||
overflow: visible;
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
/* 全屏状态 */
|
||||
.edui-default .edui-editor.edui-fullscreen {
|
||||
z-index: 999 !important;
|
||||
}
|
||||
.edui-editor div {
|
||||
width: auto;
|
||||
height: auto;
|
||||
|
||||
@ -13,6 +13,7 @@ import useAppStore from '@/stores/modules/app'
|
||||
import { useDark, useToggle } from '@vueuse/core'
|
||||
import { setThemeColor } from '@/utils/common'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getSiteAllowChange} from '@/app/api/site'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@ -24,7 +25,14 @@ systemStore.getWebsiteInfo()
|
||||
systemStore.getWebsiteLayout()
|
||||
|
||||
const toggleDark = useToggle(useDark())
|
||||
|
||||
const getSiteAllowChangeFn = ()=>{
|
||||
getSiteAllowChange().then(({data})=>{
|
||||
let isAllowChange = data.is_allow ? true : false
|
||||
localStorage.setItem('isAllowChange',isAllowChange.toString())
|
||||
})
|
||||
|
||||
}
|
||||
getSiteAllowChangeFn()
|
||||
watch(route, () => {
|
||||
useAppStore().$patch(state => {
|
||||
state.route = route
|
||||
@ -39,3 +47,9 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style>
|
||||
.el-page-header__header .el-page-header__left .el-page-header__content{
|
||||
font-size: 14px !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -63,6 +63,6 @@ export function releaseVersion(id: number) {
|
||||
return request.put(`channel/app/version/${ id }/release`, {}, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
export function generateSignCert(params: Record<string, any>) {
|
||||
return request.post(`channel/app/generate_sign_cert`, params, { showSuccessMessage: true });
|
||||
export function generateSingCert(params: Record<string, any>) {
|
||||
return request.post(`channel/app/generate_sing_cert`, params, { showSuccessMessage: true });
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ export function getMemberList(params: Record<string, any>) {
|
||||
* @returns
|
||||
*/
|
||||
export function getMemberInfo(id: number) {
|
||||
return request.get(`member/member/${id}`);
|
||||
return request.get(`member/member/${ id }`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,7 +61,7 @@ export function getRegisterChannelType(params: Record<string, any>) {
|
||||
* @param member_id
|
||||
*/
|
||||
export function deleteMember(member_id: number) {
|
||||
return request.delete(`member/member/${member_id}`, { showSuccessMessage: true })
|
||||
return request.delete(`member/member/${ member_id }`, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/***************************************************** 会员标签 ****************************************************/
|
||||
@ -81,7 +81,7 @@ export function getMemberLabelList(params: Record<string, any>) {
|
||||
* @returns
|
||||
*/
|
||||
export function getMemberLabelInfo(label_id: number) {
|
||||
return request.get(`member/label/${label_id}`);
|
||||
return request.get(`member/label/${ label_id }`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,7 +98,7 @@ export function addMemberLabel(params: Record<string, any>) {
|
||||
* @param params
|
||||
*/
|
||||
export function updateMemberLabel(params: Record<string, any>) {
|
||||
return request.put(`member/label/${params.label_id}`, params, { showSuccessMessage: true })
|
||||
return request.put(`member/label/${ params.label_id }`, params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,7 +107,7 @@ export function updateMemberLabel(params: Record<string, any>) {
|
||||
* @returns
|
||||
*/
|
||||
export function deleteMemberLabel(label_id: number) {
|
||||
return request.delete(`member/label/${label_id}`, { showSuccessMessage: true })
|
||||
return request.delete(`member/label/${ label_id }`, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,7 +122,7 @@ export function getMemberLabelAll() {
|
||||
* @param params
|
||||
*/
|
||||
export function editMemberDetail(params: Record<string, any>) {
|
||||
return request.put(`member/member/modify/${params.member_id}/${params.field}`, params, { showSuccessMessage: true })
|
||||
return request.put(`member/member/modify/${ params.member_id }/${ params.field }`, params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,7 +143,7 @@ export function memberBatchModify(params: Record<string, any>) {
|
||||
* @param change_type
|
||||
*/
|
||||
export function getChangeTypeList(change_type: string) {
|
||||
return request.get(`member/account/change_type/${change_type}`)
|
||||
return request.get(`member/account/change_type/${ change_type }`)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -321,7 +321,7 @@ export function getBalanceStatus() {
|
||||
* 获取余额变动类型
|
||||
*/
|
||||
export function getAccountType(params: Record<string, any>) {
|
||||
return request.get(`member/account/change_type/${params.account_type}`)
|
||||
return request.get(`member/account/change_type/${ params.account_type }`)
|
||||
}
|
||||
|
||||
|
||||
@ -357,7 +357,7 @@ export function getCashOutList(params: Record<string, any>) {
|
||||
* @param id
|
||||
*/
|
||||
export function getCashOutDetail(id: number) {
|
||||
return request.get(`member/cash_out/${id}`, {})
|
||||
return request.get(`member/cash_out/${ id }`, {})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -365,14 +365,18 @@ export function getCashOutDetail(id: number) {
|
||||
* @param params
|
||||
*/
|
||||
export function memberAudit(params: Record<string, any>) {
|
||||
return request.put(`member/cash_out/audit/${params.id}/${params.action}`, params, { showSuccessMessage: true })
|
||||
return request.put(`member/cash_out/audit/${ params.id }/${ params.action }`, params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员取消提现
|
||||
* @param params
|
||||
*/
|
||||
export function memberCancel(params: Record<string, any>) {
|
||||
return request.put(`member/cash_out/cancel/${params.id}`, params, { showSuccessMessage: true,showErrorMessage: true })
|
||||
return request.put(`member/cash_out/cancel/${ params.id }`, params, {
|
||||
showSuccessMessage: true,
|
||||
showErrorMessage: true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -381,7 +385,7 @@ export function memberCancel(params: Record<string, any>) {
|
||||
* @param params
|
||||
*/
|
||||
export function memberTransfer(params: Record<string, any>) {
|
||||
return request.put(`member/cash_out/transfer/${params.id}`, params, { showSuccessMessage: true })
|
||||
return request.put(`member/cash_out/transfer/${ params.id }`, params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -389,14 +393,15 @@ export function memberTransfer(params: Record<string, any>) {
|
||||
* @param params
|
||||
*/
|
||||
export function memberRemark(params: Record<string, any>) {
|
||||
return request.put(`member/cash_out/remark/${params.id}`, params, { showSuccessMessage: true })
|
||||
return request.put(`member/cash_out/remark/${ params.id }`, params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查打款进度
|
||||
* @param id
|
||||
*/
|
||||
export function memberCheck(id: number) {
|
||||
return request.put(`member/cash_out/check/${id}`, {}, { showSuccessMessage: true })
|
||||
return request.put(`member/cash_out/check/${ id }`, {}, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -404,7 +409,7 @@ export function memberCheck(id: number) {
|
||||
* @param params
|
||||
*/
|
||||
export function editMemberStatus(params: Record<string, any>) {
|
||||
return request.put(`member/setstatus/${params.status}`, params, { showSuccessMessage: true })
|
||||
return request.put(`member/setstatus/${ params.status }`, params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -453,6 +458,7 @@ export function getGrowthRuleDict() {
|
||||
export function getPointRuleDict() {
|
||||
return request.get(`member/dict/point_rule`)
|
||||
}
|
||||
|
||||
/***************************************************** 会员等级 ****************************************************/
|
||||
|
||||
/**
|
||||
@ -470,7 +476,7 @@ export function getMemberLevelPageList(params: Record<string, any>) {
|
||||
* @returns
|
||||
*/
|
||||
export function getMemberLevelInfo(level_id: number) {
|
||||
return request.get(`member/level/${level_id}`);
|
||||
return request.get(`member/level/${ level_id }`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -487,7 +493,7 @@ export function addMemberLevel(params: Record<string, any>) {
|
||||
* @param params
|
||||
*/
|
||||
export function updateMemberLevel(params: Record<string, any>) {
|
||||
return request.put(`member/level/${params.level_id}`, params, { showSuccessMessage: true })
|
||||
return request.put(`member/level/${ params.level_id }`, params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -496,7 +502,7 @@ export function updateMemberLevel(params: Record<string, any>) {
|
||||
* @returns
|
||||
*/
|
||||
export function deleteMemberLevel(level_id: number) {
|
||||
return request.delete(`member/level/${level_id}`, { showSuccessMessage: true })
|
||||
return request.delete(`member/level/${ level_id }`, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -519,7 +525,7 @@ export function getMemberBenefitsContent() {
|
||||
* 获取会员礼包内容
|
||||
*/
|
||||
export function getMemberGiftsContent(params: Record<string, any>) {
|
||||
return request.post(`member/gifts/content`, { params });
|
||||
return request.post(`member/gifts/content`, params);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -555,7 +561,7 @@ export function getMemberAddress(params: Record<string, any>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加收货地址
|
||||
* 添加收货地址
|
||||
*/
|
||||
export function addMemberAddress(params: Record<string, any>) {
|
||||
return request.post(`member/address`, params);
|
||||
|
||||
@ -113,3 +113,7 @@ export function getPreviewPoster(params: Record<string, any>) {
|
||||
export function getPosterGenerate(params: Record<string, any>) {
|
||||
return request.get(`sys/poster/generate`, { params, showErrorMessage: false })
|
||||
}
|
||||
// 判断是否安装imagemagick扩展
|
||||
export function checkImagick() {
|
||||
return request.get(`sys/check_imagick`, { showErrorMessage: false })
|
||||
}
|
||||
@ -291,13 +291,13 @@ export function getSiteAddons() {
|
||||
* @returns
|
||||
*/
|
||||
export function getShowApp() {
|
||||
return request.get('site/showApp')
|
||||
return request.get('site/showCustomer')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取营销列表
|
||||
* 获取站点应用特殊
|
||||
* @returns
|
||||
*/
|
||||
export function getShowMarketing() {
|
||||
return request.get('site/showMarketing')
|
||||
export function getShowSpecialMenu() {
|
||||
return request.get('site/special_menu')
|
||||
}
|
||||
|
||||
@ -21,6 +21,23 @@ export function getVerifyDetail(verifyCode: string) {
|
||||
return request.get(`verify/verify/${ verifyCode }`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取核销详情
|
||||
* @param verifyCode
|
||||
* @returns
|
||||
*/
|
||||
export function getVerifyDetailInfo(verifyCode: string) {
|
||||
return request.get(`verify/detail/${ verifyCode }`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 核销
|
||||
* @param verifyCode
|
||||
* @returns
|
||||
*/
|
||||
export function verify(verifyCode: string, params: Record<string, any>) {
|
||||
return request.post(`verify/verify/${ verifyCode }`,params,{ showSuccessMessage: true})
|
||||
}
|
||||
/***************************************************** 核销员 ****************************************************/
|
||||
|
||||
/**
|
||||
|
||||
@ -95,3 +95,10 @@ export function syncSiteWeapp(params: Record<string, any>) {
|
||||
export function getAuthRecord(params: Record<string, any>) {
|
||||
return request.get('wxoplatform/authorization/record', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消授权
|
||||
*/
|
||||
export function cancelAuthorization(params: Record<string, any>) {
|
||||
return request.post('wxoplatform/authorization/cancel', params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
BIN
admin/src/app/assets/images/app_store/app_manage.png
Normal file
BIN
admin/src/app/assets/images/app_store/app_manage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
admin/src/app/assets/images/app_store/app_type_addon.png
Normal file
BIN
admin/src/app/assets/images/app_store/app_type_addon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 518 B |
BIN
admin/src/app/assets/images/app_store/app_type_app.png
Normal file
BIN
admin/src/app/assets/images/app_store/app_type_app.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 478 B |
BIN
admin/src/app/assets/images/app_store/system_version.png
Normal file
BIN
admin/src/app/assets/images/app_store/system_version.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 834 B |
BIN
admin/src/app/assets/images/write.png
Normal file
BIN
admin/src/app/assets/images/write.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 481 B |
@ -538,7 +538,7 @@ const open = (addonKey: string = '', callback = null) => {
|
||||
active.value = 'upgrade'
|
||||
if (callback) callback()
|
||||
} else {
|
||||
if (addonKey && frameworkVersion.value != newFrameworkVersion.value) {
|
||||
if (addonKey && addonKey.indexOf('niucloud-admin') == -1 && frameworkVersion.value != newFrameworkVersion.value) {
|
||||
ElMessage({ message: '存在新版本框架,请先升级框架', type: 'error' })
|
||||
if (callback) callback()
|
||||
return
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
"menuTypeButton": "按钮",
|
||||
"menuDeleteTips": "删除菜单会删除当前菜单以及该菜单下所有子菜单,是否确认删除?",
|
||||
"initializeMenu":"重置菜单",
|
||||
"initializeMenuTipsOne":"重置菜单会将应用或插件的dict目录下的菜单配置文件中,菜单配置更新到数据库一般用做开发者修改了dict菜单配置文件后,同步到数据库操作。",
|
||||
"initializeMenuTipsOne":"重置菜单会将应用或插件的resources/**/loader/menu/目录下的菜单配置文件中的菜单配置更新到数据库。一般用做开发者修改了菜单配置文件后,同步到数据库操作。",
|
||||
"initializeMenuTipsTwo":"如果用户手动调整过以下菜单项,通常允许进行本项操作,操作会重置为原始菜单。 请谨慎使用!",
|
||||
"addMenu": "添加菜单",
|
||||
"updateMenu": "编辑菜单",
|
||||
|
||||
@ -29,5 +29,8 @@
|
||||
"encodingAesKeyPlaceholder": "请输入EncodingAESKey",
|
||||
"cleartextModeTips": "明文模式下,不使用消息体加解密功能,安全系数较低",
|
||||
"compatibleModeTips": "兼容模式下,明文、密文将共存,方便开发者调试和维护",
|
||||
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高"
|
||||
}
|
||||
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高",
|
||||
"wechatBaseUri": "借权域名",
|
||||
"wechatBaseUriPlaceholder": "",
|
||||
"wechatBaseUriTips": "默认留空,填写后将替换https://open.weixin.gg.com/获取授权!"
|
||||
}
|
||||
|
||||
@ -243,7 +243,7 @@
|
||||
"carouselSearchSwiperSet": "轮播图设置",
|
||||
"carouselSearchSwiperControl": "展示开关",
|
||||
"carouselSearchSwiperInterval": "切换间隔 / 秒",
|
||||
"carouselSearchSwiperTips": "建议上传尺寸相同的图片,推荐尺寸750*350;鼠标拖拽可调整图片顺序",
|
||||
"carouselSearchSwiperTips": "建议上传尺寸相同的图片,推荐尺寸750*580;鼠标拖拽可调整图片顺序",
|
||||
"carouselSearchTabStyle": "选项卡样式",
|
||||
"carouselSearchStyle": "搜索框样式",
|
||||
"noColor": "常规颜色",
|
||||
|
||||
@ -244,7 +244,7 @@
|
||||
"carouselSearchSwiperSet": "轮播图设置",
|
||||
"carouselSearchSwiperControl": "展示开关",
|
||||
"carouselSearchSwiperInterval": "切换间隔 / 秒",
|
||||
"carouselSearchSwiperTips": "建议上传尺寸相同的图片,推荐尺寸750*350;鼠标拖拽可调整图片顺序",
|
||||
"carouselSearchSwiperTips": "建议上传尺寸相同的图片,推荐尺寸750*580;鼠标拖拽可调整图片顺序",
|
||||
"carouselSearchTabStyle": "选项卡样式",
|
||||
"carouselSearchStyle": "搜索框样式",
|
||||
"noColor": "常规颜色",
|
||||
|
||||
@ -121,6 +121,7 @@
|
||||
"fieldStatistics":"字段统计",
|
||||
"viewInformation":"查看信息",
|
||||
|
||||
"deleteTips":"确定删除该条数据吗"
|
||||
|
||||
"deleteTips":"确定删除该条数据吗",
|
||||
"startDate": "开始日期",
|
||||
"endDate": "结束日期"
|
||||
}
|
||||
|
||||
@ -4,5 +4,9 @@
|
||||
"admin": "平台端",
|
||||
"adminBgImgTip": "建议上传尺寸为450*400px",
|
||||
"site": "站点端",
|
||||
"siteBgImgTip": "建议上传尺寸为620*980px"
|
||||
"siteBgImgTip": "建议上传尺寸为620*980px",
|
||||
"siteLoginLogo": "站点登录Logo",
|
||||
"siteLoginLogoTips": "站点端登录Logo,建议图片尺寸:132*40像素;图片格式:jpg、png、jpeg。",
|
||||
"siteLoginBgImg": "站点登录背景图",
|
||||
"siteLoginBgImgTips": "站点端登录背景图,建议图片尺寸:1920*1280像素;图片格式:jpg、png、jpeg。"
|
||||
}
|
||||
|
||||
@ -7,5 +7,6 @@
|
||||
"validTimeTips": "过期后将重新获取定位信息",
|
||||
"validTimePlaceholder": "请输入定位有效期",
|
||||
"validTimeFormatTips": "格式输入错误",
|
||||
"validTimeNotZeroTips": "定位有效期不能小于5分钟"
|
||||
}
|
||||
"validTimeNotZeroTips": "定位有效期不能小于5分钟",
|
||||
"aMapKey": "高德地图KEY"
|
||||
}
|
||||
|
||||
@ -74,5 +74,9 @@
|
||||
"foldText":"展开/折叠",
|
||||
"appName": "套餐内含应用",
|
||||
"addonName": "套餐内含插件",
|
||||
"siteInitTips":"确定要初始化站点吗?该操作将删除站点内所有数据,该操作无法退回,确定要继续初始化吗?"
|
||||
"siteInitTips":"确定要初始化站点吗?该操作将删除站点内所有数据,该操作无法退回,确定要继续初始化吗?",
|
||||
"createTimeStartDate": "开始时间(创建时间)",
|
||||
"createTimeEndDate": "结束时间(创建时间)",
|
||||
"expireTimeStartDate": "开始时间(到期时间)",
|
||||
"expireTimeEndDate": "结束时间(到期时间)"
|
||||
}
|
||||
|
||||
@ -24,5 +24,6 @@
|
||||
"startBackUp": "开始备份",
|
||||
"backUpEnd": "备份完成",
|
||||
"remark": "备注",
|
||||
"remarkEmpty": "无"
|
||||
"remarkEmpty": "无",
|
||||
"status": "状态"
|
||||
}
|
||||
|
||||
@ -50,6 +50,7 @@ import { img } from '@/utils/common'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { t } from '@/lang'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const addonIndexRoute = useUserStore().addonIndexRoute
|
||||
const router = useRouter()
|
||||
@ -69,10 +70,17 @@ getAppList()
|
||||
|
||||
const toLink = (item: any) => {
|
||||
if (item.url) {
|
||||
router.push(item.url)
|
||||
// 判断如果携带is_target=true就通过新窗口打开
|
||||
if (item.url.indexOf('is_target=true') != -1) {
|
||||
const url = router.resolve(item.url)
|
||||
window.open(url.href)
|
||||
} else {
|
||||
router.push(item.url)
|
||||
}
|
||||
} else {
|
||||
addonIndexRoute[item.key] && router.push({ name: addonIndexRoute[item.key] })
|
||||
}
|
||||
storage.set({ key: 'activeAppKey', data: item.key })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -6,19 +6,19 @@
|
||||
<template v-if="Object.keys(marketingList).length">
|
||||
|
||||
<template v-for="(item, index) in marketingList" :key="index + 'b'">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex justify-between items-center" v-if="item.list.length > 0">
|
||||
<span class="text-page-title">{{ item.title }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap plug-list pb-10 plug-large">
|
||||
<div class="cursor-pointer mt-[20px] mr-4 bg-[#f7f7f7]" v-for="(childItem,childIndex) in item.list" :key="childIndex" @click="toLink(childItem)">
|
||||
<div class="w-[264px] flex py-[20px] px-[17px] app-item relative">
|
||||
<el-image class="w-[40px] h-[40px] mr-[10px]" :src="img(childItem.icon)" fit="contain">
|
||||
<el-image class="w-[40px] h-[40px] mr-[10px] rounded-[6px] overflow-hidden" :src="img(childItem.icon)" fit="contain">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<img class="w-[40px] h-[40px]" src="@/app/assets/images/index/app_default.png" />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</el-image>
|
||||
<div class="flex flex-col justify-between w-[180px]">
|
||||
<div class="text-[14px] flex items-center">
|
||||
@ -45,7 +45,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { getShowMarketing } from '@/app/api/site'
|
||||
// import { getShowMarketing } from '@/app/api/site'
|
||||
import { img } from '@/utils/common'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import { useRouter } from 'vue-router'
|
||||
@ -57,15 +57,21 @@ const marketingList = ref<Record<string, any>[]>([])
|
||||
|
||||
const loading = ref(true)
|
||||
const getMarketingList = async () => {
|
||||
const res = await getShowMarketing()
|
||||
marketingList.value = res.data
|
||||
// const res = await getShowMarketing()
|
||||
// marketingList.value = res.data
|
||||
loading.value = false
|
||||
}
|
||||
getMarketingList()
|
||||
// getMarketingList()
|
||||
|
||||
const toLink = (item: any) => {
|
||||
if (item.url) {
|
||||
router.push(item.url)
|
||||
// 判断如果携带is_target=true就通过新窗口打开
|
||||
if (item.url.indexOf('is_target=true') != -1) {
|
||||
const url = router.resolve(item.url)
|
||||
window.open(url.href)
|
||||
} else {
|
||||
router.push(item.url)
|
||||
}
|
||||
} else {
|
||||
addonIndexRoute[item.key] && router.push({ name: addonIndexRoute[item.key] })
|
||||
}
|
||||
|
||||
@ -1,92 +0,0 @@
|
||||
<template>
|
||||
<!--营销管理-->
|
||||
<div class="main-container" v-loading="loading">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
|
||||
<template v-if="Object.keys(marketingList).length">
|
||||
|
||||
<template v-for="(item, index) in marketingList" :key="index + 'b'">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ item.title }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap plug-list pb-10 plug-large">
|
||||
<div class="cursor-pointer mt-[20px] mr-4 bg-[#f7f7f7]" v-for="(childItem,childIndex) in item.list" :key="childIndex" @click="toLink(childItem)">
|
||||
<div class="w-[264px] flex py-[20px] px-[17px] app-item relative">
|
||||
<el-image class="w-[40px] h-[40px] mr-[10px]" :src="img(childItem.icon)" fit="contain">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<img class="w-[40px] h-[40px]" src="@/app/assets/images/index/app_default.png" />
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<div class="flex flex-col justify-between w-[180px]">
|
||||
<div class="text-[14px] flex items-center">
|
||||
<span class="app-text max-w-[170px]">{{ childItem.title }}</span>
|
||||
<!-- <span class="iconfont iconxiaochengxu2 text-[#00b240] ml-[4px] !text-[14px]"></span>-->
|
||||
</div>
|
||||
<!-- <el-icon color="#666">
|
||||
<QuestionFilled />
|
||||
</el-icon> -->
|
||||
<p class="app-text text-[12px] text-[#999]">{{childItem.desc}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<div class="empty flex items-center justify-center" v-if="!loading && !Object.keys(marketingList).length">
|
||||
<el-empty :description="t('emptyAppData')" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { getSiteAddons,getShowMarketing } from '@/app/api/site'
|
||||
import { img } from '@/utils/common'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { t } from '@/lang'
|
||||
|
||||
const addonIndexRoute = useUserStore().addonIndexRoute
|
||||
const router = useRouter()
|
||||
const marketingList = ref<Record<string, any>[]>([])
|
||||
|
||||
const loading = ref(true)
|
||||
const getMarketingList = async () => {
|
||||
// const res = await getSiteAddons()
|
||||
// appList.value = res.data
|
||||
// loading.value = false
|
||||
|
||||
const res = await getShowMarketing();
|
||||
marketingList.value = res.data
|
||||
loading.value = false
|
||||
}
|
||||
getMarketingList()
|
||||
|
||||
const toLink = (item: any) => {
|
||||
if (item.url) {
|
||||
router.push(item.url)
|
||||
} else {
|
||||
addonIndexRoute[item.key] && router.push({ name: addonIndexRoute[item.key] })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-text {
|
||||
overflow: hidden;
|
||||
/* 超出部分隐藏 */
|
||||
white-space: nowrap;
|
||||
/* 禁止文本换行 */
|
||||
text-overflow: ellipsis;
|
||||
/* 显示省略号 */
|
||||
}
|
||||
.app-item:hover{
|
||||
transition: 0.5s;
|
||||
box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.1);
|
||||
}
|
||||
</style>
|
||||
@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="flex flex-col justify-between items-start">
|
||||
<div class="text-[14px] text-[#1D1F3A]">系统最新版本为</div>
|
||||
<div class="text-[14px] text-[#1D1F3A] font-bold">v{{ newVersion.version_no }}</div>
|
||||
<div class="text-[14px] text-[#1D1F3A] font-bold">v{{ newVersion.version_no }}({{ versionCode }})</div>
|
||||
<div class="text-[#9699B6] text-[16px]" v-if="!shouldShowUpgradeButton">
|
||||
<span>已是最新</span>
|
||||
</div>
|
||||
|
||||
@ -19,13 +19,19 @@
|
||||
<el-form-item :label="t('url')" prop="url">
|
||||
<el-input v-model.trim="sysUserLogTableData.searchParam.url" :placeholder="t('urlPlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('操作时间')" prop="create_time">
|
||||
<el-date-picker v-model="sysUserLogTableData.searchParam.create_time" type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
|
||||
:end-placeholder="t('endDate')" />
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadSysUserLogList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="flex justify-end items-center w-[20%]">
|
||||
<div class="flex justify-end items-center w-[10%]">
|
||||
<div>
|
||||
<el-button type="primary" class="w-[100px]" @click="clearEvent()">{{ t('清空日志') }}</el-button>
|
||||
</div>
|
||||
@ -83,7 +89,8 @@ const sysUserLogTableData = reactive({
|
||||
searchParam: {
|
||||
ip: '',
|
||||
username: '',
|
||||
url: ''
|
||||
url: '',
|
||||
create_time:''
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -96,7 +96,6 @@ const refreshMenu = () => {
|
||||
).then(() => {
|
||||
refreshLoading.value = true
|
||||
menuRefresh({}).then(res => {
|
||||
location.reload()
|
||||
refreshLoading.value = false
|
||||
}).catch(() => {
|
||||
refreshLoading.value = false
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
|
||||
<!-- <div class="flex justify-between items-center">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mt-[20px]">
|
||||
<el-form :inline="true" :model="roleTableData.searchParam" ref="searchFormRef">
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
<el-button type="primary" class="w-[100px]" @click="addEvent">
|
||||
{{ t('addMenu') }}
|
||||
</el-button>
|
||||
<el-button class="w-[100px]" @click="refreshMenu">
|
||||
<el-button class="w-[100px]" :loading="refreshLoading" @click="refreshMenu">
|
||||
{{ t('initializeMenu') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@ -130,6 +130,7 @@ const getMenuList = () => {
|
||||
}
|
||||
getMenuList()
|
||||
// 重置菜单
|
||||
const refreshLoading = ref(false)
|
||||
const refreshMenu = () => {
|
||||
ElMessageBox.confirm(h('div', null, [
|
||||
h('p', null, t('initializeMenuTipsOne')),
|
||||
@ -141,9 +142,11 @@ const refreshMenu = () => {
|
||||
// type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
refreshLoading.value = true
|
||||
menuRefresh({}).then(res => {
|
||||
location.reload()
|
||||
refreshLoading.value = false
|
||||
}).catch(() => {
|
||||
refreshLoading.value = false
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
@ -6,9 +6,7 @@
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
</div>
|
||||
<el-tabs v-model="activeName" class="mt-[20px]">
|
||||
<el-tab-pane :label="t('管理员')" name="userList">
|
||||
<div class="flex justify-between items-center mt-[20px]">
|
||||
<div class="flex justify-between items-center mt-[20px]">
|
||||
<el-form :inline="true" :model="userTableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('accountNumber')" prop="search">
|
||||
<el-input v-model.trim="userTableData.searchParam.search" class="input-width" :placeholder="t('accountNumberPlaceholder')" />
|
||||
@ -84,11 +82,14 @@
|
||||
@size-change="loadUserList()" @current-change="loadUserList" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- <el-tabs v-model="activeName" class="mt-[20px]">
|
||||
<el-tab-pane :label="t('管理员')" name="userList">
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('管理员角色')" name="userRole">
|
||||
<userRole></userRole>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-tabs> -->
|
||||
<edit-user ref="editUserDialog" @complete="loadUserList()" />
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { generateSignCert } from '@/app/api/app'
|
||||
import { generateSingCert } from '@/app/api/app'
|
||||
import { img } from '@/utils/common'
|
||||
|
||||
const showDialog = ref(false)
|
||||
@ -135,7 +135,7 @@ const confirm = async (formEl: FormInstance | undefined) => {
|
||||
await formEl.validate()
|
||||
loading.value = true
|
||||
|
||||
generateSignCert(formData).then(res => {
|
||||
generateSingCert(formData).then(res => {
|
||||
loading.value = false
|
||||
showDialog.value = false
|
||||
window.open(img(res.data), '_blank')
|
||||
|
||||
@ -45,6 +45,24 @@
|
||||
<template v-if="oplatformConfig.app_id && oplatformConfig.app_secret">
|
||||
<el-button type="primary" @click="router.push('/channel/weapp/config')">{{ weappConfig.app_id ? t("seeConfig") : t("weappSettingBtn") }}</el-button>
|
||||
<el-button type="primary" plain @click="authBindWeapp">{{ weappConfig.is_authorization ? t("refreshAuth") : t("authWeapp") }}</el-button>
|
||||
<template v-if="weappConfig.is_authorization">
|
||||
<el-button type="primary" plain @click="cencelAuth" >{{ t("取消授权") }}</el-button>
|
||||
<el-tooltip class="box-item" effect="light" placement="top">
|
||||
<el-icon color="#666" size="15px" class="ml-[5px]">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
<template #content>
|
||||
<div>
|
||||
<div>
|
||||
非通过API创建的开放平台账号需要登录微信公众品平台进行取消授权操作
|
||||
</div>
|
||||
<div>
|
||||
微信公众平台: <a class="ml-[3px] text-[var(--el-color-primary)]" target="_blank" href="https://mp.weixin.qq.com/cgi-bin/loginpage">https://mp.weixin.qq.com/cgi-bin/loginpage</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button type="primary" @click="router.push('/channel/weapp/config')">{{ t("weappSettingBtn") }}</el-button>
|
||||
@ -107,9 +125,10 @@ import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { img } from '@/utils/common'
|
||||
import { getWeappConfig } from '@/app/api/weapp'
|
||||
import { getAuthorizationUrl } from '@/app/api/wxoplatform'
|
||||
import { getAuthorizationUrl ,cancelAuthorization} from '@/app/api/wxoplatform'
|
||||
import { getWxoplatform } from '@/app/api/sys'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@ -158,6 +177,24 @@ const authBindWeapp = () => {
|
||||
window.open(data)
|
||||
})
|
||||
}
|
||||
const repeat = ref(false)
|
||||
const cencelAuth = () => {
|
||||
ElMessageBox.confirm(t('确认取消授权吗?'), t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
if (repeat.value) return
|
||||
repeat.value = true
|
||||
cancelAuthorization({}).then(() => {
|
||||
repeat.value = false
|
||||
}).catch(() => {
|
||||
repeat.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -14,13 +14,32 @@
|
||||
</el-tabs>
|
||||
|
||||
<div class="mt-[20px]" v-if="!weappConfig.is_authorization">
|
||||
<el-button type="primary" @click="insert" :loading="uploading" :disabled="loading">{{ t('cloudRelease') }}</el-button>
|
||||
<el-button type="primary" @click="openDialog" :loading="uploading" :disabled="loading">{{ t('cloudRelease') }}</el-button>
|
||||
<el-button @click="localInsert" :disabled="loading">{{ t('localRelease') }}</el-button>
|
||||
</div>
|
||||
<div class="mt-[20px]" v-else>
|
||||
<el-button type="primary" @click="againUpload" :loading="uploading" :disabled="loading">{{ t('uploadWeapp') }}</el-button>
|
||||
</div>
|
||||
|
||||
<div class="text-[14px] mt-[15px]">
|
||||
<div class="flex items-center">
|
||||
<div v-if="weappTableData.version_info.release_version">
|
||||
线上版本: <span class="mr-10 text-primary">{{ weappTableData.version_info.release_version }}</span>
|
||||
</div>
|
||||
<div v-if="weappTableData.version_info.release_time">
|
||||
发布时间: <span >{{ weappTableData.version_info.release_time }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div v-if="weappTableData.version_info.exp_version" class="mt-2">
|
||||
体验版本: <span class="mr-10 text-primary">{{ weappTableData.version_info.exp_version }}</span>
|
||||
</div>
|
||||
<div v-if="weappTableData.version_info.exp_time" class="mt-2">
|
||||
过期时间: <span >{{ weappTableData.version_info.exp_time }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table class="mt-[15px]" :data="weappTableData.data" v-loading="weappTableData.loading" size="default">
|
||||
<template #empty>
|
||||
<span>{{ t('emptyData') }}</span>
|
||||
@ -71,6 +90,42 @@
|
||||
<el-button @click="dialogVisible = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" @click="insert">
|
||||
{{ t('confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="cloudVersionDialogVisible" :title="t('codeDownTwoDesc')" width="600px" :before-close="handleCloseCloudVersion">
|
||||
<el-form ref="cloudRuleFormRef" :model="form" :rules="formRules" label-width="120px">
|
||||
<el-form-item prop="type" :label="t('版本号类型')">
|
||||
<div>
|
||||
<el-radio-group v-model="form.type">
|
||||
<el-radio :label="1">{{ t('默认') }}</el-radio>
|
||||
<el-radio :label="2">{{ t('自定义') }}</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="mt-[10px] text-[12px] text-[#999] leading-[20px]">默认为列表版本号递增,自定义则为手动输入版本号进行上传,首位必须大于1</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="version" :label="t('code')" v-if="form.type == 2">
|
||||
<div class="flex items-end">
|
||||
<el-form-item prop="code1">
|
||||
<el-input v-model.number="form.code1" class="!w-[70px]" :placeholder="t('codePlaceholder')" />
|
||||
</el-form-item>
|
||||
<span class="mx-[10px]">.</span>
|
||||
<el-form-item prop="code2">
|
||||
<el-input v-model.number="form.code2" class="!w-[70px]" :placeholder="t('codePlaceholder')" />
|
||||
</el-form-item>
|
||||
<span class="mx-[10px]">.</span>
|
||||
<el-form-item prop="code3">
|
||||
<el-input v-model.number="form.code3" class="!w-[70px]" :placeholder="t('codePlaceholder')" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleCloseCloudVersion">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" @click="save">
|
||||
{{ t('confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
@ -116,19 +171,22 @@ const weappTableData:{
|
||||
limit: number,
|
||||
total: number,
|
||||
loading: boolean,
|
||||
data: AnyObject
|
||||
data: AnyObject,
|
||||
version_info: AnyObject
|
||||
} = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
data: []
|
||||
data: [],
|
||||
version_info: {}
|
||||
})
|
||||
const form = ref({
|
||||
desc: '',
|
||||
code: '',
|
||||
path: '',
|
||||
content: ''
|
||||
type:1,
|
||||
version: '',
|
||||
code1: '1',
|
||||
code2: '0',
|
||||
code3: '0'
|
||||
})
|
||||
const uploadSuccessShowDialog = ref(false)
|
||||
const authCode = ref('')
|
||||
@ -161,7 +219,62 @@ const handleClick = (val: any) => {
|
||||
router.push({ path: activeName.value })
|
||||
}
|
||||
const ruleFormRef = ref<any>(null)
|
||||
|
||||
const cloudRuleFormRef = ref<any>(null)
|
||||
// 表单校验规则
|
||||
const formRules = reactive({
|
||||
code1: [
|
||||
{
|
||||
validator: (rule: any, value: number, callback: any) => {
|
||||
if (value < 1) {
|
||||
callback(new Error(t('必须大于1')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
code2: [
|
||||
{
|
||||
validator: (rule: any, value: number, callback: any) => {
|
||||
if (value < 0) {
|
||||
callback(new Error(t('必须大于0')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
code3: [
|
||||
{
|
||||
validator: (rule: any, value: number, callback: any) => {
|
||||
if (value < 0) {
|
||||
callback(new Error(t('必须大于0')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
version:[
|
||||
{
|
||||
required: true,
|
||||
validator: (rule: any, value: string, callback: any) => {
|
||||
if(form.value.type == 2){
|
||||
if(!form.value.code1 || !form.value.code2 || !form.value.code3){
|
||||
callback(new Error(t('请填写版本号')));
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
/**
|
||||
* 获取版本列表
|
||||
*/
|
||||
@ -177,6 +290,7 @@ const getWeappVersionListFn = (page: number = 1) => {
|
||||
weappTableData.data = res.data.data
|
||||
weappTableData.total = res.data.total
|
||||
if (page == 1 && weappTableData.data.length && weappTableData.data[0].status == 0) getWeappUploadLogFn(weappTableData.data[0].task_key)
|
||||
weappTableData.version_info = res.data.version_info
|
||||
}).catch(() => {
|
||||
weappTableData.loading = false
|
||||
})
|
||||
@ -184,10 +298,56 @@ const getWeappVersionListFn = (page: number = 1) => {
|
||||
|
||||
getWeappVersionListFn()
|
||||
|
||||
const openDialog = () => {
|
||||
if (!authCode.value) {
|
||||
authElMessageBox()
|
||||
return
|
||||
}
|
||||
if (!weappConfig.value.app_id) {
|
||||
configElMessageBox()
|
||||
return
|
||||
}
|
||||
form.value = {
|
||||
type:1,
|
||||
version: '',
|
||||
code1: '1',
|
||||
code2: '0',
|
||||
code3: '0'
|
||||
}
|
||||
cloudVersionDialogVisible.value = true
|
||||
}
|
||||
const handleClose = () => {
|
||||
ruleFormRef.value.clearValidate()
|
||||
}
|
||||
|
||||
const cloudVersionDialogVisible = ref(false)
|
||||
const handleCloseCloudVersion = () => {
|
||||
cloudVersionDialogVisible.value = false
|
||||
form.value = {
|
||||
type:1,
|
||||
version: '',
|
||||
code1: '1',
|
||||
code2: '0',
|
||||
code3: '0'
|
||||
}
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
cloudRuleFormRef.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
if (form.value.type == 2) {
|
||||
form.value.version = `${form.value.code1}.${form.value.code2}.${form.value.code3}`
|
||||
}
|
||||
delete form.value.code1
|
||||
delete form.value.code2
|
||||
delete form.value.code3
|
||||
delete form.value.type
|
||||
cloudVersionDialogVisible.value = false
|
||||
insert()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const uploading = ref(false)
|
||||
const insert = () => {
|
||||
if (!authCode.value) {
|
||||
@ -206,7 +366,7 @@ const insert = () => {
|
||||
|
||||
setWeappVersion(form.value).then(res => {
|
||||
getWeappVersionListFn()
|
||||
getWeappPreviewImage()
|
||||
getWeappPreviewImage()
|
||||
uploading.value = false
|
||||
}).catch(() => {
|
||||
uploading.value = false
|
||||
|
||||
@ -250,10 +250,6 @@ getWeappConfig().then(res => {
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
getWeappConfig().then(res => {
|
||||
Object.assign(res.data)
|
||||
})
|
||||
|
||||
/**
|
||||
* 复制
|
||||
*/
|
||||
|
||||
@ -36,6 +36,11 @@
|
||||
<el-input v-model.trim="formData.app_secret" :placeholder="t('appSecretPlaceholder')" class="input-width" clearable />
|
||||
<div class="form-tip">{{ t('wechatAppsecretTips') }}</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- <el-form-item :label="t('wechatBaseUri')" prop="base_uri" v-if="!formData.is_authorization">
|
||||
<el-input v-model.trim="formData.base_uri" :placeholder="t('wechatBaseUriPlaceholder')" class="input-width" clearable />
|
||||
<div class="form-tip">{{ t('wechatBaseUriTips') }}</div>
|
||||
</el-form-item> -->
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card !border-none mt-[15px]" shadow="never">
|
||||
@ -140,7 +145,8 @@ const formData = reactive<Record<string, any>>({
|
||||
token: '',
|
||||
encoding_aes_key: '',
|
||||
encryption_type: 'not_encrypt',
|
||||
is_authorization: 0
|
||||
is_authorization: 0,
|
||||
base_uri: ''
|
||||
})
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
@ -63,6 +63,23 @@
|
||||
<el-switch v-model="diyStore.global.bottomTabBar.isShow" />
|
||||
<div class="text-sm text-gray-400">{{ t('tabbarSwitchTips') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('选择导航')">
|
||||
<tabbar-select-popup v-model="diyStore.global.bottomTabBar.designNav" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="edit-attr-item-wrap" v-if="diyStore.global.copyright.control">
|
||||
<h3 class="mb-[10px]">{{ t('版权信息内容') }}</h3>
|
||||
<el-form label-width="80px" class="px-[10px]">
|
||||
<el-form-item :label="t('版权信息')" class="display-block">
|
||||
<el-switch v-model="diyStore.global.copyright.isShow" />
|
||||
<div class="text-sm text-gray-400">{{ t('此处控制当前页面版权信息是否显示') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('文字颜色')" class="display-block">
|
||||
<el-color-picker v-model="diyStore.global.copyright.textColor" show-alpha />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="edit-attr-item-wrap">
|
||||
@ -178,6 +195,7 @@ import { t } from '@/lang'
|
||||
import { watch, ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
import { img } from '@/utils/common'
|
||||
import tabbarSelectPopup from './tabbar-select-popup.vue'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
|
||||
|
||||
211
admin/src/app/views/diy/components/tabbar-select-popup.vue
Normal file
211
admin/src/app/views/diy/components/tabbar-select-popup.vue
Normal file
@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div>
|
||||
<div @click="show">
|
||||
<slot>
|
||||
<el-input v-model="selectData.title" :placeholder="t('请选择底部导航')" readonly class="link-input">
|
||||
<template #suffix>
|
||||
<div @click.stop="clear">
|
||||
<el-icon v-if="selectData.key">
|
||||
<Close />
|
||||
</el-icon>
|
||||
<el-icon v-else>
|
||||
<ArrowRight />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</slot>
|
||||
</div>
|
||||
<el-dialog v-model="showDialog" :title="t('底部导航选择')" width="850px" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
<el-table class="" :data="tableData.data" size="large" v-loading="tableData.loading" height="400px">
|
||||
<template #empty>
|
||||
<span>{{ !tableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
<el-table-column min-width="7%">
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.checked" @change="handleCheckChange($event,row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="title" :label="t('title')" min-width="30%" >
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.info.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="key" :label="t('key')" min-width="30%"/>
|
||||
|
||||
<el-table-column :label="t('type')" min-width="30%">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.info.type === 'app' ? t('app') : t('addon') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="tableData.total"
|
||||
@size-change="loadBottomNavList()" @current-change="loadBottomNavList" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" @click="save">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref, reactive, nextTick,computed } from 'vue'
|
||||
import { FormInstance, ElMessage } from "element-plus";
|
||||
import { getDiyBottomList } from '@/app/api/diy'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
const prop = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
key: '',
|
||||
title: '',
|
||||
}
|
||||
}
|
||||
},
|
||||
ignore: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
})
|
||||
|
||||
const clear = () => {
|
||||
selectData.value = {
|
||||
key: '',
|
||||
title: ''
|
||||
}
|
||||
setTimesSelected()
|
||||
}
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'confirm', 'success'])
|
||||
|
||||
const selectData: any = computed({
|
||||
get() {
|
||||
return prop.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
const searchFormRef = ref<FormInstance>()
|
||||
|
||||
const timeListTableRef = ref()
|
||||
const showDialog = ref(false)
|
||||
const show = () => {
|
||||
showDialog.value = true
|
||||
loadBottomNavList()
|
||||
}
|
||||
|
||||
const tableData: any = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
})
|
||||
|
||||
// 获取自定义页面列表
|
||||
const loadBottomNavList = (page: number = 1) => {
|
||||
tableData.loading = true
|
||||
tableData.page = page
|
||||
|
||||
getDiyBottomList({}).then(res => {
|
||||
tableData.loading = false
|
||||
|
||||
const len = Math.ceil(res.data.length / tableData.limit)
|
||||
const data = cloneDeep(res.data)
|
||||
const dataGather = []
|
||||
for (let i = 0; i < len; i++) {
|
||||
dataGather[i] = data.splice(0, tableData.limit)
|
||||
}
|
||||
tableData.data = dataGather[tableData.page - 1]
|
||||
tableData.data.forEach((item: any) => {
|
||||
item.checked = item.key == selectData.value.key
|
||||
})
|
||||
tableData.total = res.data.length
|
||||
setTimesSelected();
|
||||
}).catch(() => {
|
||||
tableData.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
loadBottomNavList()
|
||||
const handleCheckChange = (isSelect: any, row: any) => {
|
||||
if (isSelect) {
|
||||
selectData.value = {
|
||||
key: row.key,
|
||||
title: row.info.title
|
||||
};
|
||||
} else {
|
||||
selectData.value = {
|
||||
key: '',
|
||||
title: ''
|
||||
};
|
||||
}
|
||||
setTimesSelected()
|
||||
}
|
||||
|
||||
// // 表格设置选中状态
|
||||
const setTimesSelected = () => {
|
||||
nextTick(() => {
|
||||
for (let i = 0; i < tableData.data.length; i++) {
|
||||
tableData.data[i].checked = false
|
||||
if (selectData.value.key == tableData.data[i].key) {
|
||||
tableData.data[i].checked = true
|
||||
selectData.value.key = tableData.data[i].key
|
||||
selectData.value.title = tableData.data[i].info.title
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
|
||||
loadBottomNavList()
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
if (selectData.value.key == '') {
|
||||
ElMessage({
|
||||
type: 'warning',
|
||||
message: `${ t('请选择底部导航') }`
|
||||
})
|
||||
return;
|
||||
}
|
||||
selectData.value = {
|
||||
key: selectData.value.key,
|
||||
title: selectData.value.title
|
||||
}
|
||||
showDialog.value = false;
|
||||
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
showDialog,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-item-wrap {
|
||||
margin-right: 10px !important;
|
||||
margin-bottom: 10px !important;
|
||||
|
||||
&.last-child {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -185,7 +185,7 @@
|
||||
<el-slider v-model="diyStore.editComponent.margin.top" show-input size="small" :min="-100" class="ml-[10px] diy-nav-slider" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('marginBottom')" v-if="diyStore.editComponent.ignore.indexOf('marginBottom') == -1">
|
||||
<el-slider v-model="diyStore.editComponent.margin.bottom" show-input size="small" class="ml-[10px] diy-nav-slider" />
|
||||
<el-slider v-model="diyStore.editComponent.margin.bottom" show-input size="small" class="ml-[10px] diy-nav-slider" :min="-100" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('marginBoth')" v-if="diyStore.editComponent.ignore.indexOf('marginBoth') == -1">
|
||||
<el-slider v-model="diyStore.editComponent.margin.both" show-input size="small" class="ml-[10px] diy-nav-slider" />
|
||||
@ -455,7 +455,7 @@ initPage({
|
||||
diyStore.components.push(com)
|
||||
}
|
||||
}
|
||||
|
||||
console.log( component.value )
|
||||
loadDiyTemplatePages(data.type)
|
||||
|
||||
// 加载预览
|
||||
|
||||
@ -256,7 +256,7 @@ const editEvent = (data: any) => {
|
||||
// 设为使用
|
||||
const setUse = (id: any) => {
|
||||
setUseDiyPage({ id }).then(() => {
|
||||
loadDiyPageList()
|
||||
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
|
||||
})
|
||||
}
|
||||
const repeat = ref(false)
|
||||
@ -275,7 +275,7 @@ const copyEvent = (id: any) => {
|
||||
|
||||
copyDiy({ id }).then((res: any) => {
|
||||
if (res.code == 1) {
|
||||
loadDiyPageList()
|
||||
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
|
||||
}
|
||||
repeat.value = false
|
||||
}).catch(err => {
|
||||
@ -294,7 +294,7 @@ const deleteEvent = (id: number) => {
|
||||
}
|
||||
).then(() => {
|
||||
deleteDiyPage(id).then(() => {
|
||||
loadDiyPageList()
|
||||
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
@ -358,7 +358,7 @@ const shareEvent = async (formEl: FormInstance | undefined) => {
|
||||
id: shareFormId.value,
|
||||
share: JSON.stringify(shareFormData)
|
||||
}).then(() => {
|
||||
loadDiyPageList()
|
||||
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
|
||||
shareDialogVisible.value = false
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
@ -30,8 +30,8 @@
|
||||
|
||||
<div class="w-[700px]">
|
||||
<div class="flex flex-wrap">
|
||||
<!-- 多应用切换启动页 -->
|
||||
<el-button type="primary" @click="showDialog = true" v-if="siteApps.length > 1">{{ t('changePage') }}</el-button>
|
||||
<!-- 多应用切换启动页 v-if="siteApps.length > 1" -->
|
||||
<el-button type="primary" @click="showDialog = true">{{ t('changePage') }}</el-button>
|
||||
<el-button type="primary" @click="toDecorate()" v-show="page.use_template.action == 'decorate'" class="ml-[12px]">{{ t('decorate') }}</el-button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="bottomNavTableData.page" v-model:page-size="bottomNavTableData.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="bottomNavTableData.total"
|
||||
@size-change="loadbottomNavList()" @current-change="loadbottomNavList" />
|
||||
@size-change="loadBottomNavList()" @current-change="loadBottomNavList" />
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
|
||||
@ -91,7 +91,7 @@ const editEvent = (data)=> {
|
||||
<!-- 设置弹窗标题 -->
|
||||
<style scoped>
|
||||
/* 使用深度选择器 */
|
||||
::v-deep .custom-theme-dialog .el-dialog__title {
|
||||
:deep(.custom-theme-dialog .el-dialog__title) {
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -159,10 +159,10 @@
|
||||
<spread-popup ref="spreadPopupRef" />
|
||||
|
||||
<!-- 表单提交成功页弹出框 -->
|
||||
<form-submit-popup ref="formSubmitPopupRef" @complete="loadDiyFormList" />
|
||||
<form-submit-popup ref="formSubmitPopupRef" @complete="loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)" />
|
||||
|
||||
<!-- 表单填写设置弹出框 -->
|
||||
<form-write-popup ref="formWritePopupRef" @complete="loadDiyFormList" />
|
||||
<form-write-popup ref="formWritePopupRef" @complete="loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)" />
|
||||
|
||||
<records-detail ref="recordsDetailDialog"/>
|
||||
|
||||
@ -352,7 +352,7 @@ const copyEvent = (id: any) => {
|
||||
|
||||
copyForm({ form_id: id }).then((res: any) => {
|
||||
if (res.code == 1) {
|
||||
loadDiyFormList()
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
}
|
||||
repeat.value = false
|
||||
}).catch(() => {
|
||||
@ -371,7 +371,7 @@ const deleteEvent = (form_id: number) => {
|
||||
}
|
||||
).then(() => {
|
||||
deleteDiyForm({ form_ids: [form_id] }).then(() => {
|
||||
loadDiyFormList()
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
@ -438,7 +438,7 @@ const batchDeleteForms = () => {
|
||||
deleteDiyForm({
|
||||
form_ids
|
||||
}).then(() => {
|
||||
loadDiyFormList()
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
repeat.value = false
|
||||
}).catch(() => {
|
||||
repeat.value = false
|
||||
@ -497,7 +497,7 @@ const shareEvent = async (formEl: FormInstance | undefined) => {
|
||||
form_id: shareFormId.value,
|
||||
share: JSON.stringify(shareFormData)
|
||||
}).then(() => {
|
||||
loadDiyFormList()
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
shareDialogVisible.value = false
|
||||
}).catch(() => {
|
||||
})
|
||||
@ -510,12 +510,12 @@ const spreadPopupRef = ref(null)
|
||||
|
||||
const spreadEvent = (data: any) => {
|
||||
const pagePath = '/app/pages/index/diy_form'
|
||||
const columnName = 'form_id'
|
||||
const columnValue = data.form_id
|
||||
const paramsArr = [
|
||||
{ name: 'form_id', value: data.form_id },
|
||||
];
|
||||
const title = '表单推广'
|
||||
const folder = 'diy_form'
|
||||
|
||||
spreadPopupRef.value?.show(pagePath, columnName, columnValue, title, folder)
|
||||
spreadPopupRef.value?.show(pagePath, paramsArr, title, folder);
|
||||
}
|
||||
|
||||
// 表单提交成功页弹出框
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-drawer v-model="showDialog" title="退款详情" direction="rtl" :before-close="handleClose" class="member-detail-drawer">
|
||||
<el-drawer v-model="showDialog" title="退款详情" direction="rtl" :before-close="handleClose" size="1300px">
|
||||
<div class="main-container" v-loading="loading">
|
||||
<div class="relative" v-if="formData">
|
||||
<div class="flex mb-[20px] justify-between text-[15px]">
|
||||
@ -152,8 +152,4 @@ defineExpose({
|
||||
setFormData
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.member-detail-drawer{
|
||||
width: 1300px !important;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -3,11 +3,16 @@
|
||||
<!-- <div class="flex justify-between items-center">
|
||||
<layoutHeader></layoutHeader>
|
||||
</div> -->
|
||||
<div class="flex justify-between items-center py-[24px] pl-[62px] pr-[64px] home-head">
|
||||
<div class="flex items-center" v-if="webConfig">
|
||||
<img class="w-[32x] h-[32px] rounded-full" v-if="webConfig.icon" :src="img(webConfig.icon)" alt="">
|
||||
<img class="w-[32x] h-[32px] rounded-full" v-else src="@/app/assets/images/icon-addon.png" alt="">
|
||||
<span class="ml-[10px] text-[16px] font-bold">{{webConfig.site_name}}</span>
|
||||
<div class="flex justify-between items-center py-[24px] pr-[64px] home-head">
|
||||
<div class="flex items-center w-[219px] justify-center" v-if="webConfig">
|
||||
<div class="max-w-[82px] h-[40px] overflow-hidden">
|
||||
<el-image class="h-[40px]" :src="img(webConfig.icon)" fit="contain">
|
||||
<template #error>
|
||||
<div class="flex justify-center items-center w-full h-full"><img class="max-w-[70px]" src="@/app/assets/images/logo.default.png" alt="" object-fit="contain"></div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
<span class="ml-[10px] max-w-[120px] text-[16px] font-bold truncate">{{webConfig.site_name}}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div v-if="userStore.userInfo.is_super_admin" class="border-primary border-[1px] h-[30px] px-[15px] flex items-center rounded-[6px] mr-[10px] cursor-pointer" @click="toHome">
|
||||
@ -198,11 +203,11 @@
|
||||
{{ item.site_group.group_name }}
|
||||
</div>
|
||||
<el-scrollbar class="flex pb-[20px] pt-[4px] box-border !h-[260px] w-[100%] scrollbarBox">
|
||||
<div class="flex mx-[30px] mt-[14px] leading-[1] items-center w-full" v-for="app in item.site_group.app_name">
|
||||
<div class="flex mx-[30px] mt-[14px] leading-[1] items-center" v-for="app in item.site_group.app_name">
|
||||
<div class="nc-iconfont nc-icon-duiV6mm text-[#466CEA]"></div>
|
||||
<div class="text-[14px] text-[#666666] ml-[3px] truncate">{{ app }}</div>
|
||||
</div>
|
||||
<div class="flex mx-[30px] mt-[14px] leading-[1] text-center w-full" v-for="addon in item.site_group.addon_name">
|
||||
<div class="flex mx-[30px] mt-[14px] leading-[1] text-center" v-for="addon in item.site_group.addon_name">
|
||||
<div class="nc-iconfont nc-icon-duiV6mm text-[#466CEA]"></div>
|
||||
<div class="text-[14px] text-[#666666] ml-[3px] truncate">{{ addon }}</div>
|
||||
</div>
|
||||
@ -358,6 +363,7 @@ const selectSite = (site: any) => {
|
||||
}
|
||||
|
||||
const toHome = () => {
|
||||
window.localStorage.setItem('admin.siteId', 0)
|
||||
if (!window.localStorage.getItem('admin.token')) {
|
||||
window.localStorage.setItem('admin.token', getToken())
|
||||
window.localStorage.setItem('admin.comparisonTokenStorage', getToken())
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -33,8 +33,8 @@
|
||||
<el-main class="login-main w-full mt-[120px] justify-center h-0" v-else-if="!imgLoading && loginType == 'site'">
|
||||
<div>
|
||||
<div class="login-main-left flex flex-col items-center justify-center">
|
||||
<div class="w-[130px] h-[40px] overflow-hidden" v-if="webSite.site_login_logo">
|
||||
<el-image class="w-full h-full" :src="img(webSite.site_login_logo)" fit="contain">
|
||||
<div class="w-[130px] h-[40px] overflow-hidden" v-if="loginConfig.site_login_logo">
|
||||
<el-image class="w-full h-full" :src="img(loginConfig.site_login_logo)" fit="contain">
|
||||
<template #error>
|
||||
<div class="flex justify-center items-center w-full h-full">
|
||||
<img class="max-w-[130px]" src="@/app/assets/images/white_logo.png" alt="" object-fit="contain" />
|
||||
@ -119,7 +119,7 @@ route.redirectedFrom && (route.query.redirect = route.redirectedFrom.path)
|
||||
const webSite: any = computed(() => useSystemStore().website)
|
||||
|
||||
const siteBackgroundStyle = computed(() => ({
|
||||
backgroundImage: webSite.value?.site_login_bg_img ? `url(${img(webSite.value.site_login_bg_img)})` : '',
|
||||
backgroundImage: loginConfig.value?.site_login_bg_img ? `url(${img(loginConfig.value.site_login_bg_img)})` : '',
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-drawer v-model="showDialog" title="核销记录详情" direction="rtl" :before-close="handleClose" class="member-detail-drawer">
|
||||
<el-drawer v-model="showDialog" title="核销记录详情" direction="rtl" :before-close="handleClose" size="1300px">
|
||||
<div class="main-container" v-loading="loading">
|
||||
<el-tabs v-model="activeName" class="pb-[10px]" @tab-change="handleClick">
|
||||
<el-tab-pane label="核销信息" name="verifyInfo" />
|
||||
@ -28,7 +28,7 @@
|
||||
<div class="flex items-center mt-[15px]">
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('核销人员') }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ verifyData.member ? verifyData.member.nickname : '--' }}
|
||||
{{ (verifyData.is_admin == 1 ? '后台核销' : verifyData.member?.nickname) || '--' }}
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
@ -156,8 +156,4 @@ defineExpose({
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.member-detail-drawer{
|
||||
width: 1300px !important;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -39,15 +39,17 @@
|
||||
<template #empty>
|
||||
<span>{{ !recordTable.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
|
||||
<el-table-column prop="code" :show-overflow-tooltip="true" :label="t('verifyCode')" align="left" min-width="150" />
|
||||
<el-table-column prop="type_name" :label="t('verifyType')" align="left" min-width="150" />
|
||||
<el-table-column :label="t('verifyTime')" min-width="180" align="center" :show-overflow-tooltip="true">
|
||||
<el-table-column :label="t('verifyTime')" min-width="180" align="left" :show-overflow-tooltip="true">
|
||||
<template #default="{ row }">
|
||||
{{ row.create_time || '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="member.nickname" :label="t('verifyer')" min-width="180" align="center">
|
||||
<el-table-column prop="type_name" :label="t('verifyType')" align="left" min-width="150" />
|
||||
<el-table-column prop="code" :show-overflow-tooltip="true" :label="t('verifyCode')" align="left" min-width="150" />
|
||||
<el-table-column :label="t('verifyer')" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.is_admin == 1 ? '后台核销' : row.member?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('operation')" align="right" fixed="right" width="100">
|
||||
<template #default="{ row }">
|
||||
@ -89,7 +91,7 @@ const recordTable = reactive({
|
||||
data: [],
|
||||
searchParam: {
|
||||
code: '',
|
||||
type: '',
|
||||
type: route.query.type || '',
|
||||
verifier_member_id: '',
|
||||
create_time: []
|
||||
}
|
||||
|
||||
130
admin/src/app/views/marketing/verify_index.vue
Normal file
130
admin/src/app/views/marketing/verify_index.vue
Normal file
@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="main-container min-h-[300px]">
|
||||
<div class="">
|
||||
<el-card class="box-card mt-[10px]!border-none table-search-wrap" shadow="never">
|
||||
<div class="flex p-[10px] items-end">
|
||||
<div class="flex items-center w-[500px] h-[45px] border-[2px] border-primary rounded-l-lg">
|
||||
<span class="h-[15px] ml-[10px] pr-[10px] border-r-[1px] border-[#DEE1E7] leading-[1]">
|
||||
<img class="w-[15px]" src="@/app/assets/images/write.png" />
|
||||
</span>
|
||||
<input class="w-[400px] h-[40px] outline-none pl-[10px] text-[18px] bg-transparent"
|
||||
v-model="verifycode" />
|
||||
</div>
|
||||
<div class="bg-primary h-[45px] flex items-center px-[20px] rounded-[10px] ml-[10px] text-[#fff] cursor-pointer"
|
||||
@click="handelVerify">
|
||||
<span>{{ t("核销") }}</span>
|
||||
</div>
|
||||
<div class="bg-primary h-[45px] flex items-center px-[20px] rounded-[10px] ml-[10px] text-[#fff] cursor-pointer"
|
||||
@click="router.push('/marketing/verify')">
|
||||
<span>{{ t("核销记录") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
</div>
|
||||
<el-dialog v-model="showDialog" :title="t('核销')" width="500px" :destroy-on-close="true">
|
||||
<div>
|
||||
<h3 class="panel-title !text-sm">{{ t('核销信息') }}</h3>
|
||||
<div class="flex items-center mt-[15px]">
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('核销码') }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{verifycode}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center mt-[15px]">
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('核销类型') }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ verifyInfo.type_name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center mt-[15px]" v-for="(item,index) in verifyInfo.fixed" :key="index">
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ item.title }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ item.value }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="box-card mt-[15px] !border-none" shadow="never" v-for="(item,index) in verifyContentData.diy" :key="index">
|
||||
<h3 class="panel-title !text-sm">{{ item.title }}</h3>
|
||||
|
||||
<div class="flex items-center mt-[15px]" v-for="(subItem,subIndex) in item.list" :key="subIndex">
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ subItem.title }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ subItem.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[15px]">
|
||||
<h3 class="panel-title !text-sm">{{ t('商品信息') }}</h3>
|
||||
<el-table :data="verifyGoodsList" size="large">
|
||||
<el-table-column :label="t('商品名称')" align="left" width="300">
|
||||
<template #default="{ row }">
|
||||
<div class="flex">
|
||||
<div class="flex items-center shrink-0">
|
||||
<el-image v-if="row.cover" class="w-[50px] h-[50px] mr-[10rpx]" :src="img(row.cover)" fit="contain">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<img class="w-[50px] h-[50px] mr-[10rpx]" src="@/app/assets/images/goods_default.png" />
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<img v-else class="w-[50px] h-[50px] mr-[10rpx]" src="@/app/assets/images/goods_default.png" fit="contain" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<p class="multi-hidden text-[14px]">{{ row.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="num" :label="t('数量')" min-width="50" align="right" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="verifyCode">{{ t("confirm") }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang='ts' setup>
|
||||
import { ref } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import {img} from '@/utils/common'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getVerifyDetailInfo ,verify} from '@/app/api/verify'
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const verifycode = ref('')
|
||||
const verifyInfo = ref<any>([])
|
||||
const verifyContentData: any = ref({})
|
||||
const verifyGoodsList: any = ref([])
|
||||
const showDialog = ref(false)
|
||||
const handelVerify = () => {
|
||||
if (verifycode.value == '') return
|
||||
getVerifyDetailInfo(verifycode.value).then((res) => {
|
||||
showDialog.value = true
|
||||
verifyInfo.value = res.data
|
||||
verifyContentData.value = res.data.value.content || {}
|
||||
verifyGoodsList.value = res.data.value.list || []
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const isSubmit = ref(false)
|
||||
const verifyCode = () => {
|
||||
if (verifycode.value == '') return
|
||||
if (isSubmit.value) return
|
||||
isSubmit.value = true
|
||||
verify(verifycode.value).then(() => {
|
||||
showDialog.value = false
|
||||
isSubmit.value = false
|
||||
}).catch(() => {
|
||||
isSubmit.value = false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-drawer v-model="showDialog" :title="popTitle" direction="rtl" :before-close="handleClose" class="member-detail-drawer">
|
||||
<el-drawer v-model="showDialog" :title="popTitle" direction="rtl" :before-close="handleClose" class="member-detail-drawer" size="1300px">
|
||||
<div class="main-container" v-loading="loading">
|
||||
<div class="bg-page py-[20px] pr-[30px] relative flex">
|
||||
<div class="member-info w-[250px]">
|
||||
@ -375,17 +375,20 @@ const getMemberInfoFn = async (bool=false) => {
|
||||
Object.keys(data).forEach((item) => {
|
||||
formData[item] = data[item]
|
||||
})
|
||||
|
||||
if(!data.member_label_array){
|
||||
formData.member_label_array =[]
|
||||
formData.member_label_name=''
|
||||
}
|
||||
if (formData?.member_label_array && Object.keys(formData.member_label_array)?.length) {
|
||||
formData.member_label = Object.values(formData.member_label_array).map((item: any, index) => {
|
||||
return item.label_id
|
||||
})
|
||||
|
||||
|
||||
formData.member_label_name = Object.values(formData.member_label_array).map((item: any, index) => {
|
||||
return item.label_name
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
loading.value = false
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
@ -406,8 +409,4 @@ defineExpose({
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.member-detail-drawer{
|
||||
width: 1300px !important;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -121,7 +121,7 @@ const formRules = computed(() => {
|
||||
if (!value) return callback()
|
||||
|
||||
// 非空值时,验证身份证号格式
|
||||
const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/
|
||||
const reg = /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/
|
||||
if (!reg.test(value)) {
|
||||
callback(new Error('请输入正确的身份证号'))
|
||||
} else {
|
||||
|
||||
@ -147,7 +147,7 @@ const deleteEvent = (id: number) => {
|
||||
}
|
||||
).then(() => {
|
||||
deleteMemberLevel(id).then(() => {
|
||||
loadMemberLevelList()
|
||||
loadMemberLevelList(getTablePageStorage(memberLevelTableData.searchParam).page);
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
|
||||
@ -28,7 +28,7 @@ const componentStyle = computed(() => {
|
||||
if (prop.value.weight) {
|
||||
style += 'font-weight: bold;'
|
||||
}
|
||||
if (!prop.value.fontFamily || prop.value.fontFamily == 'static/font/SourceHanSansCN-Regular.ttf') {
|
||||
if (!prop.value.fontFamily || prop.value.fontFamily == 'static/font/PingFang-Medium.ttf') {
|
||||
style += 'font-family: poster_default_font;'
|
||||
}
|
||||
const box: any = document.getElementById(prop.value.id)
|
||||
|
||||
@ -28,7 +28,7 @@ const componentStyle = computed(() => {
|
||||
if (prop.value.weight) {
|
||||
style += 'font-weight: bold;'
|
||||
}
|
||||
if (!prop.value.fontFamily || prop.value.fontFamily == 'static/font/SourceHanSansCN-Regular.ttf') {
|
||||
if (!prop.value.fontFamily || prop.value.fontFamily == 'static/font/PingFang-Medium.ttf') {
|
||||
style += 'font-family: poster_default_font;'
|
||||
}
|
||||
const box: any = document.getElementById(prop.value.id)
|
||||
|
||||
@ -28,7 +28,7 @@ const componentStyle = computed(() => {
|
||||
if (prop.value.weight) {
|
||||
style += 'font-weight: bold;'
|
||||
}
|
||||
if (!prop.value.fontFamily || prop.value.fontFamily == 'static/font/SourceHanSansCN-Regular.ttf') {
|
||||
if (!prop.value.fontFamily || prop.value.fontFamily == 'static/font/PingFang-Medium.ttf') {
|
||||
style += 'font-family: poster_default_font;'
|
||||
}
|
||||
const box: any = document.getElementById(prop.value.id)
|
||||
|
||||
@ -28,7 +28,7 @@ const componentStyle = computed(() => {
|
||||
if (prop.value.weight) {
|
||||
style += `font-weight: bold;`;
|
||||
}
|
||||
if (!prop.value.fontFamily || prop.value.fontFamily == 'static/font/SourceHanSansCN-Regular.ttf') {
|
||||
if (!prop.value.fontFamily || prop.value.fontFamily == 'static/font/PingFang-Medium.ttf') {
|
||||
style += `font-family: poster_default_font;`;
|
||||
}
|
||||
let box: any = document.getElementById(prop.value.id)
|
||||
|
||||
@ -529,17 +529,20 @@ const save = (callback: any) => {
|
||||
|
||||
const api = posterStore.id ? editPoster : addPoster
|
||||
api(data).then((res: any) => {
|
||||
isRepeat.value = false
|
||||
// isRepeat.value = false
|
||||
if (res.code == 1) {
|
||||
if (posterStore.id) {
|
||||
isRepeat.value = false // 不刷新
|
||||
} else {
|
||||
// isRepeat.value = false
|
||||
location.href = `${location.origin}${backPath}`
|
||||
}
|
||||
if (callback) callback(res.data.id)
|
||||
}
|
||||
}).catch(() => {
|
||||
isRepeat.value = false
|
||||
}).finally(() => {
|
||||
// isRepeat.value = false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,13 @@
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
<el-button type="primary" class="w-[100px]" @click="dialogVisible = true">{{ t('添加海报') }}</el-button>
|
||||
</div>
|
||||
|
||||
<div class="mt-[20px]" v-if="!isImagick && !posterTableData.loading">
|
||||
<el-alert type="warning" show-icon :closable="false">
|
||||
<template #title>
|
||||
<span class="!text-[14px]">检测到PHP未安装ImageMagick扩展,需安装后才能使用海报功能</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
</div>
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="posterTableData.searchParam" ref="searchFormDiyPosterRef">
|
||||
<el-form-item :label="t('posterName')" prop="name">
|
||||
@ -103,7 +109,7 @@ import { reactive, ref, computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { ElMessageBox, FormInstance } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { getPosterPageList, getPosterType, modifyPosterStatus, modifyPosterDefault, deletePoster, getPreviewPoster } from '@/app/api/poster'
|
||||
import { getPosterPageList, getPosterType, modifyPosterStatus, modifyPosterDefault, deletePoster, getPreviewPoster ,checkImagick} from '@/app/api/poster'
|
||||
import { img, setTablePageStorage, getTablePageStorage } from '@/utils/common'
|
||||
|
||||
const router = useRouter()
|
||||
@ -219,7 +225,7 @@ const modifyPosterStatusFn = (id:any, status:any) => {
|
||||
modifyPosterStatus({
|
||||
id, status
|
||||
}).then((res) => {
|
||||
loadPosterPageList()
|
||||
loadPosterPageList(getTablePageStorage(posterTableData.searchParam).page)
|
||||
isRepeat.value = false
|
||||
})
|
||||
}
|
||||
@ -231,7 +237,7 @@ const modifyPosterDefaultFn = (id:any) => {
|
||||
modifyPosterDefault({
|
||||
id
|
||||
}).then((res) => {
|
||||
loadPosterPageList()
|
||||
loadPosterPageList(getTablePageStorage(posterTableData.searchParam).page)
|
||||
isRepeat.value = false
|
||||
})
|
||||
}
|
||||
@ -248,7 +254,7 @@ const deleteEvent = (id: number) => {
|
||||
if (isRepeat.value) return
|
||||
isRepeat.value = true
|
||||
deletePoster(id).then(() => {
|
||||
loadPosterPageList()
|
||||
loadPosterPageList(getTablePageStorage(posterTableData.searchParam).page)
|
||||
isRepeat.value = false
|
||||
}).catch(() => {
|
||||
isRepeat.value = false
|
||||
@ -271,7 +277,7 @@ const previewPoster = (data: any) => {
|
||||
if (res.data) {
|
||||
previewPosterUrl.value = res.data
|
||||
previewDialogVisible.value = true
|
||||
}
|
||||
}
|
||||
isRepeat.value = false
|
||||
})
|
||||
}
|
||||
@ -281,6 +287,15 @@ const resetForm = (formEl: FormInstance | undefined) => {
|
||||
formEl.resetFields()
|
||||
loadPosterPageList()
|
||||
}
|
||||
const isImagick = ref(true)
|
||||
// 判断是否安装imagemagick扩展
|
||||
const checkImagickFn = () => {
|
||||
checkImagick().then((res:any) => {
|
||||
isImagick.value = res.data
|
||||
|
||||
})
|
||||
}
|
||||
checkImagickFn()
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@ -129,7 +129,7 @@ const modifyPrinterStatusEvent = (printer_id: any, status: any) => {
|
||||
printer_id,
|
||||
status
|
||||
}).then((res) => {
|
||||
loadPrinterList()
|
||||
loadPrinterList(getTablePageStorage(printerTable.searchParam).page)
|
||||
isRepeat.value = false
|
||||
})
|
||||
}
|
||||
@ -163,7 +163,7 @@ const deleteEvent = (id: number) => {
|
||||
if (repeat.value) return
|
||||
repeat.value = true
|
||||
deletePrinter(id).then(() => {
|
||||
loadPrinterList()
|
||||
loadPrinterList(getTablePageStorage(printerTable.searchParam).page)
|
||||
repeat.value = false
|
||||
}).catch(() => {
|
||||
repeat.value = false
|
||||
@ -214,7 +214,7 @@ const refreshTokenEvent = (printer_id: any) => {
|
||||
if (repeat.value) return
|
||||
repeat.value = true
|
||||
refreshPrinterToken(printer_id).then((res: any) => {
|
||||
loadPrinterList()
|
||||
loadPrinterList(getTablePageStorage(printerTable.searchParam).page)
|
||||
repeat.value = false
|
||||
}).catch(() => {
|
||||
repeat.value = false
|
||||
|
||||
@ -148,7 +148,7 @@ const deleteEvent = (id: number) => {
|
||||
}
|
||||
).then(() => {
|
||||
deletePrinterTemplate(id).then(() => {
|
||||
loadPrinterTemplateList()
|
||||
loadPrinterTemplateList(getTablePageStorage(printerTemplateTable.searchParam).page)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -23,6 +23,18 @@
|
||||
<upload-image v-model="formData.site_bg" />
|
||||
<div class="form-tip">{{t('siteBgImgTip')}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('siteLoginLogo')">
|
||||
<div>
|
||||
<upload-image v-model="formData.site_login_logo" />
|
||||
<p class="text-[12px] text-[#a9a9a9]">{{ t('siteLoginLogoTips') }}</p>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('siteLoginBgImg')">
|
||||
<div>
|
||||
<upload-image v-model="formData.site_login_bg_img" />
|
||||
<p class="text-[12px] text-[#a9a9a9]">{{ t('siteLoginBgImgTips') }}</p>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
@ -51,7 +63,9 @@ const formData = reactive<Record<string, number | string>>({
|
||||
is_captcha: 0,
|
||||
is_site_captcha: 0,
|
||||
bg: '',
|
||||
site_bg: ''
|
||||
site_bg: '',
|
||||
site_login_logo: '',
|
||||
site_login_bg_img: ''
|
||||
})
|
||||
|
||||
const getFormData = async () => {
|
||||
|
||||
@ -25,7 +25,11 @@
|
||||
<el-form-item :label="t('createTime')">
|
||||
<div class="input-width"> {{ formData.create_time }} </div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('发送结果')">
|
||||
<div class="input-width" v-if="formData.status == 'sending'"> 发送失败 </div>
|
||||
<div class="input-width" v-if="formData.status == 'success'"> 发送成功 </div>
|
||||
<div class="input-width" v-if="formData.status == 'fail'"> {{ formData.result }} </div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
@ -55,7 +59,9 @@ const initialFormData = {
|
||||
name: '',
|
||||
nickname: '',
|
||||
mobile: '',
|
||||
sms_type_name: ''
|
||||
sms_type_name: '',
|
||||
status:'',
|
||||
result:''
|
||||
}
|
||||
const formData: Record<string, any> = reactive({ ...initialFormData })
|
||||
|
||||
|
||||
@ -22,15 +22,19 @@
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('putOnRecordEdit') }}</h3>
|
||||
<el-form-item :label="t('icp')" prop="icp">
|
||||
<el-input v-model.trim="formData.icp" :placeholder="t('icpPlaceholder')" class="input-width" clearable maxlength="20"/>
|
||||
<div class="form-tip">{{ t('网站的ICP备案号,显示在PC端底部') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('govRecord')" >
|
||||
<el-input v-model.trim="formData.gov_record" :placeholder="t('govRecordPlaceholder')" class="input-width" clearable maxlength="50"/>
|
||||
<div class="form-tip">{{ t('公安部门登记的备案信息,显示在PC底部') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('govUrl')" >
|
||||
<el-input v-model.trim="formData.gov_url" :placeholder="t('govUrlPlaceholder')" class="input-width" clearable />
|
||||
<div class="form-tip">{{ t('PC底部显示的网站公安点击跳转的链接') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('marketSupervisionUrl')" >
|
||||
<el-input v-model.trim="formData.market_supervision_url" rows="4" clearable :placeholder="t('marketSupervisionUrlPlaceholder')" class="input-width" />
|
||||
<div class="form-tip">{{ t('PC底部显示的市场监督管理局点击跳转的链接') }}</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@ -10,7 +10,11 @@
|
||||
<el-form-item :label="t('mapKey')" prop="key">
|
||||
<el-input v-model.trim="formData.key" class="input-width" clearable />
|
||||
<span class="ml-2 cursor-pointer tutorial-btn" @click="tutorialFn">{{ t('clickTutorial') }}</span>
|
||||
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn">{{ t('clickSecretKey') }}</span>
|
||||
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn('https://lbs.qq.com/dev/console/key/manage')">{{ t('clickSecretKey') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aMapKey')" prop="key">
|
||||
<el-input v-model.trim="formData.amap_key" class="input-width" clearable />
|
||||
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn('https://lbs.amap.com/')">{{ t('clickSecretKey') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('isOpen')" prop="is_open">
|
||||
<el-switch v-model="formData.is_open" :active-value="1" :inactive-value="0" />
|
||||
@ -42,6 +46,7 @@ const loading = ref(true)
|
||||
const formRef = ref<FormInstance>()
|
||||
const formData = reactive({
|
||||
key: '',
|
||||
amap_key: '',
|
||||
is_open: 0,
|
||||
valid_time: 0
|
||||
})
|
||||
@ -71,9 +76,7 @@ const formRules = computed(() => {
|
||||
const setFormData = async () => {
|
||||
loading.value = true
|
||||
const service_data = await (await getMap()).data
|
||||
formData.key = service_data.key
|
||||
formData.is_open = service_data.is_open
|
||||
formData.valid_time = service_data.valid_time
|
||||
Object.assign(formData, service_data)
|
||||
loading.value = false
|
||||
}
|
||||
setFormData()
|
||||
@ -107,8 +110,8 @@ const tutorialFn = () => {
|
||||
/**
|
||||
* 点击访问腾讯地图
|
||||
*/
|
||||
const secretFn = () => {
|
||||
window.open('https://lbs.qq.com/dev/console/key/manage')
|
||||
const secretFn = (url: string) => {
|
||||
window.open(url)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<h3 class="panel-title !text-sm">{{ payItems.name }}</h3>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center justify-between p-[10px] table-item-border bg">
|
||||
<div class="flex items-center justify-between px-[10px] table-item-border bg h-[50px]">
|
||||
<span class="text-base w-[230px]">{{ t('payType') }}</span>
|
||||
<span class="text-base w-[110px] text-center">{{ t('onState') }}</span>
|
||||
<span class="text-base w-[80px] text-center" v-if="isEdit">{{ t('templateName') }}</span>
|
||||
@ -52,7 +52,6 @@
|
||||
<el-button type="primary" :loading="loading" @click="saveFn(formRef)">{{ t('save') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-for="(item, index) in payTypeList">
|
||||
<component :is="item.setting_component" :ref="(el) => setPayTypeRefs(el, item.key)" v-if="item.setting_component" @complete="setConfigInfo"/>
|
||||
</template>
|
||||
@ -210,6 +209,6 @@ const cancelFn = () => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-item-border {
|
||||
@apply border-b border-[var(--el-border-color)];
|
||||
@apply border-b border-[var(--el-border-color-lighter)];
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -20,18 +20,6 @@
|
||||
<p class="text-[12px] text-[#a9a9a9]">{{ t('iconPlaceholder') }}</p>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('siteLoginLogo')" v-if="appType == 'admin'">
|
||||
<div>
|
||||
<upload-image v-model="formData.site_login_logo" />
|
||||
<p class="text-[12px] text-[#a9a9a9]">{{ t('siteLoginLogoTips') }}</p>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('siteLoginBgImg')" v-if="appType == 'admin'">
|
||||
<div>
|
||||
<upload-image v-model="formData.site_login_bg_img" />
|
||||
<p class="text-[12px] text-[#a9a9a9]">{{ t('siteLoginBgImgTips') }}</p>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('keywords')">
|
||||
<el-input v-model.trim="formData.keywords" :placeholder="t('keywordsPlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||
</el-form-item>
|
||||
|
||||
@ -11,6 +11,10 @@
|
||||
<upload-image v-model="formData.head_img" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('手机号')" prop="mobile">
|
||||
<el-input v-model.trim="formData.mobile" :placeholder="t('请输入手机号')" clearable class="input-width" maxlength="11" show-word-limit />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('userRealName')" prop="real_name">
|
||||
<el-input v-model.trim="formData.real_name" :placeholder="t('userRealNamePlaceholder')" :readonly="realnameInput" @click="realnameInput = false" @blur="realnameInput = true" clearable class="input-width" maxlength="10" show-word-limit />
|
||||
</el-form-item>
|
||||
@ -97,6 +101,7 @@ const formData = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
head_img: '',
|
||||
mobile: '',
|
||||
real_name: '',
|
||||
confirm_password: '',
|
||||
create_site_limit: [],
|
||||
@ -112,6 +117,19 @@ const formRules = computed(() => {
|
||||
password: [
|
||||
{ required: userStore.userInfo && userStore.userInfo.is_super_admin == true, message: t('passwordPlaceholder'), trigger: 'blur' }
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: t('请输入手机号'), trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: string, callback: any) => {
|
||||
if (!Test.mobile(value)) {
|
||||
callback(new Error(t('手机号格式错误')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
real_name: [
|
||||
{ required: true, message: t('userRealNamePlaceholder'), trigger: 'blur' }
|
||||
],
|
||||
@ -173,6 +191,7 @@ const setFormData = (uid: number = 0) => {
|
||||
getUserInfo(uid).then(({ data }) => {
|
||||
formData.value.uid = data.uid
|
||||
formData.value.username = data.username
|
||||
formData.value.mobile = data.mobile
|
||||
formData.value.real_name = data.real_name
|
||||
formData.value.head_img = data.head_img
|
||||
loading.value = false
|
||||
@ -184,6 +203,7 @@ const setFormData = (uid: number = 0) => {
|
||||
username: '',
|
||||
password: '',
|
||||
head_img: '',
|
||||
mobile: '',
|
||||
real_name: '',
|
||||
confirm_password: '',
|
||||
create_site_limit: [],
|
||||
@ -254,7 +274,7 @@ defineExpose({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.displayPass {
|
||||
::v-deep .el-input__inner{
|
||||
:deep(.el-input__inner){
|
||||
-webkit-text-security: disc !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,14 +36,14 @@
|
||||
|
||||
<el-form-item prop="create_time" v-if="isShow">
|
||||
<el-date-picker v-model="siteTableData.searchParam.create_time" type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
|
||||
:end-placeholder="t('endDate')" />
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('createTimeStartDate')"
|
||||
:end-placeholder="t('createTimeEndDate')" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="expire_time" v-if="isShow">
|
||||
<el-date-picker v-model="siteTableData.searchParam.expire_time" type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
|
||||
:end-placeholder="t('endDate')" />
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('expireTimeStartDate')"
|
||||
:end-placeholder="t('expireTimeEndDate')" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
@ -476,8 +476,6 @@ const infoEvent = (data: any) => {
|
||||
router.push({ path: '/admin/site/info', query: { id: data.site_id } })
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 编辑站点详情
|
||||
* @param data
|
||||
|
||||
@ -46,6 +46,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" :label="t('accountNumber')" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="mobile" :label="t('手机号')" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="real_name" :label="t('userRealName')" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="site_num" :label="t('siteNum')" min-width="120" show-overflow-tooltip align="center" />
|
||||
<!-- <el-table-column prop="create_time" :label="t('createTime')" min-width="180" align="center">
|
||||
|
||||
@ -24,27 +24,35 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mb-[20px]">
|
||||
<el-col :span="6">
|
||||
<el-col :span="6" >
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('realname') }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ detail.real_name || '--' }}
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col :span="6" :offset="6">
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('addTime') }}</span>
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('手机号') }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ detail.create_time }}
|
||||
{{ detail.mobile || '--' }}
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mb-[20px]">
|
||||
<el-col :span="6">
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('addTime') }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ detail.create_time }}
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col :span="6" :offset="6">
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('lastLoginTime') }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ detail.last_time || '' }}
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col :span="6" :offset="6">
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mb-[20px]">
|
||||
<el-col :span="6">
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('lastLoginIP') }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ detail.last_ip || '' }}
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
<el-table-column prop="version" :label="t('currentVersion')" width="120" />
|
||||
<el-table-column prop="backup_dir" :label="t('backupDir')" width="220" />
|
||||
<el-table-column prop="complete_time" :label="t('completeTime')" width="220" />
|
||||
<el-table-column prop="remark" :label="t('remark')" :show-overflow-tooltip="true">
|
||||
<el-table-column prop="remark" :label="t('remark')">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.remark" class="multi-hidden">{{ row.remark }}</span>
|
||||
<span v-else>{{ t('remarkEmpty') }}</span>
|
||||
@ -599,18 +599,13 @@ const restoreUpgradeBackupFn = (id: any, task: any = '') => {
|
||||
} else {
|
||||
// 延迟2秒请求,等待恢复数据加载完成
|
||||
setTimeout(() => {
|
||||
restoreContents = []
|
||||
restoreUpgradeBackupFn(id, data.task)
|
||||
}, 2000)
|
||||
}
|
||||
}).catch(() => {
|
||||
if (showDialog.value) {
|
||||
setTimeout(() => {
|
||||
restoreUpgradeBackupFn(id, task)
|
||||
}, 2000)
|
||||
} else {
|
||||
repeat.value = false
|
||||
tableData.loading = false
|
||||
}
|
||||
repeat.value = false
|
||||
tableData.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
@ -863,7 +858,7 @@ const batchDelete = () => {
|
||||
background-color: var(--el-table-header-bg-color);
|
||||
}
|
||||
|
||||
::v-deep .number-of-steps {
|
||||
:deep(.number-of-steps) {
|
||||
.el-step__line {
|
||||
margin: 0 25px;
|
||||
background: #dddddd;
|
||||
|
||||
@ -126,7 +126,9 @@ const prop = defineProps({
|
||||
},
|
||||
ignore: {
|
||||
type: Array,
|
||||
default: []
|
||||
default: () => {
|
||||
return [] // 指定需要忽略的自定义链接,例如:['DIY_MAKE_PHONE_CALL'],表示隐藏拨打电话
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -91,6 +91,17 @@ const handleEditorReady = (editor) => {
|
||||
emit('handleBlur', editor.getContent()) // 把内容传出去
|
||||
})
|
||||
|
||||
// 全屏切换监听
|
||||
editor.addListener('fullscreenchanged', (type, fullscreen) =>{
|
||||
const editorDom = editor.ui.getDom()
|
||||
if (fullscreen) {
|
||||
editorDom.classList.add('edui-fullscreen')
|
||||
} else {
|
||||
editorDom.classList.remove('edui-fullscreen')
|
||||
}
|
||||
console.log('全屏切换', fullscreen)
|
||||
})
|
||||
|
||||
// 方案二:原型链扩展(如果编辑器版本支持)
|
||||
const originalCount = editor.getContentLength; // 原生统计方法
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex border-t border-b main-wrap border-color w-full attachment-wrap" :class="scene == 'select' ? 'h-[40vh]' : 'h-full'">
|
||||
<div class="flex border-t border-b main-wrap border-color w-full attachment-wrap" :class="scene == 'select' ? 'h-[546px]' : 'h-full'">
|
||||
|
||||
<!-- 分组 -->
|
||||
<div class="group-wrap w-[180px] p-[15px] h-full border-r border-color flex flex-col">
|
||||
@ -82,7 +82,7 @@
|
||||
<div class="flex items-center">
|
||||
<el-tooltip placement="top">
|
||||
<template #content>{{ item.real_name }}</template>
|
||||
<div class="truncate my-[10px] cursor-pointer text-base flex-1 text-center">{{ item.real_name }}</div>
|
||||
<div class="truncate my-[10px] cursor-pointer text-base flex-1 text-center h-[20px] leading-[20px]">{{ item.real_name }}</div>
|
||||
</el-tooltip>
|
||||
<!-- 图片操作 -->
|
||||
<el-dropdown :hide-on-click="false" v-if="scene == 'attachment'" class="attachment-action hidden ">
|
||||
@ -232,12 +232,12 @@ const attachment: Record<string, any> = reactive({
|
||||
loading: true,
|
||||
page: 1,
|
||||
total: 0,
|
||||
limit: prop.scene == 'select' ? 10 : 20,
|
||||
limit: prop.scene == 'select' ? 18 : 20,
|
||||
data: []
|
||||
})
|
||||
|
||||
if (prop.scene == 'select') {
|
||||
attachment.limit = 10
|
||||
attachment.limit = 18
|
||||
if (prop.type == 'icon') {
|
||||
attachment.limit = 20
|
||||
}
|
||||
@ -365,7 +365,7 @@ const getAttachmentList = debounce((page: number = 1) => {
|
||||
attachment.data[i].image_list.push(img(res.data.data[i].thumb))
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
}).catch((err) => {
|
||||
attachment.loading = false
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<span @click="openDialog" class="cursor-pointer">
|
||||
<slot></slot>
|
||||
</span>
|
||||
<el-dialog v-model="showDialog" :title="t('upload.select' + type)" width="60%" class="attachment-dialog" :destroy-on-close="true">
|
||||
<el-dialog v-model="showDialog" :title="t('upload.select' + type)" width="920px" class="attachment-dialog" :destroy-on-close="true">
|
||||
|
||||
<attachment :limit="limit" :type="type" ref="attachmentRef" />
|
||||
|
||||
|
||||
74
admin/src/components/upload-audio/index.vue
Normal file
74
admin/src/components/upload-audio/index.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<el-upload v-bind="upload" class="upload-file">
|
||||
<slot>
|
||||
<el-button type="primary">{{ t('上传音频') }}</el-button>
|
||||
</slot>
|
||||
</el-upload>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getToken } from '@/utils/common'
|
||||
import { UploadFile, ElMessage } from 'element-plus'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const prop = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
api: {
|
||||
type: String,
|
||||
default: 'sys/audio'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const value = computed({
|
||||
get () {
|
||||
return prop.modelValue
|
||||
},
|
||||
set (value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const upload: Record<string, any> = {
|
||||
action: `${import.meta.env.VITE_APP_BASE_URL}/${prop.api}`,
|
||||
showFileList: false,
|
||||
headers: {},
|
||||
accept: 'audio/*,.mp3,.wav,.ogg,.m4a,.flac,.aac,.wma',
|
||||
beforeUpload: (file: File) => {
|
||||
const audioTypes = ['audio/mp3', 'audio/wav', 'audio/ogg', 'audio/m4a', 'audio/flac', 'audio/aac', 'audio/wma', 'audio/mpeg']
|
||||
const audioExtensions = ['.mp3', '.wav', '.ogg', '.m4a', '.flac', '.aac', '.wma']
|
||||
|
||||
const isAudio = audioTypes.includes(file.type) || audioExtensions.some(ext => file.name.toLowerCase().endsWith(ext))
|
||||
|
||||
if (!isAudio) {
|
||||
ElMessage({ message: t('请上传音频文件'), type: 'error' })
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
onSuccess: (response: any, uploadFile: UploadFile) => {
|
||||
if (response.code != undefined && response.code != 1) {
|
||||
ElMessage({ message: response.msg, type: 'error' })
|
||||
return
|
||||
}
|
||||
value.value = response.data.url
|
||||
ElMessage({ message: t('upload.success'), type: 'success' })
|
||||
}
|
||||
}
|
||||
upload.headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||
upload.headers[import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.upload-file .el-upload {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -23,6 +23,10 @@ const prop = defineProps({
|
||||
api: {
|
||||
type: String,
|
||||
default: 'sys/document/document'
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: '.doc,.docx,.xml,.txt,.pem,.zip,.rar,.7z,.crt,.key,.xls,.xlsx'
|
||||
}
|
||||
})
|
||||
|
||||
@ -42,12 +46,12 @@ const upload = computed(() => {
|
||||
headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||
headers[import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
||||
const baseURL = import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`
|
||||
|
||||
|
||||
return {
|
||||
action: `${baseURL}${prop.api}`,
|
||||
showFileList: false,
|
||||
accept: prop.accept,
|
||||
headers,
|
||||
accept: '.doc,.docx,.xml,.txt,.pem,.zip,.rar,.7z,.crt,.key,.xls,.xlsx',
|
||||
onSuccess: (response: any, uploadFile: UploadFile) => {
|
||||
if (response.code != undefined && response.code != 1) {
|
||||
ElMessage({ message: response.msg, type: 'error' })
|
||||
|
||||
@ -1,223 +1,228 @@
|
||||
{
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"info": "详情",
|
||||
"createTime": "创建时间",
|
||||
"sort": "排序",
|
||||
"status": "状态",
|
||||
"operation": "操作",
|
||||
"more": "更多",
|
||||
"statusNormal": "正常",
|
||||
"statusDeactivate": "停用",
|
||||
"startUsing": "启用",
|
||||
"confirm": "确认",
|
||||
"save": "保存",
|
||||
"back": "返回",
|
||||
"cancel": "取消",
|
||||
"search": "搜索",
|
||||
"reset": "重置",
|
||||
"refresh": "刷新",
|
||||
"refreshSuccess": "刷新成功",
|
||||
"select": "选择",
|
||||
"export": "导出列表",
|
||||
"exportPlaceholder": "确定要导出数据吗?",
|
||||
"exportTip": "批量导出数据",
|
||||
"exportConfirm": "确定并导出",
|
||||
"warning": "提示",
|
||||
"isShow": "是否显示",
|
||||
"show": "显示",
|
||||
"hidden": "隐藏",
|
||||
"icon": "图标",
|
||||
"userName": "用户名",
|
||||
"headImg": "头像",
|
||||
"accountNumber": "账号",
|
||||
"accountSettings": "账号设置",
|
||||
"realName": "名称",
|
||||
"realNamePlaceholder": "请输入用户名称",
|
||||
"password": "密码",
|
||||
"confirmPassword": "确认密码",
|
||||
"image": "图片",
|
||||
"video": "视频",
|
||||
"rename": "重命名",
|
||||
"lookOver": "查看",
|
||||
"selectAll": "全选",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"copy": "复制",
|
||||
"complete": "完成",
|
||||
"copySuccess": "复制成功",
|
||||
"notSupportCopy": "浏览器不支持一键复制,请手动进行复制",
|
||||
"selectPlaceholder": "全部",
|
||||
"provincePlaceholder": "请选择省",
|
||||
"cityPlaceholder": "请选择市",
|
||||
"districtPlaceholder": "请选择区/县",
|
||||
"emptyData": "暂无数据",
|
||||
"emptyQrCode": "暂无二维码",
|
||||
"fileErr": "无法显示",
|
||||
"upload": {
|
||||
"root": "上传",
|
||||
"selectimage": "选择图片",
|
||||
"selectvideo": "选择视频",
|
||||
"selecticon": "选择图标",
|
||||
"selectnews": "选择图文",
|
||||
"uploadimage": "上传图片",
|
||||
"uploadvideo": "上传视频",
|
||||
"addAttachmentCategory": "添加分组",
|
||||
"attachmentCategoryPlaceholder": "请输入分组名称",
|
||||
"attachmentEmpty": "暂无附件,请点击上传按钮上传",
|
||||
"iconEmpty": "暂无图标",
|
||||
"deleteCategoryTips": "确定要删除该分组吗?",
|
||||
"deleteAttachmentTips": "确定要删除所选附件吗?如所选附件已被使用删除将会受到影响,请谨慎操作!",
|
||||
"move": "移动",
|
||||
"moveCategory": "移动分组",
|
||||
"moveTo": "移动至",
|
||||
"placeholderimageName": "请输入图片名称",
|
||||
"placeholdervideoName": "请输入视频名称",
|
||||
"placeholdericonName": "请输入图标名称",
|
||||
"success": "上传成功",
|
||||
"triggerUpperLimit": "可选数量已达上限",
|
||||
"mediaEmpty": "暂无素材,请点击上传按钮上传"
|
||||
},
|
||||
"tabs": {
|
||||
"closeLeft": "关闭左侧",
|
||||
"closeRight": "关闭右侧",
|
||||
"closeOther": "关闭其他"
|
||||
},
|
||||
"layout": {
|
||||
"layoutSetting": "主题设置",
|
||||
"darkMode": "黑暗模式",
|
||||
"sidebarMode": "主题风格",
|
||||
"themeColor": "主题颜色",
|
||||
"detectionLoginOperation": "确定",
|
||||
"detectionLoginContent": "已检测到有其他账号登录,需要刷新后才能继续操作。",
|
||||
"detectionLoginTip": "提示",
|
||||
"layoutStyle": "布局风格",
|
||||
"tab": "开启标签栏"
|
||||
},
|
||||
"axios": {
|
||||
"unknownError": "未知错误",
|
||||
"400": "错误的请求",
|
||||
"401": "请重新登录",
|
||||
"403": "拒绝访问",
|
||||
"404": "请求错误",
|
||||
"405": "请求方法未允许",
|
||||
"408": "请求超时",
|
||||
"409": "请求跨域",
|
||||
"500": "服务器端出错,错误原因:",
|
||||
"501": "网络未实现",
|
||||
"502": "网络错误",
|
||||
"503": "服务不可用",
|
||||
"504": "网络超时",
|
||||
"505": "http版本不支持该请求",
|
||||
"timeout": "网络请求超时!",
|
||||
"requestError": "请求错误",
|
||||
"errNetwork": "网络请求错误",
|
||||
"baseUrlError": " 接口请求错误,请检查VITE_APP_BASE_URL参数配置或者伪静态配置, <a style='text-decoration: underline;' href='https://www.kancloud.cn/niucloud/niucloud-admin-develop/3213750' target='blank'>点击查看相关手册</a>"
|
||||
},
|
||||
"linkPlaceholder": "请选择跳转链接",
|
||||
"selectLinkTips": "链接选择",
|
||||
"diyLinkName": "链接名称",
|
||||
"diyLinkNamePlaceholder": "请输入链接名称",
|
||||
"diyLinkNameNotEmpty": "链接名称不能为空",
|
||||
"diyLinkUrl": "跳转路径",
|
||||
"diyLinkUrlPlaceholder": "请输入跳转路径",
|
||||
"diyLinkUrlNotEmpty": "跳转路径不能为空",
|
||||
"diyAppletId": "小程序AppID",
|
||||
"diyAppletIdPlaceholder": "请输入要打开的小程序的appid",
|
||||
"diyAppletIdNotEmpty": "小程序AppID不能为空",
|
||||
"diyAppletPage": "小程序路径",
|
||||
"diyAppletPagePlaceholder": "请输入要打开的小程序路径",
|
||||
"diyAppletPageNotEmpty": "小程序路径不能为空",
|
||||
"diyMakePhone": "电话号码",
|
||||
"diyMakePhonePlaceholder": "请输入电话号码",
|
||||
"diyMakePhoneNotEmpty": "电话号码不能为空",
|
||||
"returnToPreviousPage": "返回",
|
||||
"preview": "预览",
|
||||
"emptyApp": "暂未安装任何应用",
|
||||
"newInfo": "最新消息",
|
||||
"visitWap": "访问店铺",
|
||||
"mapSetting": "地图设置",
|
||||
"mapKey": "腾讯地图KEY",
|
||||
"indexTemplate": "首页模版",
|
||||
"indexSwitch": "切换首页",
|
||||
"indexWarning": "你确定要切换首页吗?",
|
||||
"originalPassword": "原始密码",
|
||||
"newPassword": "新密码",
|
||||
"passwordCopy": "确认密码",
|
||||
"passwordTip": "修改密码时必填.不修改密码时留空",
|
||||
"originalPasswordPlaceholder": "请输入原始密码",
|
||||
"passwordPlaceholder": "请输入新密码",
|
||||
"originalPasswordHint": "原始密码不能为空",
|
||||
"newPasswordHint": "请输入确认密码",
|
||||
"doubleCipherHint": "两次新密码不同",
|
||||
"confirmPasswordError": "两次新密码不同",
|
||||
"upgrade": {
|
||||
"upgradeButton": "立即升级",
|
||||
"title": "升级",
|
||||
"upgradingTips": "有正在执行的升级任务,",
|
||||
"clickView": "点击查看",
|
||||
"dirPermission": "目录读写权限",
|
||||
"path": "路径",
|
||||
"demand": "要求",
|
||||
"readable": "可读",
|
||||
"write": "可写",
|
||||
"upgradeSuccess": "升级成功",
|
||||
"localBuild": "本地编译",
|
||||
"cloudBuild": "重试",
|
||||
"rollback": "回滚",
|
||||
"showDialogCloseTips": "升级任务尚未完成,关闭将取消升级,是否要继续关闭?",
|
||||
"upgradeCompleteTips": "升级完成后还必须要重新编译admin wap web端,以免影响到程序正常运行。",
|
||||
"upgradeTips": "应用和插件升级时,系统会自动备份当前程序及数据库。升级功能不会造成您当前程序的损坏或者数据的丢失,请放心使用,但是升级过程可能会因为兼容性等各种原因出现意外的升级错误,当出现错误时,系统将会自动回退上一版本!若回退失败,请参考链接<a href='https://www.kancloud.cn/niushop/niushop_v6/3228611' target='_blank' class='text-primary'> https://www.kancloud.cn/niushop/niushop_v6/3228611 </a>手动回退上一版本!",
|
||||
"knownToKnow": "我已知晓,不需要再次提示",
|
||||
"cloudBuildErrorTips": "一键云编译队列任务过多,请等待几分钟后重试!",
|
||||
"isNeedBackup": "是否需要备份",
|
||||
"isNeedBackupTips": "检测到已存在备份,此次升级是否需要备份,不需要备份在升级出现异常时将会恢复最近的一次备份。",
|
||||
"isNeedBackupBtn": "查看备份记录",
|
||||
"option": "选项",
|
||||
"isNeedCloudbuild": "是否需要云编译",
|
||||
"cloudbuildTips": "此次升级的同时是否需要进行云编译"
|
||||
},
|
||||
"cloudbuild": {
|
||||
"title": "云编译",
|
||||
"executingTips": "有正在执行的编译任务,",
|
||||
"clickView": "点击查看",
|
||||
"dirPermission": "目录读写权限",
|
||||
"path": "路径",
|
||||
"demand": "要求",
|
||||
"readable": "可读",
|
||||
"write": "可写",
|
||||
"cloudbuildSuccess": "编译完成",
|
||||
"showDialogCloseTips": "编译任务尚未完成,关闭将取消编译,是否要继续关闭?"
|
||||
},
|
||||
"formSelectContentTitle": "表单名称",
|
||||
"formSelectContentTitlePlaceholder": "请输入表单名称",
|
||||
"formSelectContentTypeName": "表单类型",
|
||||
"formSelectContentTypeNamePlaceholder": "请选择表单类型",
|
||||
"formSelectContentTypeAll": "全部",
|
||||
"formSelectContentTips": "请选择表单",
|
||||
"appName": "应用名/版本信息",
|
||||
"appIdentification": "应用标识",
|
||||
"introduction": "简介",
|
||||
"type": "类型",
|
||||
"localAppText": "插件管理",
|
||||
"upgrade2": "升级",
|
||||
"installLabel": "已安装",
|
||||
"uninstalledLabel": "未安装",
|
||||
"buyLabel": "已购买",
|
||||
"cloudBuild": "云编译",
|
||||
"newVersion": "最新版本",
|
||||
"tipText": "标识指开发应用或插件的文件夹名称",
|
||||
"gxx": "更新信息",
|
||||
"return": "返回",
|
||||
"nextStep": "下一步",
|
||||
"prev": "上一步",
|
||||
"viewUpgradeContent": "查看升级内容",
|
||||
"testDirectoryPermissions": "检测目录权限",
|
||||
"backupFiles": "备份文件",
|
||||
"startUpgrade": "开始升级",
|
||||
"upgradeEnd": "升级完成",
|
||||
"cloudBuildTips": "是否要进行云编译该操作可能会影响到正在访问的客户是否要继续操作?",
|
||||
"promoteUrl": "推广链接",
|
||||
"downLoadQRCode": "下载二维码",
|
||||
"configureFailed": "配置失败"
|
||||
}
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"info": "详情",
|
||||
"createTime": "创建时间",
|
||||
"sort": "排序",
|
||||
"status": "状态",
|
||||
"operation": "操作",
|
||||
"more": "更多",
|
||||
"statusNormal": "正常",
|
||||
"statusDeactivate": "停用",
|
||||
"startUsing": "启用",
|
||||
"confirm": "确认",
|
||||
"save": "保存",
|
||||
"back": "返回",
|
||||
"cancel": "取消",
|
||||
"search": "搜索",
|
||||
"reset": "重置",
|
||||
"refresh": "刷新",
|
||||
"refreshSuccess": "刷新成功",
|
||||
"select": "选择",
|
||||
"export": "导出列表",
|
||||
"exportPlaceholder": "确定要导出数据吗?",
|
||||
"exportTip": "批量导出数据",
|
||||
"exportConfirm": "确定并导出",
|
||||
"warning": "提示",
|
||||
"isShow": "是否显示",
|
||||
"show": "显示",
|
||||
"hidden": "隐藏",
|
||||
"icon": "图标",
|
||||
"userName": "用户名",
|
||||
"headImg": "头像",
|
||||
"accountNumber": "账号",
|
||||
"accountSettings": "账号设置",
|
||||
"realName": "名称",
|
||||
"realNamePlaceholder": "请输入用户名称",
|
||||
"password": "密码",
|
||||
"confirmPassword": "确认密码",
|
||||
"image": "图片",
|
||||
"video": "视频",
|
||||
"rename": "重命名",
|
||||
"lookOver": "查看",
|
||||
"selectAll": "全选",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"copy": "复制",
|
||||
"complete": "完成",
|
||||
"copySuccess": "复制成功",
|
||||
"notSupportCopy": "浏览器不支持一键复制,请手动进行复制",
|
||||
"selectPlaceholder": "全部",
|
||||
"provincePlaceholder": "请选择省",
|
||||
"cityPlaceholder": "请选择市",
|
||||
"districtPlaceholder": "请选择区/县",
|
||||
"emptyData": "暂无数据",
|
||||
"emptyQrCode": "暂无二维码",
|
||||
"fileErr": "无法显示",
|
||||
"upload": {
|
||||
"root": "上传",
|
||||
"selectimage": "选择图片",
|
||||
"selectvideo": "选择视频",
|
||||
"selecticon": "选择图标",
|
||||
"selectnews": "选择图文",
|
||||
"uploadimage": "上传图片",
|
||||
"uploadvideo": "上传视频",
|
||||
"addAttachmentCategory": "添加分组",
|
||||
"attachmentCategoryPlaceholder": "请输入分组名称",
|
||||
"attachmentEmpty": "暂无附件,请点击上传按钮上传",
|
||||
"iconEmpty": "暂无图标",
|
||||
"deleteCategoryTips": "确定要删除该分组吗?",
|
||||
"deleteAttachmentTips": "确定要删除所选附件吗?如所选附件已被使用删除将会受到影响,请谨慎操作!",
|
||||
"move": "移动",
|
||||
"moveCategory": "移动分组",
|
||||
"moveTo": "移动至",
|
||||
"placeholderimageName": "请输入图片名称",
|
||||
"placeholdervideoName": "请输入视频名称",
|
||||
"placeholdericonName": "请输入图标名称",
|
||||
"success": "上传成功",
|
||||
"triggerUpperLimit": "可选数量已达上限",
|
||||
"mediaEmpty": "暂无素材,请点击上传按钮上传"
|
||||
},
|
||||
"tabs": {
|
||||
"closeLeft": "关闭左侧",
|
||||
"closeRight": "关闭右侧",
|
||||
"closeOther": "关闭其他"
|
||||
},
|
||||
"layout": {
|
||||
"layoutSetting": "主题设置",
|
||||
"darkMode": "黑暗模式",
|
||||
"sidebarMode": "主题风格",
|
||||
"themeColor": "主题颜色",
|
||||
"detectionLoginOperation": "确定",
|
||||
"detectionLoginContent": "已检测到有其他账号登录,需要刷新后才能继续操作。",
|
||||
"detectionLoginTip": "提示",
|
||||
"layoutStyle": "布局风格",
|
||||
"tab": "开启标签栏"
|
||||
},
|
||||
"axios": {
|
||||
"unknownError": "未知错误",
|
||||
"400": "错误的请求",
|
||||
"401": "请重新登录",
|
||||
"403": "拒绝访问",
|
||||
"404": "请求错误",
|
||||
"405": "请求方法未允许",
|
||||
"408": "请求超时",
|
||||
"409": "请求跨域",
|
||||
"500": "服务器内部错误",
|
||||
"501": "网络未实现",
|
||||
"502": "网络错误",
|
||||
"503": "服务不可用",
|
||||
"504": "网络超时",
|
||||
"505": "http版本不支持该请求",
|
||||
"timeout": "网络请求超时!",
|
||||
"requestError": "请求错误",
|
||||
"errNetwork": "网络请求错误",
|
||||
"baseUrlError": " 接口请求错误,请检查VITE_APP_BASE_URL参数配置或者伪静态配置, <a style='text-decoration: underline;' href='https://www.kancloud.cn/niucloud/niucloud-admin-develop/3213750' target='blank'>点击查看相关手册</a>"
|
||||
},
|
||||
"linkPlaceholder": "请选择跳转链接",
|
||||
"selectLinkTips": "链接选择",
|
||||
"diyLinkName": "链接名称",
|
||||
"diyLinkNamePlaceholder": "请输入链接名称",
|
||||
"diyLinkNameNotEmpty": "链接名称不能为空",
|
||||
"diyLinkUrl": "跳转路径",
|
||||
"diyLinkUrlPlaceholder": "请输入跳转路径",
|
||||
"diyLinkUrlNotEmpty": "跳转路径不能为空",
|
||||
"diyAppletId": "小程序AppID",
|
||||
"diyAppletIdPlaceholder": "请输入要打开的小程序的appid",
|
||||
"diyAppletIdNotEmpty": "小程序AppID不能为空",
|
||||
"diyAppletPage": "小程序路径",
|
||||
"diyAppletPagePlaceholder": "请输入要打开的小程序路径",
|
||||
"diyAppletPageNotEmpty": "小程序路径不能为空",
|
||||
"diyMakePhone": "电话号码",
|
||||
"diyMakePhonePlaceholder": "请输入电话号码",
|
||||
"diyMakePhoneNotEmpty": "电话号码不能为空",
|
||||
"returnToPreviousPage": "返回",
|
||||
"preview": "预览",
|
||||
"emptyApp": "暂未安装任何应用",
|
||||
"newInfo": "最新消息",
|
||||
"visitWap": "访问店铺",
|
||||
"mapSetting": "地图设置",
|
||||
"mapKey": "腾讯地图KEY",
|
||||
"indexTemplate": "首页模版",
|
||||
"indexSwitch": "切换首页",
|
||||
"indexWarning": "你确定要切换首页吗?",
|
||||
"originalPassword": "原始密码",
|
||||
"newPassword": "新密码",
|
||||
"passwordCopy": "确认密码",
|
||||
"passwordTip": "修改密码时必填.不修改密码时留空",
|
||||
"originalPasswordPlaceholder": "请输入原始密码",
|
||||
"passwordPlaceholder": "请输入新密码",
|
||||
"originalPasswordHint": "原始密码不能为空",
|
||||
"newPasswordHint": "请输入确认密码",
|
||||
"doubleCipherHint": "两次新密码不同",
|
||||
"confirmPasswordError": "两次新密码不同",
|
||||
"upgrade": {
|
||||
"upgradeButton": "立即升级",
|
||||
"title": "升级",
|
||||
"upgradingTips": "有正在执行的升级任务,",
|
||||
"clickView": "点击查看",
|
||||
"dirPermission": "目录读写权限",
|
||||
"path": "路径",
|
||||
"demand": "要求",
|
||||
"readable": "可读",
|
||||
"write": "可写",
|
||||
"upgradeSuccess": "升级成功",
|
||||
"localBuild": "本地编译",
|
||||
"cloudBuild": "重试",
|
||||
"rollback": "回滚",
|
||||
"showDialogCloseTips": "升级任务尚未完成,关闭将取消升级,是否要继续关闭?",
|
||||
"upgradeCompleteTips": "升级完成后还必须要重新编译admin wap web端,以免影响到程序正常运行。",
|
||||
"upgradeTips": "应用和插件升级时,系统会自动备份当前程序及数据库。升级功能不会造成您当前程序的损坏或者数据的丢失,请放心使用,但是升级过程可能会因为兼容性等各种原因出现意外的升级错误,当出现错误时,系统将会自动回退上一版本!若回退失败,请参考链接<a href='https://www.kancloud.cn/niushop/niushop_v6/3228611' target='_blank' class='text-primary'> https://www.kancloud.cn/niushop/niushop_v6/3228611 </a>手动回退上一版本!",
|
||||
"knownToKnow": "我已知晓,不需要再次提示",
|
||||
"cloudBuildErrorTips": "一键云编译队列任务过多,请等待几分钟后重试!",
|
||||
"isNeedBackup": "是否需要备份",
|
||||
"isNeedBackupTips": "检测到已存在备份,此次升级是否需要备份,不需要备份在升级出现异常时将会恢复最近的一次备份。",
|
||||
"isNeedBackupBtn": "查看备份记录",
|
||||
"option": "选项",
|
||||
"isNeedCloudbuild": "是否需要云编译",
|
||||
"cloudbuildTips": "此次升级的同时是否需要进行云编译"
|
||||
},
|
||||
"cloudbuild": {
|
||||
"title": "云编译",
|
||||
"executingTips": "有正在执行的编译任务,",
|
||||
"clickView": "点击查看",
|
||||
"dirPermission": "目录读写权限",
|
||||
"path": "路径",
|
||||
"demand": "要求",
|
||||
"readable": "可读",
|
||||
"write": "可写",
|
||||
"cloudbuildSuccess": "编译完成",
|
||||
"showDialogCloseTips": "编译任务尚未完成,关闭将取消编译,是否要继续关闭?"
|
||||
},
|
||||
"formSelectContentTitle": "表单名称",
|
||||
"formSelectContentTitlePlaceholder": "请输入表单名称",
|
||||
"formSelectContentTypeName": "表单类型",
|
||||
"formSelectContentTypeNamePlaceholder": "请选择表单类型",
|
||||
"formSelectContentTypeAll": "全部",
|
||||
"formSelectContentTips": "请选择表单",
|
||||
"appName": "应用名/版本信息",
|
||||
"appIdentification": "应用标识",
|
||||
"introduction": "简介",
|
||||
"type": "类型",
|
||||
"localAppText": "插件管理",
|
||||
"upgrade2": "升级",
|
||||
"installLabel": "已安装",
|
||||
"uninstalledLabel": "未安装",
|
||||
"buyLabel": "已购买",
|
||||
"cloudBuild": "云编译",
|
||||
"newVersion": "最新版本",
|
||||
"tipText": "标识指开发应用或插件的文件夹名称",
|
||||
"gxx": "更新信息",
|
||||
"return": "返回",
|
||||
"nextStep": "下一步",
|
||||
"prev": "上一步",
|
||||
"viewUpgradeContent": "查看升级内容",
|
||||
"testDirectoryPermissions": "检测目录权限",
|
||||
"backupFiles": "备份文件",
|
||||
"startUpgrade": "开始升级",
|
||||
"upgradeEnd": "升级完成",
|
||||
"cloudBuildTips": "是否要进行云编译该操作可能会影响到正在访问的客户是否要继续操作?",
|
||||
"promoteUrl": "推广链接",
|
||||
"downLoadQRCode": "下载二维码",
|
||||
"configureFailed": "配置失败",
|
||||
"lefttitle": "左侧标题",
|
||||
"righttitle": "右侧标题",
|
||||
"leftDesc": "左侧简介",
|
||||
"rightDesc": "右侧简介",
|
||||
"descPlaceholder": "请输入简介内容"
|
||||
}
|
||||
@ -17,10 +17,10 @@
|
||||
|
||||
<div v-if="systemStore.menuIsCollapse" class="text-left text-[14px] mt-[3px] w-[75px] using-hidden ml-[10px]">{{ item.meta.title || item.meta.short_title }}</div>
|
||||
<div v-else class="text-center text-[12px] using-hidden mt-1">{{ item.meta.short_title || item.meta.title }}</div>
|
||||
<div v-if="systemStore.menuIsCollapse && item.name=='app_store' && recentlyUpdated.length>0" class="text-[11px] bg-[#DA203E] px-[10px] rounded-[12px] text-[#fff] absolute right-[6px]">更新</div>
|
||||
<div v-if="!systemStore.menuIsCollapse && item.name=='app_store' && recentlyUpdated.length>0" class="w-[7px] h-[7px] bg-[#DA203E] absolute flex items-center justify-center rounded-full top-[4px] right-[14px]"></div>
|
||||
<div v-if="systemStore.menuIsCollapse && item.original_name=='tool' && isNewVersion" class="text-[11px] bg-[#DA203E] px-[10px] rounded-[12px] text-[#fff] absolute right-[6px]">更新</div>
|
||||
<div v-if="!systemStore.menuIsCollapse && item.original_name=='tool' && isNewVersion" class="w-[7px] h-[7px] bg-[#DA203E] absolute flex items-center justify-center rounded-full top-[4px] right-[14px]"></div>
|
||||
<div v-if="systemStore.menuIsCollapse && item.name=='app_store' && (recentlyUpdated.length>0 || isNewVersion)" class="text-[11px] bg-[#DA203E] px-[10px] rounded-[12px] text-[#fff] absolute right-[6px]">更新</div>
|
||||
<div v-if="!systemStore.menuIsCollapse && item.name=='app_store' && (recentlyUpdated.length>0 || isNewVersion)" class="w-[7px] h-[7px] bg-[#DA203E] absolute flex items-center justify-center rounded-full top-[4px] right-[14px]"></div>
|
||||
<!-- <div v-if="systemStore.menuIsCollapse && item.original_name=='tool' && isNewVersion" class="text-[11px] bg-[#DA203E] px-[10px] rounded-[12px] text-[#fff] absolute right-[6px]">更新</div>
|
||||
<div v-if="!systemStore.menuIsCollapse && item.original_name=='tool' && isNewVersion" class="w-[7px] h-[7px] bg-[#DA203E] absolute flex items-center justify-center rounded-full top-[4px] right-[14px]"></div> -->
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@ -79,7 +79,7 @@ routers.forEach(item => {
|
||||
|
||||
const oneMenuActive = ref(oneMenuData.value[0].name)
|
||||
watch(route, () => {
|
||||
twoMenuData.value = route.matched[1].children ?? []
|
||||
twoMenuData.value = route.matched[2].children ?? []
|
||||
oneMenuActive.value = route.matched[1].name == ADMIN_ROUTE.children[0].name ? route.matched[2].name : route.matched[1].name
|
||||
defaultOpeneds.value = twoMenuData.value.map(item => item.name)
|
||||
}, { immediate: true })
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
<!-- 面包屑导航 -->
|
||||
<div class="flex items-center h-full pl-[10px] hidden-xs-only">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item v-for="(route, index) in breadcrumb" :key="index">{{route.meta.title }}</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-for="(route, index) in breadcrumb" :key="index" :to="route.path" class="inter">{{route.meta.title }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<el-scrollbar>
|
||||
<div class="p-[15px]">
|
||||
<router-view v-slot="{ Component, route }" v-if="appStore.routeRefreshTag">
|
||||
<keep-alive :include="tabbarStore.tabNames">
|
||||
<keep-alive :include="tabbarStore.tabNames" :max="15">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
@ -25,7 +25,7 @@
|
||||
<!-- 主体 end -->
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -42,4 +42,8 @@ const tabbarStore = useTabbarStore()
|
||||
.bg-page {
|
||||
background-color: #F7F7FA;
|
||||
}
|
||||
:deep(.inter .el-breadcrumb__inner){
|
||||
font-weight: inherit !important;
|
||||
color: var(--el-text-color-regular) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
<!-- 面包屑导航 -->
|
||||
<div class="flex items-center h-full pl-[10px] hidden-xs-only">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item v-for="(route, index) in breadcrumb" :key="index">{{route.meta.title }}</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-for="(route, index) in breadcrumb" :key="index" :to="route.path" class="inter">{{route.meta.title }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<el-scrollbar>
|
||||
<div class="p-[15px]">
|
||||
<router-view v-slot="{ Component, route }" v-if="appStore.routeRefreshTag">
|
||||
<keep-alive :include="tabbarStore.tabNames">
|
||||
<keep-alive :include="tabbarStore.tabNames" :max="15">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
@ -25,7 +25,7 @@
|
||||
<!-- 主体 end -->
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -38,4 +38,9 @@ const appStore = useAppStore()
|
||||
const tabbarStore = useTabbarStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.inter .el-breadcrumb__inner){
|
||||
font-weight: inherit !important;
|
||||
color: var(--el-text-color-regular) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<template v-if="meta.show">
|
||||
<el-sub-menu v-if="routes.children" :index="String(routes.name)">
|
||||
<el-sub-menu v-if="hasVisibleChild" :index="String(routes.name)">
|
||||
<template #title>
|
||||
<span :class="['ml-[10px]']">{{ meta.title }}</span>
|
||||
</template>
|
||||
<menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" />
|
||||
</el-sub-menu>
|
||||
<template v-else>
|
||||
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
|
||||
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
|
||||
<template #title>
|
||||
<span :class="[{'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}, {'ml-[10px]': routes.meta.class == 2, 'ml-[15px]': routes.meta.class == 3}]">{{ meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else>
|
||||
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-else>
|
||||
<template #title>
|
||||
<span :class="[{'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}, {'ml-[10px]': routes.meta.class == 2, 'ml-[15px]': routes.meta.class == 3}]">{{ meta.title }}</span>
|
||||
</template>
|
||||
@ -24,13 +24,16 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRouter , useRoute} from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import { img } from '@/utils/common'
|
||||
import menuItem from './menu-item.vue'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const props = defineProps({
|
||||
routes: {
|
||||
type: Object,
|
||||
@ -41,12 +44,42 @@ const userStore = useUserStore()
|
||||
const siteInfo = userStore.siteInfo
|
||||
const meta = computed(() => props.routes.meta)
|
||||
|
||||
const hasVisibleChild = computed(() => {
|
||||
if (!props.routes.children || !Array.isArray(props.routes.children)) {
|
||||
return false
|
||||
}
|
||||
return props.routes.children.some(child => child.meta?.show === 1)
|
||||
})
|
||||
|
||||
const addons = computed(() => {
|
||||
const addons:Record<string, any> = {}
|
||||
siteInfo?.apps.forEach((item: any) => { addons[item.key] = item })
|
||||
siteInfo?.site_addons.forEach((item: any) => { addons[item.key] = item })
|
||||
return addons
|
||||
})
|
||||
// 统一处理跳转逻辑
|
||||
const handleJump = (routeName: string) => {
|
||||
// 检查目标路由是否在特殊菜单列表中
|
||||
const specialMenuNames = storage.get('specialMenuNames')
|
||||
const specialMenuNamesLevel1 = storage.get('specialMenuNamesLevel1')
|
||||
const isInSpecialMenus = specialMenuNames.includes(routeName)
|
||||
// 核心逻辑:如果不在特殊菜单中,就删除activeAppKey
|
||||
if (!isInSpecialMenus) {
|
||||
storage.remove('activeAppKey')
|
||||
} else {
|
||||
}
|
||||
// 点击特殊菜单的一级,跳转应用列表
|
||||
if (specialMenuNamesLevel1.includes(routeName)) {
|
||||
routeName = 'addon_list'
|
||||
}
|
||||
// 跳转时添加随机查询参数(用于触发页面感知)
|
||||
const query = route.name === routeName
|
||||
? { refresh: Date.now() } // 相同路由时添加随机参数
|
||||
: {};
|
||||
|
||||
// 执行跳转
|
||||
router.push({ name: routeName, query });
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
<el-scrollbar class="h-[calc( 100vh - 64px )]">
|
||||
<el-menu :default-active="oneMenuActive" :router="true" class="aside-menu" :unique-opened="true">
|
||||
<template v-for="(item, index) in oneMenuData" :key="index">
|
||||
<el-menu-item :index="item.original_name" @click="router.push({ name: item.name })" v-if="item.meta.show">
|
||||
<el-menu-item :index="item.original_name" @click="handleJump(item.name)" v-if="item.meta.show">
|
||||
<div v-if="item.meta.icon" class="w-[16px] h-[16px] relative flex justify-center">
|
||||
<icon :name="item.meta.icon" class="absolute top-[50%] -translate-y-[50%]" />
|
||||
</div>
|
||||
@ -33,7 +33,7 @@
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<el-scrollbar v-if="twoMenuData.length" class="two-menu w-[190px]">
|
||||
<div class="w-[190px] h-[64px] flex items-center justify-center text-[16px] border-0 border-b-[1px] border-solid border-[#eee]">{{ route.matched[1].meta.title }}</div>
|
||||
<div class="w-[190px] h-[64px] flex items-center justify-center text-[16px] border-0 border-b-[1px] border-solid border-[#eee]">{{ route.matched[2].meta.title }}</div>
|
||||
<el-menu :default-active="route.name" :router="true" class="aside-menu" :collapse="systemStore.menuIsCollapse">
|
||||
<menu-item v-for="(route, index) in twoMenuData" :routes="route" :key="index" />
|
||||
</el-menu>
|
||||
@ -49,9 +49,10 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import useSystemStore from '@/stores/modules/system'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import menuItem from './menu-item.vue'
|
||||
import { getShowApp, getShowMarketing } from '@/app/api/site'
|
||||
import { getShowApp,getShowSpecialMenu} from '@/app/api/site'
|
||||
import { img } from '@/utils/common'
|
||||
import { findFirstValidRoute } from '@/router/routers'
|
||||
import { findFirstValidRoute,formatRouters } from '@/router/routers'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const systemStore = useSystemStore()
|
||||
const userStore = useUserStore()
|
||||
@ -136,87 +137,222 @@ const oneMenuActive = ref(route.matched[1].name)
|
||||
const appList = ref(null)
|
||||
const marketingList = ref(null)
|
||||
// const loading = ref(true);
|
||||
|
||||
const getAppList = async () => {
|
||||
const res = await getShowApp()
|
||||
appList.value = res.data
|
||||
// loading.value = false;
|
||||
// 应用列表的key
|
||||
storage.set({ key: 'defaultAppList', data: appList.value })
|
||||
}
|
||||
const getMarketingList = async () => {
|
||||
const res = await getShowMarketing()
|
||||
marketingList.value = res.data
|
||||
const specialList = ref<Record<string, any>[]>([])
|
||||
const getShowSpecialMenuList = async () => {
|
||||
const res = await getShowSpecialMenu()
|
||||
// specialList.value = formatRouters(res.data.list)
|
||||
specialList.value = res.data.list
|
||||
// 应用列表特殊菜单
|
||||
storage.set({ key: 'specialAppList', data: specialList.value })
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getAppList() // 确保数据先加载
|
||||
await getMarketingList()
|
||||
|
||||
const specialMenuNames = ref<string[]>([])
|
||||
const specialMenuNamesLevel1 = ref<string[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
getAppList()
|
||||
getShowSpecialMenuList()
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
specialMenuNamesLevel1.value = collectSpecialMenuNamesLevel1(processedSpecialMenus)
|
||||
specialMenuNames.value = collectSpecialMenuNames(processedSpecialMenus)
|
||||
storage.set({ key: 'specialMenuNames', data: specialMenuNames.value })
|
||||
storage.set({ key: 'specialMenuNamesLevel1', data: specialMenuNamesLevel1.value })
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
// if (!appList.value || loading.value) return; // 确保数据加载完毕
|
||||
const addonKeys = appList.value?.addon?.list?.map(item => item.key) ?? []
|
||||
const toolKeys = appList.value?.tool?.list?.map(item => item.key) ?? []
|
||||
const allKeys = [...addonKeys, ...toolKeys]
|
||||
const marketingKeys = marketingList.value?.marketing?.list?.map(item => item.key) ?? []
|
||||
const matchedName = route.matched[1]?.name
|
||||
if (allKeys.includes(matchedName)) {
|
||||
oneMenuActive.value = 'addon'
|
||||
twoMenuData.value = route.matched[1]?.children ?? []
|
||||
} else if (marketingKeys.includes(matchedName)) {
|
||||
oneMenuActive.value = 'active'
|
||||
twoMenuData.value = route.matched[1]?.children ?? []
|
||||
} else if (route.meta.attr !== '') {
|
||||
oneMenuActive.value = route.matched[2]?.name
|
||||
twoMenuData.value = route.matched[1]?.children ?? []
|
||||
} else {
|
||||
// 多应用
|
||||
if (siteInfo?.apps.length > 1) {
|
||||
twoMenuData.value = route.matched[1]?.children
|
||||
oneMenuActive.value = route.matched[1]?.name
|
||||
} else {
|
||||
// 单应用
|
||||
const oneMenu = route.matched[1]
|
||||
if (oneMenu.meta.addon === '') {
|
||||
oneMenuActive.value = route.matched[1]?.name
|
||||
twoMenuData.value = route.matched[1]?.children ?? []
|
||||
} else {
|
||||
if (oneMenu.meta.addon === siteInfo?.apps[0]?.key) {
|
||||
oneMenuActive.value = route.matched[2]?.name
|
||||
twoMenuData.value = route.matched[2]?.children ?? []
|
||||
} else {
|
||||
oneMenuActive.value = route.matched[1]?.name
|
||||
twoMenuData.value = route.matched[1]?.children ?? []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// watch(route, () => {
|
||||
// if (route.meta.attr != '') {
|
||||
// oneMenuActive.value = route.matched[2].name
|
||||
// twoMenuData.value = route.matched[1].children ?? []
|
||||
// onMounted(async () => {
|
||||
// await getAppList() // 确保数据先加载
|
||||
// })
|
||||
|
||||
// watchEffect(() => {
|
||||
// // if (!appList.value || loading.value) return; // 确保数据加载完毕
|
||||
// const addonKeys = appList.value?.addon?.list?.map(item => item.key) ?? []
|
||||
// const toolKeys = appList.value?.tool?.list?.map(item => item.key) ?? []
|
||||
// const allKeys = [...addonKeys, ...toolKeys]
|
||||
// const marketingKeys = marketingList.value?.marketing?.list?.map(item => item.key) ?? []
|
||||
// const matchedName = route.matched[1]?.name
|
||||
// if (allKeys.includes(matchedName)) {
|
||||
// oneMenuActive.value = 'addon'
|
||||
// twoMenuData.value = route.matched[1]?.children ?? []
|
||||
// } else if (marketingKeys.includes(matchedName)) {
|
||||
// oneMenuActive.value = 'active'
|
||||
// twoMenuData.value = route.matched[1]?.children ?? []
|
||||
// } else if (route.meta.attr !== '') {
|
||||
// oneMenuActive.value = route.matched[2]?.name
|
||||
// twoMenuData.value = route.matched[1]?.children ?? []
|
||||
// } else {
|
||||
// // 多应用
|
||||
// if (siteInfo?.apps.length > 1) {
|
||||
// twoMenuData.value = route.matched[1].children
|
||||
// oneMenuActive.value = route.matched[1].name
|
||||
// twoMenuData.value = route.matched[1]?.children
|
||||
// oneMenuActive.value = route.matched[1]?.name
|
||||
// } else {
|
||||
// // 单应用
|
||||
// const oneMenu = route.matched[1]
|
||||
// if (oneMenu.meta.addon == '') {
|
||||
// oneMenuActive.value = route.matched[1].name
|
||||
// twoMenuData.value = route.matched[1].children ?? []
|
||||
// if (oneMenu.meta.addon === '') {
|
||||
// oneMenuActive.value = route.matched[1]?.name
|
||||
// twoMenuData.value = route.matched[1]?.children ?? []
|
||||
// } else {
|
||||
// if (oneMenu.meta.addon == siteInfo?.apps[0].key) {
|
||||
// oneMenuActive.value = route.matched[2].name
|
||||
// twoMenuData.value = route.matched[2].children ?? []
|
||||
// if (oneMenu.meta.addon === siteInfo?.apps[0]?.key) {
|
||||
// oneMenuActive.value = route.matched[2]?.name
|
||||
// twoMenuData.value = route.matched[2]?.children ?? []
|
||||
// } else {
|
||||
// oneMenuActive.value = route.matched[1].name
|
||||
// twoMenuData.value = route.matched[1].children ?? []
|
||||
// oneMenuActive.value = route.matched[1]?.name
|
||||
// twoMenuData.value = route.matched[1]?.children ?? []
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }, { immediate: true })
|
||||
// })
|
||||
|
||||
// 从 addonKeys 中提取所有需要匹配的 key
|
||||
const getAddonAllKeys = (addonData) => {
|
||||
if (!addonData || typeof addonData !== 'object') return [];
|
||||
const allKeys = [];
|
||||
Object.values(addonData).forEach(category => {
|
||||
if (Array.isArray(category.list)) {
|
||||
category.list.forEach(item => {
|
||||
if (item.key) allKeys.push(item.key);
|
||||
});
|
||||
}
|
||||
});
|
||||
return allKeys;
|
||||
};
|
||||
// 处理 specialMenusKeys 子菜单 show 的方法
|
||||
const handleSpecialMenus = () => {
|
||||
const specialMenusKeys = storage.get('specialAppList')
|
||||
if (Array.isArray(specialMenusKeys) && specialMenusKeys.length) {
|
||||
const processedSpecialMenus = JSON.parse(JSON.stringify(specialMenusKeys));
|
||||
const activeAppKey = storage.get('activeAppKey');
|
||||
|
||||
// 收集所有特殊菜单的name
|
||||
processedSpecialMenus.forEach(menu => {
|
||||
if (menu.children && Array.isArray(menu.children)) {
|
||||
const traverseChildren = (children) => {
|
||||
children.forEach(child => {
|
||||
if (child && child.is_show !== undefined) {
|
||||
child.is_show = (child.menu_key === activeAppKey) ? 1 : 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
traverseChildren(menu.children);
|
||||
}
|
||||
});
|
||||
// 过滤掉 children 为空的特殊菜单
|
||||
const filteredSpecialMenus = processedSpecialMenus.filter(menu => {
|
||||
return menu.children && menu.children.length > 0;
|
||||
});
|
||||
return formatRouters(filteredSpecialMenus);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
// 提取所有特殊菜单的name
|
||||
const collectSpecialMenuNames = (menus: any[]) => {
|
||||
const names: string[] = []
|
||||
const traverse = (children: any[]) => {
|
||||
children.forEach(child => {
|
||||
if (child.name) {
|
||||
names.push(child.name)
|
||||
}
|
||||
// 递归处理子菜单
|
||||
if (child.children && Array.isArray(child.children)) {
|
||||
traverse(child.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
menus.forEach(menu => {
|
||||
if (menu.children && Array.isArray(menu.children)) {
|
||||
traverse(menu.children)
|
||||
}
|
||||
})
|
||||
return names
|
||||
}
|
||||
// 提取所有一级特殊菜单的name
|
||||
const collectSpecialMenuNamesLevel1 = (menus: any[]) =>{
|
||||
const names: string[] = []
|
||||
menus.forEach(menu => {
|
||||
if (menu.name) {
|
||||
names.push(menu.name)
|
||||
}
|
||||
})
|
||||
return names
|
||||
}
|
||||
|
||||
// 统一处理跳转逻辑
|
||||
const handleJump = (routeName: string) => {
|
||||
// 检查目标路由是否在特殊菜单列表中
|
||||
const isInSpecialMenus = specialMenuNames.value.includes(routeName)
|
||||
// 核心逻辑:如果不在特殊菜单中,就删除activeAppKey
|
||||
if (!isInSpecialMenus) {
|
||||
storage.remove('activeAppKey')
|
||||
} else {
|
||||
}
|
||||
|
||||
// 执行跳转
|
||||
router.push({ name: routeName })
|
||||
}
|
||||
|
||||
watch(route, () => {
|
||||
if (route.meta.attr != '') {
|
||||
oneMenuActive.value = route.matched[1].name
|
||||
twoMenuData.value = route.matched[1].children ?? []
|
||||
} else {
|
||||
// 多应用
|
||||
if (siteInfo?.apps.length > 1) {
|
||||
twoMenuData.value = route.matched[2].children
|
||||
oneMenuActive.value = route.matched[2].name
|
||||
} else {
|
||||
// 单应用
|
||||
const oneMenu = route.matched[2]
|
||||
if (oneMenu.meta.addon == '') {
|
||||
oneMenuActive.value = route.matched[2].name
|
||||
twoMenuData.value = route.matched[2].children ?? []
|
||||
} else {
|
||||
if (oneMenu.meta.addon == siteInfo?.apps[0].key) {
|
||||
oneMenuActive.value = route.matched[3].name
|
||||
twoMenuData.value = route.matched[3].children ?? []
|
||||
} else {
|
||||
oneMenuActive.value = route.matched[2].name
|
||||
twoMenuData.value = route.matched[2].children ?? []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const addonKeys = storage.get('defaultAppList')
|
||||
const addonAllKeys = getAddonAllKeys(addonKeys);
|
||||
twoMenuData.value = twoMenuData.value.filter((child) =>{
|
||||
return !child.name || !addonAllKeys.includes(child.name);
|
||||
});
|
||||
if(oneMenuActive.value == 'addon'){
|
||||
// 处理特殊菜单并插入到 twoMenuData 中(与 addon_list 同级)
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
if (processedSpecialMenus.length) {
|
||||
// 先找到 addon_list 在 twoMenuData 中的索引
|
||||
const addonListIndex = twoMenuData.value.findIndex(
|
||||
(item) => item.name === 'addon_list'
|
||||
);
|
||||
if (addonListIndex !== -1) {
|
||||
// 将特殊菜单插入到 addon_list 后面(同级)
|
||||
twoMenuData.value.splice(
|
||||
addonListIndex + 1,
|
||||
0,
|
||||
...processedSpecialMenus
|
||||
);
|
||||
} else {
|
||||
// 如果没有 addon_list,直接将特殊菜单添加到 twoMenuData 中
|
||||
twoMenuData.value.push(...processedSpecialMenus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@ -309,6 +445,27 @@ watchEffect(() => {
|
||||
.el-menu-item{
|
||||
padding-left: 20px !important;
|
||||
}
|
||||
.el-sub-menu{
|
||||
.el-sub-menu__title{
|
||||
margin: 0 8px 2px;
|
||||
height: 40px;
|
||||
padding-left: 18px;
|
||||
border-radius: 2px;
|
||||
span{
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
&:hover{
|
||||
background-color: transparent;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
.el-menu-item{
|
||||
padding-left: 30px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<!-- 面包屑导航 -->
|
||||
<div class="flex items-center h-full pl-[10px] hidden-xs-only">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item v-for="(route, index) in breadcrumb" :key="index">{{route.meta.title }}</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-for="(route, index) in breadcrumb" :key="index" :to="route.path" class="inter">{{route.meta.title }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<el-scrollbar>
|
||||
<div class="p-[15px]">
|
||||
<router-view v-slot="{ Component, route }" v-if="appStore.routeRefreshTag">
|
||||
<keep-alive :include="tabbarStore.tabNames">
|
||||
<keep-alive :include="tabbarStore.tabNames" :max="15">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<template v-if="meta.show">
|
||||
<el-sub-menu v-if="routes.children" :index="String(routes.name)">
|
||||
<el-sub-menu v-if="hasVisibleChild" :index="String(routes.name)">
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
|
||||
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
|
||||
@ -15,7 +15,7 @@
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
<template v-else>
|
||||
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
|
||||
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
|
||||
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
|
||||
@ -23,7 +23,7 @@
|
||||
<span class="ml-[10px]">{{ meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else>
|
||||
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-else>
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
|
||||
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
|
||||
@ -39,11 +39,12 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { ref, computed, watch , onMounted, onUnmounted} from 'vue'
|
||||
import menuItem from './menu-item.vue'
|
||||
import useSystemStore from '@/stores/modules/system'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import storage from '@/utils/storage'
|
||||
import { findFirstValidRoute ,formatRouters} from '@/router/routers'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@ -62,6 +63,9 @@ const props = defineProps({
|
||||
const systemStore = useSystemStore()
|
||||
const meta = computed(() => props.routes.meta)
|
||||
|
||||
// 存储所有特殊菜单的name
|
||||
const specialMenuNames = ref<string[]>([])
|
||||
const specialMenuNamesLevel1 = ref<string[]>([])
|
||||
const addons = computed(() => {
|
||||
const addons:Record<string, any> = {}
|
||||
userStore.siteInfo?.apps.forEach((item: any) => { addons[item.key] = item })
|
||||
@ -72,7 +76,12 @@ const addons = computed(() => {
|
||||
const systemAddonKeys = computed(() => {
|
||||
return userStore.siteInfo?.site_addons.map((item: any) => item.key)
|
||||
})
|
||||
|
||||
const hasVisibleChild = computed(() => {
|
||||
if (!props.routes.children || !Array.isArray(props.routes.children)) {
|
||||
return false
|
||||
}
|
||||
return props.routes.children.some(child => child.meta?.show === 1)
|
||||
})
|
||||
const addonRouters: Record<string, any> = {}
|
||||
routers.forEach(item => {
|
||||
item.original_name = item.name
|
||||
@ -86,8 +95,139 @@ routers.forEach(item => {
|
||||
|
||||
const addonsMenus = ref(null)
|
||||
|
||||
// 提取所有特殊菜单的name
|
||||
const collectSpecialMenuNames = (menus: any[]) => {
|
||||
const names: string[] = []
|
||||
const traverse = (children: any[]) => {
|
||||
children.forEach(child => {
|
||||
if (child.name) {
|
||||
names.push(child.name)
|
||||
}
|
||||
// 递归处理子菜单
|
||||
if (child.children && Array.isArray(child.children)) {
|
||||
traverse(child.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
menus.forEach(menu => {
|
||||
if (menu.children && Array.isArray(menu.children)) {
|
||||
traverse(menu.children)
|
||||
}
|
||||
})
|
||||
return names
|
||||
}
|
||||
// 提取所有一级特殊菜单的name
|
||||
const collectSpecialMenuNamesLevel1 = (menus: any[]) =>{
|
||||
const names: string[] = []
|
||||
menus.forEach(menu => {
|
||||
if (menu.name) {
|
||||
names.push(menu.name)
|
||||
}
|
||||
})
|
||||
return names
|
||||
}
|
||||
|
||||
// 1. 从 addonKeys 中提取所有需要匹配的 key(核心:遍历所有分类的 list)
|
||||
const getAddonAllKeys = (addonData) => {
|
||||
// 先判断 addonKeys 是否有效,避免报错
|
||||
if (!addonData || typeof addonData !== 'object') return [];
|
||||
|
||||
// 存储所有分类下的 key
|
||||
const allKeys = [];
|
||||
// 遍历 addonKeys.data 下的所有分类(如 marketing_active、marketing_tool)
|
||||
Object.values(addonData).forEach(category => {
|
||||
// 每个分类下的 list 可能为空,先判断
|
||||
if (Array.isArray(category.list)) {
|
||||
// 提取当前分类 list 里的所有 key,push 到 allKeys
|
||||
category.list.forEach(item => {
|
||||
if (item.key) allKeys.push(item.key);
|
||||
});
|
||||
}
|
||||
});
|
||||
return allKeys;
|
||||
};
|
||||
|
||||
// 处理 specialMenusKeys 子菜单 show 的方法
|
||||
const handleSpecialMenus = () => {
|
||||
const specialMenusKeys = storage.get('specialAppList')
|
||||
if (Array.isArray(specialMenusKeys) && specialMenusKeys.length) {
|
||||
const processedSpecialMenus = JSON.parse(JSON.stringify(specialMenusKeys));
|
||||
const activeAppKey = storage.get('activeAppKey');
|
||||
|
||||
// 收集所有特殊菜单的name
|
||||
processedSpecialMenus.forEach(menu => {
|
||||
if (menu.children && Array.isArray(menu.children)) {
|
||||
const traverseChildren = (children) => {
|
||||
children.forEach(child => {
|
||||
if (child && child.is_show !== undefined) {
|
||||
child.is_show = (child.menu_key === activeAppKey) ? 1 : 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
traverseChildren(menu.children);
|
||||
}
|
||||
});
|
||||
// 过滤掉 children 为空的特殊菜单
|
||||
const filteredSpecialMenus = processedSpecialMenus.filter(menu => {
|
||||
return menu.children && menu.children.length > 0;
|
||||
});
|
||||
return formatRouters(filteredSpecialMenus);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
// 统一处理跳转逻辑
|
||||
const handleJump = (routeName: string) => {
|
||||
// 检查目标路由是否在特殊菜单列表中
|
||||
const isInSpecialMenus = specialMenuNames.value.includes(routeName)
|
||||
// 核心逻辑:如果不在特殊菜单中,就删除activeAppKey
|
||||
if (!isInSpecialMenus) {
|
||||
storage.remove('activeAppKey')
|
||||
} else {
|
||||
}
|
||||
// 点击特殊菜单的一级,跳转应用列表
|
||||
if (specialMenuNamesLevel1.value.includes(routeName)) {
|
||||
routeName = 'addon_list'
|
||||
}
|
||||
// 跳转时添加随机查询参数(用于触发页面感知)
|
||||
const query = route.name === routeName
|
||||
? { refresh: Date.now() } // 相同路由时添加随机参数
|
||||
: {};
|
||||
|
||||
// 执行跳转
|
||||
router.push({ name: routeName, query });
|
||||
}
|
||||
|
||||
watch(route, () => {
|
||||
const addonKeys = storage.get('defaultAppList')
|
||||
// console.log('addonKeys', addonKeys)
|
||||
if (props.routes.name == 'addon_list') {
|
||||
const addonAllKeys = getAddonAllKeys(addonKeys);
|
||||
// 步骤2:过滤 children,保留 name 不在 addonAllKeys 中的项
|
||||
if (props.routes.children) {
|
||||
props.routes.children = props.routes.children.filter(child => {
|
||||
// 若 child 没有 name 或 name 不在 addonAllKeys 中,保留;否则删除
|
||||
return !child.name || !addonAllKeys.includes(child.name);
|
||||
});
|
||||
// 处理 specialMenusKeys,根据 activeAppKey 设置子菜单 show
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
if (processedSpecialMenus.length) {
|
||||
const newChildren = [...(props.routes.children || [])];
|
||||
processedSpecialMenus.forEach(special => {
|
||||
const index = newChildren.findIndex(child => child.name === special.name);
|
||||
if (index !== -1) {
|
||||
// 存在就替换(更新 show)
|
||||
newChildren[index] = special;
|
||||
} else {
|
||||
newChildren.push(special);
|
||||
}
|
||||
});
|
||||
props.routes.children = newChildren;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (systemAddonKeys.value.includes(route.meta.addon) && addonRouters[route.meta.addon]) {
|
||||
addonsMenus.value = addonRouters[route.meta.addon]
|
||||
} else if (route.meta.attr && addonRouters[route.meta.attr]) {
|
||||
@ -97,7 +237,7 @@ watch(route, () => {
|
||||
}
|
||||
}
|
||||
|
||||
const marketingKeys = storage.get('darksideMarketingKeys')
|
||||
const marketingKeys = storage.get('defaultMarketingKeys')
|
||||
const matchedName = route.matched[1]?.name
|
||||
if (props.routes.name == 'marketing_list') {
|
||||
if (marketingKeys && marketingKeys.includes(matchedName)) {
|
||||
@ -107,7 +247,42 @@ watch(route, () => {
|
||||
addonsMenus.value = null
|
||||
}
|
||||
}
|
||||
|
||||
}, { immediate: true })
|
||||
|
||||
// 监听 localStorage 中 activeAppKey 的变化
|
||||
onMounted(() => {
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
specialMenuNames.value = collectSpecialMenuNames(processedSpecialMenus)
|
||||
specialMenuNamesLevel1.value = collectSpecialMenuNamesLevel1(processedSpecialMenus)
|
||||
const handleStorageChange = (event: StorageEvent) => {
|
||||
if (event.key === 'activeAppKey') {
|
||||
if (props.routes.name == 'addon_list') {
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
if (processedSpecialMenus.length && props.routes.children) {
|
||||
const newChildren = [...(props.routes.children || [])];
|
||||
processedSpecialMenus.forEach(special => {
|
||||
const index = newChildren.findIndex(child => child.name === special.name);
|
||||
if (index !== -1) {
|
||||
// 存在就替换(更新 show)
|
||||
newChildren[index] = special;
|
||||
} else {
|
||||
newChildren.push(special);
|
||||
}
|
||||
});
|
||||
props.routes.children = newChildren;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('storage', handleStorageChange);
|
||||
|
||||
// 组件卸载时移除事件监听
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('storage', handleStorageChange);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@ -30,9 +30,9 @@ import useSystemStore from '@/stores/modules/system'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import menuItem from './menu-item.vue'
|
||||
import { img } from '@/utils/common'
|
||||
import { findFirstValidRoute } from '@/router/routers'
|
||||
import { getShowMarketing } from '@/app/api/site'
|
||||
import { findFirstValidRoute ,formatRouters} from '@/router/routers'
|
||||
import storage from '@/utils/storage'
|
||||
import {getShowApp, getShowSpecialMenu} from '@/app/api/site'
|
||||
|
||||
const systemStore = useSystemStore()
|
||||
const userStore = useUserStore()
|
||||
@ -43,23 +43,29 @@ const addonIndexRoute = userStore.addonIndexRoute
|
||||
const menuData = ref<Record<string, any>[]>([])
|
||||
const addonRouters: Record<string, any> = {}
|
||||
const logoUrl = computed(() => {
|
||||
return userStore.siteInfo.icon ? userStore.siteInfo.icon : systemStore.website.icon
|
||||
return userStore.siteInfo.logo ? userStore.siteInfo.logo : systemStore.website.logo
|
||||
})
|
||||
|
||||
const getMarketingList = async () => {
|
||||
const res = await getShowMarketing()
|
||||
const marketingList = res.data
|
||||
const marketingKeys = marketingList?.marketing?.list?.map(item => item.key) ?? []
|
||||
// menuData.value.forEach((item, index, arr) => {
|
||||
// if (marketingKeys.includes(item.name)) {
|
||||
// arr.splice(index, 1)
|
||||
// }
|
||||
// })
|
||||
storage.set({ key: 'darksideMarketingKeys', data: marketingKeys })
|
||||
const appList = ref<Record<string, any>[]>([])
|
||||
|
||||
const getAppList = async () => {
|
||||
const res = await getShowApp()
|
||||
appList.value = res.data
|
||||
|
||||
storage.set({ key: 'defaultAppList', data: appList.value })
|
||||
}
|
||||
const specialList = ref<Record<string, any>[]>([])
|
||||
const getShowSpecialMenuList = async () => {
|
||||
const res = await getShowSpecialMenu()
|
||||
// specialList.value = formatRouters(res.data.list)
|
||||
specialList.value = res.data.list
|
||||
storage.set({ key: 'specialAppList', data: specialList.value })
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getMarketingList()
|
||||
getAppList()
|
||||
getShowSpecialMenuList()
|
||||
})
|
||||
|
||||
routers.forEach((item, index) => {
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<!-- 面包屑导航 -->
|
||||
<div class="flex items-center h-full pl-[10px]">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item v-for="(route, index) in breadcrumb" :key="index">{{route.meta.title }}</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-for="(route, index) in breadcrumb" :key="index" :to="route.path" class="inter">{{route.meta.title }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<el-scrollbar>
|
||||
<div class="p-[15px]">
|
||||
<router-view v-slot="{ Component, route }" v-if="appStore.routeRefreshTag">
|
||||
<keep-alive :include="tabbarStore.tabNames">
|
||||
<keep-alive :include="tabbarStore.tabNames" :max="15">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<template v-if="meta.show">
|
||||
<el-sub-menu v-if="routes.children" :index="String(routes.name)">
|
||||
<el-sub-menu v-if="hasVisibleChild" :index="String(routes.name)">
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
|
||||
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
|
||||
@ -15,7 +15,7 @@
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
<template v-else>
|
||||
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
|
||||
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
|
||||
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
|
||||
@ -23,7 +23,7 @@
|
||||
<span class="ml-[10px]">{{ meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else>
|
||||
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-else>
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
|
||||
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
|
||||
@ -34,16 +34,16 @@
|
||||
</template>
|
||||
<div v-if="routes.is_border" class="!border-0 !border-t-[1px] border-solid mx-[25px] bg-[#f7f7f7] my-[5px]"></div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, computed, watch , onMounted, onUnmounted} from 'vue'
|
||||
import menuItem from './menu-item.vue'
|
||||
import useSystemStore from '@/stores/modules/system'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import storage from '@/utils/storage'
|
||||
import { findFirstValidRoute ,formatRouters} from '@/router/routers'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@ -62,8 +62,11 @@ const props = defineProps({
|
||||
const systemStore = useSystemStore()
|
||||
const meta = computed(() => props.routes.meta)
|
||||
|
||||
// 存储所有特殊菜单的name
|
||||
const specialMenuNames = ref<string[]>([])
|
||||
const specialMenuNamesLevel1 = ref<string[]>([])
|
||||
const addons = computed(() => {
|
||||
const addons:Record<string, any> = {}
|
||||
const addons: Record<string, any> = {}
|
||||
userStore.siteInfo?.apps.forEach((item: any) => { addons[item.key] = item })
|
||||
userStore.siteInfo?.site_addons.forEach((item: any) => { addons[item.key] = item })
|
||||
return addons
|
||||
@ -72,7 +75,12 @@ const addons = computed(() => {
|
||||
const systemAddonKeys = computed(() => {
|
||||
return userStore.siteInfo?.site_addons.map((item: any) => item.key)
|
||||
})
|
||||
|
||||
const hasVisibleChild = computed(() => {
|
||||
if (!props.routes.children || !Array.isArray(props.routes.children)) {
|
||||
return false
|
||||
}
|
||||
return props.routes.children.some(child => child.meta?.show === 1)
|
||||
})
|
||||
const addonRouters: Record<string, any> = {}
|
||||
routers.forEach(item => {
|
||||
item.original_name = item.name
|
||||
@ -86,8 +94,129 @@ routers.forEach(item => {
|
||||
|
||||
const addonsMenus = ref(null)
|
||||
|
||||
// 提取所有特殊菜单的name
|
||||
const collectSpecialMenuNames = (menus: any[]) => {
|
||||
const names: string[] = []
|
||||
const traverse = (children: any[]) => {
|
||||
children.forEach(child => {
|
||||
if (child.name) {
|
||||
names.push(child.name)
|
||||
}
|
||||
// 递归处理子菜单
|
||||
if (child.children && Array.isArray(child.children)) {
|
||||
traverse(child.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
menus.forEach(menu => {
|
||||
if (menu.children && Array.isArray(menu.children)) {
|
||||
traverse(menu.children)
|
||||
}
|
||||
})
|
||||
return names
|
||||
}
|
||||
// 提取所有一级特殊菜单的name
|
||||
const collectSpecialMenuNamesLevel1 = (menus: any[]) =>{
|
||||
const names: string[] = []
|
||||
menus.forEach(menu => {
|
||||
if (menu.name) {
|
||||
names.push(menu.name)
|
||||
}
|
||||
})
|
||||
return names
|
||||
}
|
||||
|
||||
// 从 addonKeys 中提取所有需要匹配的 key
|
||||
const getAddonAllKeys = (addonData) => {
|
||||
if (!addonData || typeof addonData !== 'object') return [];
|
||||
const allKeys = [];
|
||||
Object.values(addonData).forEach(category => {
|
||||
if (Array.isArray(category.list)) {
|
||||
category.list.forEach(item => {
|
||||
if (item.key) allKeys.push(item.key);
|
||||
});
|
||||
}
|
||||
});
|
||||
return allKeys;
|
||||
};
|
||||
|
||||
// 处理 specialMenusKeys 子菜单 show 的方法
|
||||
const handleSpecialMenus = () => {
|
||||
const specialMenusKeys = storage.get('specialAppList')
|
||||
if (Array.isArray(specialMenusKeys) && specialMenusKeys.length) {
|
||||
const processedSpecialMenus = JSON.parse(JSON.stringify(specialMenusKeys));
|
||||
const activeAppKey = storage.get('activeAppKey');
|
||||
|
||||
// 收集所有特殊菜单的name
|
||||
processedSpecialMenus.forEach(menu => {
|
||||
if (menu.children && Array.isArray(menu.children)) {
|
||||
const traverseChildren = (children) => {
|
||||
children.forEach(child => {
|
||||
if (child && child.is_show !== undefined) {
|
||||
child.is_show = (child.menu_key === activeAppKey) ? 1 : 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
traverseChildren(menu.children);
|
||||
}
|
||||
});
|
||||
// 过滤掉 children 为空的特殊菜单
|
||||
const filteredSpecialMenus = processedSpecialMenus.filter(menu => {
|
||||
return menu.children && menu.children.length > 0;
|
||||
});
|
||||
return formatRouters(filteredSpecialMenus);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
// 统一处理跳转逻辑
|
||||
const handleJump = (routeName: string) => {
|
||||
// 检查目标路由是否在特殊菜单列表中
|
||||
const isInSpecialMenus = specialMenuNames.value.includes(routeName)
|
||||
// 核心逻辑:如果不在特殊菜单中,就删除activeAppKey
|
||||
if (!isInSpecialMenus) {
|
||||
storage.remove('activeAppKey')
|
||||
} else {
|
||||
}
|
||||
// 点击特殊菜单的一级,跳转应用列表
|
||||
if (specialMenuNamesLevel1.value.includes(routeName)) {
|
||||
routeName = 'addon_list'
|
||||
}
|
||||
// 跳转时添加随机查询参数(用于触发页面感知)
|
||||
const query = route.name === routeName
|
||||
? { refresh: Date.now() } // 相同路由时添加随机参数
|
||||
: {};
|
||||
|
||||
// 执行跳转
|
||||
router.push({ name: routeName, query });
|
||||
}
|
||||
|
||||
watch(route, () => {
|
||||
const addonKeys = storage.get('defaultAppList')
|
||||
if (props.routes.name == 'addon_list') {
|
||||
const addonAllKeys = getAddonAllKeys(addonKeys);
|
||||
if (props.routes.children) {
|
||||
// 过滤掉不需要显示的子菜单
|
||||
props.routes.children = props.routes.children.filter(child => {
|
||||
return !child.name || !addonAllKeys.includes(child.name);
|
||||
});
|
||||
// 处理特殊菜单
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
if (processedSpecialMenus.length) {
|
||||
const newChildren = [...(props.routes.children || [])];
|
||||
processedSpecialMenus.forEach(special => {
|
||||
const index = newChildren.findIndex(child => child.name === special.name);
|
||||
if (index !== -1) {
|
||||
newChildren[index] = special;
|
||||
} else {
|
||||
newChildren.push(special);
|
||||
}
|
||||
});
|
||||
props.routes.children = newChildren;
|
||||
}
|
||||
}
|
||||
|
||||
if (systemAddonKeys.value.includes(route.meta.addon) && addonRouters[route.meta.addon]) {
|
||||
addonsMenus.value = addonRouters[route.meta.addon]
|
||||
} else if (route.meta.attr && addonRouters[route.meta.attr]) {
|
||||
@ -107,12 +236,46 @@ watch(route, () => {
|
||||
addonsMenus.value = null
|
||||
}
|
||||
}
|
||||
// console.log('addonsMenus', props.routes)
|
||||
}, { immediate: true })
|
||||
|
||||
// 监听 localStorage 中 activeAppKey 的变化
|
||||
onMounted(() => {
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
specialMenuNames.value = collectSpecialMenuNames(processedSpecialMenus)
|
||||
specialMenuNamesLevel1.value = collectSpecialMenuNamesLevel1(processedSpecialMenus)
|
||||
const handleStorageChange = (event: StorageEvent) => {
|
||||
if (event.key === 'activeAppKey') {
|
||||
if (props.routes.name == 'addon_list') {
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
if (processedSpecialMenus.length && props.routes.children) {
|
||||
const newChildren = [...(props.routes.children || [])];
|
||||
processedSpecialMenus.forEach(special => {
|
||||
const index = newChildren.findIndex(child => child.name === special.name);
|
||||
if (index !== -1) {
|
||||
newChildren[index] = special;
|
||||
} else {
|
||||
newChildren.push(special);
|
||||
}
|
||||
});
|
||||
props.routes.children = newChildren;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('storage', handleStorageChange);
|
||||
|
||||
// 组件卸载时移除事件监听
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('storage', handleStorageChange);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.el-sub-menu{
|
||||
.el-icon{
|
||||
.el-sub-menu {
|
||||
.el-icon {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,8 +30,9 @@ import useSystemStore from '@/stores/modules/system'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import menuItem from './menu-item.vue'
|
||||
import { img } from '@/utils/common'
|
||||
import { findFirstValidRoute } from '@/router/routers'
|
||||
import { getShowMarketing } from '@/app/api/site'
|
||||
import { findFirstValidRoute ,formatRouters} from '@/router/routers'
|
||||
import { getShowApp,getShowSpecialMenu} from '@/app/api/site'
|
||||
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const systemStore = useSystemStore()
|
||||
@ -43,23 +44,30 @@ const addonIndexRoute = userStore.addonIndexRoute
|
||||
const menuData = ref<Record<string, any>[]>([])
|
||||
const addonRouters: Record<string, any> = {}
|
||||
const logoUrl = computed(() => {
|
||||
return userStore.siteInfo.icon ? userStore.siteInfo.icon : systemStore.website.icon
|
||||
return userStore.siteInfo.logo ? userStore.siteInfo.logo : systemStore.website.logo
|
||||
})
|
||||
|
||||
const getMarketingList = async () => {
|
||||
const res = await getShowMarketing()
|
||||
const marketingList = res.data
|
||||
const marketingKeys = marketingList?.marketing?.list?.map(item => item.key) ?? []
|
||||
// menuData.value.forEach((item, index, arr) => {
|
||||
// if (marketingKeys.includes(item.name)) {
|
||||
// arr.splice(index, 1)
|
||||
// }
|
||||
// })
|
||||
storage.set({ key: 'defaultMarketingKeys', data: marketingKeys })
|
||||
const appList = ref<Record<string, any>[]>([])
|
||||
|
||||
const getAppList = async () => {
|
||||
const res = await getShowApp()
|
||||
appList.value = res.data
|
||||
|
||||
storage.set({ key: 'defaultAppList', data: appList.value })
|
||||
}
|
||||
const specialList = ref<Record<string, any>[]>([])
|
||||
const getShowSpecialMenuList = async () => {
|
||||
const res = await getShowSpecialMenu()
|
||||
// specialList.value = formatRouters(res.data.list)
|
||||
specialList.value = res.data.list
|
||||
|
||||
storage.set({ key: 'specialAppList', data: specialList.value })
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getMarketingList()
|
||||
getAppList()
|
||||
getShowSpecialMenuList()
|
||||
})
|
||||
|
||||
routers.forEach(item => {
|
||||
@ -87,6 +95,7 @@ routers.forEach(item => {
|
||||
addonRouters[item.meta.addon] = item
|
||||
}
|
||||
|
||||
// console.log('menuData', menuData.value)
|
||||
// 排序, 功能正确,改了排序后需要把菜单排序的默认值重新调整一下【多应用一级菜单,单应用二级菜单】
|
||||
// menuData.value.sort((a, b) => {
|
||||
// if (a.meta.sort && b.meta.sort) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user