Niucloud JAVA版框架1.1.0更新内容
    新增
    * 增加小程序展示线上版本号
    * 发布小程序添加自定义版本号
    * 添加后台取消小程序绑定微信公众平台授权
    * 管理端核销功能
    * 用户端导入微信地址功能

    优化
    * 装修页面时可自定义底部导航
    * 小程序添加版权信息展示
    * 应用菜单结构优化:取消独立营销菜单,统一在应用菜单下按分类
    * 分页列表增加缓存,记录当前页信息,操作完成后,返回当前页
    * 开启标签栏时对标签页面进行缓存
    * 统一框架装修自定义使用底部导航数据结构

    修复
    * 删除表单报错
    * admin首页点击未安装、已安装应用,页面跳转空白
    * 修复插件云安装错误时同时显示安装失败和安装完成

商城1.0.5更新内容
    新增
    * 用户端添加订单开具发票功能
    * 管理后台添加补开发票功能

    优化
    * 商品列表增加相关活动的关联展示
    * 限时折扣、积分商城增加批量操作
    * 积分商品,若有规格未参与积分活动,则无法购买
    * 复制商品,提示语优化

    修复
    * 满减送活动赠品设置多件,只展示1件
    * 满减送赠品未扣除库存问题
    * 商品列表批量设置分类后商品分类查询不到商品的问题
    * 商品标签状态已经关闭添加商品时依旧可以选择
    * 商家地址库联系方式搜索无效
    * 商品分类拖动排序无效
    * 参与满减活动再使用优惠券,订单0元申请退款时,退款金额出现负数
    * 积分商城规格值设置不参与积分兑换提交订单未拦截
    * 虚拟商品核销码设置的永久有效,核销时提示商品已过期
    * 订单满减送退款售后点击详情报错
    * 按会员标签发放优惠券异常一直未发放
    * 微信支付申请退款售后点击转账跳转404
    * 订单列表输入会员编号、账号点击搜索筛选不出来内容
    * 多商品组商品来源选择分组,只有一个商品分类时多商品组商品展示不正确
    * 商品组件排序方式选择为价格时报错
    * 图文导航选择积分商品报错
This commit is contained in:
wangchen147 2025-12-24 12:30:43 +08:00
parent c196a8622d
commit 86198c273f
3715 changed files with 34261 additions and 23120 deletions

View File

@ -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']

View File

@ -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",

View File

@ -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"
}
}
}

View File

@ -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': "关闭对话框",

View File

@ -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;

View File

@ -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>

View File

@ -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 });
}

View File

@ -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);

View File

@ -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 })
}

View File

@ -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')
}

View File

@ -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})
}
/***************************************************** 核销员 ****************************************************/
/**

View File

@ -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 })
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

View File

@ -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

View File

@ -7,7 +7,7 @@
"menuTypeButton": "按钮",
"menuDeleteTips": "删除菜单会删除当前菜单以及该菜单下所有子菜单,是否确认删除?",
"initializeMenu":"重置菜单",
"initializeMenuTipsOne":"重置菜单会将应用或插件的dict目录下的菜单配置文件中菜单配置更新到数据库一般用做开发者修改了dict菜单配置文件后,同步到数据库操作。",
"initializeMenuTipsOne":"重置菜单会将应用或插件的resources/**/loader/menu/目录下的菜单配置文件中的菜单配置更新到数据库。一般用做开发者修改了菜单配置文件后,同步到数据库操作。",
"initializeMenuTipsTwo":"如果用户手动调整过以下菜单项,通常允许进行本项操作,操作会重置为原始菜单。 请谨慎使用!",
"addMenu": "添加菜单",
"updateMenu": "编辑菜单",

View File

@ -29,5 +29,8 @@
"encodingAesKeyPlaceholder": "请输入EncodingAESKey",
"cleartextModeTips": "明文模式下,不使用消息体加解密功能,安全系数较低",
"compatibleModeTips": "兼容模式下,明文、密文将共存,方便开发者调试和维护",
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高"
}
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高",
"wechatBaseUri": "借权域名",
"wechatBaseUriPlaceholder": "",
"wechatBaseUriTips": "默认留空填写后将替换https://open.weixin.gg.com/获取授权!"
}

View File

@ -243,7 +243,7 @@
"carouselSearchSwiperSet": "轮播图设置",
"carouselSearchSwiperControl": "展示开关",
"carouselSearchSwiperInterval": "切换间隔 / 秒",
"carouselSearchSwiperTips": "建议上传尺寸相同的图片推荐尺寸750*350鼠标拖拽可调整图片顺序",
"carouselSearchSwiperTips": "建议上传尺寸相同的图片推荐尺寸750*580鼠标拖拽可调整图片顺序",
"carouselSearchTabStyle": "选项卡样式",
"carouselSearchStyle": "搜索框样式",
"noColor": "常规颜色",

