fix admin

This commit is contained in:
CQ 2025-10-15 18:07:03 +08:00
parent 4282130387
commit 231f476a6f
32 changed files with 1496 additions and 717 deletions

View File

@ -147,7 +147,10 @@ div.edui-box {
overflow: visible; overflow: visible;
z-index: 1 !important; z-index: 1 !important;
} }
/* 全屏状态 */
.edui-default .edui-editor.edui-fullscreen {
z-index: 999 !important;
}
.edui-editor div { .edui-editor div {
width: auto; width: auto;
height: auto; height: auto;

View File

@ -291,13 +291,13 @@ export function getSiteAddons() {
* @returns * @returns
*/ */
export function getShowApp() { export function getShowApp() {
return request.get('site/showApp') return request.get('site/showCustomer')
} }
/** /**
* *
* @returns * @returns
*/ */
export function getShowMarketing() { export function getShowSpecialMenu() {
return request.get('site/showMarketing') return request.get('site/special_menu')
} }

View File

@ -299,7 +299,7 @@ const getUpgradeTaskFn = () => {
if (!upgradeContent.value) { if (!upgradeContent.value) {
upgradeContent.value = data.upgrade_content upgradeContent.value = data.upgrade_content
if (upgradeContent.value || !data.upgrade_content || !Array.isArray(data.upgrade_content.content)) { if (!data.upgrade_content || !Array.isArray(data.upgrade_content.content)) {
return return
} }
@ -691,7 +691,7 @@ defineExpose({
white-space: pre-wrap; white-space: pre-wrap;
} }
::v-deep .number-of-steps { :deep(.number-of-steps) {
.el-step__line { .el-step__line {
margin: 0 25px; margin: 0 25px;
background: #dddddd; background: #dddddd;

View File

@ -50,6 +50,7 @@ import { img } from '@/utils/common'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { t } from '@/lang' import { t } from '@/lang'
import storage from '@/utils/storage'
const addonIndexRoute = useUserStore().addonIndexRoute const addonIndexRoute = useUserStore().addonIndexRoute
const router = useRouter() const router = useRouter()
@ -73,6 +74,7 @@ const toLink = (item: any) => {
} else { } else {
addonIndexRoute[item.key] && router.push({ name: addonIndexRoute[item.key] }) addonIndexRoute[item.key] && router.push({ name: addonIndexRoute[item.key] })
} }
storage.set({ key: 'activeAppKey', data: item.key })
} }
</script> </script>

View File

@ -45,7 +45,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue' import { ref } from 'vue'
import { getShowMarketing } from '@/app/api/site' // import { getShowMarketing } from '@/app/api/site'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@ -57,8 +57,8 @@ const marketingList = ref<Record<string, any>[]>([])
const loading = ref(true) const loading = ref(true)
const getMarketingList = async () => { const getMarketingList = async () => {
const res = await getShowMarketing() // const res = await getShowMarketing()
marketingList.value = res.data // marketingList.value = res.data
loading.value = false loading.value = false
} }
getMarketingList() getMarketingList()

View File

@ -62,7 +62,7 @@
</div> </div>
<div v-show="formData.package_type == 'cloud'"> <div v-show="formData.package_type == 'cloud'">
<el-form-item :label="t('icon')"> <el-form-item :label="t('icon')" prop="build.icon">
<div class="input-width" > <div class="input-width" >
<upload-file v-model="formData.build.icon" accept=".zip" api="sys/document/applet"></upload-file> <upload-file v-model="formData.build.icon" accept=".zip" api="sys/document/applet"></upload-file>
</div> </div>
@ -80,7 +80,7 @@
<div class="form-tip flex items-center">证书可以自己生成也可通过niucloud提供的<span class="text-primary cursor-pointer" @click="generateSingCertRef.open()">证书生成工具生成</span></div> <div class="form-tip flex items-center">证书可以自己生成也可通过niucloud提供的<span class="text-primary cursor-pointer" @click="generateSingCertRef.open()">证书生成工具生成</span></div>
</el-form-item> </el-form-item>
<div v-show="formData.cert.type == 'private'"> <div v-show="formData.cert.type == 'private'">
<el-form-item :label="t('certFile')" prop="cert.cert_file"> <el-form-item :label="t('certFile')" prop="cert.file">
<div class="input-width" > <div class="input-width" >
<upload-file v-model="formData.cert.file" accept="" api="sys/document/android_cert"></upload-file> <upload-file v-model="formData.cert.file" accept="" api="sys/document/android_cert"></upload-file>
</div> </div>
@ -166,6 +166,7 @@ const initialFormData = {
}, },
cert: { cert: {
type: 'public', type: 'public',
file: '',
key_alias: '', key_alias: '',
key_password: '', key_password: '',
store_password: '' store_password: ''
@ -211,7 +212,7 @@ const formRules = computed(() => {
'build.icon': [ 'build.icon': [
{ required: formData.package_type == 'cloud', message: '请上传图标文件', trigger: 'blur' }, { required: formData.package_type == 'cloud', message: '请上传图标文件', trigger: 'blur' },
], ],
'cert.cert_file': [ 'cert.file': [
{ required: formData.package_type == 'cloud' && formData.cert.type == 'private', message: '请上传证书文件', trigger: 'blur' } { required: formData.package_type == 'cloud' && formData.cert.type == 'private', message: '请上传证书文件', trigger: 'blur' }
], ],
'cert.key_alias': [ 'cert.key_alias': [

View File

@ -65,7 +65,7 @@
<el-button type="primary" link v-if="row.status == 'upload_success'" @click="releaseEvent(row)">{{ t('release') }}</el-button> <el-button type="primary" link v-if="row.status == 'upload_success'" @click="releaseEvent(row)">{{ t('release') }}</el-button>
<el-button type="primary" link v-if="row.status == 'create_fail'" @click="handleFailReason(row)">{{ t('failReason') }}</el-button> <el-button type="primary" link v-if="row.status == 'create_fail'" @click="handleFailReason(row)">{{ t('failReason') }}</el-button>
<el-button type="primary" link v-if="row.package_path && row.upgrade_type != 'market'" @click="downloadEvent(row)">{{ t('download') }}</el-button> <el-button type="primary" link v-if="row.package_path && row.upgrade_type != 'market'" @click="downloadEvent(row)">{{ t('download') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row)">{{ t('delete') }}</el-button> <el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template> </template>
</el-table-column> </el-table-column>

View File

@ -35,7 +35,7 @@
<div class="mt-[16px] flex justify-end"> <div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="bottomNavTableData.page" v-model:page-size="bottomNavTableData.limit" <el-pagination v-model:current-page="bottomNavTableData.page" v-model:page-size="bottomNavTableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="bottomNavTableData.total" layout="total, sizes, prev, pager, next, jumper" :total="bottomNavTableData.total"
@size-change="loadbottomNavList()" @current-change="loadbottomNavList" /> @size-change="loadBottomNavList()" @current-change="loadBottomNavList" />
</div> </div>
</el-card> </el-card>

View File

@ -91,7 +91,7 @@ const editEvent = (data)=> {
<!-- 设置弹窗标题 --> <!-- 设置弹窗标题 -->
<style scoped> <style scoped>
/* 使用深度选择器 */ /* 使用深度选择器 */
::v-deep .custom-theme-dialog .el-dialog__title { :deep(.custom-theme-dialog .el-dialog__title) {
font-size: 16px; font-size: 16px;
} }
</style> </style>

View File

@ -1383,7 +1383,7 @@ html.dark .table-head-bg {
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
::v-deep .number-of-steps { :deep(.number-of-steps) {
.el-step__line { .el-step__line {
margin: 0 25px; margin: 0 25px;
background: #dddddd; background: #dddddd;

View File

@ -529,17 +529,20 @@ const save = (callback: any) => {
const api = posterStore.id ? editPoster : addPoster const api = posterStore.id ? editPoster : addPoster
api(data).then((res: any) => { api(data).then((res: any) => {
isRepeat.value = false // isRepeat.value = false
if (res.code == 1) { if (res.code == 1) {
if (posterStore.id) { if (posterStore.id) {
isRepeat.value = false // isRepeat.value = false //
} else { } else {
// isRepeat.value = false
location.href = `${location.origin}${backPath}` location.href = `${location.origin}${backPath}`
} }
if (callback) callback(res.data.id) if (callback) callback(res.data.id)
} }
}).catch(() => { }).catch(() => {
isRepeat.value = false isRepeat.value = false
}).finally(() => {
// isRepeat.value = false
}) })
} }

View File

@ -6,7 +6,7 @@
<span class="text-page-title">{{ pageName }}</span> <span class="text-page-title">{{ pageName }}</span>
<el-button type="primary" class="w-[100px]" @click="dialogVisible = true">{{ t('添加海报') }}</el-button> <el-button type="primary" class="w-[100px]" @click="dialogVisible = true">{{ t('添加海报') }}</el-button>
</div> </div>
<div class="mt-[20px]" v-if="!isImagick"> <div class="mt-[20px]" v-if="!isImagick && !posterTableData.loading">
<el-alert type="warning" show-icon :closable="false"> <el-alert type="warning" show-icon :closable="false">
<template #title> <template #title>
<span class="!text-[14px]">检测到PHP未安装ImageMagick扩展需安装后才能使用海报功能</span> <span class="!text-[14px]">检测到PHP未安装ImageMagick扩展需安装后才能使用海报功能</span>
@ -287,11 +287,10 @@ const resetForm = (formEl: FormInstance | undefined) => {
formEl.resetFields() formEl.resetFields()
loadPosterPageList() loadPosterPageList()
} }
const isImagick = ref(false) const isImagick = ref(true)
// imagemagick // imagemagick
const checkImagickFn = () => { const checkImagickFn = () => {
checkImagick().then((res:any) => { checkImagick().then((res:any) => {
console.log(res)
isImagick.value = res.data isImagick.value = res.data
}) })

View File

@ -22,19 +22,19 @@
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('putOnRecordEdit') }}</h3> <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-form-item :label="t('icp')" prop="icp">
<el-input v-model.trim="formData.icp" :placeholder="t('icpPlaceholder')" class="input-width" clearable maxlength="20"/> <el-input v-model.trim="formData.icp" :placeholder="t('icpPlaceholder')" class="input-width" clearable maxlength="20"/>
<div class="form-tip">{{ t('网站的ICP备案号显示在H5和PC端底部') }}</div> <div class="form-tip">{{ t('网站的ICP备案号,显示在PC端底部') }}</div>
</el-form-item> </el-form-item>
<el-form-item :label="t('govRecord')" > <el-form-item :label="t('govRecord')" >
<el-input v-model.trim="formData.gov_record" :placeholder="t('govRecordPlaceholder')" class="input-width" clearable maxlength="50"/> <el-input v-model.trim="formData.gov_record" :placeholder="t('govRecordPlaceholder')" class="input-width" clearable maxlength="50"/>
<div class="form-tip">{{ t('公安部门登记的备案信息显示在pc底部') }}</div> <div class="form-tip">{{ t('公安部门登记的备案信息,显示在PC底部') }}</div>
</el-form-item> </el-form-item>
<el-form-item :label="t('govUrl')" > <el-form-item :label="t('govUrl')" >
<el-input v-model.trim="formData.gov_url" :placeholder="t('govUrlPlaceholder')" class="input-width" clearable /> <el-input v-model.trim="formData.gov_url" :placeholder="t('govUrlPlaceholder')" class="input-width" clearable />
<div class="form-tip">{{ t('H5和PC底部显示的网站公安点击跳转的链接') }}</div> <div class="form-tip">{{ t('PC底部显示的网站公安点击跳转的链接') }}</div>
</el-form-item> </el-form-item>
<el-form-item :label="t('marketSupervisionUrl')" > <el-form-item :label="t('marketSupervisionUrl')" >
<el-input v-model.trim="formData.market_supervision_url" rows="4" clearable :placeholder="t('marketSupervisionUrlPlaceholder')" class="input-width" /> <el-input v-model.trim="formData.market_supervision_url" rows="4" clearable :placeholder="t('marketSupervisionUrlPlaceholder')" class="input-width" />
<div class="form-tip">{{ t('H5和PC底部显示的市场监督管理局点击跳转的链接') }}</div> <div class="form-tip">{{ t('PC底部显示的市场监督管理局点击跳转的链接') }}</div>
</el-form-item> </el-form-item>
</div> </div>
</el-card> </el-card>

View File

@ -1,82 +1,86 @@
<template> <template>
<el-dialog v-model="showDialog" :title="formData.uid ? t('updateUser') : t('addUser')" width="750px" :destroy-on-close="true"> <el-dialog v-model="showDialog" :title="formData.uid ? t('updateUser') : t('addUser')" width="750px" :destroy-on-close="true">
<el-scrollbar> <el-scrollbar>
<div class="max-h-[60vh]"> <div class="max-h-[60vh]">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" autocomplete="off" v-loading="loading"> <el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" autocomplete="off" v-loading="loading">
<el-form-item :label="t('username')" prop="username"> <el-form-item :label="t('username')" prop="username">
<el-input v-model.trim="formData.username" clearable :placeholder="t('usernamePlaceholder')" class="input-width" :readonly="formData.uid" :disabled="formData.uid" @click="realnameInput = false" @blur="realnameInput = true" /> <el-input v-model.trim="formData.username" clearable :placeholder="t('usernamePlaceholder')" class="input-width" :readonly="formData.uid" :disabled="formData.uid" @click="realnameInput = false" @blur="realnameInput = true" />
</el-form-item> </el-form-item>
<el-form-item :label="t('headImg')"> <el-form-item :label="t('headImg')">
<upload-image v-model="formData.head_img" /> <upload-image v-model="formData.head_img" />
</el-form-item> </el-form-item>
<el-form-item :label="t('userRealName')" prop="real_name"> <el-form-item :label="t('手机号')" prop="mobile">
<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-input v-model.trim="formData.mobile" :placeholder="t('请输入手机号')" clearable class="input-width" maxlength="11" show-word-limit />
</el-form-item> </el-form-item>
<el-form-item :label="t('password')" prop="password"> <el-form-item :label="t('userRealName')" prop="real_name">
<el-input v-model.trim="formData.password" :class="passwordType == 'text' ? '' :'displayPass'" clearable :placeholder="t('passwordPlaceholder')" class="input-width" :readonly="passwordInput" @click="passwordInput = false" @blur="passwordInput = true" > <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 />
<template #suffix> </el-form-item>
<el-icon @click="togglePasswordVisibility" class="cursor-pointer">
<component :is=" passwordType === 'password' ? 'Hide' : 'View'" />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item :label="t('confirmPassword')" prop="confirm_password"> <el-form-item :label="t('password')" prop="password">
<el-input v-model.trim="formData.confirm_password" :class="confirmPasswordType == 'text' ? '' :'displayPass'" :placeholder="t('confirmPasswordPlaceholder')" clearable class="input-width" :readonly="confirmPasswordInput" @click="confirmPasswordInput = false" @blur="confirmPasswordInput = true" > <el-input v-model.trim="formData.password" :class="passwordType == 'text' ? '' :'displayPass'" clearable :placeholder="t('passwordPlaceholder')" class="input-width" :readonly="passwordInput" @click="passwordInput = false" @blur="passwordInput = true" >
<template #suffix> <template #suffix>
<el-icon @click="toggleConfirmPasswordVisibility" class="cursor-pointer"> <el-icon @click="togglePasswordVisibility" class="cursor-pointer">
<component :is=" confirmPasswordType === 'password' ? 'Hide' : 'View'" /> <component :is=" passwordType === 'password' ? 'Hide' : 'View'" />
</el-icon> </el-icon>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="t('userCreateSiteLimit')" v-if="!formData.uid && Object.keys(siteGroup).length" prop="create_site_limit"> <el-form-item :label="t('confirmPassword')" prop="confirm_password">
<div> <el-input v-model.trim="formData.confirm_password" :class="confirmPasswordType == 'text' ? '' :'displayPass'" :placeholder="t('confirmPasswordPlaceholder')" clearable class="input-width" :readonly="confirmPasswordInput" @click="confirmPasswordInput = false" @blur="confirmPasswordInput = true" >
<div>{{ t('siteGroup') }}</div> <template #suffix>
<el-checkbox-group v-model="formData.group_ids" @change="groupSelect"> <el-icon @click="toggleConfirmPasswordVisibility" class="cursor-pointer">
<el-checkbox v-for="item in siteGroup" :label="item.group_id">{{ item.group_name }}</el-checkbox> <component :is=" confirmPasswordType === 'password' ? 'Hide' : 'View'" />
</el-checkbox-group> </el-icon>
</div> </template>
<div class="w-full"> </el-input>
<div>{{ t('userCreateSiteLimit') }}</div> </el-form-item>
<el-table :data="formData.create_site_limit" size="large" class="w-full">
<el-table-column :label="t('siteGroup')" :show-overflow-tooltip="true"> <el-form-item :label="t('userCreateSiteLimit')" v-if="!formData.uid && Object.keys(siteGroup).length" prop="create_site_limit">
<template #default="{ row }"> <div>
{{ siteGroup[row.group_id] ? siteGroup[row.group_id].group_name : '' }} <div>{{ t('siteGroup') }}</div>
</template> <el-checkbox-group v-model="formData.group_ids" @change="groupSelect">
</el-table-column> <el-checkbox v-for="item in siteGroup" :label="item.group_id">{{ item.group_name }}</el-checkbox>
<el-table-column :label="t('createSiteNum')"> </el-checkbox-group>
<template #default="{ $index }">
<el-input v-model.number.trim="formData.create_site_limit[$index].num">
</el-input>
</template>
</el-table-column>
<el-table-column :label="t('siteMonth')">
<template #default="{ $index }">
<el-input v-model.number.trim="formData.create_site_limit[$index].month">
<template #append>{{ t('month') }}</template>
</el-input>
</template>
</el-table-column>
</el-table>
</div>
</el-form-item>
</el-form>
</div> </div>
</el-scrollbar> <div class="w-full">
<div>{{ t('userCreateSiteLimit') }}</div>
<el-table :data="formData.create_site_limit" size="large" class="w-full">
<el-table-column :label="t('siteGroup')" :show-overflow-tooltip="true">
<template #default="{ row }">
{{ siteGroup[row.group_id] ? siteGroup[row.group_id].group_name : '' }}
</template>
</el-table-column>
<el-table-column :label="t('createSiteNum')">
<template #default="{ $index }">
<el-input v-model.number.trim="formData.create_site_limit[$index].num">
</el-input>
</template>
</el-table-column>
<el-table-column :label="t('siteMonth')">
<template #default="{ $index }">
<el-input v-model.number.trim="formData.create_site_limit[$index].month">
<template #append>{{ t('month') }}</template>
</el-input>
</template>
</el-table-column>
</el-table>
</div>
</el-form-item>
</el-form>
</div>
</el-scrollbar>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button> <el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{ t('confirm') }}</el-button> <el-button type="primary" :loading="loading" @click="confirm(formRef)">{{ t('confirm') }}</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -93,119 +97,135 @@ const userStore = useUserStore()
const showDialog = ref(false) const showDialog = ref(false)
const loading = ref(true) const loading = ref(true)
const formData = ref({ const formData = ref({
uid: 0, uid: 0,
username: '', username: '',
password: '', password: '',
head_img: '', head_img: '',
real_name: '', mobile: '',
confirm_password: '', real_name: '',
create_site_limit: [], confirm_password: '',
group_ids: [] create_site_limit: [],
group_ids: []
}) })
const siteGroup = ref({}) const siteGroup = ref({})
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
const formRules = computed(() => { const formRules = computed(() => {
return { return {
username: [ username: [
{ required: true, message: t('usernamePlaceholder'), trigger: 'blur' } { required: true, message: t('usernamePlaceholder'), trigger: 'blur' }
], ],
password: [ password: [
{ required: userStore.userInfo && userStore.userInfo.is_super_admin == true, message: t('passwordPlaceholder'), trigger: 'blur' } { required: userStore.userInfo && userStore.userInfo.is_super_admin == true, message: t('passwordPlaceholder'), trigger: 'blur' }
], ],
real_name: [ mobile: [
{ required: true, message: t('userRealNamePlaceholder'), trigger: 'blur' } { required: true, message: t('请输入手机号'), trigger: 'blur' },
], {
confirm_password: [ validator: (rule: any, value: string, callback: any) => {
{ required: userStore.userInfo && userStore.userInfo.is_super_admin == true, message: t('confirmPasswordPlaceholder'), trigger: 'blur' }, if (!Test.mobile(value)) {
{ callback(new Error(t('手机号格式错误')))
validator: (rule: any, value: string, callback: any) => { } else {
if (value != formData.value.password) callback(new Error(t('confirmPasswordError'))) callback()
else callback() }
}, },
trigger: 'blur' trigger: 'blur'
}
],
real_name: [
{ required: true, message: t('userRealNamePlaceholder'), trigger: 'blur' }
],
confirm_password: [
{ required: userStore.userInfo && userStore.userInfo.is_super_admin == true, message: t('confirmPasswordPlaceholder'), trigger: 'blur' },
{
validator: (rule: any, value: string, callback: any) => {
if (value != formData.value.password) callback(new Error(t('confirmPasswordError')))
else callback()
},
trigger: 'blur'
}
],
create_site_limit: [
{
validator: (rule: any, value: string, callback: any) => {
if (formData.value.uid) callback()
let verify = true
for (let i = 0; i < formData.value.create_site_limit.length; i++) {
const item = formData.value.create_site_limit[i]
if (Test.empty(item.num)) {
callback(t('siteNumPlaceholder'))
verify = false
break
} }
], if (item.num < 1) {
create_site_limit: [ callback(t('siteNumCannotLtOne'))
{ verify = false
validator: (rule: any, value: string, callback: any) => { break
if (formData.value.uid) callback()
let verify = true
for (let i = 0; i < formData.value.create_site_limit.length; i++) {
const item = formData.value.create_site_limit[i]
if (Test.empty(item.num)) {
callback(t('siteNumPlaceholder'))
verify = false
break
}
if (item.num < 1) {
callback(t('siteNumCannotLtOne'))
verify = false
break
}
if (Test.empty(item.month)) {
callback(t('siteMonthPlaceholder'))
verify = false
break
}
if (item.month < 0) {
callback(t('siteMonthCannotLtOne'))
verify = false
break
}
}
if (verify) callback()
}
} }
] if (Test.empty(item.month)) {
} callback(t('siteMonthPlaceholder'))
verify = false
break
}
if (item.month < 0) {
callback(t('siteMonthCannotLtOne'))
verify = false
break
}
}
if (verify) callback()
}
}
]
}
}) })
getSiteGroupAll().then(({ data }) => { getSiteGroupAll().then(({ data }) => {
const list: any = {} const list: any = {}
data.forEach((item: any) => { data.forEach((item: any) => {
list[item.group_id] = item list[item.group_id] = item
}) })
siteGroup.value = list siteGroup.value = list
}) })
const setFormData = (uid: number = 0) => { const setFormData = (uid: number = 0) => {
if (uid) { if (uid) {
getUserInfo(uid).then(({ data }) => { getUserInfo(uid).then(({ data }) => {
formData.value.uid = data.uid formData.value.uid = data.uid
formData.value.username = data.username formData.value.username = data.username
formData.value.real_name = data.real_name formData.value.mobile = data.mobile
formData.value.head_img = data.head_img formData.value.real_name = data.real_name
loading.value = false formData.value.head_img = data.head_img
showDialog.value = true loading.value = false
}) showDialog.value = true
} else { })
formData.value = { } else {
uid: 0, formData.value = {
username: '', uid: 0,
password: '', username: '',
head_img: '', password: '',
real_name: '', head_img: '',
confirm_password: '', mobile: '',
create_site_limit: [], real_name: '',
group_ids: [] confirm_password: '',
} create_site_limit: [],
loading.value = false group_ids: []
showDialog.value = true
} }
loading.value = false
showDialog.value = true
}
} }
const emits = defineEmits(['complete']) const emits = defineEmits(['complete'])
const groupSelect = (groupIds: number[]) => { const groupSelect = (groupIds: number[]) => {
const list:any = [] const list:any = []
groupIds.forEach(item => { groupIds.forEach(item => {
list.push({ list.push({
group_id: item, group_id: item,
num: 1, num: 1,
month: 1 month: 1
})
}) })
formData.value.create_site_limit = list })
formData.value.create_site_limit = list
} }
/** /**
@ -213,22 +233,22 @@ const groupSelect = (groupIds: number[]) => {
* @param formEl * @param formEl
*/ */
const confirm = async (formEl: FormInstance | undefined) => { const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return if (loading.value || !formEl) return
await formEl.validate(async (valid) => { await formEl.validate(async (valid) => {
if (valid) { if (valid) {
loading.value = true loading.value = true
const save = formData.value.uid ? editUser : addUser const save = formData.value.uid ? editUser : addUser
save(formData.value).then(() => { save(formData.value).then(() => {
loading.value = false loading.value = false
showDialog.value = false showDialog.value = false
emits('complete') emits('complete')
}).catch(() => { }).catch(() => {
loading.value = false loading.value = false
}) })
} }
}) })
} }
const realnameInput = ref(true) const realnameInput = ref(true)
@ -238,23 +258,23 @@ const confirmPasswordInput = ref(true)
const passwordType = ref('password') const passwordType = ref('password')
const togglePasswordVisibility = () => { const togglePasswordVisibility = () => {
passwordType.value = passwordType.value === 'password' ? 'text' : 'password' passwordType.value = passwordType.value === 'password' ? 'text' : 'password'
} }
const confirmPasswordType = ref('password') const confirmPasswordType = ref('password')
const toggleConfirmPasswordVisibility = () => { const toggleConfirmPasswordVisibility = () => {
confirmPasswordType.value = confirmPasswordType.value === 'password' ? 'text' : 'password' confirmPasswordType.value = confirmPasswordType.value === 'password' ? 'text' : 'password'
} }
defineExpose({ defineExpose({
showDialog, showDialog,
setFormData setFormData
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.displayPass { .displayPass {
::v-deep .el-input__inner{ :deep(.el-input__inner){
-webkit-text-security: disc !important; -webkit-text-security: disc !important;
} }
} }

View File

@ -1,98 +1,99 @@
<template> <template>
<!--站点用户--> <!--站点用户-->
<div class="main-container"> <div class="main-container">
<el-card class="box-card !border-none" shadow="never"> <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> <span class="text-page-title">{{ pageName }}</span>
<div> <div>
<el-button type="primary" class="w-[100px]" @click="userEditRef.setFormData()">{{ t('addUser') }}</el-button> <el-button type="primary" class="w-[100px]" @click="userEditRef.setFormData()">{{ t('addUser') }}</el-button>
</div> </div>
</div> </div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never"> <el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="userTableData.searchParam" ref="searchFormRef" class="search-form"> <el-form :inline="true" :model="userTableData.searchParam" ref="searchFormRef" class="search-form">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model.trim="userTableData.searchParam.username" :placeholder="t('userNamePlaceholder')" /> <el-input v-model.trim="userTableData.searchParam.username" :placeholder="t('userNamePlaceholder')" />
</el-form-item> </el-form-item>
<!-- <el-form-item :label="t('createTime')" prop="create_time"> <!-- <el-form-item :label="t('createTime')" prop="create_time">
<el-date-picker v-model="userTableData.searchParam.create_time" type="datetimerange" <el-date-picker v-model="userTableData.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 prop="last_time">
<el-date-picker v-model="userTableData.searchParam.last_time" type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')" value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
:end-placeholder="t('endDate')" /> :end-placeholder="t('endDate')" />
</el-form-item> --> </el-form-item>
<el-form-item prop="last_time"> <el-form-item>
<el-date-picker v-model="userTableData.searchParam.last_time" type="datetimerange" <el-button type="primary" @click="loadUserList()">{{ t('search') }}</el-button>
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')" <el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
:end-placeholder="t('endDate')" /> </el-form-item>
</el-form-item> </el-form>
<el-form-item> </el-card>
<el-button type="primary" @click="loadUserList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div> <div>
<el-table :data="userTableData.data" size="large" v-loading="userTableData.loading"> <el-table :data="userTableData.data" size="large" v-loading="userTableData.loading">
<template #empty> <template #empty>
<span>{{ !userTableData.loading ? t('emptyData') : '' }}</span> <span>{{ !userTableData.loading ? t('emptyData') : '' }}</span>
</template> </template>
<el-table-column :label="t('headImg')" width="100" align="left"> <el-table-column :label="t('headImg')" width="100" align="left">
<template #default="{ row }"> <template #default="{ row }">
<div class="w-[54px] h-[54px] flex items-center justify-center"> <div class="w-[54px] h-[54px] flex items-center justify-center">
<img v-if="row.head_img" :src="img(row.head_img)" class="w-[54px] rounded-full" /> <img v-if="row.head_img" :src="img(row.head_img)" class="w-[54px] rounded-full" />
<img v-else src="@/app/assets/images/member_head.png" class="w-[54px] rounded-full" /> <img v-else src="@/app/assets/images/member_head.png" class="w-[54px] rounded-full" />
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="username" :label="t('accountNumber')" min-width="120" show-overflow-tooltip /> <el-table-column prop="username" :label="t('accountNumber')" 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="mobile" :label="t('手机号')" 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="real_name" :label="t('userRealName')" min-width="120" show-overflow-tooltip />
<!-- <el-table-column prop="create_time" :label="t('createTime')" min-width="180" align="center"> <el-table-column prop="site_num" :label="t('siteNum')" min-width="120" show-overflow-tooltip align="center" />
<template #default="{ row }"> <!-- <el-table-column prop="create_time" :label="t('createTime')" min-width="180" align="center">
{{ row.create_time || '' }} <template #default="{ row }">
</template> {{ row.create_time || '' }}
</el-table-column> --> </template>
<el-table-column prop="last_time" :label="t('lastLoginTime')" min-width="180" align="center"> </el-table-column> -->
<template #default="{ row }"> <el-table-column prop="last_time" :label="t('lastLoginTime')" min-width="180" align="center">
{{ row.last_time || '' }} <template #default="{ row }">
</template> {{ row.last_time || '' }}
</el-table-column> </template>
<el-table-column :label="t('lastLoginIP')" min-width="180" align="center"> </el-table-column>
<template #default="{ row }"> <el-table-column :label="t('lastLoginIP')" min-width="180" align="center">
{{ row.last_ip || '' }} <template #default="{ row }">
</template> {{ row.last_ip || '' }}
</el-table-column> </template>
<el-table-column :label="t('operation')" align="right" fixed="right" width="180"> </el-table-column>
<template #default="{ row }"> <el-table-column :label="t('operation')" align="right" fixed="right" width="180">
<el-button type="primary" link @click="detailEvent(row.uid)">{{ t('detail') }}</el-button> <template #default="{ row }">
<template v-if="!row.is_super_admin"> <el-button type="primary" link @click="detailEvent(row.uid)">{{ t('detail') }}</el-button>
<el-button type="primary" link @click="editEvent(row.uid)" >{{ t('edit') }}</el-button> <template v-if="!row.is_super_admin">
<el-button type="primary" link @click="detailEvent(row.uid, 'userCreateSiteLimit')" >{{ t('userCreateSiteLimit') }}</el-button> <el-button type="primary" link @click="editEvent(row.uid)" >{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.uid)" >{{ t('delete') }}</el-button> <el-button type="primary" link @click="detailEvent(row.uid, 'userCreateSiteLimit')" >{{ t('userCreateSiteLimit') }}</el-button>
</template> <el-button type="primary" link @click="deleteEvent(row.uid)" >{{ t('delete') }}</el-button>
<!-- <div class="manage-option text-right "> </template>
<template v-if="!row.is_super_admin"> <!-- <div class="manage-option text-right ">
<el-button type="primary" link @click="editEvent(row.uid)" >{{ t('edit') }}</el-button> <template v-if="!row.is_super_admin">
<el-button type="primary" link @click="detailEvent(row.uid, 'userCreateSiteLimit')" >{{ t('userCreateSiteLimit') }}</el-button> <el-button type="primary" link @click="editEvent(row.uid)" >{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.uid)" >{{ t('delete') }}</el-button> <el-button type="primary" link @click="detailEvent(row.uid, 'userCreateSiteLimit')" >{{ t('userCreateSiteLimit') }}</el-button>
</template> <el-button type="primary" link @click="deleteEvent(row.uid)" >{{ t('delete') }}</el-button>
</div> --> </template>
</template> </div> -->
</el-table-column> </template>
</el-table> </el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end"> <div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="userTableData.page" v-model:page-size="userTableData.limit" <el-pagination v-model:current-page="userTableData.page" v-model:page-size="userTableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="userTableData.total" layout="total, sizes, prev, pager, next, jumper" :total="userTableData.total"
@size-change="loadUserList()" @current-change="loadUserList" /> @size-change="loadUserList()" @current-change="loadUserList" />
</div> </div>
</div> </div>
</el-card> </el-card>
<user-edit ref="userEditRef" @complete="loadUserList()"/> <user-edit ref="userEditRef" @complete="loadUserList()"/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -111,47 +112,47 @@ const pageName = route.meta.title
const userEditRef = ref(null) const userEditRef = ref(null)
const userTableData = reactive({ const userTableData = reactive({
page: 1, page: 1,
limit: 10, limit: 10,
total: 0, total: 0,
loading: true, loading: true,
data: [], data: [],
searchParam: { searchParam: {
username: '', username: '',
site_name: '', site_name: '',
// create_time: [], // create_time: [],
last_time: [] last_time: []
} }
}) })
const searchFormRef = ref<FormInstance>() const searchFormRef = ref<FormInstance>()
const resetForm = (formEl: FormInstance | undefined) => { const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return if (!formEl) return
formEl.resetFields() formEl.resetFields()
loadUserList() loadUserList()
} }
/** /**
* 获取用户列表 * 获取用户列表
*/ */
const loadUserList = (page: number = 1) => { const loadUserList = (page: number = 1) => {
userTableData.loading = true userTableData.loading = true
userTableData.page = page userTableData.page = page
getUserList({ getUserList({
page: userTableData.page, page: userTableData.page,
limit: userTableData.limit, limit: userTableData.limit,
...userTableData.searchParam ...userTableData.searchParam
}).then(res => { }).then(res => {
userTableData.loading = false userTableData.loading = false
userTableData.data = res.data.data userTableData.data = res.data.data
userTableData.total = res.data.total userTableData.total = res.data.total
}).catch(() => { }).catch(() => {
userTableData.loading = false userTableData.loading = false
}) })
} }
loadUserList() loadUserList()
@ -159,7 +160,7 @@ loadUserList()
* 查看详情 * 查看详情
*/ */
const detailEvent = (uid: number, tab: string = '') => { const detailEvent = (uid: number, tab: string = '') => {
router.push({ path: '/admin/site/user_info', query: { uid, tab } }) router.push({ path: '/admin/site/user_info', query: { uid, tab } })
} }
/** /**
@ -167,69 +168,69 @@ const detailEvent = (uid: number, tab: string = '') => {
* @param uid * @param uid
*/ */
const editEvent = (uid: number) => { const editEvent = (uid: number) => {
userEditRef.value.setFormData(uid) userEditRef.value.setFormData(uid)
} }
/** /**
* 删除用户 * 删除用户
*/ */
const deleteEvent = (uid: number) => { const deleteEvent = (uid: number) => {
ElMessageBox.confirm(t('userDeleteTips'), t('warning'), ElMessageBox.confirm(t('userDeleteTips'), t('warning'),
{ {
confirmButtonText: t('confirm'), confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'), cancelButtonText: t('cancel'),
type: 'warning' type: 'warning'
} }
).then(() => { ).then(() => {
deleteUser(uid).then(res => { deleteUser(uid).then(res => {
loadUserList() loadUserList()
}).catch(() => { }).catch(() => {
})
}) })
})
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-table tr td) { :deep(.el-table tr td) {
height: 100px !important; height: 100px !important;
} }
:deep(.el-input__wrapper){ :deep(.el-input__wrapper){
box-shadow: none !important; box-shadow: none !important;
border-radius: 4px !important; border-radius: 4px !important;
border: 1px solid #D1D5DB !important; border: 1px solid #D1D5DB !important;
height: 32px !important; height: 32px !important;
} }
:deep(.el-select__wrapper){ :deep(.el-select__wrapper){
box-shadow: none !important; box-shadow: none !important;
border-radius: 4px !important; border-radius: 4px !important;
border: 1px solid #D1D5DB !important; border: 1px solid #D1D5DB !important;
height: 32px !important; height: 32px !important;
} }
:deep(.el-button){ :deep(.el-button){
border-radius: 4px !important; border-radius: 4px !important;
} }
/* 设置 el-select 的 placeholder 颜色 */ /* 设置 el-select 的 placeholder 颜色 */
:deep(.search-form .el-select__placeholder.is-transparent) { :deep(.search-form .el-select__placeholder.is-transparent) {
color: #C4C7DA; color: #C4C7DA;
font-size: 12px; font-size: 12px;
} }
/* 设置 el-select 选中后的颜色 */ /* 设置 el-select 选中后的颜色 */
:deep(.search-form .el-select__placeholder) { :deep(.search-form .el-select__placeholder) {
color: #4F516D; color: #4F516D;
font-size: 12px; font-size: 12px;
} }
/* 设置 el-input 的 placeholder 颜色 */ /* 设置 el-input 的 placeholder 颜色 */
:deep(.search-form .el-input__inner::placeholder) { :deep(.search-form .el-input__inner::placeholder) {
color: #C4C7DA; color: #C4C7DA;
font-size: 12px; font-size: 12px;
} }
/* 设置 el-input 输入内容后的颜色 */ /* 设置 el-input 输入内容后的颜色 */
:deep(.search-form .el-input__inner) { :deep(.search-form .el-input__inner) {
color: #4F516D; color: #4F516D;
font-size: 12px; font-size: 12px;
} }
/* 设置 el-date-picker 的 placeholder 颜色 */ /* 设置 el-date-picker 的 placeholder 颜色 */
@ -245,43 +246,43 @@ const deleteEvent = (uid: number) => {
} }
.manage-option { .manage-option {
line-height: 50px; line-height: 50px;
padding: 0 30px; padding: 0 30px;
position: absolute; position: absolute;
right: 0; right: 0;
width: 100vw; width: 100vw;
bottom: 0; bottom: 0;
background-color: #f4f6f9; background-color: #f4f6f9;
transition: all .3s; transition: all .3s;
box-shadow: 0 4px 4px rgba(220, 220, 220, .3); box-shadow: 0 4px 4px rgba(220, 220, 220, .3);
opacity: 0; opacity: 0;
z-index: 999; z-index: 999;
white-space: nowrap; white-space: nowrap;
} }
/* 当行被 hover 时 */ /* 当行被 hover 时 */
:deep(.el-table__row:hover) { :deep(.el-table__row:hover) {
position: relative; position: relative;
z-index: 10; z-index: 10;
} }
/* 当行被 hover 时,其下的单元格允许溢出 */ /* 当行被 hover 时,其下的单元格允许溢出 */
:deep(.el-table__row:hover .el-table__cell) { :deep(.el-table__row:hover .el-table__cell) {
overflow: visible; overflow: visible;
} }
/* 当行被 hover 时,显示 manage-option 并调整其位置 */ /* 当行被 hover 时,显示 manage-option 并调整其位置 */
:deep(.el-table__row:hover .manage-option) { :deep(.el-table__row:hover .manage-option) {
opacity: 1; opacity: 1;
bottom: -51px; bottom: -51px;
} }
:deep(.el-table__fixed-body-wrapper:hover), :deep(.el-table__fixed-body-wrapper:hover),
:deep(.el-table__fixed-body-wrapper .el-table__row:hover) { :deep(.el-table__fixed-body-wrapper .el-table__row:hover) {
z-index: 10; z-index: 10;
} }
:deep(.el-table__fixed-body-wrapper .el-table__row:hover .el-table__cell) { :deep(.el-table__fixed-body-wrapper .el-table__row:hover .el-table__cell) {
overflow: visible; overflow: visible;
} }
</style> </style>

View File

@ -1,127 +1,135 @@
<template> <template>
<!--用户详情--> <!--用户详情-->
<div class="main-container" v-loading="loading"> <div class="main-container" v-loading="loading">
<el-card class="card !border-none" shadow="never"> <el-card class="card !border-none" shadow="never">
<el-page-header :content="pageName" :icon="ArrowLeft" @back="back()" /> <el-page-header :content="pageName" :icon="ArrowLeft" @back="back()" />
</el-card> </el-card>
<el-card class="box-card mt-[15px] !border-none" shadow="never"> <el-card class="box-card mt-[15px] !border-none" shadow="never">
<h3 class="panel-title !text-sm">{{ t('userInfo') }}</h3> <h3 class="panel-title !text-sm">{{ t('userInfo') }}</h3>
<el-row :gutter="20" class="mt-[20px] mb-[20px]"> <el-row :gutter="20" class="mt-[20px] mb-[20px]">
<el-col :span="6"> <el-col :span="6">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('uid') }}</span> <span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('uid') }}</span>
<span class="text-[14px] text-[#666666]"> <span class="text-[14px] text-[#666666]">
{{ detail.uid }} {{ detail.uid }}
</span> </span>
</el-col> </el-col>
<el-col :span="6" :offset="6"> <el-col :span="6" :offset="6">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('username') }}</span> <span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('username') }}</span>
<span class="text-[14px] text-[#666666]"> <span class="text-[14px] text-[#666666]">
{{ detail.username }} {{ detail.username }}
</span> </span>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20" class="mb-[20px]"> <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] w-[130px] text-right mr-[20px]">{{ t('realname') }}</span>
<span class="text-[14px] text-[#666666]"> <span class="text-[14px] text-[#666666]">
{{ detail.real_name || '--' }} {{ detail.real_name || '--' }}
</span> </span>
</el-col> </el-col>
<el-col :span="6" :offset="6"> <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]"> <span class="text-[14px] text-[#666666]">
{{ 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 }} {{ detail.create_time }}
</span> </span>
</el-col> </el-col>
</el-row> <el-col :span="6" :offset="6">
<el-row :gutter="20" class="mb-[20px]"> <span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('lastLoginTime') }}</span>
<el-col :span="6"> <span class="text-[14px] text-[#666666]">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('lastLoginTime') }}</span>
<span class="text-[14px] text-[#666666]">
{{ detail.last_time || '' }} {{ detail.last_time || '' }}
</span> </span>
</el-col> </el-col>
<el-col :span="6" :offset="6"> </el-row>
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('lastLoginIP') }}</span> <el-row :gutter="20" class="mb-[20px]">
<span class="text-[14px] text-[#666666]"> <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 || '' }} {{ detail.last_ip || '' }}
</span> </span>
</el-col> </el-col>
</el-row> </el-row>
</el-card> </el-card>
<el-card class="box-card mt-[15px] !border-none" shadow="never"> <el-card class="box-card mt-[15px] !border-none" shadow="never">
<el-tabs v-model="currTab"> <el-tabs v-model="currTab">
<el-tab-pane :label="t('siteInfo')" name="siteInfo"> <el-tab-pane :label="t('siteInfo')" name="siteInfo">
<el-table :data="detail.roles" size="large"> <el-table :data="detail.roles" size="large">
<el-table-column prop="site_id" :label="t('siteId')" width="100px" /> <el-table-column prop="site_id" :label="t('siteId')" width="100px" />
<el-table-column prop="site_name" :label="t('siteName')" /> <el-table-column prop="site_name" :label="t('siteName')" />
<el-table-column prop="is_admin" :label="t('isAdmin')" min-width="180" align="center"> <el-table-column prop="is_admin" :label="t('isAdmin')" min-width="180" align="center">
<template #default="{ row }"> <template #default="{ row }">
{{ row.is_admin ? t('yes') : t('no') }} {{ row.is_admin ? t('yes') : t('no') }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('status')" min-width="80" align="center"> <el-table-column :label="t('status')" min-width="80" align="center">
<template #default="{ row }"> <template #default="{ row }">
<template v-if="row.site_status_name"> <template v-if="row.site_status_name">
<el-tag class="ml-2" type="success" v-if="row.site_status == 1">{{ row.site_status_name }}</el-tag> <el-tag class="ml-2" type="success" v-if="row.site_status == 1">{{ row.site_status_name }}</el-tag>
<el-tag class="ml-2" type="error" v-else-if="row.site_status == 3"> <el-tag class="ml-2" type="error" v-else-if="row.site_status == 3">
{{ row.site_status_name }} {{ row.site_status_name }}
</el-tag> </el-tag>
<el-tag class="ml-2" type="error" v-else> <el-tag class="ml-2" type="error" v-else>
{{ row.site_status_name }} {{ row.site_status_name }}
</el-tag> </el-tag>
</template> </template>
<el-tag class="ml-2" type="error" v-else> <el-tag class="ml-2" type="error" v-else>
{{ t('siteEmpty') }} {{ t('siteEmpty') }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="expire_time" :label="t('expireTime')" /> <el-table-column prop="expire_time" :label="t('expireTime')" />
<el-table-column :label="t('operation')" align="right" fixed="right"> <el-table-column :label="t('operation')" align="right" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click="siteInfo(row)" v-if="row.site_status_name">{{ t('info') }}</el-button> <el-button type="primary" link @click="siteInfo(row)" v-if="row.site_status_name">{{ t('info') }}</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="t('userCreateSiteLimit')" name="userCreateSiteLimit" v-if="!detail.is_super_admin"> <el-tab-pane :label="t('userCreateSiteLimit')" name="userCreateSiteLimit" v-if="!detail.is_super_admin">
<div class="flex justify-end mb-[16px]"> <div class="flex justify-end mb-[16px]">
<el-button type="primary" @click="createSiteLimitRef.setFormData()">{{ t('addSserCreateSiteLimit') }}</el-button> <el-button type="primary" @click="createSiteLimitRef.setFormData()">{{ t('addSserCreateSiteLimit') }}</el-button>
</div> </div>
<el-table :data="userCreateSiteLimit" size="large"> <el-table :data="userCreateSiteLimit" size="large">
<el-table-column :label="t('siteGroup')"> <el-table-column :label="t('siteGroup')">
<template #default="{ row }"> <template #default="{ row }">
{{ siteGroup[row.group_id] ? siteGroup[row.group_id].group_name : '' }} {{ siteGroup[row.group_id] ? siteGroup[row.group_id].group_name : '' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('createdSiteNum')"> <el-table-column :label="t('createdSiteNum')">
<template #default="{ row }"> <template #default="{ row }">
{{ siteGroup[row.group_id] ? siteGroup[row.group_id].site_num : 0 }} {{ siteGroup[row.group_id] ? siteGroup[row.group_id].site_num : 0 }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="num" :label="t('createSiteNum')" align="center"/> <el-table-column prop="num" :label="t('createSiteNum')" align="center"/>
<el-table-column prop="month" :label="t('createSiteTimeLimit')" align="center"> <el-table-column prop="month" :label="t('createSiteTimeLimit')" align="center">
<template #default="{ row }"> <template #default="{ row }">
{{ row.month }}{{ t('month') }} {{ row.month }}{{ t('month') }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('operation')" align="right" fixed="right"> <el-table-column :label="t('operation')" align="right" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click="createSiteLimitRef.setFormData(row.id)">{{ t('edit') }}</el-button> <el-button type="primary" link @click="createSiteLimitRef.setFormData(row.id)">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteCreateSiteTimeLimit(row.id)">{{ t('delete') }}</el-button> <el-button type="primary" link @click="deleteCreateSiteTimeLimit(row.id)">{{ t('delete') }}</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-card> </el-card>
<create-site-limit ref="createSiteLimitRef" :site-group="siteGroup" :uid="uid" @complete="getUserCreateSiteLimitFn"/> <create-site-limit ref="createSiteLimitRef" :site-group="siteGroup" :uid="uid" @complete="getUserCreateSiteLimitFn"/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -139,7 +147,7 @@ const router = useRouter()
const pageName = route.meta.title const pageName = route.meta.title
const back = () => { const back = () => {
router.push('/admin/site/user') router.push('/admin/site/user')
} }
const uid: number = parseInt(route.query.uid || 0) const uid: number = parseInt(route.query.uid || 0)
const loading = ref(true) const loading = ref(true)
@ -150,22 +158,22 @@ const userCreateSiteLimit = ref([])
const createSiteLimitRef = ref(null) const createSiteLimitRef = ref(null)
getUserInfo(uid).then(({ data }) => { getUserInfo(uid).then(({ data }) => {
detail.value = data detail.value = data
loading.value = false loading.value = false
}).catch() }).catch()
getUserSiteGroupAll({ uid }).then(({ data }) => { getUserSiteGroupAll({ uid }).then(({ data }) => {
const list: any = {} const list: any = {}
data.forEach((item: any) => { data.forEach((item: any) => {
list[item.group_id] = item list[item.group_id] = item
}) })
siteGroup.value = list siteGroup.value = list
}) })
const getUserCreateSiteLimitFn = () => { const getUserCreateSiteLimitFn = () => {
getUserCreateSiteLimit(uid).then(({ data }) => { getUserCreateSiteLimit(uid).then(({ data }) => {
userCreateSiteLimit.value = data userCreateSiteLimit.value = data
}) })
} }
getUserCreateSiteLimitFn() getUserCreateSiteLimitFn()
@ -174,22 +182,22 @@ getUserCreateSiteLimitFn()
* @param data * @param data
*/ */
const siteInfo = (data: any) => { const siteInfo = (data: any) => {
router.push({ path: '/admin/site/info', query: { id: data.site_id } }) router.push({ path: '/admin/site/info', query: { id: data.site_id } })
} }
const deleteCreateSiteTimeLimit = (id: number) => { const deleteCreateSiteTimeLimit = (id: number) => {
ElMessageBox.confirm(t('createSiteTimeLimitDeleteTips'), t('warning'), ElMessageBox.confirm(t('createSiteTimeLimitDeleteTips'), t('warning'),
{ {
confirmButtonText: t('confirm'), confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'), cancelButtonText: t('cancel'),
type: 'warning' type: 'warning'
} }
).then(() => { ).then(() => {
delUserCreateSiteLimit(id).then(() => { delUserCreateSiteLimit(id).then(() => {
getUserCreateSiteLimitFn() getUserCreateSiteLimitFn()
}).catch(() => { }).catch(() => {
})
}) })
})
} }
</script> </script>

View File

@ -858,7 +858,7 @@ const batchDelete = () => {
background-color: var(--el-table-header-bg-color); background-color: var(--el-table-header-bg-color);
} }
::v-deep .number-of-steps { :deep(.number-of-steps) {
.el-step__line { .el-step__line {
margin: 0 25px; margin: 0 25px;
background: #dddddd; background: #dddddd;

View File

@ -53,7 +53,7 @@
<el-timeline-item :hollow="true"> <el-timeline-item :hollow="true">
<div class="text-[16px] text-[#1D1F3A]">编译uniapp代码</div> <div class="text-[16px] text-[#1D1F3A]">编译uniapp代码</div>
<div class="p-[10px] bg-[#F9F9FB] mt-[10px] text-[#4F516D] text-[14px] w-[1085px] border-[#F1F1F8] border-solid border-[1px] h-[40px] flex items-center rounded-[4px]"> <div class="p-[10px] bg-[#F9F9FB] mt-[10px] text-[#4F516D] text-[14px] w-[1085px] border-[#F1F1F8] border-solid border-[1px] h-[40px] flex items-center rounded-[4px]">
<span>云编会将uniapp端的vue代码编译为对应的html文件同时将生成的代码下载到系统 niucloud下的</span> <span>云编会将uniapp端的vue代码编译为对应的html文件同时将生成的代码下载到系统 niucloud下的</span>
<span class="text-[#F09000] mx-[3px] font-bold">public/wap</span> <span class="text-[#F09000] mx-[3px] font-bold">public/wap</span>
<span>目录中这样手机端网页的访问路径将变为</span> <span>目录中这样手机端网页的访问路径将变为</span>
<span class="text-primary ml-[3px] font-500"> https:///wap</span> <span class="text-primary ml-[3px] font-500"> https:///wap</span>
@ -62,7 +62,7 @@
<el-timeline-item :hollow="true"> <el-timeline-item :hollow="true">
<div class="text-[16px] text-[#1D1F3A]">编译web代码</div> <div class="text-[16px] text-[#1D1F3A]">编译web代码</div>
<div class="p-[10px] bg-[#F9F9FB] mt-[10px] text-[#4F516D] text-[14px] w-[1085px] border-[#F1F1F8] border-solid border-[1px] h-[40px] flex items-center rounded-[4px]"> <div class="p-[10px] bg-[#F9F9FB] mt-[10px] text-[#4F516D] text-[14px] w-[1085px] border-[#F1F1F8] border-solid border-[1px] h-[40px] flex items-center rounded-[4px]">
<span>云编会将web端的vue代码编译为对应的html文件同时将生成的代码下载到系统 niucloud下的</span> <span>云编会将web端的vue代码编译为对应的html文件同时将生成的代码下载到系统 niucloud下的</span>
<span class="text-[#F09000] mx-[3px] font-bold">public/web</span> <span class="text-[#F09000] mx-[3px] font-bold">public/web</span>
<span>目录中这样电脑端网页的访问路径将变为</span> <span>目录中这样电脑端网页的访问路径将变为</span>
<span class="text-primary ml-[3px] font-500"> https:///web</span> <span class="text-primary ml-[3px] font-500"> https:///web</span>

View File

@ -91,6 +91,17 @@ const handleEditorReady = (editor) => {
emit('handleBlur', editor.getContent()) // 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; // const originalCount = editor.getContentLength; //

View File

@ -103,7 +103,7 @@
"405": "请求方法未允许", "405": "请求方法未允许",
"408": "请求超时", "408": "请求超时",
"409": "请求跨域", "409": "请求跨域",
"500": "服务器端出错,错误原因:", "500": "服务器内部错误",
"501": "网络未实现", "501": "网络未实现",
"502": "网络错误", "502": "网络错误",
"503": "服务不可用", "503": "服务不可用",

View File

@ -42,4 +42,8 @@ const tabbarStore = useTabbarStore()
.bg-page { .bg-page {
background-color: #F7F7FA; background-color: #F7F7FA;
} }
:deep(.inter .el-breadcrumb__inner){
font-weight: inherit !important;
color: var(--el-text-color-regular) !important;
}
</style> </style>

View File

@ -38,4 +38,9 @@ const appStore = useAppStore()
const tabbarStore = useTabbarStore() const tabbarStore = useTabbarStore()
</script> </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>
<template v-if="meta.show"> <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> <template #title>
<span :class="['ml-[10px]']">{{ meta.title }}</span> <span :class="['ml-[10px]']">{{ meta.title }}</span>
</template> </template>
<menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" /> <menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" />
</el-sub-menu> </el-sub-menu>
<template v-else> <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> <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> <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> </template>
</el-menu-item> </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> <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> <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> </template>
@ -29,6 +29,7 @@ import { computed } from 'vue'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import menuItem from './menu-item.vue' import menuItem from './menu-item.vue'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import storage from '@/utils/storage'
const router = useRouter() const router = useRouter()
const props = defineProps({ const props = defineProps({
@ -41,12 +42,37 @@ const userStore = useUserStore()
const siteInfo = userStore.siteInfo const siteInfo = userStore.siteInfo
const meta = computed(() => props.routes.meta) 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 = computed(() => {
const addons:Record<string, any> = {} const addons:Record<string, any> = {}
siteInfo?.apps.forEach((item: any) => { addons[item.key] = item }) siteInfo?.apps.forEach((item: any) => { addons[item.key] = item })
siteInfo?.site_addons.forEach((item: any) => { addons[item.key] = item }) siteInfo?.site_addons.forEach((item: any) => { addons[item.key] = item })
return addons 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'
}
//
router.push({ name: routeName })
}
</script> </script>

View File

@ -17,7 +17,7 @@
<el-scrollbar class="h-[calc( 100vh - 64px )]"> <el-scrollbar class="h-[calc( 100vh - 64px )]">
<el-menu :default-active="oneMenuActive" :router="true" class="aside-menu" :unique-opened="true"> <el-menu :default-active="oneMenuActive" :router="true" class="aside-menu" :unique-opened="true">
<template v-for="(item, index) in oneMenuData" :key="index"> <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"> <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%]" /> <icon :name="item.meta.icon" class="absolute top-[50%] -translate-y-[50%]" />
</div> </div>
@ -49,9 +49,10 @@ import { useRoute, useRouter } from 'vue-router'
import useSystemStore from '@/stores/modules/system' import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import menuItem from './menu-item.vue' 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 { img } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers' import { findFirstValidRoute,formatRouters } from '@/router/routers'
import storage from '@/utils/storage'
const systemStore = useSystemStore() const systemStore = useSystemStore()
const userStore = useUserStore() const userStore = useUserStore()
@ -136,87 +137,222 @@ const oneMenuActive = ref(route.matched[1].name)
const appList = ref(null) const appList = ref(null)
const marketingList = ref(null) const marketingList = ref(null)
// const loading = ref(true); // const loading = ref(true);
const getAppList = async () => { const getAppList = async () => {
const res = await getShowApp() const res = await getShowApp()
appList.value = res.data appList.value = res.data
// loading.value = false; // key
storage.set({ key: 'defaultAppList', data: appList.value })
} }
const getMarketingList = async () => { const specialList = ref<Record<string, any>[]>([])
const res = await getShowMarketing() const getShowSpecialMenuList = async () => {
marketingList.value = res.data 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() // const specialMenuNames = ref<string[]>([])
await getMarketingList() 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, () => { // onMounted(async () => {
// if (route.meta.attr != '') { // await getAppList() //
// oneMenuActive.value = route.matched[2].name // })
// twoMenuData.value = route.matched[1].children ?? []
// 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 { // } else {
// // // //
// if (siteInfo?.apps.length > 1) { // if (siteInfo?.apps.length > 1) {
// twoMenuData.value = route.matched[1].children // twoMenuData.value = route.matched[1]?.children
// oneMenuActive.value = route.matched[1].name // oneMenuActive.value = route.matched[1]?.name
// } else { // } else {
// // // //
// const oneMenu = route.matched[1] // const oneMenu = route.matched[1]
// if (oneMenu.meta.addon == '') { // if (oneMenu.meta.addon === '') {
// oneMenuActive.value = route.matched[1].name // oneMenuActive.value = route.matched[1]?.name
// twoMenuData.value = route.matched[1].children ?? [] // twoMenuData.value = route.matched[1]?.children ?? []
// } else { // } else {
// if (oneMenu.meta.addon == siteInfo?.apps[0].key) { // if (oneMenu.meta.addon === siteInfo?.apps[0]?.key) {
// oneMenuActive.value = route.matched[2].name // oneMenuActive.value = route.matched[2]?.name
// twoMenuData.value = route.matched[2].children ?? [] // twoMenuData.value = route.matched[2]?.children ?? []
// } else { // } else {
// oneMenuActive.value = route.matched[1].name // oneMenuActive.value = route.matched[1]?.name
// twoMenuData.value = route.matched[1].children ?? [] // 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[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 ?? []
}
}
}
}
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> </script>
<style lang="scss"> <style lang="scss">
@ -309,6 +445,27 @@ watchEffect(() => {
.el-menu-item{ .el-menu-item{
padding-left: 20px !important; 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

@ -1,6 +1,6 @@
<template> <template>
<template v-if="meta.show"> <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> <template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1"> <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" /> <icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
@ -15,7 +15,7 @@
</template> </template>
</el-sub-menu> </el-sub-menu>
<template v-else> <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> <template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1"> <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" /> <icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
@ -23,7 +23,7 @@
<span class="ml-[10px]">{{ meta.title }}</span> <span class="ml-[10px]">{{ meta.title }}</span>
</template> </template>
</el-menu-item> </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> <template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1"> <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" /> <icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
@ -39,11 +39,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter, useRoute } from 'vue-router' 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 menuItem from './menu-item.vue'
import useSystemStore from '@/stores/modules/system' import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import storage from '@/utils/storage' import storage from '@/utils/storage'
import { findFirstValidRoute ,formatRouters} from '@/router/routers'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
@ -62,6 +63,9 @@ const props = defineProps({
const systemStore = useSystemStore() const systemStore = useSystemStore()
const meta = computed(() => props.routes.meta) const meta = computed(() => props.routes.meta)
// name
const specialMenuNames = ref<string[]>([])
const specialMenuNamesLevel1 = ref<string[]>([])
const addons = computed(() => { 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?.apps.forEach((item: any) => { addons[item.key] = item })
@ -72,7 +76,12 @@ const addons = computed(() => {
const systemAddonKeys = computed(() => { const systemAddonKeys = computed(() => {
return userStore.siteInfo?.site_addons.map((item: any) => item.key) 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> = {} const addonRouters: Record<string, any> = {}
routers.forEach(item => { routers.forEach(item => {
item.original_name = item.name item.original_name = item.name
@ -86,8 +95,135 @@ routers.forEach(item => {
const addonsMenus = ref(null) 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'
}
//
router.push({ name: routeName })
}
watch(route, () => { watch(route, () => {
const addonKeys = storage.get('defaultAppList')
// console.log('addonKeys', addonKeys)
if (props.routes.name == 'addon_list') { 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]) { if (systemAddonKeys.value.includes(route.meta.addon) && addonRouters[route.meta.addon]) {
addonsMenus.value = addonRouters[route.meta.addon] addonsMenus.value = addonRouters[route.meta.addon]
} else if (route.meta.attr && addonRouters[route.meta.attr]) { } else if (route.meta.attr && addonRouters[route.meta.attr]) {
@ -97,7 +233,7 @@ watch(route, () => {
} }
} }
const marketingKeys = storage.get('darksideMarketingKeys') const marketingKeys = storage.get('defaultMarketingKeys')
const matchedName = route.matched[1]?.name const matchedName = route.matched[1]?.name
if (props.routes.name == 'marketing_list') { if (props.routes.name == 'marketing_list') {
if (marketingKeys && marketingKeys.includes(matchedName)) { if (marketingKeys && marketingKeys.includes(matchedName)) {
@ -107,7 +243,42 @@ watch(route, () => {
addonsMenus.value = null addonsMenus.value = null
} }
} }
}, { immediate: true }) }, { 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> </script>
<style lang="scss"> <style lang="scss">

View File

@ -30,9 +30,9 @@ import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import menuItem from './menu-item.vue' import menuItem from './menu-item.vue'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers' import { findFirstValidRoute ,formatRouters} from '@/router/routers'
import { getShowMarketing } from '@/app/api/site'
import storage from '@/utils/storage' import storage from '@/utils/storage'
import {getShowApp, getShowSpecialMenu} from '@/app/api/site'
const systemStore = useSystemStore() const systemStore = useSystemStore()
const userStore = useUserStore() const userStore = useUserStore()
@ -46,20 +46,26 @@ const logoUrl = computed(() => {
return userStore.siteInfo.icon ? userStore.siteInfo.icon : systemStore.website.icon return userStore.siteInfo.icon ? userStore.siteInfo.icon : systemStore.website.icon
}) })
const getMarketingList = async () => { const appList = ref<Record<string, any>[]>([])
const res = await getShowMarketing()
const marketingList = res.data const getAppList = async () => {
const marketingKeys = marketingList?.marketing?.list?.map(item => item.key) ?? [] const res = await getShowApp()
// menuData.value.forEach((item, index, arr) => { appList.value = res.data
// if (marketingKeys.includes(item.name)) {
// arr.splice(index, 1) storage.set({ key: 'defaultAppList', data: appList.value })
// } }
// }) const specialList = ref<Record<string, any>[]>([])
storage.set({ key: 'darksideMarketingKeys', data: marketingKeys }) 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(() => { onMounted(() => {
getMarketingList() getAppList()
getShowSpecialMenuList()
}) })
routers.forEach((item, index) => { routers.forEach((item, index) => {

View File

@ -1,6 +1,6 @@
<template> <template>
<template v-if="meta.show"> <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> <template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1"> <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" /> <icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
@ -15,7 +15,7 @@
</template> </template>
</el-sub-menu> </el-sub-menu>
<template v-else> <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> <template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1"> <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" /> <icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
@ -23,7 +23,7 @@
<span class="ml-[10px]">{{ meta.title }}</span> <span class="ml-[10px]">{{ meta.title }}</span>
</template> </template>
</el-menu-item> </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> <template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1"> <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" /> <icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
@ -34,16 +34,16 @@
</template> </template>
<div v-if="routes.is_border" class="!border-0 !border-t-[1px] border-solid mx-[25px] bg-[#f7f7f7] my-[5px]"></div> <div v-if="routes.is_border" class="!border-0 !border-t-[1px] border-solid mx-[25px] bg-[#f7f7f7] my-[5px]"></div>
</template> </template>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter, useRoute } from 'vue-router' 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 menuItem from './menu-item.vue'
import useSystemStore from '@/stores/modules/system' import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import storage from '@/utils/storage' import storage from '@/utils/storage'
import { findFirstValidRoute ,formatRouters} from '@/router/routers'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
@ -62,8 +62,11 @@ const props = defineProps({
const systemStore = useSystemStore() const systemStore = useSystemStore()
const meta = computed(() => props.routes.meta) const meta = computed(() => props.routes.meta)
// name
const specialMenuNames = ref<string[]>([])
const specialMenuNamesLevel1 = ref<string[]>([])
const addons = computed(() => { 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?.apps.forEach((item: any) => { addons[item.key] = item })
userStore.siteInfo?.site_addons.forEach((item: any) => { addons[item.key] = item }) userStore.siteInfo?.site_addons.forEach((item: any) => { addons[item.key] = item })
return addons return addons
@ -72,7 +75,12 @@ const addons = computed(() => {
const systemAddonKeys = computed(() => { const systemAddonKeys = computed(() => {
return userStore.siteInfo?.site_addons.map((item: any) => item.key) 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> = {} const addonRouters: Record<string, any> = {}
routers.forEach(item => { routers.forEach(item => {
item.original_name = item.name item.original_name = item.name
@ -86,8 +94,125 @@ routers.forEach(item => {
const addonsMenus = ref(null) 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'
}
//
router.push({ name: routeName })
}
watch(route, () => { watch(route, () => {
const addonKeys = storage.get('defaultAppList')
if (props.routes.name == 'addon_list') { 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]) { if (systemAddonKeys.value.includes(route.meta.addon) && addonRouters[route.meta.addon]) {
addonsMenus.value = addonRouters[route.meta.addon] addonsMenus.value = addonRouters[route.meta.addon]
} else if (route.meta.attr && addonRouters[route.meta.attr]) { } else if (route.meta.attr && addonRouters[route.meta.attr]) {
@ -107,12 +232,46 @@ watch(route, () => {
addonsMenus.value = null addonsMenus.value = null
} }
} }
// console.log('addonsMenus', props.routes)
}, { immediate: true }) }, { 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> </script>
<style lang="scss"> <style lang="scss">
.el-sub-menu{ .el-sub-menu {
.el-icon{ .el-icon {
width: auto; width: auto;
} }
} }

View File

@ -30,8 +30,9 @@ import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import menuItem from './menu-item.vue' import menuItem from './menu-item.vue'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers' import { findFirstValidRoute ,formatRouters} from '@/router/routers'
import { getShowMarketing } from '@/app/api/site' import { getShowApp,getShowSpecialMenu} from '@/app/api/site'
import storage from '@/utils/storage' import storage from '@/utils/storage'
const systemStore = useSystemStore() const systemStore = useSystemStore()
@ -46,20 +47,27 @@ const logoUrl = computed(() => {
return userStore.siteInfo.icon ? userStore.siteInfo.icon : systemStore.website.icon return userStore.siteInfo.icon ? userStore.siteInfo.icon : systemStore.website.icon
}) })
const getMarketingList = async () => { const appList = ref<Record<string, any>[]>([])
const res = await getShowMarketing()
const marketingList = res.data const getAppList = async () => {
const marketingKeys = marketingList?.marketing?.list?.map(item => item.key) ?? [] const res = await getShowApp()
// menuData.value.forEach((item, index, arr) => { appList.value = res.data
// if (marketingKeys.includes(item.name)) {
// arr.splice(index, 1) storage.set({ key: 'defaultAppList', data: appList.value })
// } }
// }) const specialList = ref<Record<string, any>[]>([])
storage.set({ key: 'defaultMarketingKeys', data: marketingKeys }) 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(() => { onMounted(() => {
getMarketingList() getAppList()
getShowSpecialMenuList()
}) })
routers.forEach(item => { routers.forEach(item => {
@ -87,6 +95,7 @@ routers.forEach(item => {
addonRouters[item.meta.addon] = item addonRouters[item.meta.addon] = item
} }
// console.log('menuData', menuData.value)
// , // ,
// menuData.value.sort((a, b) => { // menuData.value.sort((a, b) => {
// if (a.meta.sort && b.meta.sort) { // if (a.meta.sort && b.meta.sort) {

View File

@ -128,7 +128,7 @@ const dark = computed(() => {
}) })
const isMenuSearch = ref(false) const isMenuSearch = ref(false)
const routers = userStore.routers const routers = userStore.routers
const getParentTitleChain=(meta:any) =>{ const getParentTitleChain = (meta: any) => {
let titles = [] let titles = []
let current = meta?.parent_route let current = meta?.parent_route
@ -139,35 +139,47 @@ const getParentTitleChain=(meta:any) =>{
current = current.parent_route current = current.parent_route
} }
return titles.join(' - ') return titles.join(' - ');
} };
const flattenRoutes = (routes:any, parent = null)=> {
let flat = [];
routes.forEach(route => {
const { path, name, meta = {}, short_title, children } = route
const isLeaf = meta.type ==1 && meta.show==1
if(isLeaf){
const title = meta.title || short_title || ''
const parentTitleChain = getParentTitleChain(meta)
const fullTitle = parentTitleChain ? `${parentTitleChain} - ${title}` : title
const item = {
path,
name,
title,
parent_title: parentTitleChain,
full_title: fullTitle
};
flat.push(item); // 2. flattenRoutes parentShow show
const flattenRoutes = (routes: any, parent = null, parentShow = 1) => {
let flat = [];
routes.forEach(route => {
const { path, name, meta = {}, short_title, children } = route;
// show = show1 && show1
// show 1
const currentShow = meta.show === undefined ? 1 : meta.show;
const finalShow = currentShow && parentShow; //
// type=1 + show=1+
const isLeaf = meta.type === 1 && finalShow === 1;
if (isLeaf) {
const title = meta.title || short_title || '';
const parentTitleChain = getParentTitleChain(meta);
const fullTitle = parentTitleChain ? `${parentTitleChain} - ${title}` : title;
const item = {
path,
name,
title,
parent_title: parentTitleChain,
full_title: fullTitle
};
flat.push(item);
} }
// finalShow parentShow
if (children && children.length > 0) { if (children && children.length > 0) {
flat = flat.concat(flattenRoutes(children, route)) flat = flat.concat(flattenRoutes(children, route, finalShow));
} }
}); });
return flat; return flat;
} };
const flatRoutes = flattenRoutes(routers)
const flatRoutes = flattenRoutes(routers);
const selectedRoute = ref('') const selectedRoute = ref('')
const handleRouteSelect = (name:any) => { const handleRouteSelect = (name:any) => {
if (name) { if (name) {

View File

@ -1,18 +1,18 @@
<template> <template>
<template v-if="meta.show"> <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> <template #title>
<span :class="['ml-[10px]']">{{ meta.title }}</span> <span :class="['ml-[10px]']">{{ meta.title }}</span>
</template> </template>
<menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" /> <menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" />
</el-sub-menu> </el-sub-menu>
<template v-else> <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> <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> <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> </template>
</el-menu-item> </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> <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> <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> </template>
@ -28,6 +28,7 @@ import { useRouter } from 'vue-router'
import { computed } from 'vue' import { computed } from 'vue'
import menuItem from './menu-item.vue' import menuItem from './menu-item.vue'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import storage from '@/utils/storage'
const router = useRouter() const router = useRouter()
const props = defineProps({ const props = defineProps({
@ -40,13 +41,37 @@ const userStore = useUserStore()
const siteInfo = userStore.siteInfo const siteInfo = userStore.siteInfo
const meta = computed(() => props.routes.meta) 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 = computed(() => {
const addons:Record<string, any> = {} const addons:Record<string, any> = {}
siteInfo?.apps.forEach((item: any) => { addons[item.key] = item }) siteInfo?.apps.forEach((item: any) => { addons[item.key] = item })
siteInfo?.site_addons.forEach((item: any) => { addons[item.key] = item }) siteInfo?.site_addons.forEach((item: any) => { addons[item.key] = item })
return addons 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'
}
//
router.push({ name: routeName })
}
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -20,7 +20,7 @@
<el-scrollbar class="h-[calc( 100vh - 64px )]"> <el-scrollbar class="h-[calc( 100vh - 64px )]">
<el-menu :default-active="oneMenuActive" :router="true" class="aside-menu" :unique-opened="true" :collapse="systemStore.menuIsCollapse"> <el-menu :default-active="oneMenuActive" :router="true" class="aside-menu" :unique-opened="true" :collapse="systemStore.menuIsCollapse">
<template v-for="(item, index) in oneMenuData" :key="index"> <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"> <div v-if="item.meta.icon" class="w-[16px] h-[16px] relative flex justify-center">
<el-image class="w-[16px] h-[16px] rounded-[50%] overflow-hidden" :src="item.meta.icon" fit="fill" v-if="isUrl(item.meta.icon)"/> <el-image class="w-[16px] h-[16px] rounded-[50%] overflow-hidden" :src="item.meta.icon" fit="fill" v-if="isUrl(item.meta.icon)"/>
<icon :name="item.meta.icon" class="absolute top-[50%] -translate-y-[50%]" v-else /> <icon :name="item.meta.icon" class="absolute top-[50%] -translate-y-[50%]" v-else />
@ -38,8 +38,8 @@
</el-scrollbar> </el-scrollbar>
</div> </div>
<el-scrollbar v-if="twoMenuData.length" class="two-menu w-[132px]"> <el-scrollbar v-if="twoMenuData.length" class="two-menu w-[152px]">
<div class="w-[132px] h-[64px] flex items-center justify-center text-[16px] border-b-[1px] border-solid border-[var(--el-border-color-lighter)]"> <div class="w-[152px] h-[64px] flex items-center justify-center text-[16px] border-b-[1px] border-solid border-[var(--el-border-color-lighter)]">
{{ route.matched[1].meta.title }} {{ route.matched[1].meta.title }}
</div> </div>
@ -57,12 +57,13 @@
import { ref, watch, computed, onMounted, watchEffect } from 'vue' import { ref, watch, computed, onMounted, watchEffect } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import useSystemStore from '@/stores/modules/system' import useSystemStore from '@/stores/modules/system'
import { getShowApp, getShowMarketing } from '@/app/api/site'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import { img, isUrl } from '@/utils/common' import { img, isUrl } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers'
import menuItem from './menu-item.vue' import menuItem from './menu-item.vue'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { getShowApp,getShowSpecialMenu} from '@/app/api/site'
import { findFirstValidRoute,formatRouters } from '@/router/routers'
import storage from '@/utils/storage'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -147,20 +148,35 @@ const marketingList = ref(null)
// const loading = ref(true); // const loading = ref(true);
const oneMenuActive = ref(route.matched[1].name) const oneMenuActive = ref(route.matched[1].name)
const getAppList = async () => { const getAppList = async () => {
const res = await getShowApp() const res = await getShowApp()
appList.value = res.data appList.value = res.data
// loading.value = false;
storage.set({ key: 'defaultAppList', data: appList.value })
} }
const getMarketingList = async () => { const specialList = ref<Record<string, any>[]>([])
const res = await getShowMarketing() const getShowSpecialMenuList = async () => {
marketingList.value = res.data 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() // const specialMenuNames = ref<string[]>([])
await getMarketingList() const specialMenuNamesLevel1 = ref<string[]>([])
onMounted(() => {
getAppList()
getShowSpecialMenuList()
const processedSpecialMenus = handleSpecialMenus();
specialMenuNames.value = collectSpecialMenuNames(processedSpecialMenus)
specialMenuNamesLevel1.value = collectSpecialMenuNamesLevel1(processedSpecialMenus)
storage.set({ key: 'specialMenuNames', data: specialMenuNames.value })
storage.set({ key: 'specialMenuNamesLevel1', data: specialMenuNamesLevel1.value })
}) })
// //
const menuOption = ref([]) const menuOption = ref([])
const secondMenuShowWayFn = () => { const secondMenuShowWayFn = () => {
@ -173,75 +189,192 @@ const secondMenuShowWayFn = () => {
} }
} }
watchEffect(() => { // watchEffect(() => {
// if (!appList.value || loading.value) return; // // // if (!appList.value || loading.value) return; //
const addonKeys = appList.value?.addon?.list?.map(item => item.key) ?? [] // const addonKeys = appList.value?.addon?.list?.map(item => item.key) ?? []
const toolKeys = appList.value?.tool?.list?.map(item => item.key) ?? [] // const toolKeys = appList.value?.tool?.list?.map(item => item.key) ?? []
const allKeys = [...addonKeys, ...toolKeys] // const allKeys = [...addonKeys, ...toolKeys]
const marketingKeys = marketingList.value?.marketing?.list?.map(item => item.key) ?? [] // const marketingKeys = marketingList.value?.marketing?.list?.map(item => item.key) ?? []
const matchedName = route.matched[1]?.name // const matchedName = route.matched[1]?.name
if (allKeys.includes(matchedName)) { // if (allKeys.includes(matchedName)) {
oneMenuActive.value = 'addon' // oneMenuActive.value = 'addon'
twoMenuData.value = route.matched[1]?.children ?? [] // twoMenuData.value = route.matched[1]?.children ?? []
} else if (marketingKeys.includes(matchedName)) { // } else if (marketingKeys.includes(matchedName)) {
oneMenuActive.value = 'active' // oneMenuActive.value = 'active'
twoMenuData.value = route.matched[1]?.children ?? [] // twoMenuData.value = route.matched[1]?.children ?? []
} else if (route.meta.attr !== '') { // } else if (route.meta.attr !== '') {
oneMenuActive.value = route.matched[2]?.name // oneMenuActive.value = route.matched[2]?.name
twoMenuData.value = route.matched[1]?.children ?? [] // 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 ?? []
// }
// }
// }
// }
// secondMenuShowWayFn()
// })
// 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 { } else {
// //
if (siteInfo?.apps.length > 1) { if (siteInfo?.apps.length > 1) {
twoMenuData.value = route.matched[1]?.children twoMenuData.value = route.matched[1].children
oneMenuActive.value = route.matched[1]?.name oneMenuActive.value = route.matched[1].name
} else { } else {
// //
const oneMenu = route.matched[1] const oneMenu = route.matched[1]
if (oneMenu.meta.addon === '') { if (oneMenu.meta.addon == '') {
oneMenuActive.value = route.matched[1]?.name oneMenuActive.value = route.matched[1].name
twoMenuData.value = route.matched[1]?.children ?? [] twoMenuData.value = route.matched[1].children ?? []
} else { } else {
if (oneMenu.meta.addon === siteInfo?.apps[0]?.key) { if (oneMenu.meta.addon == siteInfo?.apps[0].key) {
oneMenuActive.value = route.matched[2]?.name oneMenuActive.value = route.matched[2].name
twoMenuData.value = route.matched[2]?.children ?? [] twoMenuData.value = route.matched[2].children ?? []
} else { } else {
oneMenuActive.value = route.matched[1]?.name oneMenuActive.value = route.matched[1].name
twoMenuData.value = route.matched[1]?.children ?? [] twoMenuData.value = route.matched[1].children ?? []
} }
} }
} }
} }
secondMenuShowWayFn() secondMenuShowWayFn()
}) 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);
}
}
}
// watch(route, () => { }, { immediate: true })
// 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 ?? []
// }
// }
// }
// }
// }, { immediate: true })
</script> </script>
<style lang="scss"> <style lang="scss">
@ -287,7 +420,7 @@ watchEffect(() => {
.two-menu { .two-menu {
.aside-menu:not(.el-menu--collapse) { .aside-menu:not(.el-menu--collapse) {
width: 132px; width: 152px;
padding-top: 16px; padding-top: 16px;
border: 0; border: 0;
@ -343,6 +476,30 @@ watchEffect(() => {
margin-left: 0 !important; margin-left: 0 !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: 40px !important;
span{
margin-left: 0 !important;
}
}
}
} }
} }
} }