update admin

This commit is contained in:
全栈小学生 2023-10-10 16:46:59 +08:00
parent 63bf044a64
commit 992729a53d
82 changed files with 3069 additions and 1117 deletions

View File

@ -6,4 +6,8 @@ import request from '@/utils/request'
*/
export function getApply(params: Record<string, any>) {
return request.get(`auth/authaddon`, {params})
}
}
export function getAppManage(params: Record<string, any>) {
return request.get(`auth/app_manage`, {params})
}

View File

@ -32,13 +32,6 @@ export function getLoginConfig() {
return request.get('login/config')
}
/**
*
* @returns
*/
export function setStarAddon(params: Record<string, any>) {
return request.post('auth/appstar', params)
}
/**
*
*/

View File

@ -1,17 +0,0 @@
import request from '@/utils/request'
/**
*
* @returns
*/
export function getShopTransaction() {
return request.get('shop/transaction')
}
/**
*
* @returns
*/
export function setShopTransaction(param:any) {
return request.post('shop/transaction',param, {showSuccessMessage: true})
}

View File

@ -1,8 +1,5 @@
import request from '@/utils/request'
// USER_CODE_BEGIN -- sys_dict
/**
*
* @param params
@ -32,7 +29,6 @@ export function addDict(params: Record<string, any>) {
/**
*
* @param id
* @param params
* @returns
*/
@ -42,7 +38,6 @@ export function addDictData(params: Record<string, any>) {
/**
*
* @param id
* @param params
* @returns
*/
@ -74,12 +69,10 @@ export function setDictData(id:number,params: Record<string, any>) {
export function getDictAll() {
return request.get(`dict/all`)
}
// USER_CODE_END -- sys_dict
/**
*
* @param id
* @param params
* @param type
* @returns
*/
export function useDictionary(type: string) {

View File

@ -40,7 +40,7 @@ export function getModuleVersion() {
/**
*
* @param addon
* @param params
* @returns
*/
export function downloadVersion(params: Record<string, any>) {

View File

@ -1,7 +1,5 @@
import request from '@/utils/request'
/***************************************************** 账单列表 **************************************************/
/**

View File

@ -15,4 +15,3 @@ export function getUserInfo(type: string) {
export function setUserInfo(params: Record<string, any>) {
return request.put(`auth/edit`, params, {showSuccessMessage: true});
}

View File

@ -25,7 +25,7 @@ export function getUrl() {
* @returns
*/
export function getRoleList(params: Record<string, any>) {
return request.get('sys/role', { params })
return request.get('sys/role', {params})
}
/**
@ -42,7 +42,7 @@ export function getRoleInfo(roleId: number) {
* @returns
*/
export function addRole(params: Record<string, any>) {
return request.post(`sys/role`, params, { showSuccessMessage: true })
return request.post(`sys/role`, params, {showSuccessMessage: true})
}
/**
@ -50,7 +50,7 @@ export function addRole(params: Record<string, any>) {
* @param params
*/
export function editRole(params: Record<string, any>) {
return request.put(`sys/role/${params.role_id}`, params, { showSuccessMessage: true })
return request.put(`sys/role/${params.role_id}`, params, {showSuccessMessage: true})
}
/**
@ -58,7 +58,7 @@ export function editRole(params: Record<string, any>) {
* @param roleId
*/
export function deleteRole(roleId: number) {
return request.delete(`sys/role/${roleId}`, { showSuccessMessage: true })
return request.delete(`sys/role/${roleId}`, {showSuccessMessage: true})
}
/**
@ -66,8 +66,9 @@ export function deleteRole(roleId: number) {
* @param params
*/
export function edstatus(params: Record<string, any>) {
return request.put(`sys/role/status/${params.role_id}/${params.status}`, { showSuccessMessage: true })
return request.put(`sys/role/status/${params.role_id}/${params.status}`, {showSuccessMessage: true})
}
/**
*
* @returns
@ -83,12 +84,14 @@ export function allRole() {
export function getSystem() {
return request.get(`sys/menu/system_menu`)
}
/**
*
*/
export function getAddonList() {
return request.get(`app/getAddonList`)
}
/**
*
* @param key
@ -96,6 +99,7 @@ export function getAddonList() {
export function getaddonMenu(key: any) {
return request.get(`sys/menu/addon_menu/${key}`)
}
/***************************************************** 全部菜单 ****************************************************/
/**
@ -120,7 +124,7 @@ export function getMenuInfo(menu_key: string) {
* @returns
*/
export function addMenu(params: Record<string, any>) {
return request.post('sys/menu', params, { showSuccessMessage: true })
return request.post('sys/menu', params, {showSuccessMessage: true})
}
/**
@ -128,29 +132,33 @@ export function addMenu(params: Record<string, any>) {
* @param params
*/
export function editMenu(params: Record<string, any>) {
return request.put(`sys/menu/${params.menu_key}`, params, { showSuccessMessage: true })
return request.put(`sys/menu/${params.menu_key}`, params, {showSuccessMessage: true})
}
/**
*
* @param menu_key
*/
export function deleteMenu(menu_key: string) {
return request.delete(`sys/menu/${menu_key}`, { showSuccessMessage: true })
return request.delete(`sys/menu/${menu_key}`, {showSuccessMessage: true})
}
/**
*
*
*
*/
export function getSystemMenu() {
return request.get(`sys/menu/system_menu`)
}
/**
*
*
*
*/
export function getAddonMenu(key:any) {
export function getAddonMenu(key: any) {
return request.get(`sys/menu/addon_menu/${key}`)
}
/***************************************************** 站点菜单 ****************************************************/
/**
@ -186,7 +194,7 @@ export function getWebConfig() {
* @returns
*/
export function setWebsite(params: Record<string, any>) {
return request.post(`sys/config/website`, params, { showSuccessMessage: true })
return request.post(`sys/config/website`, params, {showSuccessMessage: true})
}
/**
@ -211,7 +219,7 @@ export function getService() {
* @returns
*/
export function setCopyright(params: Record<string, any>) {
return request.put(`sys/config/copyright`, params, { showSuccessMessage: true })
return request.put(`sys/config/copyright`, params, {showSuccessMessage: true})
}
/**
@ -220,7 +228,7 @@ export function setCopyright(params: Record<string, any>) {
* @returns
*/
export function getAttachmentCategoryList(params: Record<string, any>) {
return request.get(`sys/attachment/category`, { params })
return request.get(`sys/attachment/category`, {params})
}
/**
@ -228,7 +236,7 @@ export function getAttachmentCategoryList(params: Record<string, any>) {
* @param params
*/
export function addAttachmentCategory(params: Record<string, any>) {
return request.post(`sys/attachment/category`, params, { showSuccessMessage: true })
return request.post(`sys/attachment/category`, params, {showSuccessMessage: true})
}
/**
@ -237,7 +245,7 @@ export function addAttachmentCategory(params: Record<string, any>) {
* @returns
*/
export function editAttachmentCategory(params: Record<string, any>) {
return request.put(`sys/attachment/category/${params.id}`, params, { showSuccessMessage: true })
return request.put(`sys/attachment/category/${params.id}`, params, {showSuccessMessage: true})
}
/**
@ -246,7 +254,7 @@ export function editAttachmentCategory(params: Record<string, any>) {
* @returns
*/
export function deleteAttachmentCategory(id: number) {
return request.delete(`sys/attachment/category/${id}`, { showSuccessMessage: true })
return request.delete(`sys/attachment/category/${id}`, {showSuccessMessage: true})
}
/**
@ -255,7 +263,7 @@ export function deleteAttachmentCategory(id: number) {
* @returns
*/
export function getAttachmentList(params: Record<string, any>) {
return request.get(`sys/attachment`, { params })
return request.get(`sys/attachment`, {params})
}
/**
@ -264,7 +272,7 @@ export function getAttachmentList(params: Record<string, any>) {
* @returns
*/
export function deleteAttachment(params: Record<string, any>) {
return request.delete(`sys/attachment/del`, { data: params, showSuccessMessage: true })
return request.delete(`sys/attachment/del`, {data: params, showSuccessMessage: true})
}
/**
@ -294,7 +302,7 @@ export function getShortcutMenu() {
*
*/
export function setShortcutMenu(params: Record<string, any>) {
return request.put(`sys/config/shortcut_menu`, params, { showSuccessMessage: true })
return request.put(`sys/config/shortcut_menu`, params, {showSuccessMessage: true})
}
/**
@ -303,7 +311,7 @@ export function setShortcutMenu(params: Record<string, any>) {
* @returns
*/
export function getIconCategoryList(params: Record<string, any>) {
return request.get(`sys/attachment/icon_category`, { params })
return request.get(`sys/attachment/icon_category`, {params})
}
/**
@ -312,7 +320,7 @@ export function getIconCategoryList(params: Record<string, any>) {
* @returns
*/
export function getIconList(params: Record<string, any>) {
return request.get(`sys/attachment/icon`, { params })
return request.get(`sys/attachment/icon`, {params})
}
/***************************************************** 地址管理 ****************************************************/
@ -338,14 +346,22 @@ export function getAreatree(level: number = 1) {
*
*/
export function getAddressInfo(params: any) {
return request.get(`sys/area/get_info`, { params })
return request.get(`sys/area/get_info`, {params})
}
/**
*
*/
export function getContraryAddress(params: any) {
return request.get(`sys/area/contrary`, { params })
return request.get(`sys/area/contrary`, {params})
}
/**
*
* @param code
*/
export function getAreaByCode(code: number | string) {
return request.get(`sys/area/code/${code}`)
}
/***************************************************** 存储设置 ****************************************************/
@ -371,7 +387,7 @@ export function getStorageInfo(type: string) {
* @returns
*/
export function editStorage(params: Record<string, any>) {
return request.put(`sys/storage/${params.storage_type}`, params, { showSuccessMessage: true })
return request.put(`sys/storage/${params.storage_type}`, params, {showSuccessMessage: true})
}
/***************************************************** 支付设置 ****************************************************/
@ -389,7 +405,7 @@ export function getPayConfig(type: string) {
* @returns
*/
export function setPayConfig(params: Record<string, any>) {
return request.put(`pay/config/${params.type}`, params, { showSuccessMessage: true });
return request.put(`pay/config/${params.type}`, params, {showSuccessMessage: true});
}
/**
@ -405,7 +421,7 @@ export function getPayList() {
*
* @param channel
*/
export function getTransferInfo(channel) {
export function getTransferInfo(channel: string) {
return request.get(`pay/channel/lists/${channel}`)
}
@ -425,7 +441,7 @@ export function setTransferInfo(params: Record<string, any>) {
* @returns
*/
export function getCronList(params: any) {
return request.get(`sys/schedule/list`, { params })
return request.get(`sys/schedule/list`, {params})
}
/**
@ -465,7 +481,7 @@ export function getWeek() {
* @returns
*/
export function addCron(params: Record<string, any>) {
return request.post(`sys/schedule`, params, { showSuccessMessage: true })
return request.post(`sys/schedule`, params, {showSuccessMessage: true})
}
/**
@ -473,7 +489,7 @@ export function addCron(params: Record<string, any>) {
* @returns
*/
export function editCron(params: Record<string, any>) {
return request.put(`sys/schedule/${params.id}`, params, { showSuccessMessage: true })
return request.put(`sys/schedule/${params.id}`, params, {showSuccessMessage: true})
}
/**
@ -481,7 +497,7 @@ export function editCron(params: Record<string, any>) {
* @returns
*/
export function deleteCron(id: string) {
return request.delete(`sys/schedule/${id}`, { showSuccessMessage: true })
return request.delete(`sys/schedule/${id}`, {showSuccessMessage: true})
}
/***************************************************** 协议管理 ****************************************************/
@ -507,7 +523,7 @@ export function getAgreementInfo(key: string) {
* @returns
*/
export function editAgreement(params: Record<string, any>) {
return request.put(`sys/agreement/${params.key}`, params, { showSuccessMessage: true })
return request.put(`sys/agreement/${params.key}`, params, {showSuccessMessage: true})
}
/**
@ -542,7 +558,7 @@ export function getConfigLogin() {
* @returns
*/
export function setConfigLogin(params: Record<string, any>) {
return request.put(`sys/config/login`, params, { showSuccessMessage: true })
return request.put(`sys/config/login`, params, {showSuccessMessage: true})
}
/**
@ -556,7 +572,7 @@ export function getPayConfigList() {
*
*/
export function setPatConfig(params: Record<string, any>) {
return request.post(`pay/channel/set/all`, params, { showSuccessMessage: true })
return request.post(`pay/channel/set/all`, params, {showSuccessMessage: true})
}
@ -565,14 +581,14 @@ export function setPatConfig(params: Record<string, any>) {
*
*/
export function menuRefresh(params: Record<string, any>) {
return request.post(`sys/menu/refresh`, {}, { showSuccessMessage: true })
return request.post(`sys/menu/refresh`, {}, {showSuccessMessage: true})
}
/**
*
*/
export function clearSchemaCache(params: Record<string, any>) {
return request.post(`sys/schema/clear`, {}, { showSuccessMessage: true })
return request.post(`sys/schema/clear`, {}, {showSuccessMessage: true})
}
/***************************************************** 地图设置 ****************************************************/
@ -581,7 +597,7 @@ export function clearSchemaCache(params: Record<string, any>) {
* key
*/
export function setMap(params: Record<string, any>) {
return request.put(`sys/config/map`, params, { showSuccessMessage: true })
return request.put(`sys/config/map`, params, {showSuccessMessage: true})
}
/**
@ -603,30 +619,30 @@ export function getIndexList() {
*
*/
export function setIndexList(params: Record<string, any>) {
return request.put(`sys/config/site_index`, params, { showSuccessMessage: true })
return request.put(`sys/config/site_index`, params, {showSuccessMessage: true})
}
/**
*
*/
export function getPayAuditList(params: Record<string, any>) {
return request.get('pay/audit', { params })
return request.get('pay/audit', {params})
}
/**
*
* @returns
* @returns
*/
export function payAuditPass(outTradeNo: string) {
return request.put(`pay/pass/${outTradeNo}`, {}, { showSuccessMessage: true })
return request.put(`pay/pass/${outTradeNo}`, {}, {showSuccessMessage: true})
}
/**
*
* @returns
* @returns
*/
export function payAuditRefuse(params: Record<string, any>) {
return request.put(`pay/refuse/${params.out_trade_no}`, params, { showSuccessMessage: true })
return request.put(`pay/refuse/${params.out_trade_no}`, params, {showSuccessMessage: true})
}
/**
@ -640,5 +656,5 @@ export function getPayDetail(id: number) {
*
*/
export function getWapIndexList(params: Record<string, any>) {
return request.get('sys/config/wap_index', { params })
return request.get('sys/config/wap_index', {params})
}

View File

@ -1,12 +1,14 @@
import request from '@/utils/request'
/***************************************************** 插件开发 ****************************************************/
/**
*
* @returns
*/
export function getAddonDevelop(params: Record<string, any>) {
return request.get(`addon_develop`,{params});
return request.get(`addon_develop`, {params});
}
/**
*
* @returns
@ -14,60 +16,70 @@ export function getAddonDevelop(params: Record<string, any>) {
export function getAddontype() {
return request.get(`addontype`);
}
/**
*
* @returns
*/
export function getAddonDevelopInfo(key:any) {
export function getAddonDevelopInfo(key: any) {
return request.get(`addon_develop/${key}`)
}
/**
* key是否存在
* @returns
*/
export function getAddonDevelopCheck(key:any) {
export function getAddonDevelopCheck(key: any) {
return request.get(`addon_develop/check/${key}`)
}
/**
*
* @param key
* @param params
* @returns
*/
export function addAddonDevelop(key:any,params: Record<string, any>) {
export function addAddonDevelop(key: any, params: Record<string, any>) {
return request.post(`addon_develop/${key}`, params)
}
/**
*
* @param key
* @param params
* @returns
*/
export function editAddonDevelop(key:any,params: Record<string, any>) {
export function editAddonDevelop(key: any, params: Record<string, any>) {
return request.put(`addon_develop/${key}`, params)
}
/**
*
* @param id
* @param key
* @returns
*/
export function deleteAddonDevelop(key: any) {
return request.delete(`addon_develop/${key}`, {showSuccessMessage: true})
}
/**
*
* @returns
*/
export function addonDevelopBuild(key:any) {
export function addonDevelopBuild(key: any) {
return request.post(`addon_develop/build/${key}`)
}
/**
*
* @returns
*/
export function addonDevelopDownload(key:any) {
return request.post(`addon_develop/download/${key}`,{},{ "responseType": "blob" })
export function addonDevelopDownload(key: any) {
return request.post(`addon_develop/download/${key}`, {}, {"responseType": "blob"})
}
/***************************************************** 代码生成 ****************************************************/
/**
@ -122,14 +134,16 @@ export function deleteGenerateTable(id: number) {
export function generateCreate(params: Record<string, any>) {
return request.post(`generator/download`, params)
}
/**
*
* @param params
* @param id
* @returns
*/
export function generatePreview(id: number) {
return request.get(`generator/preview/${id}`)
}
/**
*
*/
@ -143,21 +157,24 @@ export function generateTable() {
export function getSystem() {
return request.get(`sys/system`)
}
/**
*
*/
export function getGeneratorAllModel(params:any) {
return request.get(`generator/all_model`,{params})
export function getGeneratorAllModel(params: any) {
return request.get(`generator/all_model`, {params})
}
/**
*
*/
export function getGeneratorTableColumn(params:any){
return request.get(`generator/table_column`,{params})
export function getGeneratorTableColumn(params: any) {
return request.get(`generator/table_column`, {params})
}
/**
*
*/
export function generatorCheckFile(params: Record<string, any>){
return request.get(`generator/check_file`,{params})
export function generatorCheckFile(params: Record<string, any>) {
return request.get(`generator/check_file`, {params})
}

View File

@ -44,7 +44,6 @@ export function setWeappVersion(params: Record<string, any>) {
/**
*
* @param params
* @returns
*/
export function getWeappPreview() {
@ -100,7 +99,7 @@ export function getVersionList(params: Record<string, any>) {
/**
*
* @param params
* @param id
* @returns
*/
export function getVersionInfo(id: string) {
@ -118,7 +117,7 @@ export function editVersion(params: Record<string, any>) {
/**
*
* @param params
* @param id
* @returns
*/
export function deleteVersion(id: string) {

View File

@ -58,7 +58,3 @@ export function getTemplateList() {
export function getBatchAcquisition(params: Record<string, any>) {
return request.put('wechat/template/sync', params, {showSuccessMessage: true})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -24,5 +24,7 @@
"viewPathPlaceholder": "请输入组件路径",
"authIdPlaceholder": "请输入api路径",
"selectIconPlaceholder": "请选择菜单图标",
"topLevel": "顶级"
"topLevel": "顶级",
"menuShortName":"菜单短标题",
"menuShortNamePlaceholder":"请输入菜单短标题"
}

View File

@ -1,5 +1,7 @@
{
"app":"应用",
"noPlug":"暂无应用",
"descriptionLeft":"暂无安装任何应用或插件,马上去",
"link":"官方应用市场",
"descriptionRight":"逛逛",
"installApp":"安装应用"
}

View File

@ -1,7 +1,8 @@
{
"refresh":"刷新",
"refreshMenu":"刷新菜单",
"refreshMenuDesc":"新增/修改插件菜单后,需要刷新插件菜单",
"resetting": "重置",
"refreshMenu":"重置菜单",
"refreshMenuDesc":"重置菜单会按照菜单的配置文件进行重置,针对用户自己添加的菜单不处理",
"dataCache":"数据缓存",
"dataCacheDesc":"新增/修改数据表后,需要清除数据表缓存"
}

View File

@ -24,7 +24,7 @@
<span class="text-[14px] flex items-center text-[#797979]">
<span>授权码</span>
<em class="ml-[12px] mr-[10px] text-[12px] text-[#222222]">{{ authinfo.auth_code ? (isCheck ? authinfo.auth_code : hideAuthCode(authinfo.auth_code)) : '--' }}</em>
<el-icon v-if="isCheck" @click="isCheck = !isCheck" class="text-[12px] cursor-pointer"><View/></el-icon>
<el-icon v-if="!isCheck" @click="isCheck = !isCheck" class="text-[12px] cursor-pointer"><View/></el-icon>
<el-icon v-else @click="isCheck = !isCheck" class="text-[12px] cursor-pointer"><Hide /></el-icon>
</span>
</div>
@ -33,7 +33,7 @@
<el-button class="w-[154px] !h-[48px] mt-[8px]" type="primary" @click="authCodeApproveFn">授权码认证</el-button>
<el-popover ref="getAuthCodeDialog" placement="bottom" :width="478" trigger="click" class="mt-[8px]">
<div class="px-[18px] py-[8px]">
<p class="leading-[32px] text-[18px]">您在官方应用市场购买任意一款应用即可获得授权码输入正确授权码认证通过后即可支持在线升级和其它相关服务</p>
<p class="leading-[32px] text-[14px]">您在官方应用市场购买任意一款应用即可获得授权码输入正确授权码认证通过后即可支持在线升级和其它相关服务</p>
<div class="flex justify-end mt-[36px]">
<el-button class="w-[182px] !h-[48px]" plain @click="market">去应用市场逛逛</el-button>
<el-button class="w-[100px] !h-[48px]" plain @click="getAuthCodeDialog.hide()">关闭</el-button>
@ -44,7 +44,7 @@
</template>
</el-popover>
</div>
<el-dialog v-model="authCodeApproveDialog" title="授权码认证" width="400px" :before-close="closeAuthCodeApproveDialogFn">
<el-dialog v-model="authCodeApproveDialog" title="授权码认证" width="400px">
<el-form :model="formData" label-width="0" ref="formRef" :rules="formRules" class="page-form">
<el-card class="box-card !border-none" shadow="never">
<el-form-item prop="auth_code">
@ -96,12 +96,6 @@ const hideAuthCode = (res)=>{
const authCodeApproveFn = ()=>{
authCodeApproveDialog.value = true;
authinfo.value = "";
}
const closeAuthCodeApproveDialogFn = ()=>{
loading.value = true;
checkAppMange();
}
const authinfo = ref("");
@ -139,14 +133,6 @@ const formRules = reactive<FormRules>({
],
});
const setFormData = async () => {
const data = await (await getAdminAuthinfo()).data;
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key];
});
};
setFormData();
const save = async (formEl: FormInstance | undefined) => {
if (saveLoading.value || !formEl) return;

View File

@ -1,14 +1,10 @@
<template>
<div class="main-container h-[500px] w-full p-5 bg-white" v-loading="loading">
<div class="flex mb-4">
<div class="border rounded-sm switch-btn active px-4 py-1 cursor-pointer flex items-center ">
<img src="@/app/assets/images/app_store/local_icon_select.png" class="mr-1.5 w-3.5 h-3.5 mb-0.5">
{{ t('localAppText') }}
</div>
<div class="flex justify-between items-center h-[32px] mb-4">
<span class="text-[20px]">{{ t('localAppText') }}</span>
</div>
<div class="relative">
<div class="absolute right-0 top-[2px] flex items-center cursor-pointer z-20 border border-inherit">
<div class="absolute right-0 top-[2px] flex items-center cursor-pointer z-[4] border border-inherit">
<div class="flex item-center justify-center px-[6px] py-[4px]"
:class="{ 'bg-slate-200': showType == 'small' }" @click="showType = 'small'">
<img src="@/app/assets/images/app_store/switch_icon_1.png" class=" w-[16px] h-[16px]">
@ -325,6 +321,7 @@ import { TabsPaneContext, ElMessageBox, ElNotification } from 'element-plus'
import { img } from '@/utils/common'
import { Terminal, api as terminalApi } from 'vue-web-terminal'
import { useRouter } from 'vue-router'
import useUserStore from '@/stores/modules/user'
const router = useRouter()
const activeName = ref('installed')
@ -332,6 +329,7 @@ const loading = ref<Boolean>(false)
const showType = ref('large')
const downloading = ref('')
const installAfterTips = ref<string[]>([])
const userStore = useUserStore()
const downEvent = (param: Record<string, any>) => {
if (downloading.value) return
@ -475,6 +473,7 @@ const getInstallTask = (first: boolean = true) => {
if (!first) {
installStep.value = 3
localListFn()
userStore.getAppList()
notificationEl.close()
}
}
@ -498,6 +497,7 @@ const handleInstall = () => {
installAddon({ addon: currAddon.value }).then(res => {
installStep.value = 3
localListFn()
userStore.getAppList()
localInstalling.value = false
if (res.data.length) installAfterTips.value = res.data
}).catch((res) => {
@ -581,6 +581,7 @@ watch(currAddon, (nval) => {
const uninstallAddonFn = (key: string) => {
uninstallAddon({ addon: key }).then(res => {
localListFn()
userStore.getAppList()
loading.value = false
}).catch(() => {
loading.value = false

View File

@ -5,6 +5,7 @@
<script lang="ts" setup>
import { debounce } from '@/utils/common'
import { ref, computed, onMounted, nextTick, } from 'vue'
const prop = defineProps({
data: {
type: Object,
@ -18,11 +19,9 @@ const prop = defineProps({
let data = ref([])
data.value.push(prop.data)
const emit = defineEmits(['update:modelValue', 'change'])
const value = computed({
get() {
return prop.modelValue
},
set(value) {
@ -30,6 +29,7 @@ const value = computed({
}
})
const treeRef = ref(null)
//
onMounted(()=>{
nextTick(()=>{

View File

@ -71,6 +71,10 @@
</el-radio-group>
</el-form-item>
<el-form-item :label="t('menuShortName')" >
<el-input v-model="formData.menu_short_name" :placeholder="t('menuShortNamePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('sort')">
<el-input-number v-model="formData.sort" :min="0" />
</el-form-item>
@ -92,6 +96,7 @@ import type { FormInstance } from 'element-plus'
import selectMenuItem from './select-menu-item.vue'
import { addMenu, editMenu, getMenuInfo, getSystemMenu,getAddonMenu } from '@/app/api/sys'
import { getAddonDevelop } from '@/app/api/tools'
const showDialog = ref(false)
const method = ref('post')
const loading = ref(false)
@ -115,7 +120,8 @@ const initialFormData = {
is_show: 1,
menu_key: '',
app_type: '',
addon: ''
addon: '',
menu_short_name:''
}
const formData: Record<string, any> = reactive({ ...initialFormData })
@ -168,23 +174,27 @@ const formRules = computed(() => {
]
}
})
//
const getAddonDevelopFn = async () => {
let { data } = await getAddonDevelop({})
addonLst.value = [{ title: "系统", key: "" }]
addonLst.value.push(...data)
}
//
const getSystemMenuFn = async () => {
let {data} = await getSystemMenu()
sysMenuList.value = [{ menu_name: "顶级", menu_key: "" }]
sysMenuList.value.push(...data)
}
//
const getAddonMenuFn = async (key:any) => {
let {data} = await getAddonMenu(key)
addonMenuList.value = data
}
//
const addonChange =async(val:any)=>{
formData.parent_key = ''
@ -193,6 +203,7 @@ const addonChange =async(val:any)=>{
formData.parent_key = addonMenuList.value[0].menu_key
}
}
const emit = defineEmits(['complete'])
/**

View File

@ -41,8 +41,8 @@
<el-table-column prop="sort" :label="t('sort')" min-width="100" />
<el-table-column :label="t('operation')" fixed="right" align="right" width="130">
<template #default="{ row }">
<el-button type="primary" link @click="editEvent(row)" v-if="row.source == 'generator' || row.source == 'create'">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.menu_key)" v-if="row.source == 'generator' || row.source == 'create'">{{ t('delete') }}</el-button>
<el-button type="primary" link @click="editEvent(row)" >{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.menu_key)" >{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
@ -81,8 +81,8 @@
<el-table-column prop="sort" :label="t('sort')" min-width="100" />
<el-table-column :label="t('operation')" fixed="right" align="right" width="130">
<template #default="{ row }">
<el-button type="primary" link @click="editEvent(row)" v-if="row.source == 'generator' || row.source == 'create'">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.menu_key)" v-if="row.source == 'generator' || row.source == 'create'">{{ t('delete') }}</el-button>
<el-button v-if="row.menu_key.indexOf('1') ==-1" type="primary" link @click="editEvent(row)" >{{ t('edit') }}</el-button>
<el-button v-if="row.menu_key.indexOf('1') ==-1" type="primary" link @click="deleteEvent(row.menu_key)" >{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
@ -102,6 +102,7 @@ import { t } from '@/lang'
import EditMenu from '@/app/views/auth/components/edit-menu.vue'
import { getSystem, getAddonList, deleteMenu, getMenus } from '@/app/api/sys'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title
@ -112,7 +113,7 @@ const menusTableData = reactive({
data:[],
activeName: 'system'
})
console.log(menusTableData.applicationDate)
/**
* 获取菜单
*/

View File

@ -55,6 +55,7 @@ import { t } from '@/lang'
import { getRoleList, deleteRole, edstatus } from '@/app/api/sys'
import { ElMessageBox, FormInstance } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title;
@ -106,9 +107,10 @@ const editRoleDialog: Record<string, any> | null = ref(null)
const addEvent = () => {
router.push({ path: '/setting/auth/role_edit' })
}
/**
* 编辑角色
* @param data
* @param role_id
*/
const editEvent = (role_id: any) => {
router.push({

View File

@ -61,6 +61,7 @@ import { debounce } from '@/utils/common'
import { useRoute, useRouter } from 'vue-router'
import addonRole from '@/app/views/auth/components/addon-role.vue'
import { ElMessage } from 'element-plus'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title

View File

@ -55,8 +55,8 @@ import { getMenus, deleteMenu } from '@/app/api/sys'
import { t } from '@/lang'
import { ElMessageBox } from 'element-plus'
import EditMenu from '@/app/views/auth/components/edit-menu.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title

View File

@ -226,6 +226,7 @@ import { useRouter } from 'vue-router'
import { t } from '@/lang'
import {img} from '@/utils/common'
import { getAliappConfig } from '@/app/api/aliapp'
const router = useRouter()
let activeName = ref("/website/channel/aliapp");
let active = ref(2);

View File

@ -120,7 +120,6 @@ const formData = reactive<Record<string, string>>({
const formRef = ref<FormInstance>()
/**
* 获取支付宝配置
*/
@ -135,7 +134,6 @@ getAliappStatic().then(res => {
formData.request_url = res.data.domain
})
/**
* 复制
*/

View File

@ -45,7 +45,6 @@ const formData = reactive<Record<string, string | boolean>>({
const formRef = ref<FormInstance>()
const router = useRouter()
/**
* 获取pc域名
*/

View File

@ -145,6 +145,7 @@ import { useRouter } from "vue-router";
import { t } from "@/lang";
import {img} from '@/utils/common'
import { getWeappConfig } from '@/app/api/weapp'
const router = useRouter();
let activeName = ref("/website/channel/weapp");
let active = ref(2);

View File

@ -9,17 +9,10 @@
<el-tab-pane :label="t('weappRelease')" name="/website/channel/weapp/code" />
</el-tabs>
<el-card class="box-card !border-none" shadow="never">
<el-collapse v-model="activeNames">
<el-collapse-item :title="t('operatePrompt')" name="1">
<div>
<p class="indent-4">{{ t('operatePromptTipsTwo') }}</p>
</div>
</el-collapse-item>
</el-collapse>
<div class="mt-[50px]">
<el-button type="primary" @click="insert" :loading="uploading">{{ t('cloudRelease') }}</el-button>
<el-button @click="localInsert">{{ t('localRelease') }}</el-button>
<el-button type="primary" @click="insert" :loading="uploading" :disabled="weappTableData.loading">{{
t('cloudRelease') }}</el-button>
<el-button @click="localInsert" :disabled="weappTableData.loading">{{ t('localRelease') }}</el-button>
</div>
<el-table class="mt-[15px]" :data="weappTableData.data" v-loading="weappTableData.loading" size="default">
<template #empty>
@ -36,10 +29,9 @@
<el-table-column prop="create_time" :label="t('createTime')" align="center" />
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="120">
<template #default="{ row, $index }">
<div class="">
<div class="" v-if="previewContent && $index == 0 && row.status == 1 && weappTableData.page == 1">
<el-tooltip :content="previewContent" raw-content effect="light">
<el-button type="primary" link
v-if="previewContent && $index == 0 && weappTableData.page == 1">
<el-button type="primary" link>
{{ t('preview') }}</el-button>
</el-tooltip>
</div>
@ -74,13 +66,6 @@
</span>
</template>
</el-dialog>
<!-- <el-dialog v-model="uploading" title="" width="300px" :show-close="false" :align-center="true"
:close-on-click-modal="false">
<template #default>
<div v-loading="uploading" class="w-full h-[100px]" :element-loading-text="t('uploadingTips')"></div>
</template>
</el-dialog> -->
</div>
</template>
@ -147,7 +132,7 @@ const getWeappVersionListFn = (page: number = 1) => {
weappTableData.loading = false
})
}
// loadWeappTemplateList()
getWeappVersionListFn()
const handleClose = () => {
@ -168,8 +153,11 @@ const insert = () => {
if (uploading.value) return
uploading.value = true
previewContent.value = ''
setWeappVersion(form.value).then(res => {
getWeappVersionListFn()
getWeappPreviewImage()
uploading.value = false
}).catch(() => {
uploading.value = false

View File

@ -64,6 +64,7 @@ import { getTemplateList, getBatchAcquisition } from '@/app/api/weapp'
import { editNoticeStatus } from '@/app/api/notice'
import { ElLoading } from 'element-plus'
import { useRoute,useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title

View File

@ -232,6 +232,7 @@ import { useRouter } from 'vue-router'
import { t } from '@/lang'
import {img} from '@/utils/common'
import { getWechatConfig } from '@/app/api/wechat'
const router = useRouter()
let activeName = ref("/website/channel/wechat");
let active = ref(2);

View File

@ -5,7 +5,7 @@
<h3 class="mb-[10px]">{{ t('addonListSet') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<div ref="addonBoxRef">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap !cursor-move p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<div v-show="item.title" class="flex items-center pb-[10px]">
<img class="w-[60px] h-[60px] rounded-md" :src="img(item.icon)" />
<div class="flex flex-col justify-center ml-[10px] leading-[1]">

View File

@ -0,0 +1,78 @@
<template>
<!-- 内容 -->
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
内容
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('titleStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] article-slider" :min="12" :max="20"/>
</el-form-item>
<el-form-item :label="t('textFontWeight')">
<el-radio-group v-model="diyStore.editComponent.fontWeight">
<el-radio :label="'normal'">{{t('fontWeightNormal')}}</el-radio>
<el-radio :label="'bold'">{{t('fontWeightBold')}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('textColor')">
<el-color-picker v-model="diyStore.editComponent.textColor"/>
</el-form-item>
</el-form>
</div>
<!-- 组件样式 -->
<slot name="style"></slot>
</div>
</template>
<script lang="ts" setup>
import {t} from '@/lang'
import useDiyStore from '@/stores/modules/diy'
import {ref, reactive} from 'vue'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = []; //
const showDialog = ref(false)
const showStyle = () => {
showDialog.value = true
}
const selectStyle = ref(diyStore.editComponent.style)
const changeStyle = () => {
switch (selectStyle.value) {
case 'style-1':
diyStore.editComponent.subTitle.control = false
diyStore.editComponent.more.control = false
diyStore.editComponent.styleName = "风格1"
break;
case 'style-2':
diyStore.editComponent.subTitle.control = true
diyStore.editComponent.more.control = true
diyStore.editComponent.styleName = "风格2"
break;
}
diyStore.editComponent.style = selectStyle.value
showDialog.value = false
}
defineExpose({})
</script>
<style lang="scss">
.horz-blank-slider {
.el-slider__input {
width: 100px;
}
}
</style>
<style lang="scss" scoped></style>

View File

@ -67,7 +67,7 @@
<p class="text-sm text-gray-400 mb-[10px]">{{ t('graphicNavTips') }}</p>
<div ref="imageBoxRef">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap !cursor-move p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('image')" v-show="diyStore.editComponent.mode === 'graphic' || diyStore.editComponent.mode === 'img'">
<upload-image v-model="item.imageUrl" :limit="1"/>
</el-form-item>

View File

@ -13,7 +13,7 @@
</el-form-item>
<div ref="imageBoxRef">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap !cursor-move p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('image')">
<upload-image v-model="item.imageUrl" :limit="1" @change="selectImg" />
</el-form-item>

View File

@ -0,0 +1,113 @@
<template>
<!-- 内容 -->
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">公告图标</h3>
<div class="px-[22px] pb-[20px]">
<el-radio-group v-model="diyStore.editComponent.iconType" class="mb-[18px]">
<el-radio label="system">系统图标</el-radio>
<el-radio label="custom">自定义图标</el-radio>
</el-radio-group>
<div v-if="diyStore.editComponent.iconType == 'system'" class="flex items-center flex-wrap py-[8px] px-[10px] bg-[#f4f3f7] rounded">
<img src="@/app/assets/images/diy/notice/style_01.png" :class="['h-[28px] px-[10px] py-[5px] mr-[10px] rounded cursor-pointer', {'border-[1px] border-solid border-[var(--el-color-primary)]': diyStore.editComponent.systemIcon == 'style_01'}]" @click="diyStore.editComponent.systemIcon = 'style_01'" alt="">
</div>
<div v-if="diyStore.editComponent.iconType == 'custom'">
<upload-image v-model="diyStore.editComponent.imageUrl" :limit="1"/>
</div>
</div>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">公告内容</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('title')">
<el-input v-model="diyStore.editComponent.list.text" :placeholder="t('titlePlaceholder')" clearable show-word-limit/>
</el-form-item>
<el-form-item label="点击类型">
<el-radio-group v-model="diyStore.editComponent.showType" class="mb-[18px]">
<el-radio label="popup">弹出公告内容</el-radio>
<el-radio label="link">跳出链接</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('link')" class="!mb-0" v-if="diyStore.editComponent.showType == 'link'">
<diy-link v-model="diyStore.editComponent.list.link"/>
</el-form-item>
</el-form>
</div>
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('titleStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] article-slider" :min="12" :max="20"/>
</el-form-item>
<el-form-item :label="t('textFontWeight')">
<el-radio-group v-model="diyStore.editComponent.fontWeight">
<el-radio :label="'normal'">{{t('fontWeightNormal')}}</el-radio>
<el-radio :label="'bold'">{{t('fontWeightBold')}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('textColor')">
<el-color-picker v-model="diyStore.editComponent.textColor"/>
</el-form-item>
</el-form>
</div>
<!-- 组件样式 -->
<slot name="style"></slot>
</div>
</template>
<script lang="ts" setup>
import {t} from '@/lang'
import useDiyStore from '@/stores/modules/diy'
import {ref, reactive} from 'vue'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = []; //
const showDialog = ref(false)
let iconSelect = ref('0')
const showStyle = () => {
showDialog.value = true
}
const selectStyle = ref(diyStore.editComponent.style)
const changeStyle = () => {
switch (selectStyle.value) {
case 'style-1':
diyStore.editComponent.subTitle.control = false
diyStore.editComponent.more.control = false
diyStore.editComponent.styleName = "风格1"
break;
case 'style-2':
diyStore.editComponent.subTitle.control = true
diyStore.editComponent.more.control = true
diyStore.editComponent.styleName = "风格2"
break;
}
diyStore.editComponent.style = selectStyle.value
showDialog.value = false
}
defineExpose({})
</script>
<style lang="scss">
.horz-blank-slider {
.el-slider__input {
width: 100px;
}
}
.add-notice-width{
width: calc(100% - 20px);
}
</style>
<style lang="scss" scoped></style>

View File

@ -25,7 +25,7 @@
<el-tabs v-model="activeName" class="demo-tabs mt-[15px]">
<el-tab-pane :label="t('navImage')" name="navPicture">
<div ref="navItemRef">
<div v-for="(item,index) in diyBottomData.list" :key="'a'+index" :data-id="index" class="item-wrap border-2 border-dashed pt-[18px] m-[10px] mb-[15px] relative list-item">
<div v-for="(item,index) in diyBottomData.list" :key="'a'+index" :data-id="index" class="item-wrap !cursor-move border-2 border-dashed pt-[18px] m-[10px] mb-[15px] relative list-item">
<el-form-item :label="t('navIconOne')">
<div class="flex align-center">
<div class="flex flex-col justify-center items-center">

View File

@ -49,9 +49,6 @@
</p>
</div>
</div>
<div class="with-ite absolute top-0 right-0 flex flex-col hidden">
<span class="block pr-4 mt-3 text-primary" @click.stop="withEvent(item.key)"><el-icon size="18px"><StarFilled /> </el-icon></span>
</div>
</div>
</div>
</div>
@ -65,7 +62,6 @@
<script lang="ts" setup>
import { ref, reactive } from "vue";
import { getApply } from "@/app/api/apply";
import { setStarAddon } from "@/app/api/auth";
import { img } from "@/utils/common";
import { findFirstValidRoute } from "@/router/routers";
import useUserStore from "@/stores/modules/user";
@ -108,12 +104,6 @@ const toLink = (addon: string) => {
userStore.setAppMenuList(data);
router.push({ name: appLink.value[addon] });
};
const withEvent = (key: string) => {
setStarAddon({ key: key }).then(() => {
getApplelist();
});
};
</script>
<style lang="scss" scoped>

View File

@ -47,9 +47,6 @@
<p class="app-text text-[14px] text-[#999] w-[200px]">{{ item.desc }}</p>
</div>
</div>
<div class="with-ite absolute top-0 right-0 flex flex-col hidden">
<span class="block pr-4 mt-3" :class="item.is_star == 2 ? 'text-primary' : 'text-[#999]'" @click.stop="withEvent(item.key)"><el-icon size="18px"><StarFilled /></el-icon></span>
</div>
</div>
</div>
</div>
@ -63,7 +60,6 @@
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { getApply } from '@/app/api/apply'
import { setStarAddon } from '@/app/api/auth'
import { img } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers'
import useUserStore from '@/stores/modules/user'
@ -102,12 +98,6 @@ getAppLink()
const toLink = (addon: string) => {
router.push({ name: appLink.value[addon] })
}
const withEvent = (key: string) => {
setStarAddon({key: key}).then(() => {
getApplelist()
})
}
</script>
<style lang="scss" scoped>

View File

@ -29,14 +29,11 @@
<p class="app-text w-[190px] text-[17px] text-[#222] pl-3">{{ item.title }}</p>
</div>
</div>
<!-- <div class="with-ite absolute top-0 right-0 flex flex-col hidden">
<span class="block pr-4 mt-3" :class="item.is_star == 2 ? 'text-primary' : 'text-[#999]'" @click.stop="withEvent(item.key)"><el-icon size="18px"><StarFilled /></el-icon></span>
</div> -->
</div>
</div>
</div>
<div class="empty flex items-center justify-center" v-if="!loading&&!applyList.list.length" >
<el-empty :description="t('emptyData')" />
<el-empty :description="t('emptyAppData')" />
</div>
</el-card>
@ -46,24 +43,25 @@
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { getApply } from '@/app/api/apply'
import { setStarAddon } from '@/app/api/auth'
import { img } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers'
import useUserStore from '@/stores/modules/user'
import { useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
var key = storage.get('menuAppStorage')
const userStore = useUserStore()
const router = useRouter()
const applyList = reactive({
list: [],
search: {
title: ""
title: "",
key: key
}
})
let loading = ref(true)
const getApplelist = async () => {
const res = await getApply({title: applyList.search.title})
const res = await getApply({title: applyList.search.title,support_app:applyList.search.key})
applyList.list = res.data.filter(el=>{
return appLink.value[el.key] &&el.type == 'addon'
})
@ -97,12 +95,6 @@ const toLink = (addon: string) => {
userStore.setAppMenuList(data)
router.push({ name: appLink.value[addon] })
}
const withEvent = (key: string) => {
setStarAddon({key: key}).then(() => {
getApplelist()
})
}
</script>
<style lang="scss" scoped>

View File

@ -25,7 +25,14 @@
<div class="font-500 text-[13px] text-[#6D7278] mt-[14px]">{{ item.desc }}</div>
</div>
</template>
<el-empty :description="t('noPlug')" v-if="!detail.appList.length && !loading" class="mx-auto" />
<el-empty v-if="!detail.appList.length && !loading" class="mx-auto overview-empty">
<template #image>
<div class="w-[230px] mx-auto"><img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt=""></div>
</template>
<template #description>
<p class="flex items-center"><span>{{ t('descriptionLeft') }}</span><el-link type="primary" @click="goRouter">{{ t('link') }}</el-link><span>{{ t('descriptionRight') }}</span></p>
</template>
</el-empty>
</div>
</div>
@ -69,12 +76,15 @@ const itemPath = (key: any) => {
}
}
const goRouter = ()=>{
window.open('https://www.niucloud.com/product/')
}
</script>
<style lang="scss" scoped>
.main-container{
background: linear-gradient(180deg, rgba(253,253,253,0.24) 0%, #FAFAFA 100%);
min-height: calc(100vh - 63px);
min-height: calc(100vh - 64px);
}
.overview-top {
background-image: url('@/app/assets/images/index/overview.png');
@ -86,3 +96,8 @@ const itemPath = (key: any) => {
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.18);
}
</style>
<style>
.overview-empty .el-empty__image{
width: auto !important;
}
</style>

View File

@ -44,7 +44,7 @@
</div>
<div class="flex flex-wrap">
<div class="flex flex-wrap" ref="shortcutModel" v-if="edit_menu">
<div v-for="(items, index) in shortcut_menu" :style="{backgroundColor : items.bg_color}" :key="items.id" class="design-field w-[180px] h-[120px] relative mt-[30px] rounded-[5px] mr-[30px] cursor-pointer">
<div v-for="(items, index) in shortcut_menu" :style="{backgroundColor : items.bg_color}" :key="items.id" class="design-field w-[180px] h-[120px] relative mt-[30px] rounded-[5px] mr-[30px] !cursor-move">
<div class="flex items-center h-[88px]" @click="editModel(items)">
<!-- <img class="ml-[24px] w-[40px]" :src="img(items.img)"/> -->
<div class="ml-[24px] w-[40px]">

View File

@ -4,7 +4,7 @@
<div class="flex justify-between items-center mb-4">
<span class="text-[20px]">素材管理</span>
</div>
<el-tabs v-model="type" class="">
<el-tabs v-model="type">
<el-tab-pane :label="t(tab)" v-for="(tab, index) in attachmentType" :name="tab" :key="index">
<attachment scene="attachment" :type="tab" />
</el-tab-pane>
@ -27,7 +27,7 @@
<style lang="scss">
.attachment-container {
.el-card__body {
height: 100%;
height: 96%;
}
.el-tabs {

View File

@ -52,7 +52,7 @@
<el-table :data="formData.table_column" size="large" ref="tableRef" :key="toggleIndex">
<el-table-column align="center" label="操作" width="80">
<template #default>
<i class="iconfont iconfenlei vues-rank cursor-pointer"></i>
<i class="iconfont icontuodong vues-rank cursor-move"></i>
</template>
</el-table-column>
<el-table-column :label="t('columnName')" prop="column_name" min-width="130px" />

View File

@ -1,637 +0,0 @@
<template>
<div class="main-container mb-80" v-loading="loading">
<div class="detail-head !mb-[10px]">
<div class="left" @click="router.push({ path: '/tools/code' })">
<span class="iconfont iconxiangzuojiantou !text-xs"></span>
<span class="ml-[1px]">{{t('returnToPreviousPage')}}</span>
</div>
<span class="adorn">|</span>
<span class="right">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none" shadow="never">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
<el-form-item :label="t('tableName')" prop="table_name">
<el-input v-model="formData.table_name" clearable :placeholder="t('tableNamePlaceholder')" class="input-width" maxlength="64" />
</el-form-item>
<el-form-item :label="t('tableContent')" prop="table_content">
<el-input v-model="formData.table_content" clearable :placeholder="t('tableContentPlaceholder')" class="input-width" maxlength="64" />
</el-form-item>
<el-form-item :label="t('moduleName')">
<el-input v-model="formData.module_name" clearable :placeholder="t('moduleNamePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('className')">
<el-input v-model="formData.class_name" clearable :placeholder="t('classNamePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('editType')">
<el-radio-group v-model="formData.edit_type" :placeholder="t('editTypePlaceholder')">
<el-radio :label="1">{{ t('popup') }}</el-radio>
<el-radio :label="2">{{ t('page') }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</el-card>
<el-row class="flex mt-4" :gutter="15">
<el-col :span="6">
<el-card class="box-card !border-none p-none" shadow="never">
<el-divider content-position="left">{{ t('commentField') }}</el-divider>
<div class="flex flex-row m-0.5 cursor-pointer flex-wrap" :ref="tabsRefs.set">
<div class="border border-dashed border-gray-200 mt-2 text-sm mr-2 pt-1 pb-1 pl-1.5 pr-1.5 dashed-border design-field" v-for="(item, index) in fieldList.common" :key="index" :data-id="index">{{ item.column_comment }}</div>
</div>
<el-divider content-position="left">{{ t('baseField') }}</el-divider>
<div class="flex flex-row m-0.5 cursor-pointer flex-wrap" :ref="tabsRefs.set">
<div class="border border-dashed border-gray-200 mt-2 text-sm mr-2 pt-1 pb-1 pl-1.5 pr-1.5 dashed-border design-field" v-for="(item, index) in fieldList.base" :key="index" :data-id="index">{{ item.column_comment }}</div>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="box-card !border-none p-none" shadow="never">
<div class="flex flex-col" ref="fieldBoxRef">
<div class="flex flex-row m-0.5 cursor-pointer justify-between items-center border border-dashed border-gray-200 dashed-border p-2 pt-1 pb-1" v-for="(item, index) in formData.table_column" :key="index" :data-id="index" @click="onActivateField(index)" :class="index === formData.editFiledIndex ? 'activate' : ''">
<div class="flex flex-row ">
<div class="flex flex-row design-field">
<div class="text-xs text-gray-500 w-28 items-center justify-end flex">{{t('columnName') }}</div>
<el-input class="w-50 m-2" v-model="item.column_name" size="small" :placeholder="t('columnNamePlaceholder')" />
</div>
<div class="flex flex-row design-field">
<div class="text-xs text-gray-500 w-28 items-center justify-end flex">{{ t('columnComment') }}</div>
<el-input class="w-50 m-2" v-model="item.column_comment" size="small" :placeholder="t('columnCommentPlaceholder')" />
</div>
<div class="flex flex-row design-field flex-shrink-0 w-40">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{t('columnType') }}</div>
<div class="w-50 m-2">{{ item.column_type }}</div>
</div>
</div>
<div @click="onDel(index)" class="items-center justify-center flex items-center flex-shrink-0 rounded-full bg-red-400 text-white icon-btn w-7 h-7">
<el-icon>
<Delete />
</el-icon>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="box-card !border-none p-none" shadow="never" v-if="formData.table_column.length">
<el-divider content-position="left">{{ t('fieldAttribute') }}</el-divider>
<div>
<div class="flex flex-row">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('columnName') }}
</div>
<div>
<el-input class="w-50 m-2"
v-model="formData.table_column[formData.editFiledIndex]['column_name']" size="small"
:placeholder="t('columnNamePlaceholder')" />
</div>
</div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('columnComment') }}
</div>
<div>
<el-input class="w-50 m-2" v-model="formData.table_column[formData.editFiledIndex]['column_comment']" size="small" :placeholder="t('columnCommentPlaceholder')" />
</div>
</div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('isPk') }}</div>
<div>
<el-radio-group class="ml-4" v-model="formData.table_column[formData.editFiledIndex]['is_pk']">
<el-radio :label="1" size="large">{{ t('yes') }}</el-radio>
<el-radio :label="0" size="large">{{ t('no') }}</el-radio>
</el-radio-group>
</div>
</div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('viewType') }}
</div>
<div>
<el-select class="m-2" :placeholder="t('selectPlaceholder')" size="small" v-model="formData.table_column[formData.editFiledIndex]['view_type']">
<el-option :label="item.label" :value="item.value" v-for="(item, index) in viewType" :key="index" />
</el-select>
</div>
</div>
</div>
<el-divider content-position="left">{{t('addAndEdit')}}</el-divider>
<div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('isInsert') }}
</div>
<div>
<el-radio-group class="ml-4" v-model="formData.table_column[formData.editFiledIndex]['is_insert']">
<el-radio :label="1" size="large">{{ t('yes') }}</el-radio>
<el-radio :label="0" size="large">{{ t('no') }}</el-radio>
</el-radio-group>
</div>
</div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('isUpdate') }}
</div>
<div>
<el-radio-group class="ml-4" v-model="formData.table_column[formData.editFiledIndex]['is_update']">
<el-radio :label="1" size="large">{{ t('yes') }}</el-radio>
<el-radio :label="0" size="large">{{ t('no') }}</el-radio>
</el-radio-group>
</div>
</div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('isRequired') }}
</div>
<div>
<el-radio-group class="ml-4" v-model="formData.table_column[formData.editFiledIndex]['is_required']">
<el-radio :label="1" size="large">{{ t('yes') }}</el-radio>
<el-radio :label="0" size="large">{{ t('no') }}</el-radio>
</el-radio-group>
</div>
</div>
</div>
<el-divider content-position="left">{{t('listSearch')}}</el-divider>
<div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('isLists') }}</div>
<div>
<el-radio-group class="ml-4" v-model="formData.table_column[formData.editFiledIndex]['is_lists']">
<el-radio :label="1" size="large">{{ t('yes') }}</el-radio>
<el-radio :label="0" size="large">{{ t('no') }}</el-radio>
</el-radio-group>
</div>
</div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('isSearch') }}
</div>
<div>
<el-radio-group class="ml-4" v-model="formData.table_column[formData.editFiledIndex]['is_search']">
<el-radio :label="1" size="large">{{ t('yes') }}</el-radio>
<el-radio :label="0" size="large">{{ t('no') }}</el-radio>
</el-radio-group>
</div>
</div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('isQuery') }}</div>
<div>
<el-radio-group class="ml-4" v-model="formData.table_column[formData.editFiledIndex]['is_query']">
<el-radio :label="1" size="large">{{ t('yes') }}</el-radio>
<el-radio :label="0" size="large">{{ t('no') }}</el-radio>
</el-radio-group>
</div>
</div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('queryType') }}
</div>
<div>
<el-select class="m-2" :placeholder="t('selectPlaceholder')" size="small" v-model="formData.table_column[formData.editFiledIndex]['query_type']">
<el-option :label="item" :value="item" v-for="(item, index) in queryType" :key="index" />
</el-select>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<div class="fixed-footer-wrap">
<div class="fixed-footer">
<el-button type="primary" @click="onSave(formRef)">{{ t('save') }}</el-button>
<el-button @click="back()">{{ t('cancel') }}</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
import { t } from '@/lang'
import { FormInstance, ElNotification } from 'element-plus'
import { getGenerateTableInfo, editGenerateTable } from '@/app/api/tools'
import { useRoute, useRouter } from 'vue-router'
import Sortable, { SortableEvent } from 'sortablejs'
import { useTemplateRefsList } from '@vueuse/core'
import { cloneDeep, range } from 'lodash-es'
import useAppStore from '@/stores/modules/app'
const appStore = useAppStore()
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const id: number = parseInt(route.query.id || 0)
const loading = ref(true)
const tabsRefs = useTemplateRefsList<HTMLElement>()
const fieldBoxRef = ref()
let nameRepeatCount = 1
const fieldData: any = {
column_name: '',
column_comment: '',
column_type: '',
is_required: 1,
is_pk: 1,
is_insert: 1,
is_update: 1,
is_lists: 1,
is_query: 1,
is_search: 1,
query_type: '=',
view_type: 'input'
}
/**
* 表单数据
*/
const initialFormData = {
id: '',
table_name: '',
table_content: '',
module_name: '',
class_name: '',
edit_type: 1,
table_column: [],
editFiledIndex: 0
}
const fieldList: {
common: fieldData[],
base: fieldData[],
} = {
common: [
{
column_name: 'id',
column_comment: t('pk'),
column_type: 'int',
is_required: 1,
is_pk: 1,
is_insert: 0,
is_update: 0,
is_lists: 0,
is_query: 1,
is_search: 0,
query_type: '=',
view_type: 'input'
},
{
column_name: 'status',
column_comment: t('status'),
column_type: 'int',
is_required: 1,
is_pk: 0,
is_insert: 1,
is_update: 1,
is_lists: 1,
is_query: 1,
is_search: 0,
query_type: '=',
view_type: 'radio'
},
{
column_name: 'create_time',
column_comment: t('createTime'),
column_type: 'int',
is_required: 0,
is_pk: 0,
is_insert: 0,
is_update: 0,
is_lists: 1,
is_query: 1,
is_search: 0,
query_type: 'BETWEEN',
view_type: 'datetime'
},
{
column_name: 'update_time',
column_comment: t('updateTime'),
column_type: 'int',
is_required: 0,
is_pk: 0,
is_insert: 0,
is_update: 0,
is_lists: 1,
is_query: 1,
is_search: 0,
query_type: 'BETWEEN',
view_type: 'datetime'
}
],
base: [
{
column_name: 'string',
column_comment: t('string'),
column_type: 'string',
is_required: 1,
is_pk: 0,
is_insert: 1,
is_update: 1,
is_lists: 1,
is_query: 1,
is_search: 1,
query_type: '=',
view_type: 'input'
},
{
column_name: 'image',
column_comment: t('image'),
column_type: 'string',
is_required: 1,
is_pk: 0,
is_insert: 1,
is_update: 1,
is_lists: 1,
is_query: 1,
is_search: 1,
query_type: '=',
view_type: 'imageSelect'
},
{
column_name: 'radio',
column_comment: t('radio'),
column_type: 'int',
is_required: 1,
is_pk: 0,
is_insert: 1,
is_update: 1,
is_lists: 1,
is_query: 1,
is_search: 1,
query_type: '=',
view_type: 'radio'
},
{
column_name: 'checkbox',
column_comment: t('checkbox'),
column_type: 'string',
is_required: 1,
is_pk: 0,
is_insert: 1,
is_update: 1,
is_lists: 1,
is_query: 1,
is_search: 1,
query_type: '=',
view_type: 'checkbox'
},
{
column_name: 'select',
column_comment: t('select'),
column_type: 'string',
is_required: 1,
is_pk: 0,
is_insert: 1,
is_update: 1,
is_lists: 1,
is_query: 1,
is_search: 1,
query_type: '=',
view_type: 'select'
},
{
column_name: 'editor',
column_comment: t('editor'),
column_type: 'string',
is_required: 1,
is_pk: 0,
is_insert: 1,
is_update: 1,
is_lists: 1,
is_query: 1,
is_search: 1,
query_type: '=',
view_type: 'editor'
},
{
column_name: 'datetime',
column_comment: t('dateTime'),
column_type: 'string',
is_required: 1,
is_pk: 0,
is_insert: 1,
is_update: 1,
is_lists: 1,
is_query: 1,
is_search: 1,
query_type: '=',
view_type: 'datetime'
}
]
}
const queryType = ['=', '!=', '>', '>=', '<', '<=', 'LIKE', 'BETWEEN']
const viewType = [
{
label: t('formInput'),
value: 'input'
},
{
label: t('formTextarea'),
value: 'textarea'
},
{
label: t('formSelect'),
value: 'select'
},
{
label: t('formRadio'),
value: 'radio'
},
{
label: t('formCheckbox'),
value: 'checkbox'
},
{
label: t('formDateTime'),
value: 'datetime'
},
{
label: t('formImageSelect'),
value: 'imageSelect'
},
{
label: t('formEditor'),
value: 'editor'
}
]
interface SortableEvt extends SortableEvent {
originalEvent?: DragEvent
}
const onActivateField = (idx: number) => {
formData.editFiledIndex = idx
}
onMounted(() => {
const sortable = Sortable.create(fieldBoxRef.value, {
group: 'design-field',
animation: 200,
onAdd: (evt: SortableEvt) => {
const name = evt.originalEvent?.dataTransfer?.getData('name')
const field = fieldList[name]
if (field && field[evt.oldIndex!]) {
const data = cloneDeep(field[evt.oldIndex!])
if (data.is_pk) {
const is_pk = formData.table_column.find((item: { is_pk: any; }) => {
return item.is_pk
})
if (is_pk) {
ElNotification({
type: 'error',
message: t('pkRepeatTip')
})
return evt.item.remove()
}
formData.table_column.defaultSortField = data.name
formData.table_column.quickSearchField.push(data.name)
}
const isRepeat = getArrayKey(formData.table_column, 'column_name', data.column_name)
if (isRepeat) {
data.column_name = data.column_name + nameRepeatCount
nameRepeatCount++
}
formData.table_column.splice(evt.newIndex!, 0, data)
}
evt.item.remove()
nextTick(() => {
sortable.sort(range(formData.table_column.length).map((value) => value.toString()))
})
},
onEnd: (evt) => {
const temp = formData.table_column[evt.oldIndex!]
formData.table_column.splice(evt.oldIndex!, 1)
formData.table_column.splice(evt.newIndex!, 0, temp)
nextTick(() => {
sortable.sort(range(formData.table_column.length).map((value) => value.toString()))
})
}
})
tabsRefs.value.forEach((item, index) => {
Sortable.create(item, {
sort: false,
group: {
name: 'design-field',
pull: 'clone',
put: false
},
animation: 200,
setData: (dataTransfer) => {
dataTransfer.setData('name', Object.keys(fieldList)[index])
}
})
})
})
const getArrayKey = (arr: any, pk: string, value: string): any => {
for (const key in arr) {
if (arr[key][pk] == value) {
return key
}
}
return false
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const setFormData = async (id: number = 0) => {
Object.assign(formData, initialFormData)
const data = await (await getGenerateTableInfo(id)).data
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
loading.value = false
}
if (id) setFormData(id)
const formRef = ref<FormInstance>()
const validataVarName = (val: string) => {
return /^([a-zA-Z_$])([a-zA-Z0-9_$])*$/.test(val)
}
//
const formRules = computed(() => {
return {
table_name: [
{ required: true, message: t('tableNamePlaceholder'), trigger: 'blur' },
{
validator: (rule: any, value: string, callback: any) => {
if (!validataVarName(value)) {
callback(new Error(t('tableNameValidata')))
}
callback()
},
trigger: 'blur'
}
],
table_content: [
{ required: true, message: t('tableContentPlaceholder'), trigger: 'blur' }
]
}
})
const onSave = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
let msg = ''
formData.table_column.find((item) => {
if (!validataVarName(item.column_name)) {
msg = item.column_name + ' ' + t('fieldNameValidata')
return true
}
})
if (msg) {
ElNotification({
type: 'error',
message: msg
})
return
}
loading.value = true
const data = cloneDeep(formData)
data.table_column = JSON.stringify(data.table_column)
editGenerateTable(data).then((res: any) => {
loading.value = false
}).catch(() => {
loading.value = false
})
}
const onDel = (index: number) => {
if (!formData.table_column[index]) return
if (formData.editFiledIndex == index) formData.editFiledIndex = 0
formData.table_column.splice(index, 1)
}
const back = () => {
router.push({ path: '/tools/code' })
}
</script>
<style lang="scss" scoped>
.dashed-border {
border-color: var(--el-border-color-light);
&:hover {
border-color: var(--el-color-primary);
}
&.activate {
border-color: var(--el-color-primary);
}
}
.icon-btn {
background-color: var(--el-color-primary);
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div class="main-container attachment-container" v-loading="loadingArr.server_load">
<el-card class="box-card !border-none" shadow="never" v-if="systemService">
<div class="main-container attachment-container min-h-[80vh]" v-loading="loadingArr.server_load">
<el-card class="box-card !border-none" shadow="never" v-if="systemService && !loadingArr.server_load">
<div class="flex justify-between items-center">
<span class="text-[20px]">{{ pageName }}</span>
</div>

View File

@ -6,7 +6,7 @@
<span class="text-sm truncate w-[190px]">{{t('refreshMenu')}}</span>
<span class="text-xs text-gray-400 mt-1 truncate w-[190px]" :title="t('refreshMenuDesc')">{{t('refreshMenuDesc')}}</span>
</div>
<span class="plug-item-operate" @click="refreshMenu()">{{t('refresh')}}</span>
<span class="plug-item-operate" @click="refreshMenu()">{{t('resetting')}}</span>
</div>
<div class="flex items-center bg-[#F7F8FA] p-3 w-[295px] relative plug-item mr-4 mb-4 cursor-pointer">
@ -23,39 +23,37 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { t } from '@/lang'
import { clearSchemaCache, menuRefresh } from '@/app/api/sys'
import { clearSchemaCache, menuRefresh } from '@/app/api/sys'
let loading = ref<Boolean>(false);
const loading = ref<Boolean>(false)
//
const schemaCache = () => {
loading.value = true;
loading.value = true
clearSchemaCache({}).then(res => {
loading.value = false;
loading.value = false
setTimeout(() => {
//
//
location.reload()
}, 500)
}, 500)
}).catch(() => {
loading.value = false;
loading.value = false
})
}
//
const refreshMenu = ()=> {
loading.value = true;
menuRefresh({}).then(res => {
loading.value = false;
const refreshMenu = () => {
loading.value = true
menuRefresh({}).then(res => {
loading.value = false
setTimeout(() => {
//
//
location.reload()
}, 500)
}).catch(() => {
loading.value = false;
})
}, 500)
}).catch(() => {
loading.value = false
})
}
</script>
<style lang="scss" scoped>

View File

@ -10,8 +10,8 @@
{{ t('selectPlaceholder') }}
</div>
<div class="group-item px-[10px] text-xs rounded cursor-pointer flex" v-for="(item, index) in attachmentCategory.data" :key="index" :class="{ active: attachmentParam.cate_id == item.id }">
<div class="flex-1 leading-none truncate py-[10px]" @click="attachmentParam.cate_id = item.id">{{item.name }}</div>
<div class="leading-none operate py-[10px]" v-if="scene == 'attachment' && prop.type !='icon' ">
<div class="flex-1 leading-none truncate py-[10px] h-[34px]" @click="attachmentParam.cate_id = item.id">{{item.name }}</div>
<div class="leading-none operate py-[10px] h-[34px]" v-if="scene == 'attachment' && prop.type !='icon' ">
<!-- 图片操作 -->
<el-dropdown :hide-on-click="false" v-if="scene == 'attachment'">
<icon name="element-MoreFilled" class="cursor-pointer ml-[10px]" size="14px"/>

View File

@ -75,6 +75,7 @@
"layoutSetting": "主题设置",
"darkMode": "黑暗模式",
"sidebarMode": "主题风格",
"sidebarStyle": "主题样式",
"themeColor": "主题颜色",
"detectionLoginOperation": "确定",
"detectionLoginContent": "已检测到有其他账号登录,需要刷新后才能继续操作。",
@ -125,5 +126,6 @@
"generateEmail":"请输入正确的邮箱号",
"generateMax":"超过最多输入字符数",
"generateMin":"少于最少输入字符数",
"generateBetween":"请输入正确的字符信息"
"generateBetween":"请输入正确的字符信息",
"emptyAppData":"暂无应用"
}

View File

@ -0,0 +1,33 @@
<template>
<div :class="[{'group-hover:flex': props.isShowHover},'hidden fixed left-0 top-[65px] z-[5555] bg-[#fff] w-[640px] px-[28px] py-[20px] flex-wrap box-border shadow-lg ']">
<div v-for="(item, index) in data" :key="index" @click="toLink(item, props.hoverType)" class="flex items-center cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] whitespace-nowrap py-[10px] px-[15px] box-border w-[165px]">
<img :src="img(item.icon)" class="w-[44px] h-[44px] rounded-full mr-[5px]" alt="" :title="item.title">
<span>{{ item.title }}</span>
</div>
<div v-if="!data.length" class="flex-1 flex flex-col justify-center items-center pb-[30px]">
<div class="w-[130px]"><img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt=""></div>
<div class="text-[14px] text-[#909399]">暂无安装任何应用或插件马上去<a href="https://www.niucloud.com/product/" target="_blank" class="text-[var(--el-color-primary)]">官方应用市场</a>逛逛</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed } from 'vue'
import { img } from '@/utils/common'
const props = defineProps(['isShowHover','data','hoverType'])
console.log();
let data = ref([]);
if(props.data){
props.data.forEach((item,index) => {
if(item.type == "app"){
data.value.push(item);
}
});
}
const emit = defineEmits(['child-click'])
const toLink = (data,type)=>{
emit('child-click',data,type)
}
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,543 @@
<template>
<div :class="['flex', { 'two-type': sidebar == 'twoType' }, { 'three-type': sidebar == 'threeType' }]" v-if="isLoad">
<div class="w-[124px] overflow-hidden">
<!-- , { 'bright': !dark } -->
<el-aside :class="['h-screen layout-aside w-[124px] pb-[30px] px-[8px] bg-[#202033] ease-in duration-200']">
<div class="h-full flex flex-col relative">
<div class="group flex items-center justify-center h-[64px] cursor-pointer" v-if="!globalAppKey" @mouseenter="threefloatMenuHover">
<span class="iconfont iconyun1 !text-[32px] !w-auto text-[#fff]"></span>
<app-menu :isShowHover="threefloatMenu" :data="applyList" @child-click="toLink" hoverType='threefloatMenu'></app-menu>
</div>
<template v-for="(item, index) in menus" :key="index">
<template v-if="globalAppKey == item.meta.app && item.meta.parentTitle">
<div class="group flex items-center justify-center h-[64px] cursor-pointer" @mouseenter="threefloatMenuHover">
<img v-if="item.meta.parentIcon" :src="img(item.meta.parentIcon)" class="w-[40px] h-[40px]" alt="">
<div class="flex items-center justify-center w-[30px] h-[30px]" v-else>
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px" />
</div>
<app-menu :isShowHover="threefloatMenu" :data="applyList" @child-click="toLink" hoverType='threefloatMenu'></app-menu>
</div>
<div v-for="(appItem, appIndex) in item.children" :key="appIndex" @click="toLink(appItem)"
:class="['rounded-sm flex items-center px-[8px] mb-[4px] h-[40px] cursor-pointer text-[#b9b9bf] hover:bg-[var(--el-color-primary)] hover:!text-[#fff] menu-item hover:text-color whitespace-nowrap', { 'bg-[var(--el-color-primary)] !text-[#fff] menu-item-active ': localMenuKey == appItem.meta.key}]">
<icon v-if="appItem.meta.icon" :name="appItem.meta.icon" class="!w-auto" size="20px" :title="appItem.meta.title" />
<span class="text-[14px] ml-[8px]">{{appItem.meta.shortTitle}}</span>
</div>
</template>
</template>
<template v-for="(item, index) in menus" :key="index">
<div v-if="!item.meta.app && (item.meta.attr == 'common' || item.meta.attr == 'system')" @click="toLink(item)"
:class="['rounded-sm flex items-center px-[8px] mb-[4px] h-[40px] cursor-pointer text-[#b9b9bf] hover:bg-[var(--el-color-primary)] hover:!text-[#fff] menu-item hover:text-color whitespace-nowrap', { 'bg-[var(--el-color-primary)] !text-[#fff] menu-item-active ': (item.path == currentRoute.path || (currentRoute.path == '/admin' && item.path == '/index') || (currentRoute.meta.app && item.path == '/index')) }]">
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="20px" :title="item.meta.title" />
<span class="text-[14px] ml-[8px]">{{item.meta.shortTitle}}</span>
</div>
</template>
</div>
</el-aside>
</div>
<template v-for="(item, index) in menus" :key="index">
<div v-if="isTwoMenuFn(item)" class="w-[155px] box-border border-r-[1px] border-solid second-menu">
<div class="group flex flex-col items-center justify-center h-[64px] border-b-[1px] border-solid second-head cursor-pointer relative" @mouseenter="twofloatMenuHover">
{{ item.meta.title }}
</div>
<el-scrollbar class="overflow-y-auto menus-wrap">
<el-menu class="apply-menu !border-0" :router="true" unique-opened="true" :default-active="String(route.name)">
<template v-for="(twoMenu, twoIndex) in item.children">
<el-sub-menu :index="String(twoMenu.meta.title)" v-if="twoMenu.children && twoMenu.meta.show">
<template #title>
<span class="text-[14px]">{{ twoMenu.meta.title }}</span>
</template>
<template v-for="(threeMenu, threeIndex) in twoMenu.children" :key="threeIndex">
<!-- 三级菜单 -->
<el-sub-menu :index="String(threeMenu.meta.title)" class="three-menu"
v-if="threeMenu.children && threeMenu.meta.show">
<template #title>
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
</template>
<template v-for="(fourMenu, fourIndex) in threeMenu.children" :key="fourIndex">
<el-sub-menu :index="String(fourMenu.meta.title)"
v-if="fourMenu.children && fourMenu.meta.show">
<template #title>
<span class="text-[14px]">{{ fourMenu.meta.title }}</span>
</template>
<template v-for="(fiveMenu, fiveIndex) in fourMenu.children"
:key="fiveIndex">
<el-menu-item v-if="fiveMenu.meta.show" class="!h-[52px] !pl-[55px]"
:index="String(fiveMenu.name)" @click="toLink(fiveMenu)">
<template #title>
<span class="text-[14px]">{{ fiveMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else-if="fourMenu.meta.show" class="!h-[52px] !pl-[35px]"
:index="String(fourMenu.name)" @click="toLink(fourMenu)">
<template #title>
<span class="text-[14px]">{{ fourMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<!-- 二级菜单 -->
<el-menu-item v-else-if="threeMenu.meta.show" class="!h-[52px] !pl-[42px]"
:index="String(threeMenu.name)" @click="toLink(threeMenu)">
<template #title>
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else-if="twoMenu.meta.show && twoMenu.meta.key != 'official_market'"
class="!pl-[25px] text-[#333]" :index="String(twoMenu.name)" @click="toLink(twoMenu)">
<template #title>
<span class="text-[14px]">{{ twoMenu.meta.title }}</span>
</template>
</el-menu-item>
<div class="flex items-center !px-[25px] h-[56px] cursor-pointer text-[#333] el-menu-item"
v-else-if="twoMenu.meta.show && twoMenu.meta.key == 'official_market'"
@click="toLink(twoMenu)">
<span class="text-[14px]">{{ twoMenu.meta.title }}</span>
</div>
</template>
<el-menu-item v-if="!item.children" class="!pl-[25px] text-[#333]" :index="String(item.name)" @click="toLink(item)">
<template #title>
<span class="text-[14px]">{{ item.meta.title }}</span>
</template>
</el-menu-item>
<!-- 插件菜单 -->
<template v-if="plugMenuType && localMenuKey == 'app_center'">
<template v-for="(twoMenu, twoIndex) in menus">
<el-sub-menu :index="String(twoMenu.meta.title)" v-if="twoMenu.meta.app && twoMenu.meta.app == plugMenuType && twoMenu.children">
<template #title>
<span class="text-[14px]">{{ twoMenu.meta.title }}</span>
</template>
<template v-for="(threeMenu, threeIndex) in twoMenu.children" :key="threeIndex">
<!-- 三级菜单 -->
<el-sub-menu :index="String(threeMenu.meta.title)"
v-if="threeMenu.children && threeMenu.meta.show">
<template #title>
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
</template>
<template v-for="(fourMenu, fourIndex) in threeMenu.children"
:key="fourIndex">
<el-menu-item v-if="fourMenu.meta.show"
class="!h-[52px] !pl-[55px]"
:index="String(fourMenu.name)"
@click="toLink(fourMenu)">
<template #title>
<span class="text-[14px]">{{ fourMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else-if="threeMenu.meta.show"
class="!h-[52px] !pl-[35px]"
:index="String(threeMenu.name)" @click="toLink(threeMenu)">
<template #title>
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else-if="twoMenu.meta.app && twoMenu.meta.app == plugMenuType"
class="!pl-[25px] text-[#333]" :index="String(twoMenu.name)"
@click="toLink(twoMenu)">
<template #title>
<span class="text-[14px]">{{ twoMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</template>
</el-menu>
</el-scrollbar>
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import side from './side.vue'
import appMenu from './components/app-menu.vue'
import { img } from '@/utils/common'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import storage from '@/utils/storage'
import { CollectionTag } from '@element-plus/icons-vue'
import { findFirstValidRoute } from '@/router/routers'
import { getAddonByKey } from '@/app/api/addon'
import { getApply } from '@/app/api/apply'
const userStore = useUserStore()
const systemStore = useSystemStore()
const route = useRoute()
const router = useRouter()
const globalAppKey = ref('') //
const localMenuKey = ref('') //
globalAppKey.value = storage.get('menuAppStorage')
localMenuKey.value = storage.get('menuAppStorage')
const isLoad = ref(false);
// start
const applyList = ref([])
const applyTypeList = ref([])
const otherTypeList = ref([]) // \\
const getApplelist = async () => {
const res = await getApply()
applyList.value = applyList.value.concat(res.data)
applyList.value.forEach((item, index) => {
if (item.type == 'app') { applyTypeList.value.push(item.key) }
if (item.type == 'addon') { otherTypeList.value.push(item.key) }
})
isLoad.value = true;
}
getApplelist()
// end
//
const appLink = ref({})
const menus = computed(() => {
const menus = []
userStore.routers.forEach((item, index) => {
if (item.children && item.children.length) {
item.name = findFirstValidRoute(item.children)
appLink.value[item.meta.app] = findFirstValidRoute(item.children)
menus.push(item)
} else {
appLink.value[item.meta.app] = item.name
menus.push(item)
}
})
if(applyList.value && applyList.value.length){
applyList.value.forEach((item,index) =>{
menus.forEach((menuItem,menuIndex)=>{
if(item.key == menuItem.meta.key){
menuItem.meta.parentTitle = item.title;
menuItem.meta.parentIcon = item.icon;
}
})
})
}
//
if(!applyList.value.length && !globalAppKey.value){
storage.set({ key: 'menuAppStorage', data: '' })
globalAppKey.value = ""
}
if(applyList.value.length && !globalAppKey.value){
storage.set({ key: 'menuAppStorage', data: applyTypeList.value[0] })
globalAppKey.value = applyTypeList.value[0]
}
menus.forEach((item,index)=>{
if(globalAppKey.value && item.meta.app == globalAppKey.value){
item.children.forEach((childItem,childIndex) => {
menus.push(childItem);
if(childItem.children){
let parentKey = childItem.meta.key;
childItem.children.forEach((grandItem,grandIndex) => {
grandItem.parentKey = parentKey;
});
}
});
}
})
return menus
})
const dark = computed(() => {
return systemStore.dark
})
//
watch(() =>userStore.globalAppKey, (val,old) => {
getApplelist();
},{deep: true})
//
let currMetaAppType = ''
const plugMenuType = ref('') //
const currentRoute = ref('') //
watch(route, () => {
plugMenuType.value = storage.get('plugMenuTypeStorage')
const data = route.matched[1]
currentRoute.value = route.matched[1]
if(route.meta.app && route.meta.app == globalAppKey.value){
menus.value.forEach((item,index)=>{
if(item.children && item.name != route.name){
item.children.forEach((childItem,childIndex) => {
if(childItem.name == route.name) localMenuKey.value = childItem.parentKey;
});
}else if(item.name == route.name){
localMenuKey.value = item.name;
}
})
}else{
localMenuKey.value = data.meta.key
}
if(otherTypeList.value.includes(localMenuKey.value) && plugMenuType.value){
localMenuKey.value = "app_center"
}
systemStore.$patch(state => {
state.menuDrawer = false
})
}, { immediate: true })
let threefloatMenu = ref(true);
const threefloatMenuHover = ()=>{
threefloatMenu.value = true;
}
const toLink = (data, type) => {
if(type == 'threefloatMenu') threefloatMenu.value = false;
if (!data.meta && data.type == 'app' || data.meta.key != 'official_market') {
let name = data.name;
if(data.type == 'app'){
globalAppKey.value = data.key
localMenuKey.value = data.key
storage.set({ key: 'menuAppStorage', data: data.key })
storage.set({ key: 'plugMenuTypeStorage', data: '' })
const appMenuList = userStore.appMenuList
appMenuList.push(data.key)
userStore.setAppMenuList(appMenuList)
name = appLink.value[data.key];
}else if(data.meta.app){
name = getLinkName(data);
}
router.push({ name: name })
} else {
window.open('https://www.niucloud.com/product/', '_blank')
}
}
const getLinkName = (res)=>{
if(res.children && res.children.length){
return getLinkName(res.children[0])
}
return res.name
}
//
const sidebar = computed(() => {
return systemStore.sidebar
})
//
const isTwoMenuFn = (item) => {
let bool = (otherTypeList.value.includes(localMenuKey.value) && globalAppKey.value == item.meta.app)
|| (!otherTypeList.value.includes(localMenuKey.value) && (item.meta.key == localMenuKey.value || item.meta.app == localMenuKey.value) && !item.meta.app)
|| (item.meta.app && !otherTypeList.value.includes(localMenuKey.value) && item.meta.key == localMenuKey.value && localMenuKey.value.indexOf('index') == -1)
return bool;
}
</script>
<style lang="scss">
.layout-aside {
&.bright {
background-color: #F5F7F9;
li {
background-color: #F5F7F9;
&.is-active:not(.is-opened) {
position: relative;
color: #333;
background-color: #fff;
&::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 2px;
background-color: var(--el-menu-active-color);
}
}
}
}
.menu-item:hover {
color: var(--el-color-primary);
}
}
.second-menu .el-sub-menu .el-sub-menu__title {
padding-left: 25px !important;
padding-right: 25px !important;
.el-icon.el-sub-menu__icon-arrow {
right: 25px;
font-weight: bolder;
font-size: 14px;
}
}
.three-menu.el-sub-menu .el-sub-menu__title {
padding-left: 45px !important;
}
.text-color {
color: var(--el-color-primary);
}
.aside-drawer {
.el-drawer__body {
padding: 0px !important;
}
}
.cut-style {
color: #6d7278;
}
.cut-style.qx {
transform: translateX(-50%);
}
//
.two-type {
.logo-wrap {
.logo span {
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #F5F7F9;
display: flex;
justify-content: center;
align-items: center;
color: #273de3;
font-size: 25px !important;
}
}
.layout-aside {
background-color: #2b303b;
.menu-item {
color: #fff;
&.menu-item-active,
&:hover {
background-color: var(--el-color-primary);
color: #fff;
}
}
}
.second-menu {
background-color: #F5F7F9;
.el-menu {
background-color: transparent;
.el-menu-item:hover {
background-color: #fff;
color: var(--el-color-primary);
}
.el-menu-item.is-active {
background-color: #fff;
}
}
.el-sub-menu__title:hover {
background-color: #fff;
color: var(--el-color-primary);
}
}
.cut-style {
color: #FFF;
}
}
//
.three-type {
.logo-wrap {
.logo span {
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #F5F7F9;
display: flex;
justify-content: center;
align-items: center;
color: #2E7BFD;
font-size: 25px !important;
}
}
.layout-aside {
background-color: #2b303b;
.menu-item {
color: #fff;
&.menu-item-active,
&:hover {
background-color: #303848;
color: var(--el-color-primary);
}
}
}
.second-menu {
background-color: #303848;
.second-head {
color: #fff;
border-color: #364059;
}
.el-menu {
background-color: transparent;
.el-menu-item {
color: #fff;
}
.el-menu-item:hover,
.el-menu-item.is-active {
background-color: var(--el-color-primary);
}
}
.el-sub-menu__title:hover {
background-color: var(--el-color-primary);
;
}
.el-sub-menu__title {
color: #fff;
}
}
.cut-style {
color: #FFF;
}
}
.menus-wrap {
height: calc(100vh - 64px);
}
</style>

View File

@ -3,10 +3,6 @@
<el-row class="w-100 h-full w-full">
<el-col :span="12">
<div class="left-panel h-full flex items-center">
<!-- 刷新当前页 -->
<div v-if="floatMenuStyle" class="navbar-item flex items-center h-full cursor-pointer" @click="cutMenuStyleFn(false)">
<a href="javascript:;" title="切换" class="iconfont iconqiehuan2"></a>
</div>
<!-- 刷新当前页 -->
<div class="navbar-item flex items-center h-full cursor-pointer" @click="refreshRouter">
<icon name="element-Refresh" />
@ -176,15 +172,6 @@ const submitIndex = () => {
router.go(0)
})
}
//
let floatMenuStyle = ref(false);
floatMenuStyle.value = storage.get('floatMenuStyle') || false;
const cutMenuStyleFn = (bool)=>{
floatMenuStyle.value = bool;
storage.set({ key: 'floatMenuStyle', data: bool });
location.reload();
}
</script>
<style lang="scss" scoped>

View File

@ -5,6 +5,16 @@
</div>
<el-drawer v-model="drawer" :title="t('layout.layoutSetting')" size="300px">
<el-scrollbar>
<div class="setting-item flex items-baseline justify-between mb-[10px]">
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarStyle') }}</div>
<div class="">
<el-radio-group v-model="sidebarStyle" class="ml-4">
<el-radio label="oneType" size="large">样式一</el-radio>
<el-radio label="twoType" size="large">样式二</el-radio>
<el-radio label="threeType" size="large">样式三</el-radio>
</el-radio-group>
</div>
</div>
<!-- 风格切换 -->
<div class="setting-item flex items-baseline justify-between mb-[10px]">
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarMode') }}</div>
@ -75,6 +85,16 @@ const sidebar = computed({
}
})
const sidebarStyle = computed({
get() {
return systemStore.sidebarStyle
},
set(val) {
systemStore.setTheme('sidebarStyle', val)
}
})
const theme = computed({
get() {
return systemStore.theme

View File

@ -3,14 +3,24 @@
</template>
<script lang="ts" setup>
import { ref, markRaw, defineAsyncComponent } from 'vue'
import { ref,computed, watch, markRaw, defineAsyncComponent } from 'vue'
import useSystemStore from '@/stores/modules/system'
const systemStore = useSystemStore()
const modules = import.meta.glob('./*/index.vue')
const siteLayout = 'default'
const layout = ref<any>(null)
//
let themeStyle = {
'oneType': 'standard',
'twoType': 'profession',
'threeType': 'business'
}
watch(() =>systemStore.sidebarStyle, () => {location.reload();})
const adminLayout = themeStyle[systemStore.sidebarStyle]
const layout = ref<any>(null)
Object.keys(modules).forEach(key => {
key.indexOf(siteLayout) !== -1 && (layout.value = markRaw(defineAsyncComponent(modules[key])))
key.indexOf(adminLayout) !== -1 && (layout.value = markRaw(defineAsyncComponent(modules[key])))
})
</script>

View File

@ -0,0 +1,33 @@
<template>
<div :class="[{'group-hover:flex': props.isShowHover},'hidden fixed left-0 top-[65px] z-[5555] bg-[#fff] w-[640px] px-[28px] py-[20px] flex-wrap box-border shadow-lg ']">
<div v-for="(item, index) in data" :key="index" @click="toLink(item, props.hoverType)" class="flex items-center cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] whitespace-nowrap py-[10px] px-[15px] box-border w-[165px]">
<img :src="img(item.icon)" class="w-[44px] h-[44px] rounded-full mr-[5px]" alt="" :title="item.title">
<span>{{ item.title }}</span>
</div>
<div v-if="!data.length" class="flex-1 flex flex-col justify-center items-center pb-[30px]">
<div class="w-[130px]"><img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt=""></div>
<div class="text-[14px] text-[#909399]">暂无安装任何应用或插件马上去<a href="https://www.niucloud.com/product/" target="_blank" class="text-[var(--el-color-primary)]">官方应用市场</a>逛逛</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed } from 'vue'
import { img } from '@/utils/common'
const props = defineProps(['isShowHover','data','hoverType'])
console.log();
let data = ref([]);
if(props.data){
props.data.forEach((item,index) => {
if(item.type == "app"){
data.value.push(item);
}
});
}
const emit = defineEmits(['child-click'])
const toLink = (data,type)=>{
emit('child-click',data,type)
}
</script>
<style lang="scss"></style>

View File

@ -1,86 +1,24 @@
<template>
<div :class="['flex', { 'two-type': sidebar == 'twoType' }, { 'three-type': sidebar == 'threeType' }]" v-if="isLoad">
<div class="w-[65px] overflow-hidden" v-if="!floatMenuStyle">
<el-aside
:class="['h-screen layout-aside w-[65px] pb-[30px] bg-[#F7F8FA] ease-in duration-200', { 'bright': !dark }]">
<!-- 一级菜单 -->
<div class="h-full flex flex-col relative">
<div class=" flex items-center justify-center h-[64px] cursor-pointer cut-style"
@click="floatActive = !floatActive">
<span class="iconfont icontuodong !text-[30px] "></span>
</div>
<div @click="homeClick"
class="flex items-center justify-center h-[56px] cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] menu-item hover:text-color whitespace-nowrap">
<span class="iconfont iconshouye !text-[24px] "></span>
</div>
<div class="mb-[20px]">
<template v-for="(item, index) in menus" :key="index">
<div v-if="item.meta.app == '' && item.meta.attr == 'common'" @click="toLink(item)"
:class="['flex items-center justify-center h-[56px] cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] menu-item hover:text-color whitespace-nowrap', { 'bg-[#f1f2f6] text-color menu-item-active ': (item.path == currentRoute.path || (currentRoute.path == '/admin' && item.path == '/index') || (currentRoute.meta.app && item.path == '/index')) }]">
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px"
:title="item.meta.title" />
</div>
</template>
</div>
<a href="javascript:;"
class="absolute -bottom-[20px] left-[50%] cut-style iconfont icongengduo !text-[30px] qx"
@click="cutMenuStyleFn" title="切换"></a>
</div>
</el-aside>
</div>
<!-- 浮动样式的应用菜单 -->
<div class="one-menus-float-style">
<el-dialog v-model="floatActive" :show-close="false">
<div v-if="!floatMenuStyle && applyList.filter(el => { return el.type === 'app' }).length"
class="flex bg-[#fff] w-[640px] px-[28px] py-[20px] flex-wrap box-border shadow-lg one-menus-wrap">
<template v-for="(item, index) in applyList" :key="index">
<div v-if="item.type == 'app'" @click="toLink(item)" class="flex items-center cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] whitespace-nowrap py-[10px] px-[15px] box-border w-[165px]">
<img :src="img(item.icon)" class="w-[44px] h-[44px] rounded-full mr-[5px]" alt="" :title="item.title">
<span>{{ item.title }}</span>
</div>
</template>
<div v-if="!applyList.length" class="flex-1 text-center">暂无安装应用</div>
</div>
</el-dialog>
</div>
<!-- 二级菜单 -->
<template v-for="(item, index) in menus" :key="index">
<div v-if="isTwoMenuFn(item)" :class="[(floatMenuStyle ? 'w-[210px]' : 'w-[189px]'),'box-border border-r-[1px] border-solid second-menu']">
<div
class="group flex flex-col items-center justify-center h-[64px] border-b-[1px] border-solid second-head cursor-pointer relative">
<div v-if="isTwoMenuFn(item)" class="w-[210px] box-border border-r-[1px] border-solid second-menu">
<div class="group flex flex-col items-center justify-center h-[64px] border-b-[1px] border-solid second-head cursor-pointer relative" @mouseenter="twofloatMenuHover">
<div class="flex items-center">
<template v-if="floatMenuStyle">
<img v-if="item.meta.parentIcon" :src="img(item.meta.parentIcon)" class="w-[40px] h-[40px] mr-[8px]" alt="">
<div class="flex items-center justify-center w-[30px] h-[30px]" v-else>
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px" />
</div>
</template>
<img v-if="item.meta.parentIcon" :src="img(item.meta.parentIcon)" class="w-[40px] h-[40px] mr-[8px]" alt="">
<div class="flex items-center justify-center w-[30px] h-[30px]" v-else>
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px" />
</div>
<span>{{ item.meta.app ? item.meta.parentTitle : item.meta.title }}</span>
</div>
<!-- 浮动样式的应用菜单 -->
<div v-if="floatMenuStyle && applyList.filter(el => { return el.type === 'app' }).length"
class="hidden group-hover:flex absolute bg-[#fff] w-[640px] px-[28px] py-[20px] flex-wrap left-0 top-[65px] z-[5555] box-border shadow-lg">
<template v-for="(item, index) in applyList" :key="index">
<div v-if="item.type == 'app'" @click="toLink(item)"
class="flex items-center cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] whitespace-nowrap py-[10px] px-[15px] w-[165px] box-border">
<img :src="img(item.icon)" class="w-[44px] h-[44px] rounded-full mr-[5px]" alt=""
:title="item.title">
<span>{{ item.title }}</span>
</div>
</template>
<div v-if="!applyList.length" class="flex-1 text-center">暂无安装应用</div>
</div>
<app-menu :isShowHover="twofloatMenu" :data="applyList" @child-click="toLink" hoverType='twofloatMenu'></app-menu>
</div>
<el-scrollbar class="overflow-y-auto menus-wrap">
<el-menu class="apply-menu !border-0" :router="true" unique-opened="true"
:default-active="String(route.name)">
<template v-if="!floatMenuStyle || floatMenuStyle && applyTypeList.length">
<el-menu class="apply-menu !border-0" :router="true" unique-opened="true" :default-active="String(route.name)">
<template v-if="applyTypeList.length">
<template v-for="(twoMenu, twoIndex) in item.children">
<el-sub-menu :index="String(twoMenu.meta.title)"
v-if="twoMenu.children && twoMenu.meta.show">
<el-sub-menu :index="String(twoMenu.meta.title)" v-if="twoMenu.children && twoMenu.meta.show">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
@ -94,10 +32,8 @@
v-if="threeMenu.children && threeMenu.meta.show">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="threeMenu.meta.icon && floatMenuStyle"
<icon v-if="threeMenu.meta.icon"
:name="threeMenu.meta.icon" class="absolute !w-auto" size="18px" />
<span v-if="!floatMenuStyle"
class="iconfont icondian !text-[25px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title }}</span>
</template>
@ -164,7 +100,7 @@
<!-- 系统菜单 -->
<template
v-if="applyTypeList.includes(localMenuKey) || otherTypeList.includes(localMenuKey) || floatMenuStyle">
v-if="applyTypeList.includes(localMenuKey) || otherTypeList.includes(localMenuKey)">
<div class="!border-0 !border-t-[1px] border-solid mx-[25px] bg-[#f7f7f7] my-[5px]"></div>
<template v-for="(twoMenu, twoIndex) in menus">
<el-sub-menu :index="String(twoMenu.meta.title)"
@ -180,9 +116,8 @@
<el-sub-menu :index="String(threeMenu.meta.title)"
v-if="threeMenu.meta.app && threeMenu.children">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="threeMenu.meta.icon" :name="threeMenu.meta.icon"
class="absolute !w-auto" size="18px" />
<div class="w-[16px] h-[16px] relative flex items-center justify-center">
<span class="iconfont iconyuanquan_huaban1 !text-[20px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title }}</span>
</template>
@ -229,29 +164,25 @@
</template>
<!-- 插件菜单 -->
<template v-if="otherTypeList.includes(localMenuKey) && plugMenuType">
<template v-if="otherTypeList.includes(localMenuKey) && twoMenu.meta.key == 'app_center' && plugMenuType">
<template v-for="(twoMenu, twoIndex) in menus">
<el-sub-menu :index="String(twoMenu.meta.title)"
v-if="twoMenu.meta.app && twoMenu.meta.app == plugMenuType && twoMenu.children">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
class="absolute !w-auto" size="18px" />
<div class="w-[16px] h-[16px] relative flex items-center justify-center">
<span class="iconfont iconyuanquan_huaban1 !text-[20px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
<template v-for="(threeMenu, threeIndex) in twoMenu.children"
:key="threeIndex">
<template v-for="(threeMenu, threeIndex) in twoMenu.children" :key="threeIndex">
<!-- 三级菜单 -->
<el-sub-menu :index="String(threeMenu.meta.title)"
v-if="threeMenu.children && threeMenu.meta.show">
<template #title>
<div
class="w-[16px] h-[16px] relative flex items-center justify-center">
<div class="w-[16px] h-[16px] relative flex items-center justify-center">
<span class="iconfont icondian !text-[25px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title
}}</span>
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title }}</span>
</template>
<template v-for="(fourMenu, fourIndex) in threeMenu.children"
:key="fourIndex">
@ -307,85 +238,82 @@
</template>
</template>
<!-- 浮动样式 -->
<template v-if="floatMenuStyle">
<div
:class="['!border-0 border-solid mx-[25px] bg-[#f7f7f7] my-[5px]', floatMenuStyle && !applyTypeList.length ? '' : '!border-t-[1px]']">
</div>
<template v-for="(twoMenu, twoIndex) in menus">
<el-sub-menu :index="String(twoMenu.meta.title)"
v-if="twoMenu.meta.attr == 'common' && !twoMenu.meta.app && twoMenu.children">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
class="absolute !w-auto" size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
<template v-for="(threeMenu, threeIndex) in twoMenu.children" :key="threeIndex">
<el-sub-menu :index="String(threeMenu.meta.title)"
v-if="threeMenu.children && threeMenu.meta.show">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="threeMenu.meta.icon" :name="threeMenu.meta.icon"
class="absolute !w-auto" size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title }}</span>
</template>
<template v-for="(fourMenu, fourIndex) in threeMenu.children" :key="fourIndex">
<el-sub-menu :index="String(fourMenu.meta.title)"
v-if="fourMenu.children && fourMenu.meta.show">
<template #title>
<div
class="w-[16px] h-[16px] relative flex items-center justify-center">
<span class="iconfont icondian !text-[25px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ fourMenu.meta.title }}</span>
</template>
<template v-for="(fiveMenu, fiveIndex) in threeMenu.children"
:key="fiveIndex">
<el-menu-item v-if="fiveMenu.meta.show" class="!h-[52px] !pl-[55px]"
:index="String(fiveMenu.name)" @click="toLink(fiveMenu)">
<template #title>
<span class="text-[14px]">{{ fiveMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else-if="fourMenu.meta.show" class="!h-[52px] !pl-[55px]"
:index="String(fourMenu.name)" @click="toLink(fourMenu)">
<template #title>
<span class="text-[14px]">{{ fourMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<div
:class="['!border-0 border-solid mx-[25px] bg-[#f7f7f7] my-[5px]', !applyTypeList.length ? '' : '!border-t-[1px]']">
</div>
<template v-for="(twoMenu, twoIndex) in menus">
<el-sub-menu :index="String(twoMenu.meta.title)"
v-if="twoMenu.meta.attr == 'common' && !twoMenu.meta.app && twoMenu.children">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
class="absolute !w-auto" size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
<template v-for="(threeMenu, threeIndex) in twoMenu.children" :key="threeIndex">
<el-sub-menu :index="String(threeMenu.meta.title)"
v-if="threeMenu.children && threeMenu.meta.show">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center justify-center">
<span class="iconfont iconyuanquan_huaban1 !text-[20px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title }}</span>
</template>
<template v-for="(fourMenu, fourIndex) in threeMenu.children" :key="fourIndex">
<el-sub-menu :index="String(fourMenu.meta.title)"
v-if="fourMenu.children && fourMenu.meta.show">
<template #title>
<div
class="w-[16px] h-[16px] relative flex items-center justify-center">
<span class="iconfont icondian !text-[25px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ fourMenu.meta.title }}</span>
</template>
<template v-for="(fiveMenu, fiveIndex) in threeMenu.children"
:key="fiveIndex">
<el-menu-item v-if="fiveMenu.meta.show" class="!h-[52px] !pl-[55px]"
:index="String(fiveMenu.name)" @click="toLink(fiveMenu)">
<template #title>
<span class="text-[14px]">{{ fiveMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else-if="fourMenu.meta.show" class="!h-[52px] !pl-[55px]"
:index="String(fourMenu.name)" @click="toLink(fourMenu)">
<template #title>
<span class="text-[14px]">{{ fourMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item
v-else-if="threeMenu.meta.show && threeMenu.meta.key != 'official_market'"
class="!h-[52px] !pl-[52px]" :index="String(threeMenu.name)"
@click="toLink(threeMenu)">
<template #title>
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
</template>
</el-menu-item>
<div class="flex items-center !px-[52px] h-[56px] cursor-pointer text-[#333] el-menu-item"
v-else-if="threeMenu.meta.show && threeMenu.meta.key == 'official_market'"
@click="toLink(threeMenu)">
<span class="text-[15px]">{{ twoMenu.meta.title }}</span>
</div>
</template>
</el-sub-menu>
<el-menu-item v-else-if="twoMenu.meta.attr == 'common'" class="!pl-[35px] text-[#333]"
:index="String(twoMenu.name)" @click="toLink(twoMenu)">
<template #title>
<div v-if="twoMenu.meta.icon" class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
class="absolute !w-auto" size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
<el-menu-item
v-else-if="threeMenu.meta.show && threeMenu.meta.key != 'official_market'"
class="!h-[52px] !pl-[52px]" :index="String(threeMenu.name)"
@click="toLink(threeMenu)">
<template #title>
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
</template>
</el-menu-item>
<div class="flex items-center !px-[52px] h-[56px] cursor-pointer text-[#333] el-menu-item"
v-else-if="threeMenu.meta.show && threeMenu.meta.key == 'official_market'"
@click="toLink(threeMenu)">
<span class="text-[15px]">{{ twoMenu.meta.title }}</span>
</div>
</template>
</el-sub-menu>
<el-menu-item v-else-if="twoMenu.meta.attr == 'common'" class="!pl-[35px] text-[#333]"
:index="String(twoMenu.name)" @click="toLink(twoMenu)">
<template #title>
<div v-if="twoMenu.meta.icon" class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
class="absolute !w-auto" size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-menu>
</el-scrollbar>
@ -398,6 +326,7 @@
import { ref, watch, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import side from './side.vue'
import appMenu from './components/app-menu.vue'
import { img } from '@/utils/common'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
@ -407,6 +336,7 @@ import { findFirstValidRoute } from '@/router/routers'
import { getAddonByKey } from '@/app/api/addon'
import { getApply } from '@/app/api/apply'
const userStore = useUserStore()
const systemStore = useSystemStore()
const route = useRoute()
@ -432,12 +362,6 @@ const getApplelist = async () => {
isLoad.value = true;
}
getApplelist()
const floatActive = ref(false)
const homeClick = () => {
const key = storage.get('menuAppStorage')
key ? router.push({ name: appLink.value[key] }) : router.push({ path: '/' })
}
// end
//
@ -454,7 +378,7 @@ const menus = computed(() => {
menus.push(item)
}
})
if(applyList.value && applyList.value.length){
applyList.value.forEach((item,index) =>{
menus.forEach((menuItem,menuIndex)=>{
@ -465,6 +389,17 @@ const menus = computed(() => {
})
})
}
//
if(!applyList.value.length){
storage.set({ key: 'menuAppStorage', data: '' })
globalAppKey.value = ""
}
if(applyList.value.length && !globalAppKey.value){
storage.set({ key: 'menuAppStorage', data: applyTypeList.value[0] })
globalAppKey.value = applyTypeList.value[0]
}
return menus
})
@ -472,6 +407,12 @@ const dark = computed(() => {
return systemStore.dark
})
//
watch(() =>userStore.globalAppKey, (val,old) => {
getApplelist();
},{deep: true})
//
let currMetaAppType = ''
const plugMenuType = ref('') //
@ -481,14 +422,22 @@ watch(route, () => {
const data = route.matched[1]
currentRoute.value = route.matched[1]
localMenuKey.value = data.meta.key
localMenuKey.value = data.meta.key || 'overview'
systemStore.$patch(state => {
state.menuDrawer = false
})
}, { immediate: true })
const toLink = (data) => {
let twofloatMenu = ref(true);
const twofloatMenuHover = ()=>{
twofloatMenu.value = true;
}
const toLink = (data, type) => {
if(type == 'twofloatMenu') twofloatMenu.value = false;
if (!data.meta && data.type == 'app' || data.meta.key != 'official_market') {
let name = data.name;
if(data.type == 'app'){
@ -501,7 +450,6 @@ const toLink = (data) => {
const appMenuList = userStore.appMenuList
appMenuList.push(data.key)
userStore.setAppMenuList(appMenuList)
floatActive.value = false
name = appLink.value[data.key];
}
@ -516,31 +464,18 @@ const sidebar = computed(() => {
return systemStore.sidebar
})
//
const floatMenuStyle = ref(false)
floatMenuStyle.value = storage.get('floatMenuStyle') || false
const cutMenuStyleFn = () => {
floatMenuStyle.value = true
storage.set({ key: 'floatMenuStyle', data: true })
location.reload()
}
//
const isTwoMenuFn = (item) => {
let bool = (otherTypeList.value.includes(localMenuKey.value) && globalAppKey.value == item.meta.app)
|| (floatMenuStyle.value && !applyTypeList.value.includes(localMenuKey.value) && !otherTypeList.value.includes(localMenuKey.value) && globalAppKey.value == item.meta.app)
|| (floatMenuStyle.value && applyTypeList.value.includes(localMenuKey.value) && (item.meta.key == localMenuKey.value || item.meta.app == localMenuKey.value))
|| (floatMenuStyle.value && !applyTypeList.value.length && (item.meta.key == localMenuKey.value || item.meta.app == localMenuKey.value))
|| (!floatMenuStyle.value && !otherTypeList.value.includes(localMenuKey.value) && (item.meta.key == localMenuKey.value || item.meta.app == localMenuKey.value))
|| (!applyTypeList.value.includes(localMenuKey.value) && !otherTypeList.value.includes(localMenuKey.value) && globalAppKey.value && globalAppKey.value == item.meta.app)
|| (applyTypeList.value.includes(localMenuKey.value) && (item.meta.key == localMenuKey.value || item.meta.app == localMenuKey.value))
|| (!applyTypeList.value.length && (item.meta.key == localMenuKey.value || item.meta.app == localMenuKey.value))
return bool;
}
</script>
<style lang="scss">
.layout-aside {
// border-right: 1px solid var(--el-border-color-lighter);
&.bright {
background-color: #F5F7F9;
@ -732,17 +667,3 @@ const isTwoMenuFn = (item) => {
height: calc(100vh - 64px);
}
</style>
<style>
.one-menus-float-style .el-overlay{
background-color: transparent;
}
.one-menus-float-style .el-dialog__header, .one-menus-float-style .el-dialog__body{
padding: 0;
}
.one-menus-float-style .one-menus-wrap{
position: fixed;
left: 0;
top: 65px;
z-index: 5555;
}
</style>

View File

@ -0,0 +1,200 @@
<template>
<el-container :class="['h-full px-[10px]', { 'layout-header border-b border-color': !dark }]">
<el-row class="w-100 h-full w-full">
<el-col :span="12">
<div class="left-panel h-full flex items-center">
<!-- 刷新当前页 -->
<div class="navbar-item flex items-center h-full cursor-pointer" @click="refreshRouter">
<icon name="element-Refresh" />
</div>
<!-- 面包屑导航 -->
<div class="flex items-center h-full pl-[10px] hidden-xs-only">
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="(route, index) in breadcrumb" :key="index">{{ route.meta.title }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
</div>
</el-col>
<el-col :span="12">
<div class="right-panel h-full flex items-center justify-end">
<!-- 预览 只有站点时展示-->
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'site'"></i>
<!-- 切换首页 -->
<div class="navbar-item flex items-center h-full cursor-pointer" v-if="appType == 'site'" @click="checkIndexList">
<icon name="iconfont-iconqiehuan" :title="t('indexSwitch')" />
</div>
<!-- 切换语言 -->
<div class="navbar-item !px-[0] flex items-center h-full cursor-pointer">
<switch-lang />
</div>
<!-- 切换全屏 -->
<div class="navbar-item flex items-center h-full cursor-pointer" @click="toggleFullscreen">
<icon name="iconfont-icontuichuquanping" v-if="isFullscreen" />
<icon name="iconfont-iconquanping" v-else />
</div>
<!-- 布局设置 -->
<div class="navbar-item !px-[0] flex items-center h-full cursor-pointer">
<layout-setting />
</div>
<!-- 用户信息 -->
<div class="navbar-item flex items-center h-full cursor-pointer">
<user-info />
</div>
</div>
</el-col>
</el-row>
<input type="hidden" v-model="comparisonToken">
<el-dialog v-model="detectionLoginDialog" :title="t('layout.detectionLoginTip')" width="30%" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false">
<span>{{ t('layout.detectionLoginContent') }}</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="detectionLoginFn">{{ t('layout.detectionLoginOperation') }}</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="showDialog" :title="t('indexTemplate')" width="550px" :destroy-on-close="true">
<div class="flex flex-wrap">
<div v-for="(items, index) in indexList" :key="index" v-if="index_path == ''">
<div @click="index_path = items.view_path" class="index-item py-[5px] px-[10px] mr-[10px] rounded-[3px] cursor-pointer" :class="items.is_use == 1 ? 'bg-primary text-[#fff]' : ''">
<span>{{ items.name }}</span>
</div>
</div>
<div v-for="(itemTo, indexTo) in indexList" :key="indexTo" v-else>
<div @click="index_path = itemTo.view_path" class="index-item py-[5px] px-[10px] mr-[10px] rounded-[3px] cursor-pointer" :class="index_path == itemTo.view_path ? 'bg-primary text-[#fff]' : ''">
<span>{{ itemTo.name }}</span>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="submitIndex">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</el-container>
</template>
<script lang="ts" setup>
import { computed, ref, onMounted, watch } from 'vue'
import layoutSetting from './layout-setting.vue'
import switchLang from './switch-lang.vue'
import userInfo from './user-info.vue'
import { useFullscreen } from '@vueuse/core'
import useSystemStore from '@/stores/modules/system'
import useAppStore from '@/stores/modules/app'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
import { getIndexList, setIndexList } from '@/app/api/sys'
const router = useRouter()
const appType = storage.get('app_type')
const { toggle: toggleFullscreen, isFullscreen } = useFullscreen()
const systemStore = useSystemStore()
const appStore = useAppStore()
const route = useRoute()
const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
const dark = computed(() => {
return systemStore.dark
})
onMounted(() => {
//
window.onresize = () => {
return (() => {
screenWidth.value = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
})()
}
})
watch(screenWidth, () => {
if (screenWidth.value < 992) {
if (!systemStore.menuIsCollapse) systemStore.toggleMenuCollapse(true)
} else {
if (systemStore.menuIsCollapse) systemStore.toggleMenuCollapse(false)
}
})
//
const toggleMenuCollapse = () => {
systemStore.$patch((state) => {
if (screenWidth.value < 768) {
state.menuDrawer = true
state.menuIsCollapse = false
} else {
systemStore.toggleMenuCollapse(!systemStore.menuIsCollapse)
}
})
}
//
const refreshRouter = () => {
if (!appStore.routeRefreshTag) return
appStore.refreshRouterView()
}
//
const breadcrumb = computed(() => {
const matched = route.matched.filter(item => { return item.meta.title })
if (matched[0] && matched[0].path == '/') matched.splice(0, 1)
return matched
})
//
const backFn = () => {
router.go(-1)
}
const indexList = ref()
const showDialog = ref(false)
const checkIndexList = () => {
getIndexList().then(res => {
showDialog.value = true
indexList.value = res.data
for (let i = 0; i < indexList.value.length; i++) {
if (indexList.value[i].is_use == 1) {
index_path.value = indexList.value[i].view_path
}
}
})
}
const index_path = ref('')
const submitIndex = () => {
setIndexList({
view_path: index_path.value
}).then(() => {
showDialog.value = false
router.go(0)
})
}
</script>
<style lang="scss" scoped>
.layout-header {
position: relative;
z-index: 5;
box-shadow: 0px 0px 4px 0px rgba(0, 145, 255, 0.1);
}
.navbar-item {
padding: 0 8px;
&:hover {
background-color: var(--el-bg-color-page);
}
}
.index-item {
border: 1px solid;
border-color: var(--el-color-primary);
&:hover {
color: #fff;
background-color: var(--el-color-primary);
}
}</style>

View File

@ -0,0 +1,119 @@
<template>
<div class="flex w-[100%] h-[100%]" @click="drawer = true">
<div class="h-[100%] w-[100%] flex items-center justify-center px-[8px]">
<icon name="element-Setting" />
</div>
<el-drawer v-model="drawer" :title="t('layout.layoutSetting')" size="300px">
<el-scrollbar>
<div class="setting-item flex items-baseline justify-between mb-[10px]">
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarStyle') }}</div>
<div class="">
<el-radio-group v-model="sidebarStyle" class="ml-4">
<el-radio label="oneType" size="large">样式一</el-radio>
<el-radio label="twoType" size="large">样式二</el-radio>
<el-radio label="threeType" size="large">样式三</el-radio>
</el-radio-group>
</div>
</div>
<!-- 风格切换 -->
<div class="setting-item flex items-baseline justify-between mb-[10px]">
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarMode') }}</div>
<div class="">
<el-radio-group v-model="sidebar" class="ml-4">
<el-radio label="oneType" size="large">
<img class="w-[35px] h-[35px]" src="@/app/assets/images/one_type.png" alt="">
</el-radio>
<el-radio label="twoType" size="large">
<img class="w-[35px] h-[35px]" src="@/app/assets/images/two_type.png" alt="">
</el-radio>
<el-radio label="threeType" size="large">
<img class="w-[35px] h-[35px]" src="@/app/assets/images/three_type.png" alt="">
</el-radio>
</el-radio-group>
</div>
</div>
<!-- 黑暗模式 -->
<div class="setting-item flex items-center justify-between mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.darkMode') }}</div>
<div class="">
<el-switch v-model="dark" :active-value="true" :inactive-value="false" />
</div>
</div>
<!-- 主题颜色 -->
<div class="setting-item flex items-center justify-between mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.themeColor') }}</div>
<div class="">
<el-color-picker v-model="theme" />
</div>
</div>
</el-scrollbar>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import useSystemStore from '@/stores/modules/system'
import { useDark, useToggle } from '@vueuse/core'
import { setThemeColor } from '@/utils/common'
import { t } from '@/lang'
const drawer = ref(false)
const systemStore = useSystemStore()
const isDark = useDark()
const toggleDark = useToggle(isDark)
const dark = computed({
get() {
return systemStore.dark
},
set(val) {
systemStore.setTheme('dark', val)
toggleDark(val)
setThemeColor(systemStore.theme, systemStore.dark ? 'dark' : 'light')
}
})
const sidebar = computed({
get() {
return systemStore.sidebar
},
set(val) {
systemStore.setTheme('sidebar', val)
setThemeColor(systemStore.theme, systemStore.dark ? 'dark' : 'light')
}
})
const sidebarStyle = computed({
get() {
return systemStore.sidebarStyle
},
set(val) {
systemStore.setTheme('sidebarStyle', val)
}
})
const theme = computed({
get() {
return systemStore.theme
},
set(val) {
systemStore.setTheme('theme', val)
setThemeColor(systemStore.theme, systemStore.dark ? 'dark' : 'light')
}
})
</script>
<style lang="scss" scoped>
:deep(.el-drawer__header) {
margin-bottom: 0 !important;
}
.layout-style {
&>div:nth-child(2n+2) {
margin-right: 0;
}
}
</style>

View File

@ -0,0 +1,35 @@
<template>
<el-dropdown @command="switchLang" :tabindex="1" class="h-[100%] w-[100%]">
<div class="h-[100%] w-[100%] flex items-center justify-center px-[8px]">
<icon name="iconfont-iconfanyi" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="zh-cn" :disabled="systemStore.lang == 'zh-cn'">简体中文</el-dropdown-item>
<el-dropdown-item command="en" :disabled="systemStore.lang == 'en'">English</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script lang="ts" setup>
import useSystemStore from '@/stores/modules/system'
import { language } from '@/lang'
import { useRoute } from 'vue-router'
import storage from '@/utils/storage'
const route = useRoute()
const systemStore = useSystemStore()
const switchLang = (command: string) => {
systemStore.$patch((state) => {
state.lang = command
storage.set({ key: 'lang', data: command })
})
language.loadLocaleMessages(route.meta.app || '', route.path, systemStore.lang)
location.reload()
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,32 @@
<template>
<el-dropdown @command="clickEvent" :tabindex="1">
<div class="userinfo flex h-full items-center">
<el-avatar :size="25" :icon="UserFilled" />
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
<icon name="element-ArrowDown" class="ml-[5px]" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="usercenter"><router-link to="/user/center">个人中心</router-link></el-dropdown-item>
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script lang="ts" setup>
import { UserFilled } from '@element-plus/icons-vue'
import useUserStore from '@/stores/modules/user'
const userStore = useUserStore()
const clickEvent = (command: string) => {
switch (command) {
case 'logout':
userStore.logout()
break
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,40 @@
<template>
<div class="common-layout min-w-[1200px]" >
<el-container class="w-100 h-screen">
<layout-aside></layout-aside>
<el-container>
<el-header>
<layout-header></layout-header>
</el-header>
<el-main :class="['main-wrap h-full p-0',{'bg-page': dark}]">
<el-scrollbar>
<div>
<router-view v-slot="{ Component, route }" v-if="appStore.routeRefreshTag">
<component :is="Component" :key="route.fullPath" />
</router-view>
</div>
</el-scrollbar>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import layoutHeader from './components/header/index.vue'
import layoutAside from './components/aside/index.vue'
import useAppStore from '@/stores/modules/app'
import useSystemStore from '@/stores/modules/system'
const appStore = useAppStore()
const systemStore = useSystemStore()
const dark = computed(()=>{
return systemStore.dark
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,32 @@
<template>
<div :class="[{'group-hover:flex': props.isShowHover},'hidden fixed left-0 top-[65px] z-[5555] bg-[#fff] w-[640px] px-[28px] py-[20px] flex-wrap box-border shadow-lg ']">
<div v-for="(item, index) in data" :key="index" @click="toLink(item, props.hoverType)" class="flex items-center cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] whitespace-nowrap py-[10px] px-[15px] box-border w-[165px]">
<img :src="img(item.icon)" class="w-[44px] h-[44px] rounded-full mr-[5px]" alt="" :title="item.title">
<span>{{ item.title }}</span>
</div>
<div v-if="!data.length" class="flex-1 flex flex-col justify-center items-center pb-[30px]">
<div class="w-[130px]"><img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt=""></div>
<div class="text-[14px] text-[#909399]">暂无安装任何应用或插件马上去<a href="https://www.niucloud.com/product/" target="_blank" class="text-[var(--el-color-primary)]">官方应用市场</a>逛逛</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed } from 'vue'
import { img } from '@/utils/common'
const props = defineProps(['isShowHover','data','hoverType'])
let data = ref([]);
if(props.data){
props.data.forEach((item,index) => {
if(item.type == "app"){
data.value.push(item);
}
});
}
const emit = defineEmits(['child-click'])
const toLink = (data,type)=>{
emit('child-click',data,type)
}
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,613 @@
<template>
<div :class="['flex', { 'two-type': sidebar == 'twoType' }, { 'three-type': sidebar == 'threeType' }]" v-if="isLoad">
<div class="w-[65px] overflow-hidden">
<el-aside
:class="['h-screen layout-aside w-[65px] pb-[30px] bg-[#F7F8FA] ease-in duration-200', { 'bright': !dark }]">
<div class="h-full flex flex-col relative">
<div class="group flex items-center justify-center h-[64px] cursor-pointer cut-style" @mouseenter="onefloatMenuHover">
<span class="iconfont icontuodong !text-[30px] "></span>
<app-menu :isShowHover="onefloatMenu" :data="applyList" @child-click="toLink" hoverType='onefloatMenu'></app-menu>
</div>
<div @click="homeClick"
class="flex items-center justify-center h-[56px] cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] menu-item hover:text-color whitespace-nowrap">
<span class="iconfont iconshouye !text-[24px] "></span>
</div>
<div class="mb-[20px]">
<template v-for="(item, index) in menus" :key="index">
<div v-if="item.meta.app == '' && item.meta.attr == 'common'" @click="toLink(item)"
:class="['flex items-center justify-center h-[56px] cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] menu-item hover:text-color whitespace-nowrap', { 'bg-[#f1f2f6] text-color menu-item-active ': (item.path == currentRoute.path || (currentRoute.path == '/admin' && item.path == '/index') || (currentRoute.meta.app && item.path == '/index')) }]">
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px" :title="item.meta.title" />
</div>
</template>
</div>
</div>
</el-aside>
</div>
<template v-for="(item, index) in menus" :key="index">
<div v-if="isTwoMenuFn(item)" class="w-[189px] box-border border-r-[1px] border-solid second-menu">
<div class="group flex flex-col items-center justify-center h-[64px] border-b-[1px] border-solid second-head cursor-pointer relative">
{{ item.meta.app ? item.meta.parentTitle : item.meta.title }}
</div>
<el-scrollbar class="overflow-y-auto menus-wrap">
<el-menu class="apply-menu !border-0" :router="true" unique-opened="true" :default-active="String(route.name)">
<template v-for="(twoMenu, twoIndex) in item.children">
<el-sub-menu :index="String(twoMenu.meta.title)" v-if="twoMenu.children && twoMenu.meta.show">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
class="absolute !w-auto" size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
<template v-for="(threeMenu, threeIndex) in twoMenu.children" :key="threeIndex">
<!-- 三级菜单 -->
<el-sub-menu :index="String(threeMenu.meta.title)" class="three-menu"
v-if="threeMenu.children && threeMenu.meta.show">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<span class="iconfont icondian !text-[25px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title }}</span>
</template>
<template v-for="(fourMenu, fourIndex) in threeMenu.children" :key="fourIndex">
<el-sub-menu :index="String(fourMenu.meta.title)"
v-if="fourMenu.children && fourMenu.meta.show">
<template #title>
<div
class="w-[16px] h-[16px] relative flex items-center justify-center">
<span class="iconfont icondian !text-[25px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ fourMenu.meta.title }}</span>
</template>
<template v-for="(fiveMenu, fiveIndex) in fourMenu.children"
:key="fiveIndex">
<el-menu-item v-if="fiveMenu.meta.show" class="!h-[52px] !pl-[55px]"
:index="String(fiveMenu.name)" @click="toLink(fiveMenu)">
<template #title>
<span class="text-[14px]">{{ fiveMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else-if="fourMenu.meta.show" class="!h-[52px] !pl-[35px]"
:index="String(fourMenu.name)" @click="toLink(fourMenu)">
<template #title>
<span class="text-[14px]">{{ fourMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<!-- 二级菜单 -->
<el-menu-item v-else-if="threeMenu.meta.show" class="!h-[52px] !pl-[52px]"
:index="String(threeMenu.name)" @click="toLink(threeMenu)">
<template #title>
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else-if="twoMenu.meta.show && twoMenu.meta.key != 'official_market'"
class="!pl-[25px] text-[#333]" :index="String(twoMenu.name)" @click="toLink(twoMenu)">
<template #title>
<div v-if="twoMenu.meta.icon" class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
class="absolute !w-auto" size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
</el-menu-item>
<div class="flex items-center !px-[25px] h-[56px] cursor-pointer text-[#333] el-menu-item"
v-else-if="twoMenu.meta.show && twoMenu.meta.key == 'official_market'"
@click="toLink(twoMenu)">
<div v-if="twoMenu.meta.icon" class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon" class="absolute !w-auto"
size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</div>
</template>
<!-- 系统菜单 -->
<template v-if="applyTypeList.includes(localMenuKey) || otherTypeList.includes(localMenuKey)">
<div class="!border-0 !border-t-[1px] border-solid mx-[25px] bg-[#f7f7f7] my-[5px]"></div>
<template v-for="(twoMenu, twoIndex) in menus">
<el-sub-menu :index="String(twoMenu.meta.title)"
v-if="twoMenu.meta.attr == 'system' && !twoMenu.meta.app && twoMenu.children">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
class="absolute !w-auto" size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
<template v-for="(threeMenu, threeIndex) in twoMenu.children" :key="threeIndex">
<el-sub-menu :index="String(threeMenu.meta.title)"
v-if="threeMenu.meta.app && threeMenu.children">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center justify-center">
<span class="iconfont iconyuanquan_huaban1 !text-[20px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title }}</span>
</template>
<template v-for="(fourMenu, fourIndex) in threeMenu.children"
:key="fourIndex">
<!-- 三级菜单 -->
<el-sub-menu :index="String(fourMenu.meta.title)"
v-if="fourMenu.children && fourMenu.meta.show">
<template #title>
<div
class="w-[16px] h-[16px] relative flex items-center justify-center">
<span class="iconfont icondian !text-[25px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ fourMenu.meta.title
}}</span>
</template>
<template v-for="(fiveMenu, fiveIndex) in fourMenu.children"
:key="fiveIndex">
<el-menu-item v-if="fiveMenu.meta.show"
class="!h-[52px] !pl-[55px]" :index="String(fiveMenu.name)"
@click="toLink(fiveMenu)">
<template #title>
<span class="text-[14px]">{{ fiveMenu.meta.title
}}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else-if="fourMenu.meta.show"
class="!ml-[30px] !h-[52px] !pl-[35px]"
:index="String(fourMenu.name)" @click="toLink(fourMenu)">
<template #title>
<span class="text-[14px]">{{ fourMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-if="threeMenu.meta.show" class="!h-[52px] !pl-[52px]"
:index="String(threeMenu.name)" @click="toLink(threeMenu)">
<template #title>
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
<!-- 插件菜单 -->
<template v-if="otherTypeList.includes(localMenuKey) && twoMenu.meta.key == 'app_center' && plugMenuType">
<template v-for="(twoMenu, twoIndex) in menus">
<el-sub-menu :index="String(twoMenu.meta.title)"
v-if="twoMenu.meta.app && twoMenu.meta.app == plugMenuType && twoMenu.children">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center justify-center">
<span class="iconfont iconyuanquan_huaban1 !text-[20px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
<template v-for="(threeMenu, threeIndex) in twoMenu.children"
:key="threeIndex">
<!-- 三级菜单 -->
<el-sub-menu :index="String(threeMenu.meta.title)"
v-if="threeMenu.children && threeMenu.meta.show">
<template #title>
<div
class="w-[16px] h-[16px] relative flex items-center justify-center">
<span class="iconfont icondian !text-[25px]"></span>
</div>
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title
}}</span>
</template>
<template v-for="(fourMenu, fourIndex) in threeMenu.children"
:key="fourIndex">
<el-menu-item v-if="fourMenu.meta.show"
class="!h-[52px] !pl-[55px]"
:index="String(fourMenu.name)"
@click="toLink(fourMenu)">
<template #title>
<span class="text-[14px]">{{ fourMenu.meta.title
}}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else-if="threeMenu.meta.show"
class="!ml-[30px] !h-[52px] !pl-[35px]"
:index="String(threeMenu.name)" @click="toLink(threeMenu)">
<template #title>
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item
v-else-if="twoMenu.meta.app && twoMenu.meta.app == plugMenuType"
class="!pl-[25px] text-[#333]" :index="String(twoMenu.name)"
@click="toLink(twoMenu)">
<template #title>
<div v-if="twoMenu.meta.icon"
class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
class="absolute !w-auto" size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</template>
</el-sub-menu>
<el-menu-item v-else-if="twoMenu.meta.attr == 'system' && !twoMenu.meta.app"
class="!pl-[25px] text-[#333]" :index="String(twoMenu.name)"
@click="toLink(twoMenu)">
<template #title>
<div v-if="twoMenu.meta.icon"
class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
class="absolute !w-auto" size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
</el-menu-item>
</template>
</template>
</el-menu>
</el-scrollbar>
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import side from './side.vue'
import appMenu from './components/app-menu.vue'
import { img } from '@/utils/common'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import storage from '@/utils/storage'
import { CollectionTag } from '@element-plus/icons-vue'
import { findFirstValidRoute } from '@/router/routers'
import { getAddonByKey } from '@/app/api/addon'
import { getApply } from '@/app/api/apply'
const userStore = useUserStore()
const systemStore = useSystemStore()
const route = useRoute()
const router = useRouter()
const globalAppKey = ref('') //
const localMenuKey = ref('') //
globalAppKey.value = storage.get('menuAppStorage')
localMenuKey.value = storage.get('menuAppStorage')
const isLoad = ref(false);
// start
const applyList = ref([])
const applyTypeList = ref([])
const otherTypeList = ref([]) // \\
const getApplelist = async () => {
const res = await getApply()
applyList.value = applyList.value.concat(res.data)
applyList.value.forEach((item, index) => {
if (item.type == 'app') { applyTypeList.value.push(item.key) }
if (item.type == 'addon') { otherTypeList.value.push(item.key) }
})
otherTypeList.value = otherTypeList.value.concat(['member', 'app_center'])
isLoad.value = true;
}
getApplelist()
const homeClick = () => {
const key = storage.get('menuAppStorage')
key ? router.push({ name: appLink.value[key] }) : router.push({ path: '/' })
}
// end
//
const appLink = ref({})
const menus = computed(() => {
const menus = []
userStore.routers.forEach((item, index) => {
if (item.children && item.children.length) {
item.name = findFirstValidRoute(item.children)
appLink.value[item.meta.app] = findFirstValidRoute(item.children)
menus.push(item)
} else {
appLink.value[item.meta.app] = item.name
menus.push(item)
}
})
if(applyList.value && applyList.value.length){
applyList.value.forEach((item,index) =>{
menus.forEach((menuItem,menuIndex)=>{
if(item.key == menuItem.meta.key){
menuItem.meta.parentTitle = item.title;
menuItem.meta.parentIcon = item.icon;
}
})
})
}
//
if(!applyList.value.length){
storage.set({ key: 'menuAppStorage', data: '' })
globalAppKey.value = ""
}
if(applyList.value.length && !globalAppKey.value){
storage.set({ key: 'menuAppStorage', data: applyTypeList.value[0] })
globalAppKey.value = applyTypeList.value[0]
}
return menus
})
const dark = computed(() => {
return systemStore.dark
})
//
watch(() =>userStore.globalAppKey, (val,old) => {
getApplelist();
},{deep: true})
//
let currMetaAppType = ''
const plugMenuType = ref('') //
const currentRoute = ref('') //
watch(route, () => {
plugMenuType.value = storage.get('plugMenuTypeStorage')
const data = route.matched[1]
currentRoute.value = route.matched[1]
localMenuKey.value = data.meta.key
systemStore.$patch(state => {
state.menuDrawer = false
})
}, { immediate: true })
let onefloatMenu = ref(true);
let threefloatMenu = ref(true);
const onefloatMenuHover = ()=>{
onefloatMenu.value = true;
}
const threefloatMenuHover = ()=>{
threefloatMenu.value = true;
}
const toLink = (data, type) => {
if(type == 'onefloatMenu') onefloatMenu.value = false;
if (!data.meta && data.type == 'app' || data.meta.key != 'official_market') {
let name = data.name;
if(data.type == 'app'){
globalAppKey.value = data.key
localMenuKey.value = data.key
storage.set({ key: 'menuAppStorage', data: data.key })
storage.set({ key: 'plugMenuTypeStorage', data: '' })
const appMenuList = userStore.appMenuList
appMenuList.push(data.key)
userStore.setAppMenuList(appMenuList)
name = appLink.value[data.key];
}
router.push({ name: name })
} else {
window.open('https://www.niucloud.com/product/', '_blank')
}
}
//
const sidebar = computed(() => {
return systemStore.sidebar
})
//
const isTwoMenuFn = (item) => {
let bool = (otherTypeList.value.includes(localMenuKey.value) && globalAppKey.value == item.meta.app)
|| (!otherTypeList.value.includes(localMenuKey.value) && (item.meta.key == localMenuKey.value || item.meta.app == localMenuKey.value))
return bool;
}
</script>
<style lang="scss">
.layout-aside {
&.bright {
background-color: #F5F7F9;
li {
background-color: #F5F7F9;
&.is-active:not(.is-opened) {
position: relative;
color: #333;
background-color: #fff;
&::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 2px;
background-color: var(--el-menu-active-color);
}
}
}
}
.menu-item:hover {
color: var(--el-color-primary);
}
}
.second-menu .el-sub-menu .el-sub-menu__title {
padding-left: 25px !important;
padding-right: 25px !important;
.el-icon.el-sub-menu__icon-arrow {
right: 25px;
font-weight: bolder;
font-size: 14px;
}
}
.three-menu.el-sub-menu .el-sub-menu__title {
padding-left: 45px !important;
}
.text-color {
color: var(--el-color-primary);
}
.aside-drawer {
.el-drawer__body {
padding: 0px !important;
}
}
.cut-style {
color: #6d7278;
}
.cut-style.qx {
transform: translateX(-50%);
}
//
.two-type {
.logo-wrap {
.logo span {
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #F5F7F9;
display: flex;
justify-content: center;
align-items: center;
color: #273de3;
font-size: 25px !important;
}
}
.layout-aside {
// background-color: #12192D;
background-color: #2b303b;
.menu-item {
color: #fff;
&.menu-item-active,
&:hover {
background-color: var(--el-color-primary);
color: #fff;
}
}
}
.second-menu {
background-color: #F5F7F9;
.el-menu {
background-color: transparent;
.el-menu-item:hover {
background-color: #fff;
color: var(--el-color-primary);
}
.el-menu-item.is-active {
background-color: #fff;
}
}
.el-sub-menu__title:hover {
background-color: #fff;
color: var(--el-color-primary);
}
}
.cut-style {
color: #FFF;
}
}
//
.three-type {
.logo-wrap {
.logo span {
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #F5F7F9;
display: flex;
justify-content: center;
align-items: center;
color: #2E7BFD;
font-size: 25px !important;
}
}
.layout-aside {
background-color: #2b303b;
.menu-item {
color: #fff;
&.menu-item-active,
&:hover {
background-color: #303848;
color: var(--el-color-primary);
}
}
}
.second-menu {
background-color: #303848;
.second-head {
color: #fff;
border-color: #364059;
}
.el-menu {
background-color: transparent;
.el-menu-item {
color: #fff;
}
.el-menu-item:hover,
.el-menu-item.is-active {
background-color: var(--el-color-primary);
}
}
.el-sub-menu__title:hover {
background-color: var(--el-color-primary);
;
}
.el-sub-menu__title {
color: #fff;
}
}
.cut-style {
color: #FFF;
}
}
.menus-wrap {
height: calc(100vh - 64px);
}
</style>

View File

@ -0,0 +1,200 @@
<template>
<el-container :class="['h-full px-[10px]', { 'layout-header border-b border-color': !dark }]">
<el-row class="w-100 h-full w-full">
<el-col :span="12">
<div class="left-panel h-full flex items-center">
<!-- 刷新当前页 -->
<div class="navbar-item flex items-center h-full cursor-pointer" @click="refreshRouter">
<icon name="element-Refresh" />
</div>
<!-- 面包屑导航 -->
<div class="flex items-center h-full pl-[10px] hidden-xs-only">
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="(route, index) in breadcrumb" :key="index">{{ route.meta.title }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
</div>
</el-col>
<el-col :span="12">
<div class="right-panel h-full flex items-center justify-end">
<!-- 预览 只有站点时展示-->
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'site'"></i>
<!-- 切换首页 -->
<div class="navbar-item flex items-center h-full cursor-pointer" v-if="appType == 'site'" @click="checkIndexList">
<icon name="iconfont-iconqiehuan" :title="t('indexSwitch')" />
</div>
<!-- 切换语言 -->
<div class="navbar-item !px-[0] flex items-center h-full cursor-pointer">
<switch-lang />
</div>
<!-- 切换全屏 -->
<div class="navbar-item flex items-center h-full cursor-pointer" @click="toggleFullscreen">
<icon name="iconfont-icontuichuquanping" v-if="isFullscreen" />
<icon name="iconfont-iconquanping" v-else />
</div>
<!-- 布局设置 -->
<div class="navbar-item !px-[0] flex items-center h-full cursor-pointer">
<layout-setting />
</div>
<!-- 用户信息 -->
<div class="navbar-item flex items-center h-full cursor-pointer">
<user-info />
</div>
</div>
</el-col>
</el-row>
<input type="hidden" v-model="comparisonToken">
<el-dialog v-model="detectionLoginDialog" :title="t('layout.detectionLoginTip')" width="30%" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false">
<span>{{ t('layout.detectionLoginContent') }}</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="detectionLoginFn">{{ t('layout.detectionLoginOperation') }}</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="showDialog" :title="t('indexTemplate')" width="550px" :destroy-on-close="true">
<div class="flex flex-wrap">
<div v-for="(items, index) in indexList" :key="index" v-if="index_path == ''">
<div @click="index_path = items.view_path" class="index-item py-[5px] px-[10px] mr-[10px] rounded-[3px] cursor-pointer" :class="items.is_use == 1 ? 'bg-primary text-[#fff]' : ''">
<span>{{ items.name }}</span>
</div>
</div>
<div v-for="(itemTo, indexTo) in indexList" :key="indexTo" v-else>
<div @click="index_path = itemTo.view_path" class="index-item py-[5px] px-[10px] mr-[10px] rounded-[3px] cursor-pointer" :class="index_path == itemTo.view_path ? 'bg-primary text-[#fff]' : ''">
<span>{{ itemTo.name }}</span>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="submitIndex">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</el-container>
</template>
<script lang="ts" setup>
import { computed, ref, onMounted, watch } from 'vue'
import layoutSetting from './layout-setting.vue'
import switchLang from './switch-lang.vue'
import userInfo from './user-info.vue'
import { useFullscreen } from '@vueuse/core'
import useSystemStore from '@/stores/modules/system'
import useAppStore from '@/stores/modules/app'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
import { getIndexList, setIndexList } from '@/app/api/sys'
const router = useRouter()
const appType = storage.get('app_type')
const { toggle: toggleFullscreen, isFullscreen } = useFullscreen()
const systemStore = useSystemStore()
const appStore = useAppStore()
const route = useRoute()
const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
const dark = computed(() => {
return systemStore.dark
})
onMounted(() => {
//
window.onresize = () => {
return (() => {
screenWidth.value = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
})()
}
})
watch(screenWidth, () => {
if (screenWidth.value < 992) {
if (!systemStore.menuIsCollapse) systemStore.toggleMenuCollapse(true)
} else {
if (systemStore.menuIsCollapse) systemStore.toggleMenuCollapse(false)
}
})
//
const toggleMenuCollapse = () => {
systemStore.$patch((state) => {
if (screenWidth.value < 768) {
state.menuDrawer = true
state.menuIsCollapse = false
} else {
systemStore.toggleMenuCollapse(!systemStore.menuIsCollapse)
}
})
}
//
const refreshRouter = () => {
if (!appStore.routeRefreshTag) return
appStore.refreshRouterView()
}
//
const breadcrumb = computed(() => {
const matched = route.matched.filter(item => { return item.meta.title })
if (matched[0] && matched[0].path == '/') matched.splice(0, 1)
return matched
})
//
const backFn = () => {
router.go(-1)
}
const indexList = ref()
const showDialog = ref(false)
const checkIndexList = () => {
getIndexList().then(res => {
showDialog.value = true
indexList.value = res.data
for (let i = 0; i < indexList.value.length; i++) {
if (indexList.value[i].is_use == 1) {
index_path.value = indexList.value[i].view_path
}
}
})
}
const index_path = ref('')
const submitIndex = () => {
setIndexList({
view_path: index_path.value
}).then(() => {
showDialog.value = false
router.go(0)
})
}
</script>
<style lang="scss" scoped>
.layout-header {
position: relative;
z-index: 5;
box-shadow: 0px 0px 4px 0px rgba(0, 145, 255, 0.1);
}
.navbar-item {
padding: 0 8px;
&:hover {
background-color: var(--el-bg-color-page);
}
}
.index-item {
border: 1px solid;
border-color: var(--el-color-primary);
&:hover {
color: #fff;
background-color: var(--el-color-primary);
}
}</style>

View File

@ -0,0 +1,119 @@
<template>
<div class="flex w-[100%] h-[100%]" @click="drawer = true">
<div class="h-[100%] w-[100%] flex items-center justify-center px-[8px]">
<icon name="element-Setting" />
</div>
<el-drawer v-model="drawer" :title="t('layout.layoutSetting')" size="300px">
<el-scrollbar>
<div class="setting-item flex items-baseline justify-between mb-[10px]">
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarStyle') }}</div>
<div class="">
<el-radio-group v-model="sidebarStyle" class="ml-4">
<el-radio label="oneType" size="large">样式一</el-radio>
<el-radio label="twoType" size="large">样式二</el-radio>
<el-radio label="threeType" size="large">样式三</el-radio>
</el-radio-group>
</div>
</div>
<!-- 风格切换 -->
<div class="setting-item flex items-baseline justify-between mb-[10px]">
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarMode') }}</div>
<div class="">
<el-radio-group v-model="sidebar" class="ml-4">
<el-radio label="oneType" size="large">
<img class="w-[35px] h-[35px]" src="@/app/assets/images/one_type.png" alt="">
</el-radio>
<el-radio label="twoType" size="large">
<img class="w-[35px] h-[35px]" src="@/app/assets/images/two_type.png" alt="">
</el-radio>
<el-radio label="threeType" size="large">
<img class="w-[35px] h-[35px]" src="@/app/assets/images/three_type.png" alt="">
</el-radio>
</el-radio-group>
</div>
</div>
<!-- 黑暗模式 -->
<div class="setting-item flex items-center justify-between mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.darkMode') }}</div>
<div class="">
<el-switch v-model="dark" :active-value="true" :inactive-value="false" />
</div>
</div>
<!-- 主题颜色 -->
<div class="setting-item flex items-center justify-between mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.themeColor') }}</div>
<div class="">
<el-color-picker v-model="theme" />
</div>
</div>
</el-scrollbar>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import useSystemStore from '@/stores/modules/system'
import { useDark, useToggle } from '@vueuse/core'
import { setThemeColor } from '@/utils/common'
import { t } from '@/lang'
const drawer = ref(false)
const systemStore = useSystemStore()
const isDark = useDark()
const toggleDark = useToggle(isDark)
const dark = computed({
get() {
return systemStore.dark
},
set(val) {
systemStore.setTheme('dark', val)
toggleDark(val)
setThemeColor(systemStore.theme, systemStore.dark ? 'dark' : 'light')
}
})
const sidebar = computed({
get() {
return systemStore.sidebar
},
set(val) {
systemStore.setTheme('sidebar', val)
setThemeColor(systemStore.theme, systemStore.dark ? 'dark' : 'light')
}
})
const sidebarStyle = computed({
get() {
return systemStore.sidebarStyle
},
set(val) {
systemStore.setTheme('sidebarStyle', val)
}
})
const theme = computed({
get() {
return systemStore.theme
},
set(val) {
systemStore.setTheme('theme', val)
setThemeColor(systemStore.theme, systemStore.dark ? 'dark' : 'light')
}
})
</script>
<style lang="scss" scoped>
:deep(.el-drawer__header) {
margin-bottom: 0 !important;
}
.layout-style {
&>div:nth-child(2n+2) {
margin-right: 0;
}
}
</style>

View File

@ -0,0 +1,35 @@
<template>
<el-dropdown @command="switchLang" :tabindex="1" class="h-[100%] w-[100%]">
<div class="h-[100%] w-[100%] flex items-center justify-center px-[8px]">
<icon name="iconfont-iconfanyi" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="zh-cn" :disabled="systemStore.lang == 'zh-cn'">简体中文</el-dropdown-item>
<el-dropdown-item command="en" :disabled="systemStore.lang == 'en'">English</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script lang="ts" setup>
import useSystemStore from '@/stores/modules/system'
import { language } from '@/lang'
import { useRoute } from 'vue-router'
import storage from '@/utils/storage'
const route = useRoute()
const systemStore = useSystemStore()
const switchLang = (command: string) => {
systemStore.$patch((state) => {
state.lang = command
storage.set({ key: 'lang', data: command })
})
language.loadLocaleMessages(route.meta.app || '', route.path, systemStore.lang)
location.reload()
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,32 @@
<template>
<el-dropdown @command="clickEvent" :tabindex="1">
<div class="userinfo flex h-full items-center">
<el-avatar :size="25" :icon="UserFilled" />
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
<icon name="element-ArrowDown" class="ml-[5px]" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="usercenter"><router-link to="/user/center">个人中心</router-link></el-dropdown-item>
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script lang="ts" setup>
import { UserFilled } from '@element-plus/icons-vue'
import useUserStore from '@/stores/modules/user'
const userStore = useUserStore()
const clickEvent = (command: string) => {
switch (command) {
case 'logout':
userStore.logout()
break
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,40 @@
<template>
<div class="common-layout min-w-[1200px]" >
<el-container class="w-100 h-screen">
<layout-aside></layout-aside>
<el-container>
<el-header>
<layout-header></layout-header>
</el-header>
<el-main :class="['main-wrap h-full p-0',{'bg-page': dark}]">
<el-scrollbar>
<div>
<router-view v-slot="{ Component, route }" v-if="appStore.routeRefreshTag">
<component :is="Component" :key="route.fullPath" />
</router-view>
</div>
</el-scrollbar>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import layoutHeader from './components/header/index.vue'
import layoutAside from './components/aside/index.vue'
import useAppStore from '@/stores/modules/app'
import useSystemStore from '@/stores/modules/system'
const appStore = useAppStore()
const systemStore = useSystemStore()
const dark = computed(()=>{
return systemStore.dark
})
</script>
<style lang="scss" scoped></style>

View File

@ -55,7 +55,7 @@ router.beforeEach(async (to, from, next) => {
} else {
ROOT_ROUTER.redirect = { name: firstRoute }
}
console.log(currApp, ROOT_ROUTER.redirect)
// console.log(currApp, ROOT_ROUTER.redirect)
router.addRoute(ROOT_ROUTER)
// 添加动态路由

View File

@ -67,6 +67,7 @@ const addonModules = import.meta.glob('@/**/views/**/*.vue')
interface Route {
menu_name: string,
menu_short_name: string,
router_path: string,
view_path: string
menu_type: number,
@ -93,6 +94,7 @@ const createRoute = function (route: Route, parentRoute: RouteRecordRaw | null =
name: route.menu_key,
meta: {
title: route.menu_name,
shortTitle: route.menu_short_name,
icon: route.icon,
type: route.menu_type,
show: route.is_show,

View File

@ -8,7 +8,8 @@ interface System {
dark: boolean,
theme: string,
lang: string,
sidebar: string
sidebar: string,
sidebarStyle: string
}
const theme = storage.get('theme') ?? {}
@ -21,6 +22,7 @@ const useSystemStore = defineStore('system', {
dark: theme.dark ?? false,
theme: theme.theme ?? '#273de3',
sidebar: theme.sidebar ?? 'oneType',
sidebarStyle: theme.sidebarStyle ?? 'oneType',
lang: storage.get('lang') ?? 'zh-cn'
}
},

View File

@ -3,6 +3,7 @@ import { getToken, setToken, removeToken, getAppType } from '@/utils/common'
import { login, getAuthMenus } from '@/app/api/auth'
import storage from '@/utils/storage'
import router from '@/router'
import { getApply } from '@/app/api/apply'
import { formatRouters, findFirstValidRoute } from '@/router/routers'
interface User {
@ -11,10 +12,11 @@ interface User {
routers: any[],
addonIndexRoute: Record<string, symbol>,
rules: any[],
appMenuList: any[]
appMenuList: any[],
globalAppKey: string
}
const useSystemStore = defineStore('user', {
const useUserStore = defineStore('user', {
state: (): User => {
return {
token: getToken() || '',
@ -22,6 +24,7 @@ const useSystemStore = defineStore('user', {
routers: [],
addonIndexRoute: {},
rules: [],
globalAppKey: '',
appMenuList: storage.get('appMenuList' + (storage.get('userinfo') ? storage.get('userinfo').username : '')) || []
}
},
@ -74,8 +77,33 @@ const useSystemStore = defineStore('user', {
setAppMenuList(data: any) {
this.appMenuList = data
storage.set({ key: 'appMenuList' + (this.userInfo.username ? this.userInfo.username : ''), data })
},
getAppList() {
let applyList = [];
let applyTypeList = [];
let appKey = storage.get('menuAppStorage');
return new Promise((resolve, reject) => {
getApply()
.then((res) => {
applyList = applyList.concat(res.data);
applyList.forEach((item, index) => {
if (item.type == 'app') { applyTypeList.push(item.key) }
});
// 用于插件的卸载或安装
if (!applyList.length) {
this.globalAppKey = '';
}
if (applyList.length && !appKey) {
this.globalAppKey = applyTypeList[0];
}
resolve(res)
})
.catch((error) => {
reject(error)
})
})
}
}
})
export default useSystemStore
export default useUserStore

View File

@ -224,6 +224,10 @@ html.dark {
.el-button:not(.is-round){
border-radius: 2px !important;
}
// 修改表格中上传图片样式冲突的问题
.el-table .el-table__cell{
position: inherit !important;
}
// /* 多行超出隐藏 */
.multi-hidden {

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3883393 */
src: url('//at.alicdn.com/t/c/font_3883393_bq3in2lb5mh.woff2?t=1695086543767') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_bq3in2lb5mh.woff?t=1695086543767') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_bq3in2lb5mh.ttf?t=1695086543767') format('truetype');
src: url('//at.alicdn.com/t/c/font_3883393_yevzijodb3.woff2?t=1695808853045') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_yevzijodb3.woff?t=1695808853045') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_yevzijodb3.ttf?t=1695808853045') format('truetype');
}
.iconfont {
@ -13,6 +13,50 @@
-moz-osx-font-smoothing: grayscale;
}
.icongonggao:before {
content: "\e629";
}
.iconshangpinliebiao:before {
content: "\e628";
}
.icongouwuche1:before {
content: "\e680";
}
.icongouwuche:before {
content: "\e6c8";
}
.iconyun1:before {
content: "\e67e";
}
.iconicon-selected:before {
content: "\e626";
}
.iconshangpinguanli:before {
content: "\e67c";
}
.iconyuanquan_huaban1:before {
content: "\e66c";
}
.iconzhankai:before {
content: "\e67b";
}
.iconjiantou:before {
content: "\e67a";
}
.iconchajian1:before {
content: "\e679";
}
.iconicon_huojian:before {
content: "\e677";
}

View File

@ -5,6 +5,76 @@
"css_prefix_text": "icon",
"description": "系统图标",
"glyphs": [
{
"icon_id": "37443500",
"name": "展开",
"font_class": "zhankai",
"unicode": "e67b",
"unicode_decimal": 59003
},
{
"icon_id": "37443497",
"name": "箭头",
"font_class": "jiantou",
"unicode": "e67a",
"unicode_decimal": 59002
},
{
"icon_id": "37440097",
"name": "插件",
"font_class": "chajian1",
"unicode": "e679",
"unicode_decimal": 59001
},
{
"icon_id": "37401589",
"name": "icon_火箭",
"font_class": "icon_huojian",
"unicode": "e677",
"unicode_decimal": 58999
},
{
"icon_id": "37396974",
"name": "安装",
"font_class": "anzhuang",
"unicode": "e676",
"unicode_decimal": 58998
},
{
"icon_id": "37394752",
"name": "首页",
"font_class": "shouye",
"unicode": "e675",
"unicode_decimal": 58997
},
{
"icon_id": "2023693",
"name": "首页-首页",
"font_class": "shouye-shouye",
"unicode": "e638",
"unicode_decimal": 58936
},
{
"icon_id": "4933869",
"name": "点",
"font_class": "dian",
"unicode": "ec1e",
"unicode_decimal": 60446
},
{
"icon_id": "5387931",
"name": "箭头_向左两次_o",
"font_class": "jiantou_xiangzuoliangci_o",
"unicode": "eb93",
"unicode_decimal": 60307
},
{
"icon_id": "781940",
"name": "分类",
"font_class": "fenlei",
"unicode": "e6c3",
"unicode_decimal": 59075
},
{
"icon_id": "37134174",
"name": "切换",

View File

@ -161,4 +161,39 @@ export function urlToRouteRaw(url: string) {
})
return { path, query }
}
/**
*
* @param {Number} len
* @param {Boolean} firstU
* @param {Nubmer} radix
*/
export function guid(len = 10, firstU = true, radix = null) {
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
const uuid = []
radix = radix || chars.length
if (len) {
// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
} else {
let r
// rfc4122标准要求返回的uuid中,某些位为固定的字符
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
uuid[14] = '4'
for (let i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random() * 16
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
}
}
}
// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
if (firstU) {
uuid.shift()
return `u${uuid.join('')}`
}
return uuid.join('')
}

180
admin/src/utils/qqmap.ts Normal file
View File

@ -0,0 +1,180 @@
import { jsonp } from 'vue-jsonp'
const geometry: any = {}
/**
*
*/
export const createCircle = (map: any, geometriesData: any) => {
const TMap = (window as any).TMap
const LatLng = TMap.LatLng
geometriesData.radius = geometriesData.radius ?? 1000
geometriesData.center = geometriesData.center ?? { lat: map.getCenter().lat, lng: map.getCenter().lng }
const color = [
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255)
]
// 创建图形
const multiCircle = new TMap.MultiCircle({
map,
styles: { // 设置圆形样式
circle: new TMap.CircleStyle({
color: `rgba(${color.toString()}, .4)`,
showBorder: true,
borderColor: `rgb(${color.toString()})`,
borderWidth: 2
})
},
geometries: [
{
styleId: 'circle',
center: new LatLng(geometriesData.center.lat, geometriesData.center.lng),
radius: geometriesData.radius,
id: geometriesData.key
}
]
})
geometry[geometriesData.key] = { graphical: multiCircle }
// 创建图形编辑器
const editor = new TMap.tools.GeometryEditor({
map: map,
overlayList: [
{
overlay: multiCircle,
id: geometriesData.key,
}
],
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT,
activeOverlayId: geometriesData.key, // 激活图层
selectable: true // 开启点选功能
})
editor.on('adjust_complete', (data: any) => {
geometriesData.center = data.center
geometriesData.radius = data.radius
})
geometry[geometriesData.key] = { graphical: multiCircle, editor }
}
/**
*
* @param map
* @param geometriesData
*/
export const createPolygon = (map: any, geometriesData: any) => {
const TMap = (window as any).TMap
const LatLng = TMap.LatLng
const { lat, lng } = map.getCenter();
geometriesData.paths = geometriesData.paths ?? [
{ lat: lat + 0.01, lng: lng + 0.01 },
{ lat: lat - 0.01, lng: lng + 0.01 },
{ lat: lat - 0.01, lng: lng - 0.01 },
{ lat: lat + 0.01, lng: lng - 0.01 }
]
const color = [
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255)
]
const multiPolygon = new TMap.MultiPolygon({
map: map,
styles: {
polygon: new TMap.PolygonStyle({
color: `rgba(${color.toString()}, .4)`,
showBorder: true,
borderColor: `rgb(${color.toString()})`,
borderWidth: 2
})
},
geometries: [
{
id: geometriesData.key,
styleId: 'polygon',
paths: geometriesData.paths.map((item: any) => {
return new LatLng(item.lat, item.lng)
})
}
]
});
const editor = new TMap.tools.GeometryEditor({
map: map,
overlayList: [
{
overlay: multiPolygon,
id: geometriesData.key,
}
],
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT,
activeOverlayId: geometriesData.key, // 激活图层
selectable: true, // 开启点选功能
})
editor.on('adjust_complete', (data: any) => {
geometriesData.paths = data.paths
})
geometry[geometriesData.key] = { graphical: multiPolygon, editor }
}
/**
*
* @param key
*/
export const deleteGeometry = (key: string) => {
geometry[key].graphical.remove(key)
geometry[key].editor.delete()
}
/**
*
* @param key
*/
export const selectGeometry = (key: string) => {
geometry[key].editor.select([key])
}
/**
*
* @param map
* @returns
*/
export const createMarker = (map: any) => {
const TMap = (window as any).TMap
const LatLng = TMap.LatLng
return new TMap.MultiMarker({
map,
geometries: [
{
id: 'center',
position: map.getCenter(),
}
]
});
}
/**
*
* @param params
*/
export const latLngToAddress = (params: any) => {
return jsonp(`https://apis.map.qq.com/ws/geocoder/v1/?key=${params.mapKey}&location=${params.lat},${params.lng}&output=jsonp&callback=callback`)
}
/**
*
*/
export const addressToLatLng = (params: any) => {
return jsonp(`https://apis.map.qq.com/ws/geocoder/v1/?key=${params.mapKey}&address=${params.address}&output=jsonp&callback=callback`)
}

View File

@ -7,11 +7,13 @@ import useUserStore from '@/stores/modules/user'
import storage from '@/utils/storage'
interface RequestConfig extends AxiosRequestConfig {
showSuccessMessage?: boolean
showSuccessMessage?: boolean,
showErrorMessage?: boolean
}
interface InternalRequestConfig extends InternalAxiosRequestConfig {
showSuccessMessage?: boolean
showSuccessMessage?: boolean,
showErrorMessage?: boolean
}
interface requestResponse extends AxiosResponse {
@ -48,18 +50,18 @@ class Request {
// 全局响应拦截器
this.instance.interceptors.response.use(
(response: requestResponse) => {
if (response.request.responseType != 'blob') {
const res = response.data
if (res.code != 1) {
this.handleAuthError(res.code)
if (res.code != 401) ElMessage({ message: res.msg, type: 'error' })
return Promise.reject(new Error(res.msg || 'Error'))
} else {
if (response.config.showSuccessMessage) ElMessage({ message: res.msg, type: 'success' })
return res
}
}
return response.data
if (response.request.responseType != 'blob') {
const res = response.data
if (res.code != 1) {
this.handleAuthError(res.code)
if (res.code != 401 && response.config.showErrorMessage !== false) ElMessage({ message: res.msg, type: 'error' })
return Promise.reject(new Error(res.msg || 'Error'))
} else {
if (response.config.showSuccessMessage) ElMessage({ message: res.msg, type: 'success' })
return res
}
}
return response.data
},
(err: any) => {
this.handleNetworkError(err)

View File

@ -227,6 +227,12 @@ const test = {
*/
regExp(o) {
return o && Object.prototype.toString.call(o) === '[object RegExp]'
},
/**
*
*/
require(value: string) {
return /^\s*$/.test(value)
}
}