View File

@ -244,7 +244,7 @@
"carouselSearchSwiperSet": "轮播图设置",
"carouselSearchSwiperControl": "展示开关",
"carouselSearchSwiperInterval": "切换间隔 / 秒",
"carouselSearchSwiperTips": "建议上传尺寸相同的图片推荐尺寸750*350鼠标拖拽可调整图片顺序",
"carouselSearchSwiperTips": "建议上传尺寸相同的图片推荐尺寸750*580鼠标拖拽可调整图片顺序",
"carouselSearchTabStyle": "选项卡样式",
"carouselSearchStyle": "搜索框样式",
"noColor": "常规颜色",

View File

@ -121,6 +121,7 @@
"fieldStatistics":"字段统计",
"viewInformation":"查看信息",
"deleteTips":"确定删除该条数据吗"
"deleteTips":"确定删除该条数据吗",
"startDate": "开始日期",
"endDate": "结束日期"
}

View File

@ -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。"
}

View File

@ -7,5 +7,6 @@
"validTimeTips": "过期后将重新获取定位信息",
"validTimePlaceholder": "请输入定位有效期",
"validTimeFormatTips": "格式输入错误",
"validTimeNotZeroTips": "定位有效期不能小于5分钟"
}
"validTimeNotZeroTips": "定位有效期不能小于5分钟",
"aMapKey": "高德地图KEY"
}

View File

@ -74,5 +74,9 @@
"foldText":"展开/折叠",
"appName": "套餐内含应用",
"addonName": "套餐内含插件",
"siteInitTips":"确定要初始化站点吗?该操作将删除站点内所有数据,该操作无法退回,确定要继续初始化吗?"
"siteInitTips":"确定要初始化站点吗?该操作将删除站点内所有数据,该操作无法退回,确定要继续初始化吗?",
"createTimeStartDate": "开始时间(创建时间)",
"createTimeEndDate": "结束时间(创建时间)",
"expireTimeStartDate": "开始时间(到期时间)",
"expireTimeEndDate": "结束时间(到期时间)"
}

View File

@ -24,5 +24,6 @@
"startBackUp": "开始备份",
"backUpEnd": "备份完成",
"remark": "备注",
"remarkEmpty": "无"
"remarkEmpty": "无",
"status": "状态"
}

View File

@ -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>

View File

@ -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] })
}

View File

@ -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>

View File

@ -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>

View File

@ -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:''
}
})

View File

@ -96,7 +96,6 @@ const refreshMenu = () => {
).then(() => {
refreshLoading.value = true
menuRefresh({}).then(res => {
location.reload()
refreshLoading.value = false
}).catch(() => {
refreshLoading.value = false

View File

@ -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">

View File

@ -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(() => {
})

View File

@ -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>

View File

@ -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')

View File

@ -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>

View File

@ -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

View File

@ -250,10 +250,6 @@ getWeappConfig().then(res => {
loading.value = false
})
getWeappConfig().then(res => {
Object.assign(res.data)
})
/**
* 复制
*/

View File

@ -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>()

View File

@ -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()

View 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>

View File

@ -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)
//

View File

@ -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(() => {
})

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
//

View File

@ -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>

View File

@ -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

View File

@ -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'

View File

@ -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>

View File

@ -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: []
}

View 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>

View File

@ -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>

View File

@ -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 {

View File

@ -147,7 +147,7 @@ const deleteEvent = (id: number) => {
}
).then(() => {
deleteMemberLevel(id).then(() => {
loadMemberLevelList()
loadMemberLevelList(getTablePageStorage(memberLevelTableData.searchParam).page);
}).catch(() => {
})
})

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
})
}

View File

@ -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>

View File

@ -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

View File

@ -148,7 +148,7 @@ const deleteEvent = (id: number) => {
}
).then(() => {
deletePrinterTemplate(id).then(() => {
loadPrinterTemplateList()
loadPrinterTemplateList(getTablePageStorage(printerTemplateTable.searchParam).page)
})
})
}

View File

@ -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 () => {

View File

@ -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 })

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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

View File

@ -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">

View File

@ -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 || '' }}

View File

@ -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;

View File

@ -126,7 +126,9 @@ const prop = defineProps({
},
ignore: {
type: Array,
default: []
default: () => {
return [] // ['DIY_MAKE_PHONE_CALL']
}
}
})

View File

@ -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; //

View File

@ -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
})
})

View File

@ -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" />

View 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>

View File

@ -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' })

View File

@ -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": "请输入简介内容"
}

View File

@ -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 })

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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_activemarketing_tool
Object.values(addonData).forEach(category => {
// list
if (Array.isArray(category.list)) {
// list keypush 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">

View File

@ -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) => {

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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