update admin

This commit is contained in:
全栈小学生 2023-09-15 18:51:40 +08:00
parent 1580d8607a
commit c0ab4b1c69
35 changed files with 2006 additions and 388 deletions

View File

@ -1,5 +1,5 @@
// Generated by 'unplugin-auto-import' // Generated by 'unplugin-auto-import'
export {} export {}
declare global { declare global {
const ElNotification: typeof import('element-plus/es')['ElNotification']
} }

View File

@ -32,7 +32,6 @@ declare module '@vue/runtime-core' {
ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
@ -70,6 +69,7 @@ declare module '@vue/runtime-core' {
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect'] ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree'] ElTree: typeof import('element-plus/es')['ElTree']

View File

@ -5,7 +5,7 @@ import request from '@/utils/request'
* @returns * @returns
*/ */
export function getAddonLocal(params: Record<string, any>) { export function getAddonLocal(params: Record<string, any>) {
return request.get('addon/local', params, {showSuccessMessage: true}) return request.get('addon/local', params)
} }
/** /**
@ -33,13 +33,22 @@ export function installAddon(params: Record<string, any>) {
return request.post(`addon/install/${params.addon}`, params) return request.post(`addon/install/${params.addon}`, params)
} }
/**
*
* @param params
* @returns
*/
export function cloudInstallAddon(params: Record<string, any>) {
return request.post(`addon/cloudinstall/${params.addon}`, params)
}
/** /**
* *
* @param params * @param params
* @returns * @returns
*/ */
export function uninstallAddon(params: Record<string, any>) { export function uninstallAddon(params: Record<string, any>) {
return request.post(`addon/uninstall/${params.addon}`, params, {showSuccessMessage: true}) return request.post(`addon/uninstall/${params.addon}`, params, { showSuccessMessage: true })
} }
/** /**
@ -48,23 +57,22 @@ export function uninstallAddon(params: Record<string, any>) {
* @returns * @returns
*/ */
export function preInstallCheck(addon: string) { export function preInstallCheck(addon: string) {
return request.get(`addon/install/check/${addon}`, {timeout: 30 * 1000}) return request.get(`addon/install/check/${addon}`, { timeout: 30 * 1000 })
} }
/** /**
* *
* @param addon * @returns
* @param key
* @returns
*/ */
export function getAddonInstallTaskState(addon: string, key: string) { export function getAddonInstalltask() {
return request.get(`addon/install/${addon}/status/${key}`) return request.get('addon/installtask')
} }
/** /**
* *
* @param addon * @param addon
* @returns
*/ */
export function executeInstall(addon: string) { export function getAddonCloudInstallLog(addon: string) {
return request.post(`addon/install/execute/${addon}`, {}) return request.get(`addon/cloudinstall/${addon}`)
} }

88
admin/src/app/api/dict.ts Normal file
View File

@ -0,0 +1,88 @@
import request from '@/utils/request'
// USER_CODE_BEGIN -- sys_dict
/**
*
* @param params
* @returns
*/
export function getDictList(params: Record<string, any>) {
return request.get(`dict/dict`, {params})
}
/**
*
* @param id id
* @returns
*/
export function getDictInfo(id: number) {
return request.get(`dict/dict/${id}`);
}
/**
*
* @param params
* @returns
*/
export function addDict(params: Record<string, any>) {
return request.post('dict/dict', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function addDictData(params: Record<string, any>) {
return request.put(`dict/dict/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function editDict(params: Record<string, any>) {
return request.put(`dict/dict/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @returns
*/
export function deleteDict(id: number) {
return request.delete(`dict/dict/${id}`, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function setDictData(id:number,params: Record<string, any>) {
return request.put(`dict/dictionary/${id}`, params, { showErrorMessage: true,showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getDictAll() {
return request.get(`dict/all`)
}
// USER_CODE_END -- sys_dict
/**
*
* @param id
* @param params
* @returns
*/
export function useDictionary(type: string) {
return request.get(`dict/dictionary/type/${type}`)
}

View File

@ -310,7 +310,7 @@ export function getCashOutList(params: Record<string, any>) {
* @param id * @param id
*/ */
export function getCashOutDetail(id: number) { export function getCashOutDetail(id: number) {
return request.get(`member/cash_out/${id}`, {}) return request.get(`member/cash_out/${id}`)
} }
/** /**

View File

@ -61,7 +61,7 @@ export function getSmsList() {
* @returns * @returns
*/ */
export function getSmsInfo(sms_type: string) { export function getSmsInfo(sms_type: string) {
return request.get(`notice/notice/sms/${sms_type}`,) return request.get(`notice/notice/sms/${sms_type}`)
} }
/** /**

View File

@ -4,7 +4,7 @@ import request from '@/utils/request'
* *
* @returns * @returns
*/ */
export function getaddonDevelop(params: Record<string, any>) { export function getAddonDevelop(params: Record<string, any>) {
return request.get(`addon_develop`,{params}); return request.get(`addon_develop`,{params});
} }
/** /**
@ -19,7 +19,7 @@ export function getAddontype() {
* @returns * @returns
*/ */
export function getAddonDevelop(key:any) { export function getAddonDevelopInfo(key:any) {
return request.get(`addon_develop/${key}`) return request.get(`addon_develop/${key}`)
} }
/** /**
@ -147,13 +147,13 @@ export function getSystem() {
* *
*/ */
export function getGeneratorAllModel(params:any) { export function getGeneratorAllModel(params:any) {
return request.get(`generator/all_model`,params) return request.get(`generator/all_model`,{params})
} }
/** /**
* *
*/ */
export function getGeneratorTableColumn(params:any){ export function getGeneratorTableColumn(params:any){
return request.get(`generator/table_column`,params) return request.get(`generator/table_column`,{params})
} }
/** /**
* *

View File

@ -33,5 +33,11 @@
"addonVersion": "插件版本", "addonVersion": "插件版本",
"versionCode": "版本号", "versionCode": "版本号",
"createTime": "发布时间", "createTime": "发布时间",
"buyLabel": "已购买" "buyLabel": "已购买",
"installTips": "安装后需手动更新插件引用的依赖和编译各个端口的前端源码",
"localInstall":"本地安装",
"cloudInstall": "一键云安装",
"cloudInstallTips": "云安装可实现一键安装,安装后无需手动更新依赖和编译前端源码",
"installingTips": "有插件正在安装中请等待安装完成之后再进行其他操作,点击查看",
"installPercent": "安装进度"
} }

View File

@ -33,5 +33,6 @@
"preview": "预览", "preview": "预览",
"authTips": "上传代码需先绑定授权码如果已有授权请先进行绑定没有授权可到niucloud官网购买云服务之后再进行操作", "authTips": "上传代码需先绑定授权码如果已有授权请先进行绑定没有授权可到niucloud官网购买云服务之后再进行操作",
"toBind": "绑定授权", "toBind": "绑定授权",
"toNiucloud": "去niucloud官网" "toNiucloud": "去niucloud官网",
"failReason": "失败原因:"
} }

View File

@ -0,0 +1,23 @@
{
"name":"字典名称",
"namePlaceholder":"请输入字典名称",
"key":"字典关键词",
"keyPlaceholder":"请输入字典关键词",
"data":"字典数据",
"dataPlaceholder":"请输入字典数据",
"memo":"备注",
"memoPlaceholder":"请输入备注",
"addDict":"添加数据字典",
"updateDict":"编辑数据字典",
"dictDeleteTips":"确定要删除该数据吗?",
"dictData":"数据管理",
"addDictData":"添加数据",
"editDictData":"编辑数据",
"dataName":"数据名称",
"dataNamePlaceholder":"请输入数据名称",
"dataValue":"数据值",
"dataValuePlaceholder":"请输入数据值",
"sortPlaceholder":"数值越大越排前",
"momePlaceholder":"请输入备注",
"createTime":"创建时间"
}

View File

@ -8,7 +8,7 @@
"keyPlaceholder":"请输入插件标识", "keyPlaceholder":"请输入插件标识",
"keyPlaceholderErr":"插件标识格式不正确,只能以字母开头且只能输入字母、数字、下划线", "keyPlaceholderErr":"插件标识格式不正确,只能以字母开头且只能输入字母、数字、下划线",
"keyPlaceholder1":"插件标识指开发插件的文件夹名称,申请之后不能修改(只能包括字母、数字和下划线且只能以字母开头格式如f1111、f11_22)", "keyPlaceholder1":"插件标识指开发插件的文件夹名称,申请之后不能修改(只能包括字母、数字和下划线且只能以字母开头格式如f1111、f11_22)",
"keyPlaceholder2":"设置后要检测标识与niucloud应用是否重复如果重复则无法进行上传发布", "keyPlaceholder2":"插件标识设置后建议进行插件标识检测如果当前插件标识已经在niucloud官方市场注册则只能在本地使用无法在官方市场发布销售",
"desc":"插件描述", "desc":"插件描述",
"descPlaceholder":"请输入插件描述", "descPlaceholder":"请输入插件描述",
"author":"作者", "author":"作者",
@ -24,10 +24,11 @@
"typePlaceholder":"请选择插件类型", "typePlaceholder":"请选择插件类型",
"typePlaceholder1":"应用指独立开发的系统比如商城零售erp等", "typePlaceholder1":"应用指独立开发的系统比如商城零售erp等",
"typePlaceholder2":"插件:指不是独立的系统,可以是辅助应用的插件比如商城的拼团,也可以是独立的插件比如系统表单等", "typePlaceholder2":"插件:指不是独立的系统,可以是辅助应用的插件比如商城的拼团,也可以是独立的插件比如系统表单等",
"supportApp":"前置插件", "supportType":"所属应用",
"supportAppPlaceholder":"请输入前置插件", "supportApp":"支持应用",
"supportAppPlaceholder":"请选择支持应用",
"GeneratePlugins":"生成插件", "GeneratePlugins":"生成插件",
"successText":"当前插件标识无重复", "successText":"检测当前插件标识尚未在应用市场注册插件开发后可以在niucloud官方市场发布",
"warningText":"当前插件标识重复,继续使用则无法进行上传发布", "warningText":"检测到当前插件标识已经在niucloud官方市场注册开发的插件只能在本地使用无法在官方市场发布销售",
"onSaveSuccessText":"插件生成成功" "onSaveSuccessText":"插件生成成功"
} }

View File

@ -18,6 +18,7 @@
"tableNamePlaceholder":"请输入表名", "tableNamePlaceholder":"请输入表名",
"tableContentPlaceholder":"请输入描述", "tableContentPlaceholder":"请输入描述",
"addonPlaceholder":"请选择插件", "addonPlaceholder":"请选择插件",
"addonPlaceholder1":"/",
"moduleNamePlaceholder":"请输入模块名", "moduleNamePlaceholder":"请输入模块名",
"classNamePlaceholder":"请输入类名", "classNamePlaceholder":"请输入类名",
"editTypePlaceholder":"请选择编辑方式", "editTypePlaceholder":"请选择编辑方式",
@ -52,6 +53,7 @@
"isQuery":"查询", "isQuery":"查询",
"queryType":"搜索方式", "queryType":"搜索方式",
"formType": "表单类型", "formType": "表单类型",
"verifyType": "验证类型",
"formInput":"文本框", "formInput":"文本框",
"formTextarea":"文本域", "formTextarea":"文本域",
"formSelect":"下拉框", "formSelect":"下拉框",
@ -60,6 +62,7 @@
"formDateTime":"日期", "formDateTime":"日期",
"formImageSelect":"图片上传", "formImageSelect":"图片上传",
"formEditor":"富文本", "formEditor":"富文本",
"formNumber":"数字框",
"pk":"主键", "pk":"主键",
"status": "状态", "status": "状态",
"string": "字符串", "string": "字符串",
@ -102,8 +105,32 @@
"foreignKeyPlaceholder":"请输入外键", "foreignKeyPlaceholder":"请输入外键",
"addons":"关联应用", "addons":"关联应用",
"addonsPlaceholder":"请选择应用", "addonsPlaceholder":"请选择应用",
"saveAndSync":"保存并同步", "saveAndSync":"同步代码",
"saveAndDownload":"保存并下载", "saveAndDownload":"下载代码",
"saveAndSyncText":"同步的代码与项目产生冲突,是否确认覆盖?", "saveAndSyncText":"同步的代码与项目产生冲突,是否确认覆盖?",
"saveAndSyncText1":"同步的代码会加入到项目代码中,是否确认继续" "saveAndSyncText1":"同步的代码会加入到项目代码中,是否确认继续",
"mobileVerify":"手机号验证",
"numberVerify":"整数验证",
"idCardVerify":"身份证验证",
"emailVerify":"邮箱验证",
"maxVerify":"最大输入120个字符",
"minVerify":"最小输入1个字符",
"maxLabel":"最大输入字符",
"minLabel":"最小输入字符",
"minPlaceholder":"最小输入字符不能为空",
"minPlaceholder1":"最小输入字符不可大于最大输入字符",
"maxPlaceholder":"最大字输入字符不能为空",
"maxPlaceholder1":"最大输入字符不可小于最小输入字符",
"maxLabel1":"最大输入数",
"minLabel1":"最小输入数",
"min1Placeholder":"最小输入数不能为空",
"min1Placeholder1":"最小输入数不可大于最大输入数",
"max1Placeholder":"最大字输入数不能为空",
"max1Placeholder1":"最大输入数不可小于最小输入数",
"between":"输入字符区间",
"setUp":"设置",
"dictType":"数据字典",
"dictTypePlaceholder":"请选择数据字典",
"dictTypePlaceholder1":"部分字段未选择数据字典"
} }

View File

@ -44,5 +44,6 @@
"btn5":"官方市场", "btn5":"官方市场",
"saveAndSync":"同步代码", "saveAndSync":"同步代码",
"saveAndSyncText":"同步的代码与项目产生冲突,是否确认覆盖?", "saveAndSyncText":"同步的代码与项目产生冲突,是否确认覆盖?",
"saveAndSyncText1":"同步的代码会加入到项目代码中,是否确认继续" "saveAndSyncText1":"同步的代码会加入到项目代码中,是否确认继续",
"addonName": "所属插件"
} }

View File

@ -227,10 +227,8 @@
<el-scrollbar max-height="50vh"> <el-scrollbar max-height="50vh">
<div class="min-h-[150px]"> <div class="min-h-[150px]">
<div class="bg-[#fff] my-3" v-if="installCheckResult.dir"> <div class="bg-[#fff] my-3" v-if="installCheckResult.dir">
<el-alert :title="t('jobError')" type="error" :closable="false" class="mt-[20px]"
v-if="!installCheckResult.job_normal" />
<p class="pt-[20px] pl-[20px] ">{{ t('dirPermission') }}</p> <p class="pt-[20px] pl-[20px] ">{{ t('dirPermission') }}</p>
<div class="px-[20px] text-[14px]"> <div class="px-[20px] pt-[10px] text-[14px]">
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]"> <el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
<el-col :span="12"> <el-col :span="12">
<span>{{ t('path') }}</span> <span>{{ t('path') }}</span>
@ -277,45 +275,27 @@
</el-row> </el-row>
</div> </div>
</div> </div>
<div class="bg-[#fff] my-3">
<p class="pl-[20px] ">{{ t('process') }}</p>
<div class="px-[20px] text-[14px]">
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
<el-col :span="12">
<span>{{ t('name') }}</span>
</el-col>
<el-col :span="6">
<span>{{ t('demand') }}</span>
</el-col>
<el-col :span="6">
<span>{{ t('status') }}</span>
</el-col>
</el-row>
<el-row class="pb-[10px] items pl-[15px]">
<el-col :span="12">
<span>php think queue:listen</span>
</el-col>
<el-col :span="6">
<span>{{ t('open') }}</span>
</el-col>
<el-col :span="6">
<span v-if="installCheckResult.job_normal">
<el-icon color="green"><Select /></el-icon>
</span>
<span v-else>
<el-icon color="red">
<CloseBold />
</el-icon>
</span>
</el-col>
</el-row>
</div>
</div>
</div> </div>
</el-scrollbar> </el-scrollbar>
<div class="flex justify-end"> <div class="flex justify-end" v-if="mode == 'development'">
<el-button type="primary" :disabled="!installCheckResult.is_pass" @click="handleInstall">{{ t('install') <el-tooltip effect="dark" :content="t('installTips')" placement="top">
}}</el-button> <el-button type="default" :disabled="!installCheckResult.is_pass || cloudInstalling"
:loading="localInstalling" @click="handleInstall">{{
t('localInstall')
}}</el-button>
</el-tooltip>
<el-tooltip effect="dark" :content="t('cloudInstallTips')" placement="top">
<el-button type="primary" :disabled="!installCheckResult.is_pass || localInstalling"
:loading="cloudInstalling" @click="handleCloudInstall">{{
t('cloudInstall')
}}</el-button>
</el-tooltip>
</div>
<div class="flex justify-end" v-else>
<el-button type="primary" :disabled="!installCheckResult.is_pass" :loading="cloudInstalling"
@click="handleCloudInstall">{{
t('cloudInstall')
}}</el-button>
</div> </div>
</div> </div>
<div v-show="installStep == 1" class="h-[50vh] mt-[20px]"> <div v-show="installStep == 1" class="h-[50vh] mt-[20px]">
@ -334,17 +314,18 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, computed } from 'vue' import { ref, watch, computed, h } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getAddonLocal, uninstallAddon, installAddon, preInstallCheck, getAddonInstallTaskState, executeInstall } from '@/app/api/addon' import { getAddonLocal, uninstallAddon, installAddon, preInstallCheck, cloudInstallAddon, getAddonInstalltask, getAddonCloudInstallLog } from '@/app/api/addon'
import { downloadVersion } from '@/app/api/module' import { downloadVersion } from '@/app/api/module'
import { TabsPaneContext, ElMessageBox } from 'element-plus' import { TabsPaneContext, ElMessageBox, ElNotification } from 'element-plus'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import { Terminal, api as terminalApi } from 'vue-web-terminal' import { Terminal, api as terminalApi } from 'vue-web-terminal'
const activeName = ref('installed') const activeName = ref('installed')
const loading = ref<Boolean>(false) const loading = ref<Boolean>(false)
const showType = ref('large') const showType = ref('large')
const mode = ref(import.meta.env.MODE)
const downEvent = (key: string) => { const downEvent = (key: string) => {
downloadVersion(key).then(() => { downloadVersion(key).then(() => {
@ -417,102 +398,135 @@ const handleClick = (tab: TabsPaneContext, event: Event) => {
const currAddon = ref('') const currAddon = ref('')
// //
const installShowDialog = ref(false) const installShowDialog = ref(false)
//
let installTask: AnyObject = {}
//
let currTask: string = ''
//
let executedTask: string[] = []
// //
const installStep = ref(0) const installStep = ref(0)
// //
const installCheckResult = ref({}) const installCheckResult = ref({})
// //
const installWarning = ref<string[]>([]) const installWarning = ref<string[]>([])
//
let timer: null | any = null
/** /**
* 安装 * 安装
* @param key * @param key
*/ */
const installAddonFn = (key: string) => { const installAddonFn = (key: string) => {
currAddon.value = key currAddon.value = key
installAddon({ addon: key }).then(res => { installStep.value = 0
installStep.value = 0 installWarning.value = []
executedTask = [] installShowDialog.value = true
installWarning.value = []
installTask = makeIterator(Object.keys(res.data))
currTask = installTask.next().value
installShowDialog.value = true
preInstallCheck(key).then(res => { preInstallCheck(key).then(res => {
installCheckResult.value = res.data installCheckResult.value = res.data
}).catch(() => { }) }).catch(() => { })
}).catch(() => {
})
} }
/** /**
* 创建遍历器 * 获取正在进行的安装任务
* @param arr array
*/ */
const makeIterator = (arr: string[]) => { let notificationEl = null
let nextIndex = 0 const getInstallTask = (first: boolean = true) => {
return { getAddonInstalltask().then(res => {
next: function () { if (res.data) {
return nextIndex < arr.length if (first) {
? { value: arr[nextIndex++] } installLog = []
: { done: true } currAddon.value = res.data.addon
if (!installShowDialog.value) {
notificationEl = ElNotification.success({
title: t('warning'),
dangerouslyUseHTMLString: true,
message: h('div', {}, [
t('installingTips'),
h('span', { class: 'text-primary cursor-pointer', onClick: checkInstallTask }, [t('installPercent')])
]),
duration: 0,
showClose: false
})
}
}
if (res.data.error) {
return
}
if (res.data.mode == 'cloud') {
getCloudInstallLog()
}
setTimeout(() => {
getInstallTask(false)
}, 2000)
} else {
if (!first) {
installStep.value += 2
localListFn()
notificationEl.close()
}
} }
} })
}
getInstallTask()
const checkInstallTask = () => {
installShowDialog.value = true
installStep.value = 1
} }
const localInstalling = ref(false)
/**
* 安装插件
*/
const handleInstall = () => { const handleInstall = () => {
if (!installCheckResult.value.is_pass) return if (!installCheckResult.value.is_pass || localInstalling.value) return
installStep.value += 1 localInstalling.value = true
terminalApi.execute('my-terminal', 'clear') installAddon({ addon: currAddon.value }).then(res => {
installStep.value += 2
localListFn()
localInstalling.value = false
}).catch((res) => {
localInstalling.value = false
})
}
executeInstall(currAddon.value) const cloudInstalling = ref(false)
.then(() => { /**
timer = setInterval(() => { * 云安装插件
getAddonInstallTaskState(currAddon.value, currTask).then(({ data }) => { */
if (!executedTask.includes(currTask)) { const handleCloudInstall = () => {
terminalApi.execute('my-terminal', data.command) if (!installCheckResult.value.is_pass || cloudInstalling.value) return
executedTask.push(currTask) cloudInstalling.value = true
}
switch (data.state) { cloudInstallAddon({ addon: currAddon.value }).then(res => {
case 'success': installStep.value += 1
terminalApi.pushMessage('my-terminal', { content: `${data.desc}执行成功`, class: 'success' }) terminalApi.execute('my-terminal', 'clear')
if (data.step == 'installComplete') { terminalApi.pushMessage('my-terminal', { content: '开始安装插件', class: 'info' })
clearInterval(timer) getInstallTask()
installStep.value += 2 cloudInstalling.value = false
localListFn() }).catch((res) => {
} else { cloudInstalling.value = false
currTask = installTask.next().value })
} }
break
case 'fail': let installLog: string[] = []
terminalApi.pushMessage('my-terminal', { content: `${data.desc}执行失败`, class: 'error' }) const getCloudInstallLog = () => {
terminalApi.pushMessage('my-terminal', { content: `失败原因:${data.error}` }) getAddonCloudInstallLog(currAddon.value)
clearInterval(timer) .then(res => {
break const data = res.data.data ?? []
case 'warn': if (data[0] && data[0].length && installShowDialog.value == true) {
terminalApi.pushMessage('my-terminal', { content: data.error, class: 'warning' }) data[0].forEach(item => {
installWarning.value.push(data.error) if (!installLog.includes(item.action)) {
break terminalApi.pushMessage('my-terminal', { content: `正在执行:${item.action}` })
installLog.push(item.action)
if (item.code == 0) {
terminalApi.pushMessage('my-terminal', { content: item.msg, class: 'error' })
}
} }
}) })
}, 2000) }
})
.catch(() => {
notificationEl?.close()
}) })
.catch()
} }
//
watch(installShowDialog, (nval) => {
if (!installShowDialog.value) clearInterval(timer)
})
watch(currAddon, (nval) => { watch(currAddon, (nval) => {
installCheckResult.value = {} installCheckResult.value = {}
}) })

View File

@ -91,7 +91,7 @@ import { t } from '@/lang'
import type { FormInstance } from 'element-plus' import type { FormInstance } from 'element-plus'
import selectMenuItem from './select-menu-item.vue' import selectMenuItem from './select-menu-item.vue'
import { addMenu, editMenu, getMenuInfo, getSystemMenu,getAddonMenu } from '@/app/api/sys' import { addMenu, editMenu, getMenuInfo, getSystemMenu,getAddonMenu } from '@/app/api/sys'
import { getaddonDevelop } from '@/app/api/tools' import { getAddonDevelop } from '@/app/api/tools'
const showDialog = ref(false) const showDialog = ref(false)
const method = ref('post') const method = ref('post')
const loading = ref(false) const loading = ref(false)
@ -169,8 +169,8 @@ const formRules = computed(() => {
} }
}) })
// //
const getaddonDevelopFn = async () => { const getAddonDevelopFn = async () => {
let { data } = await getaddonDevelop({}) let { data } = await getAddonDevelop({})
addonLst.value = [{ title: "系统", key: "" }] addonLst.value = [{ title: "系统", key: "" }]
addonLst.value.push(...data) addonLst.value.push(...data)
} }
@ -226,7 +226,7 @@ const setFormData = async (row: any = null) => {
loading.value = true loading.value = true
Object.assign(formData, initialFormData) Object.assign(formData, initialFormData)
popTitle = t('addMenu') popTitle = t('addMenu')
getaddonDevelopFn() getAddonDevelopFn()
getSystemMenuFn() getSystemMenuFn()
if (row.menu_key) { if (row.menu_key) {
popTitle = t('updateMenu') popTitle = t('updateMenu')

View File

@ -26,7 +26,12 @@
</template> </template>
<el-table-column prop="version" :label="t('code')" align="left" /> <el-table-column prop="version" :label="t('code')" align="left" />
<!-- <el-table-column prop="desc" :label="t('content')" align="left" /> --> <!-- <el-table-column prop="desc" :label="t('content')" align="left" /> -->
<el-table-column prop="status_name" :label="t('status')" align="left" /> <el-table-column prop="status_name" :label="t('status')" align="left">
<template #default="{ row }">
<div>{{ row.status_name }}</div>
<div class="text-error" v-if="row.status == -1">{{ t('failReason') }}{{ row.fail_reason }}</div>
</template>
</el-table-column>
<el-table-column prop="create_time" :label="t('createTime')" align="center" /> <el-table-column prop="create_time" :label="t('createTime')" align="center" />
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="120"> <el-table-column :label="t('operation')" fixed="right" align="right" min-width="120">
<template #default="{ row, $index }"> <template #default="{ row, $index }">

View File

@ -0,0 +1,181 @@
<template>
<el-dialog v-model="showDialog" :title="t('dictData')" width="60%" class="diy-dialog-wrap" :destroy-on-close="true">
<div class="mb-[10px]">
<el-button type="primary" @click="addEvent">
{{ t('addDictData') }}
</el-button>
</div>
<el-table :data="tableDate" size="large" v-loading="loading">
<el-table-column :label="t('dataName')" prop="name" />
<el-table-column :label="t('dataValue')" prop="value" />
<el-table-column :label="t('sort')" align="center" min-width="100px" prop="sort" />
<el-table-column :label="t('memo')" prop="memo" />
<el-table-column :label="t('operation')" fixed="right" width="120">
<template #default="{ row, $index }">
<el-button type="primary" link @click="editEvent(row, $index)">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent($index)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirm()">{{
t('confirm')
}}</el-button>
</span>
</template>
<el-dialog v-model="dialogVisible" :title="type != 'edit' ? t('addDictData') : t('editDictData')" width="480"
class="diy-dialog-wrap" :destroy-on-close="true">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form">
<el-form-item :label="t('name')">
<el-input v-model="name" disabled class="input-width" />
</el-form-item>
<el-form-item :label="t('dataName')" prop="name">
<el-input v-model="formData.name" clearable :placeholder="t('dataNamePlaceholder')"
class="input-width" />
</el-form-item>
<el-form-item :label="t('dataValue')" prop="value">
<el-input v-model="formData.value" clearable :placeholder="t('dataValuePlaceholder')"
class="input-width" />
</el-form-item>
<el-form-item :label="t('sort')" prop="sort">
<div>
<el-input-number v-model="formData.sort" ::step="1" step-strictly :value-on-clear="0" :min="0" class="input-width" />
<p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">{{ t('sortPlaceholder') }}</p>
</div>
</el-form-item>
<el-form-item :label="t('memo')">
<el-input v-model="formData.memo" type="textarea" clearable :placeholder="t('momePlaceholder')"
class="input-width" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="submit(formRef)">{{
t('confirm')
}}</el-button>
</span>
</template>
</el-dialog>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { setDictData, getDictInfo } from '@/app/api/dict'
import { cloneDeep } from 'lodash-es'
let showDialog = ref(false)
const loading = ref(false)
const dialogVisible = ref(false)
const tableDate = ref<Array<any>>([])
const id = ref()
const type = ref('add')
const formRef = ref()
/**
* 表单数据
*/
const name = ref('')
const initialFormData = {
name: '',
value: '',
sort: 0,
memo:'',
}
const formData = ref({ ...initialFormData })
//
const formRules = computed(() => {
return {
name: [
{ required: true, message: t('dataNamePlaceholder'), trigger: 'blur' }
],
value: [
{ required: true, message: t('dataValuePlaceholder'), trigger: 'blur' }
],
}
})
const addEvent = () => {
type.value = 'add'
formData.value = cloneDeep(initialFormData)
dialogVisible.value = true
}
const tabelIndex = ref(0)
const editEvent = (row: any, index: number) => {
type.value = 'edit'
tabelIndex.value = index
formData.value = cloneDeep(initialFormData)
formData.value = Object.assign(formData.value,cloneDeep( row))
dialogVisible.value = true
}
/**
* 表单确认
*/
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
if (type.value != 'edit') {
tableDate.value.push(cloneDeep(formData.value))
} else {
tableDate.value.splice(tabelIndex.value, 1, cloneDeep(formData.value))
}
tableDate.value.sort(function(a,b){return b.sort-a.sort})
dialogVisible.value = false
}
})
}
const emit = defineEmits(['complete'])
/**
*删除
*/
const deleteEvent = (index: number) => {
tableDate.value.splice(index, 1)
}
/**
* 确认
* @param formEl
*/
const confirm = async () => {
loading.value = true
setDictData(id.value, { dictionary: JSON.stringify(tableDate.value) }).then(res => {
loading.value = false
showDialog.value = false
emit('complete')
}).catch(() => {
loading.value = false
})
}
const setFormData = async (row: any = null) => {
showDialog.value = true
loading.value = true
id.value = row.id
name.value = row.name
const data = await (await getDictInfo(row.id)).data
tableDate.value = data.dictionary
loading.value = false
}
defineExpose({
showDialog,
setFormData
})
</script>
<style lang="scss" scoped></style>
<style lang="scss">
.diy-dialog-wrap .el-form-item__label {
height: auto !important;
}
</style>

View File

@ -0,0 +1,176 @@
<template>
<el-dialog v-model="showDialog" :title="formData.id ? t('updateDict') : t('addDict')" width="480" class="diy-dialog-wrap"
:destroy-on-close="true">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form-item :label="t('name')" prop="name">
<el-input v-model="formData.name" clearable :placeholder="t('namePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('key')" prop="key">
<el-input v-model="formData.key" clearable :placeholder="t('keyPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('memo')">
<el-input v-model="formData.memo" type="textarea" clearable :placeholder="t('memoPlaceholder')" class="input-width" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{
t('confirm')
}}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { addDict, editDict, getDictInfo } from '@/app/api/dict'
let showDialog = ref(false)
const loading = ref(false)
/**
* 表单数据
*/
const initialFormData = {
id: '',
name: '',
key: '',
memo: '',
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
name: [
{ required: true, message: t('namePlaceholder'), trigger: 'blur' }
]
,
key: [
{ required: true, message: t('keyPlaceholder'), trigger: 'blur' }
]
,
data: [
{ required: true, message: t('dataPlaceholder'), trigger: 'blur' }
]
,
}
})
const emit = defineEmits(['complete'])
/**
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
let save = formData.id ? editDict : addDict
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
let data = formData
save(data).then(res => {
loading.value = false
showDialog.value = false
emit('complete')
}).catch(err => {
loading.value = false
})
}
})
}
const setFormData = async (row: any = null) => {
Object.assign(formData, initialFormData)
loading.value = true
if(row){
const data = await (await getDictInfo(row.id)).data
if (data) Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
}
loading.value = false
}
//
const mobileVerify = (rule: any, value: any, callback: any) => {
if (value && !/^1[3-9]\d{9}$/.test(value)) {
callback(new Error(t('generateMobile')))
} else {
callback()
}
}
//
const idCardVerify = (rule: any, value: any, callback: any) => {
if (value && !/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value)) {
callback(new Error(t('generateIdCard')))
} else {
callback()
}
}
//
const emailVerify = (rule: any, value: any, callback: any) => {
if (value && !/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)) {
callback(new Error(t('generateEmail')))
} else {
callback()
}
}
// 1
const minInputVerify = (rule: any, value: any, callback: any) => {
if (value && !/^\d{0,}$/.test(value)) {
callback(new Error(t('generateMin')))
} else {
callback()
}
}
// 150
const maxInputVerify = (rule: any, value: any, callback: any) => {
if (value && !/^\d{0,150}$/.test(value)) {
callback(new Error(t('generateMax')))
} else {
callback()
}
}
//
const numberVerify = (rule: any, value: any, callback: any) => {
if (!Number.isInteger(value)) {
callback(new Error(t('generateNumber')))
} else {
callback()
}
}
defineExpose({
showDialog,
setFormData
})
</script>
<style lang="scss" scoped></style>
<style lang="scss">
.diy-dialog-wrap .el-form-item__label{
height: auto !important;
}
</style>

View File

@ -0,0 +1,169 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-[20px]">{{pageName}}</span>
<el-button type="primary" @click="addEvent">
{{ t('addDict') }}
</el-button>
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="dictTable.searchParam" ref="searchFormRef">
<el-form-item :label="t('name')" prop="name">
<el-input v-model="dictTable.searchParam.name" :placeholder="t('namePlaceholder')" />
</el-form-item>
<el-form-item :label="t('key')" prop="key">
<el-input v-model="dictTable.searchParam.key" :placeholder="t('keyPlaceholder')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadDictList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[10px]">
<el-table :data="dictTable.data" size="large" v-loading="dictTable.loading">
<template #empty>
<span>{{ !dictTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="name" :label="t('name')" min-width="120" />
<el-table-column prop="key" :label="t('key')" min-width="120" />
<el-table-column prop="memo" :label="t('memo')" min-width="120" />
<el-table-column prop="create_time" :label="t('createTime')" min-width="120" />
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="120">
<template #default="{ row }">
<el-button type="primary" link @click="dictData(row)">{{ t('dictData') }}</el-button>
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="dictTable.page" v-model:page-size="dictTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="dictTable.total"
@size-change="loadDictList()" @current-change="loadDictList" />
</div>
</div>
<edit ref="editDictDialog" @complete="loadDictList" />
<dict ref="dictDialog" @complete="loadDictList" />
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { getDictList, deleteDict } from '@/app/api/dict'
import { img } from '@/utils/common'
import { ElMessageBox } from 'element-plus'
import Edit from '@/app/views/dict/components/edit.vue'
import dict from '@/app/views/dict/components/dict.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title;
let dictTable = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam:{
"name":"",
"key":""
}
})
const searchFormRef = ref<FormInstance>()
/**
* 获取数据字典列表
*/
const loadDictList = (page: number = 1) => {
dictTable.loading = true
dictTable.page = page
getDictList({
page: dictTable.page,
limit: dictTable.limit,
...dictTable.searchParam
}).then(res => {
dictTable.loading = false
dictTable.data = res.data.data
dictTable.total = res.data.total
}).catch(() => {
dictTable.loading = false
})
}
loadDictList()
const editDictDialog: Record<string, any> | null = ref(null)
/**
* 添加数据字典
*/
const addEvent = () => {
editDictDialog.value.setFormData()
editDictDialog.value.showDialog = true
}
/**
* 编辑数据字典
* @param data
*/
const editEvent = (data: any) => {
editDictDialog.value.setFormData(data)
editDictDialog.value.showDialog = true
}
const dictDialog: Record<string, any> | null = ref(null)
const dictData = (data: any) => {
dictDialog.value.setFormData(data)
}
/**
* 删除数据字典
*/
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('dictDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}
).then(() => {
deleteDict(id).then(() => {
loadDictList()
}).catch(() => {
})
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadDictList()
}
</script>
<style lang="scss" scoped>
/* 多行超出隐藏 */
.multi-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>

View File

@ -14,30 +14,24 @@
</div> </div>
<div class="flex flex-wrap plug-list pb-10 plug-large" v-if="applyList.list.length"> <div class="flex flex-wrap plug-list pb-10 plug-large" v-if="applyList.list.length">
<div v-for="(item, index) in applyList.list" :key="index + 'b'"> <div v-for="(item, index) in applyList.list" :key="index + 'b'">
<div v-if="appLink[item.key]" class="relative app-item cursor-pointer px-4 mr-4 mt-[20px] bg-[#f7f7f7] border-[1px] hover:border-primary"> <div v-if="appLink[item.key] && item.type == 'addon'" class="relative app-item cursor-pointer px-4 mr-4 mt-[20px] bg-[#f7f7f7] border-[1px] hover:border-primary">
<div @click="toLink(item.key)"> <div @click="toLink(item.key)" class="flex py-5 items-center">
<div class="flex py-5 items-center"> <div class="flex justify-center items-center">
<div class="flex justify-center items-center"> <el-image class="w-[40px] h-[40px]" :src="img(item.icon)" fit="contain">
<el-image class="w-[50px] h-[50px]" :src="img(item.icon)" fit="contain"> <template #error>
<template #error> <div class="image-slot">
<div class="image-slot"> <img class="w-[50px] h-[50px]" src="@/app/assets/images/index/app_default.png" />
<img class="w-[50px] h-[50px]" src="@/app/assets/images/index/app_default.png" /> </div>
</div> </template>
</template> </el-image>
</el-image>
</div>
<div class="flex flex-col justify-between text-left w-[190px]">
<p class="app-text w-[190px] text-[17px] text-[#222] pl-3">{{ item.title }}</p>
</div>
</div> </div>
<div class="border-t-[1px] border-[#e8e9eb] py-3"> <div class="flex flex-col justify-between text-left w-[190px]">
<p class="app-text text-[14px] text-[#999] w-[200px]">{{ item.desc }}</p> <p class="app-text w-[190px] text-[17px] text-[#222] pl-3">{{ item.title }}</p>
</div> </div>
</div> </div>
<div class="with-ite absolute top-0 right-0 flex flex-col hidden"> <!-- <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> <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> </div>
</div> </div>
@ -58,6 +52,7 @@ import { findFirstValidRoute } from '@/router/routers'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { t } from '@/lang' import { t } from '@/lang'
import storage from '@/utils/storage'
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter() const router = useRouter()
const applyList = reactive({ const applyList = reactive({
@ -89,6 +84,7 @@ const getAppLink = () => {
getAppLink() getAppLink()
const toLink = (addon: string) => { const toLink = (addon: string) => {
storage.set({ key: 'plugMenuTypeStorage', data: addon })
let data = userStore.appMenuList let data = userStore.appMenuList
if(!data.length){ if(!data.length){
data.push(addon) data.push(addon)

View File

@ -8,12 +8,13 @@
<img v-else src="@/app/assets/images/login/login_index_left.png" alt=""> <img v-else src="@/app/assets/images/login/login_index_left.png" alt="">
</div> </div>
<div class="login flex flex-col w-[400px] h-[400px] p-[40px]"> <div class="login flex flex-col w-[400px] h-[400px] p-[40px]">
<h3 class="text-center text-lg font-bold mb-[10px]">{{ webSite.site_name || t('siteTitle') }}</h3> <h3 class="text-center text-lg font-bold mb-[10px]">{{ webSite.site_name || t('siteTitle') }}</h3>
<h3 class="text-center text-2xl font-bold mb-[26px]">{{ t('platform') }}</h3> <h3 class="text-center text-2xl font-bold mb-[26px]">{{ t('platform') }}</h3>
<el-form :model="form" ref="formRef" :rules="formRules"> <el-form :model="form" ref="formRef" :rules="formRules">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="form.username" :placeholder="t('userPlaceholder')" @keyup.enter="handleLogin(formRef)" class="h-[40px] input-with-select"> <el-input v-model="form.username" :placeholder="t('userPlaceholder')"
@keyup.enter="handleLogin(formRef)" class="h-[40px] input-with-select">
<template #prepend> <template #prepend>
<icon name="element-User" /> <icon name="element-User" />
</template> </template>
@ -21,7 +22,9 @@
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input v-model="form.password" :placeholder="t('passwordPlaceholder')" type="password" @keyup.enter="handleLogin(formRef)" :show-password="true" class="h-[40px] input-with-select"> <el-input v-model="form.password" :placeholder="t('passwordPlaceholder')" type="password"
@keyup.enter="handleLogin(formRef)" :show-password="true"
class="h-[40px] input-with-select">
<template #prepend> <template #prepend>
<icon name="element-Lock" /> <icon name="element-Lock" />
</template> </template>
@ -29,7 +32,8 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" class="mt-[30px] h-[40px] w-full" @click="handleLogin(formRef)" :loading="loading">{{ loading ? t('logging') : t('login') }}</el-button> <el-button type="primary" class="mt-[30px] h-[40px] w-full" @click="handleLogin(formRef)"
:loading="loading">{{ loading ? t('logging') : t('login') }}</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -38,7 +42,8 @@
</el-main> </el-main>
<!-- 验证组件 --> <!-- 验证组件 -->
<verify @success="success" :mode="pop" captchaType="blockPuzzle" :imgSize="{ width: '330px', height: '155px' }" ref="verifyRef"></verify> <verify @success="success" :mode="pop" captchaType="blockPuzzle" :imgSize="{ width: '330px', height: '155px' }"
ref="verifyRef"></verify>
<!-- <el-footer></el-footer> --> <!-- <el-footer></el-footer> -->
</el-container> </el-container>
</template> </template>
@ -67,6 +72,9 @@ const setFormData = async (id: number = 0) => {
webSite.value = await (await getWebConfig()).data webSite.value = await (await getWebConfig()).data
storage.set({ key: 'siteInfo', data: webSite.value }) storage.set({ key: 'siteInfo', data: webSite.value })
} }
const routerList = ref({
tourism: "/tourism/index", vipcard: "/vipcard/index", cms: "/cms/article/list", shop: "/shop/hello_world"
})
setFormData() setFormData()
setWindowTitle(t('adminLogin')) setWindowTitle(t('adminLogin'))
@ -117,8 +125,11 @@ const handleLogin = async (formEl: FormInstance | undefined) => {
const loginFn = (data = {}) => { const loginFn = (data = {}) => {
loading.value = true loading.value = true
userStore.login({ username: form.username, password: form.password, ...data }).then(res => { userStore.login({ username: form.username, password: form.password, ...data }).then(res => {
const { query: { redirect } } = route // const { query: { redirect } } = route
const path = typeof redirect === 'string' ? redirect : '/' // const path = typeof redirect === 'string' ? redirect : '/'
let key = storage.get('menuAppStorage')
if(!key) storage.set({key:'menuAppStorage',data:'tourism'})
let path = key&&key!=''?routerList.value[key]:'/tourism/index'
router.push(path) router.push(path)
}).catch(() => { }).catch(() => {
loading.value = false loading.value = false
@ -161,5 +172,4 @@ const loginFn = (data = {}) => {
.login-main-left { .login-main-left {
display: none; display: none;
} }
} }</style>
</style>

View File

@ -15,53 +15,70 @@
</el-form-item> </el-form-item>
<el-form-item :label="t('icon')" prop="icon"> <el-form-item :label="t('icon')" prop="icon">
<div> <div>
<upload-image v-model="form.icon" /> <upload-image v-model="form.icon" />
<p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">{{ t('iconPlaceholder1') }}</p> <p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">{{ t('iconPlaceholder1') }}</p>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item :label="t('key')" prop="key"> <el-form-item :label="t('key')" prop="key">
<div> <div>
<el-input v-model="form.key" clearable :disabled="route.query.key" <el-input v-model="form.key" clearable :disabled="route.query.key"
:placeholder="t('keyPlaceholder')" class="input-width mr-[15px]" /> :placeholder="t('keyPlaceholder')" class="input-width mr-[15px]" />
<el-button v-if="!route.query.key" type="primary" :disabled="form.key == ''" <el-button v-if="!route.query.key" type="primary" :disabled="form.key == ''"
@click="getAddonDevelopCheckFn(form.key)">标识检测</el-button> @click="getAddonDevelopCheckFn(form.key)">官方市场标识检测</el-button>
<p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">{{ t('keyPlaceholder1') }}</p> <p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">{{ t('keyPlaceholder1') }}</p>
<p class="text-[12px] text-[#a9a9a9] leading-normal">{{ t('keyPlaceholder2') }}</p> <p class="text-[12px] text-[#a9a9a9] leading-normal">{{ t('keyPlaceholder2') }}</p>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item :label="t('desc')" prop="desc"> <el-form-item :label="t('desc')" prop="desc">
<el-input type="textarea" v-model="form.desc" clearable :placeholder="t('descPlaceholder')" class="input-width" /> <el-input type="textarea" v-model="form.desc" clearable :placeholder="t('descPlaceholder')"
class="input-width" />
</el-form-item> </el-form-item>
<el-form-item :label="t('author')" prop="author"> <el-form-item :label="t('author')" prop="author">
<el-input v-model="form.author" clearable :placeholder="t('authorPlaceholder')" class="input-width" /> <el-input v-model="form.author" clearable :placeholder="t('authorPlaceholder')" class="input-width" />
</el-form-item> </el-form-item>
<el-form-item :label="t('version')" prop="version"> <el-form-item :label="t('version')" prop="version">
<div> <div>
<el-input v-model="form.version" clearable :placeholder="t('versionPlaceholder')" class="input-width" <el-input v-model="form.version" clearable :placeholder="t('versionPlaceholder')"
onkeyup="this.value = this.value.replace(/[^\d\.]/g,'');" /> class="input-width" onkeyup="this.value = this.value.replace(/[^\d\.]/g,'');" />
<p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">{{ t('versionPlaceholder1') }}</p> <p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">{{ t('versionPlaceholder1') }}</p>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item :label="t('cover')" prop="cover"> <el-form-item :label="t('cover')" prop="cover">
<div> <div>
<upload-image v-model="form.cover" /> <upload-image v-model="form.cover" />
<p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">{{ t('coverPlaceholder1') }}</p> <p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">{{ t('coverPlaceholder1') }}</p>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item :label="t('type')" prop="type"> <el-form-item :label="t('type')" prop="type">
<div> <div>
<el-select v-model="form.type" :placeholder="t('typePlaceholder')" class="input-width" clearable <el-select v-model="form.type" :placeholder="t('typePlaceholder')" class="input-width" clearable
@change="typeChange"> @change="typeChange">
<el-option v-for="(item, key) in options" :key="key" :label="item" :value="key" /> <el-option v-for="(item, key) in options" :key="key" :label="item" :value="key" />
</el-select> </el-select>
<p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">{{ t('typePlaceholder1') }}</p> <p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">{{ t('typePlaceholder1') }}</p>
<p class="text-[12px] text-[#a9a9a9] leading-normal">{{ t('typePlaceholder2') }}</p> <p class="text-[12px] text-[#a9a9a9] leading-normal">{{ t('typePlaceholder2') }}</p>
</div> </div>
</el-form-item> </el-form-item>
<template v-if="form.type === 'addon'">
<el-form-item :label="t('supportType')">
<div>
<el-select v-model="form.support_type" class="input-width" @change="typeChange">
<el-option label="通用插件" :value="1" />
<el-option label="支持应用" :value="2" />
</el-select>
</div>
</el-form-item>
<el-form-item :label="t('supportApp')" prop="support_app" v-if="form.support_type!=1">
<el-select v-model="form.support_app" :placeholder="t('supportAppPlaceholder')" class="input-width">
<el-option v-for="(item, index) in AppLst" :label="item.title" :value="item.key"
:key="index" />
</el-select>
</el-form-item>
</template>
<!-- <el-form-item v-if="form.type != 'app'" :label="t('supportApp')" prop="support_app"> <!-- <el-form-item v-if="form.type != 'app'" :label="t('supportApp')" prop="support_app">
<el-input v-model="form.support_app" clearable :placeholder="t('supportAppPlaceholder')" <el-input v-model="form.support_app" clearable :placeholder="t('supportAppPlaceholder')"
class="input-width" /> class="input-width" />
@ -79,8 +96,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getAddontype, addAddonDevelop, editAddonDevelop, getAddonDevelop, getAddonDevelopCheck } from '@/app/api/tools' import { getAddontype, addAddonDevelop, editAddonDevelop, getAddonDevelopInfo, getAddonDevelopCheck, getAddonDevelop } from '@/app/api/tools'
import { ElMessageBox,ElMessage } from 'element-plus' import { getAddonList} from '@/app/api/sys'
import { ElMessageBox, ElMessage } from 'element-plus'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -94,7 +112,8 @@ const form = ref({
version: "", version: "",
cover: "", cover: "",
type: "", type: "",
support_app: "" support_app: "",
support_type: 1
}) })
const options = ref([]) const options = ref([])
const loading = ref(false) const loading = ref(false)
@ -147,25 +166,35 @@ const rules = ref({
type: [ type: [
{ required: true, message: t('typePlaceholder'), trigger: 'change' }, { required: true, message: t('typePlaceholder'), trigger: 'change' },
], ],
support_app: [
{ required: true, message: t('typePlaceholder'), trigger: 'change' },
],
}) })
onMounted(async () => { onMounted(async () => {
let res = await getAddontype() let res = await getAddontype()
options.value = res.data options.value = res.data
if (route.query.key) getAddonDevelopFn(route.query.key) if (route.query.key) getAddonDevelopInfoFn(route.query.key)
}) })
const typeChange = () => { const typeChange = () => {
form.value.support_app = '' form.value.support_app = ''
} }
// //
const getAddonDevelopFn = (key: any) => { const getAddonDevelopInfoFn = (key: any) => {
loading.value = true loading.value = true
getAddonDevelop(key).then(res => { getAddonDevelopInfo(key).then(res => {
form.value = Object.assign(form.value, res.data) form.value = Object.assign(form.value, res.data)
loading.value = false loading.value = false
}).catch(()=>{ }).catch(() => {
loading.value = false loading.value = false
}) })
} }
//app
const AppLst = ref<Array<any>>([])
const getAddonListFn = async () => {
let { data } = await getAddonList({})
AppLst.value = data
}
getAddonListFn()
const getAddonDevelopCheckFn = (key: any) => { const getAddonDevelopCheckFn = (key: any) => {
getAddonDevelopCheck(key).then(res => { getAddonDevelopCheck(key).then(res => {
ElMessageBox.alert(res.data ? t('warningText') : t('successText'), t('warning'), { ElMessageBox.alert(res.data ? t('warningText') : t('successText'), t('warning'), {
@ -193,12 +222,12 @@ const onSave = async (formEl: FormInstance | undefined) => {
message: t('onSaveSuccessText'), message: t('onSaveSuccessText'),
type: 'success', type: 'success',
}) })
setTimeout(()=>{ setTimeout(() => {
window.addonActiveName='pluginList' window.addonActiveName = 'pluginList'
router.push({ path: "/tools/addon"}) router.push({ path: "/tools/addon" })
},650) }, 650)
}).catch(() => { }).catch(() => {
loading.value = false loading.value = false
// showDialog.value = false // showDialog.value = false

View File

@ -148,7 +148,7 @@
<el-input v-model="params.search" :placeholder="t('titlePlaceholder')" /> <el-input v-model="params.search" :placeholder="t('titlePlaceholder')" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="getaddonDevelopFn">{{ t('search') }}</el-button> <el-button type="primary" @click="getAddonDevelopFn">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button> <el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -200,7 +200,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, toRefs, ref, onMounted } from 'vue' import { reactive, toRefs, ref, onMounted } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getaddonDevelop, deleteAddonDevelop, addonDevelopBuild,addonDevelopDownload } from '@/app/api/tools' import { getAddonDevelop, deleteAddonDevelop, addonDevelopBuild,addonDevelopDownload } from '@/app/api/tools'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
@ -228,11 +228,11 @@ onMounted(() => {
state.activeName = window.addonActiveName + '' state.activeName = window.addonActiveName + ''
window.addonActiveName = null window.addonActiveName = null
} }
getaddonDevelopFn() getAddonDevelopFn()
}) })
const getaddonDevelopFn = () => { const getAddonDevelopFn = () => {
loading.value = true loading.value = true
getaddonDevelop(state.params).then(res => { getAddonDevelop(state.params).then(res => {
state.data = res.data state.data = res.data
loading.value = false loading.value = false
}).catch(() => { }).catch(() => {
@ -244,7 +244,7 @@ const getaddonDevelopFn = () => {
const resetForm = (formEl: FormInstance | undefined) => { const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return if (!formEl) return
formEl.resetFields(); formEl.resetFields();
getaddonDevelopFn(); getAddonDevelopFn();
} }
const editEvent = (key: any) => { const editEvent = (key: any) => {
router.push({ path: '/tools/addon_edit', query: { key } }) router.push({ path: '/tools/addon_edit', query: { key } })
@ -296,7 +296,7 @@ const deleteEvent = (key: any) => {
).then(() => { ).then(() => {
loading.value = true loading.value = true
deleteAddonDevelop(key).then(() => { deleteAddonDevelop(key).then(() => {
getaddonDevelopFn() getAddonDevelopFn()
}).catch(() => { }).catch(() => {
loading.value = false loading.value = false
}) })

View File

@ -44,7 +44,7 @@
import { ref, reactive, computed, toRaw, } from 'vue' import { ref, reactive, computed, toRaw, } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import type { FormInstance } from 'element-plus' import type { FormInstance } from 'element-plus'
import { getGeneratorAllModel, getGeneratorTableColumn,getaddonDevelop } from '@/app/api/tools' import { getGeneratorAllModel, getGeneratorTableColumn,getAddonDevelop } from '@/app/api/tools'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
const showDialog = ref(false) const showDialog = ref(false)
@ -80,6 +80,9 @@ const formRules = computed(() => {
name: [ name: [
{ required: true, message: t('associatedNamePlaceholder'), trigger: 'blur' } { required: true, message: t('associatedNamePlaceholder'), trigger: 'blur' }
], ],
addon: [
{ required: true, message: t('addonsPlaceholder'), trigger: 'change' }
],
model: [ model: [
{ required: true, message: t('associatedModelPlaceholder'), trigger: 'change' } { required: true, message: t('associatedModelPlaceholder'), trigger: 'change' }
], ],
@ -97,8 +100,8 @@ const formRules = computed(() => {
* 获取关联模型 * 获取关联模型
*/ */
const modelList = ref([]) const modelList = ref([])
const getGeneratorAllModelFn = () => { const getGeneratorAllModelFn = (params:any) => {
getGeneratorAllModel().then(res => { getGeneratorAllModel(params).then(res => {
modelList.value = res.data modelList.value = res.data
}) })
} }
@ -114,13 +117,13 @@ const getGeneratorTableColumnFn = (key: any) => {
} }
// //
const addonLst = ref<Array<any>>([]) const addonLst = ref<Array<any>>([])
const getaddonDevelopFn = async () => { const getAddonDevelopFn = async () => {
let { data } = await getaddonDevelop({}) let { data } = await getAddonDevelop({})
addonLst.value = [{ title: "系统", key: "system" }] addonLst.value = [{ title: "系统", key: "system" }]
addonLst.value.push(...data) addonLst.value.push(...data)
getGeneratorAllModelFn({addon:'system'}) getGeneratorAllModelFn({addon:'system'})
} }
getaddonDevelopFn() getAddonDevelopFn()
// //
const addonChange =(val:any)=>{ const addonChange =(val:any)=>{
formData.value.model = '' formData.value.model = ''

View File

@ -0,0 +1,163 @@
<template>
<el-dialog v-model="showDialog" :title="title" width="480px" :before-close="beforeClose" :destroy-on-close="true">
<el-form :model="formData" label-width="130px" ref="formRef" :rules="formRules" class="page-form">
<el-form-item v-if="formData.validate_type == 'min'" :label="t('minLabel')" prop="min_number">
<el-input-number v-model="formData.min_number" :step="1" step-strictly :min="1" class="input-width" />
</el-form-item>
<el-form-item v-else-if="formData.validate_type == 'max'" :label="t('maxLabel')" prop="max_number">
<el-input-number v-model="formData.max_number" :step="1" step-strictly :min="1" class="input-width" />
</el-form-item>
<template v-else-if="formData.view_type === 'number'">
<el-form-item :label="t('minLabel1')" prop="view_min">
<el-input-number v-model="formData.view_min" :min="0" :value-on-clear="0" class="input-width" />
</el-form-item>
<el-form-item :label="t('maxLabel1')" prop="view_max">
<el-input-number v-model="formData.view_max" :min="1" class="input-width" />
</el-form-item>
</template>
<template v-else>
<el-form-item :label="t('minLabel')" prop="betweenMin">
<el-input-number v-model="formData.betweenMin" :step="1" step-strictly :min="1" class="input-width" />
</el-form-item>
<el-form-item :label="t('maxLabel')" prop="betweenMax">
<el-input-number v-model="formData.betweenMax" :step="1" step-strictly :min="1" class="input-width" />
</el-form-item>
</template>
<!-- <el-form-item v-else :label="t('between')" required>
<el-col :span="11">
<el-form-item prop="betweenMin">
<el-input-number v-model="formData.betweenMin" :step="1" step-strictly :min="1" class="input-width" />
</el-form-item>
</el-col>
<el-col :span="2" class="text-center">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-form-item prop="betweenMax">
<el-input-number v-model="formData.betweenMax" :step="1" step-strictly :min="1" class="input-width" />
</el-form-item>
</el-col>
</el-form-item> -->
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirm(formRef)">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup async>
import { ref, reactive, computed, toRaw, } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { cloneDeep } from 'lodash-es'
const showDialog = ref(false)
const title = ref('')
/**
* 表单数据
*/
const initialFormData = {
validate_type: "",
min_number: 1,
max_number: 120,
betweenMin: 1,
betweenMax: 120,
}
const formData: Record<string, any> = ref({ ...initialFormData })
const formRef = ref<FormInstance>()
const validateMin = (rule: any, value: any, callback: any) => {
if (!value) {
callback(new Error(t('minPlaceholder')))
} else if (value > formData.value.betweenMax) {
callback(new Error(t('minPlaceholder1')))
} else {
callback()
}
}
const validateMax = (rule: any, value: any, callback: any) => {
if (!value) {
callback(new Error(t('maxPlaceholder')))
} else if (value < formData.value.betweenMin) {
callback(new Error(t('maxPlaceholder1')))
} else {
callback()
}
}
const validateMin1 = (rule: any, value: any, callback: any) => {
if (value > formData.value.view_max) {
callback(new Error(t('min1Placeholder1')))
} else {
callback()
}
}
const validateMax1 = (rule: any, value: any, callback: any) => {
if (!value) {
callback(new Error(t('max1Placeholder')))
} else if (value < formData.value.view_min) {
callback(new Error(t('max1Placeholder1')))
} else {
callback()
}
}
//
const formRules = computed(() => {
return {
min_number: [
{ required: true, message: t('minPlaceholder'), trigger: 'change' }
],
max_number: [
{ required: true, message: t('maxPlaceholder'), trigger: 'change' }
],
betweenMin: [
{ required: true, validator: validateMin, trigger: 'change' }
],
betweenMax: [
{ required: true, validator: validateMax, trigger: 'change' }
],
view_min: [
{ required: true, validator: validateMin1, trigger: 'change' }
],
view_max: [
{ required: true, validator: validateMax1, trigger: 'change' }
],
}
})
const emit = defineEmits(['complete'])
/**
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
emit('complete', toRaw(formData.value))
showDialog.value = false
}
})
}
const setFormData = async (row: any = null) => {
formData.value = cloneDeep(Object.assign(initialFormData, row))
showDialog.value = true
}
const beforeClose = (next: any) => {
formRef.value?.clearValidate()
next()
}
defineExpose({
showDialog,
setFormData
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,87 @@
<template>
<el-dialog v-model="showDialog" :title="title" width="480px" :before-close="beforeClose" :destroy-on-close="true">
<el-form :model="formData" label-width="130px" ref="formRef" :rules="formRules" class="page-form">
<el-form-item :label="t('dictType')" >
<el-select class="input-width" :placeholder="t('dictTypePlaceholder')" v-model="formData.dict_type"
filterable remote clearable>
<el-option :label="item.name" :value="item.key" v-for="item in dicList" :key="item.key" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirm(formRef)">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup async>
import { ref, computed, toRaw, } from 'vue'
import { t } from '@/lang'
import { getDictAll } from '@/app/api/dict'
import type { FormInstance } from 'element-plus'
import { cloneDeep } from 'lodash-es'
const showDialog = ref(false)
const title = ref('')
/**
* 表单数据
*/
const initialFormData = {
dict_type: "",
}
const formData: Record<string, any> = ref({ ...initialFormData })
const formRef = ref<FormInstance>()
const dicList = ref<Array<any>>([])
//
const formRules = computed(() => {
return {
dict_type: [
{ required: true, message: t('dictTypePlaceholder'), trigger: 'change' }
],
}
})
const getDictAllFn = () => {
getDictAll().then((res) => {
dicList.value = res.data
})
}
const emit = defineEmits(['complete'])
/**
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
emit('complete', toRaw(formData.value))
showDialog.value = false
}
})
}
const setFormData = async (row: any = null) => {
formData.value = cloneDeep(Object.assign(initialFormData, row))
getDictAllFn()
showDialog.value = true
}
const beforeClose = (next: any) => {
formRef.value?.clearValidate()
next()
}
defineExpose({
showDialog,
setFormData
})
</script>
<style lang="scss" scoped></style>

View File

@ -21,8 +21,8 @@
class="input-width" maxlength="64" /> class="input-width" maxlength="64" />
</el-form-item> </el-form-item>
<el-form-item :label="t('addon')"> <el-form-item :label="t('addon')">
<el-select class="input-width" :placeholder="t('addonPlaceholder')" <el-select class="input-width" :placeholder="t('addonPlaceholder1')"
v-model="formData.addon_name" filterable remote clearable :remote-method="getaddonDevelopFn" v-model="formData.addon_name" filterable remote clearable :remote-method="getAddonDevelopFn"
@change="addonChange"> @change="addonChange">
<el-option :label="item.title" :value="item.key" v-for="item in addonList" <el-option :label="item.title" :value="item.key" v-for="item in addonList"
:key="item.key" /> :key="item.key" />
@ -56,39 +56,39 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('columnName')" prop="column_name" min-width="130px" /> <el-table-column :label="t('columnName')" prop="column_name" min-width="130px" />
<el-table-column :label="t('columnComment')" prop="" min-width="230px"> <el-table-column :label="t('columnComment')" prop="" min-width="220px">
<template #default="{ row }"> <template #default="{ row }">
<el-input class="" v-model="row.column_comment" <el-input class="" v-model="row.column_comment"
:placeholder="t('columnCommentPlaceholder')" /> :placeholder="t('columnCommentPlaceholder')" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('columnType')" prop="column_type" width="100px" /> <el-table-column :label="t('columnType')" prop="column_type" width="100px" />
<el-table-column :label="t('isPk')" prop="" align="center" width="80px"> <el-table-column :label="t('isPk')" prop="" align="center" width="65px">
<template #default="{ row }"> <template #default="{ row }">
<el-checkbox v-model="row.is_pk" disabled :true-label="1" :false-label="0" /> <el-checkbox v-model="row.is_pk" disabled :true-label="1" :false-label="0" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('isRequired')" prop="" align="center" width="80px"> <el-table-column :label="t('isRequired')" prop="" align="center" width="65px">
<template #default="{ row }"> <template #default="{ row }">
<el-checkbox v-model="row.is_required" :true-label="1" :false-label="0" /> <el-checkbox v-model="row.is_required" :true-label="1" :false-label="0" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('isInsert')" prop="" align="center" width="80px"> <el-table-column :label="t('isInsert')" prop="" align="center" width="65px">
<template #default="{ row }"> <template #default="{ row }">
<el-checkbox v-model="row.is_insert" :true-label="1" :false-label="0" /> <el-checkbox v-model="row.is_insert" :true-label="1" :false-label="0" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('isUpdate')" prop="" align="center" width="80px"> <el-table-column :label="t('isUpdate')" prop="" align="center" width="65px">
<template #default="{ row }"> <template #default="{ row }">
<el-checkbox v-model="row.is_update" :true-label="1" :false-label="0" /> <el-checkbox v-model="row.is_update" :true-label="1" :false-label="0" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('isLists')" prop="" align="center" width="80px"> <el-table-column :label="t('isLists')" prop="" align="center" width="65px">
<template #default="{ row }"> <template #default="{ row }">
<el-checkbox v-model="row.is_lists" :true-label="1" :false-label="0" /> <el-checkbox v-model="row.is_lists" :true-label="1" :false-label="0" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('isSearch')" prop="" align="center" width="80px"> <el-table-column :label="t('isSearch')" prop="" align="center" width="65px">
<template #default="{ row }"> <template #default="{ row }">
<el-checkbox v-model="row.is_search" :true-label="1" :false-label="0" /> <el-checkbox v-model="row.is_search" :true-label="1" :false-label="0" />
</template> </template>
@ -100,19 +100,51 @@
</el-table-column> --> </el-table-column> -->
<el-table-column :label="t('queryType')" prop="" min-width="170px"> <el-table-column :label="t('queryType')" prop="" min-width="170px">
<template #default="{ row }"> <template #default="{ row }">
<el-select v-if="row.is_search" :placeholder="t('selectPlaceholder')" <div class="flex items-center">
v-model="row.query_type"> <el-select class="" v-if="row.is_search" :placeholder="t('selectPlaceholder')"
<el-option :label="item" :value="item" v-for="(item, index) in queryType" v-model="row.query_type">
:key="index" /> <el-option :label="item" :value="item" v-for="(item, index) in queryType"
</el-select> :key="index" />
</el-select>
</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('formType')" prop="" min-width="170px"> <el-table-column :label="t('formType')" prop="" min-width="225px">
<template #default="{ row }"> <template #default="{ row, $index }">
<el-select :placeholder="t('selectPlaceholder')" v-model="row.view_type"> <el-select class="w-[146px]" :placeholder="t('selectPlaceholder')" v-model="row.view_type"
@change="viewTypeBtn(row, $index)">
<el-option :label="item.label" :value="item.value" v-for="(item, index) in viewType" <el-option :label="item.label" :value="item.value" v-for="(item, index) in viewType"
:key="index" /> :key="index" />
</el-select> </el-select>
<el-button class="ml-[10px]" v-if="['select', 'radio', 'checkbox'].includes(row.view_type)"
type="primary" link @click="viewTypeBtn(row, $index)">{{ t('setUp')
}}</el-button>
<el-button class="ml-[10px]" v-if="row.view_type === 'number'" type="primary" link
@click="validatorBtn(row, $index)">{{ t('setUp')
}}</el-button>
</template>
</el-table-column>
<el-table-column :label="t('verifyType')" prop="" min-width="260px">
<template #default="{ row, $index }">
<div class="flex items-center">
<el-select class="w-[196px]" :placeholder="t('selectPlaceholder')"
v-model="row.validate_type" @change="validatorBtn(row, $index)"
:disabled="!['input', 'textarea'].includes(row.view_type)">
<template v-for="(item, index) in verifyType" :key="index">
<el-option v-if="item.value === 'max'" :value="item.value" :label="`最大输入字符`" />
<el-option v-else-if="item.value === 'min'" :value="item.value"
:label="`最小输入字符`" />
<el-option v-else-if="item.value === 'between'" :value="item.value"
:label="`输入字符区间`" />
<el-option v-else :label="item.label" :value="item.value" />
</template>
</el-select>
<el-button class="ml-[10px]"
v-if="['max', 'min', 'between'].includes(row.validate_type)" type="primary" link
@click="validatorBtn(row, $index)">{{ t('setUp')
}}</el-button>
</div>
</template> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column :label="t('formValidation')" prop="" min-width="170px"> <!-- <el-table-column :label="t('formValidation')" prop="" min-width="170px">
@ -135,15 +167,22 @@
</el-radio-group> </el-radio-group>
<p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]"> <p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">
物理删除从表中把记录移除软删除通过标识使得这条记录在系统逻辑层面上不可见</p> 物理删除从表中把记录移除软删除通过标识使得这条记录在系统逻辑层面上不可见</p>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item prop="delete_column_name" :label="t('deleteField')" v-if="formData.is_delete"> <el-form-item prop="delete_column_name" :label="t('deleteField')" v-if="formData.is_delete">
<el-select class="input-width" :placeholder="t('deleteFieldPlaceholder')" <div>
v-model="formData.delete_column_name"> <el-select class="input-width" :placeholder="t('deleteFieldPlaceholder')"
<el-option :label="`${item.column_name}:${item.column_comment}`" :value="item.column_name" v-model="formData.delete_column_name">
v-for="(item, index) in formData.table_column " :key="index" /> <el-option :label="`${item.column_name}:${item.column_comment}`"
</el-select> :value="item.column_name" v-for="(item, index) in formData.table_column "
:key="index" />
</el-select>
<p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">
软删除字段需为int类型并且默认值为0</p>
</div>
</el-form-item> </el-form-item>
<el-form-item :label="t('editType')"> <el-form-item :label="t('editType')">
@ -236,6 +275,8 @@
</el-card> </el-card>
</div> </div>
<edit-associated ref="editDialog" :table_name="formData.table_name" @complete="complete" /> <edit-associated ref="editDialog" :table_name="formData.table_name" @complete="complete" />
<edit-view-type ref="editViewTypeRef" @complete="completeViewType" />
<edit-verify ref="editVerifyRef" @complete="completeVerify" />
<div class="fixed-footer-wrap"> <div class="fixed-footer-wrap">
<div class="fixed-footer"> <div class="fixed-footer">
<el-button type="primary" @click="onSave(1)">{{ t('save') }}</el-button> <el-button type="primary" @click="onSave(1)">{{ t('save') }}</el-button>
@ -252,7 +293,9 @@ import { t } from '@/lang'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import { FormInstance, ElMessageBox, ElMessage } from 'element-plus' import { FormInstance, ElMessageBox, ElMessage } from 'element-plus'
import editAssociated from '@/app/views/tools/code/components/edit-associated.vue' import editAssociated from '@/app/views/tools/code/components/edit-associated.vue'
import { getGenerateTableInfo, editGenerateTable, getaddonDevelop, generatorCheckFile, generateCreate } from '@/app/api/tools' import editViewType from '@/app/views/tools/code/components/edit-view-type.vue'
import editVerify from '@/app/views/tools/code/components/edit-verify.vue'
import { getGenerateTableInfo, editGenerateTable, getAddonDevelop, generatorCheckFile, generateCreate } from '@/app/api/tools'
import { getSystemMenu, getAddonMenu } from '@/app/api/sys' import { getSystemMenu, getAddonMenu } from '@/app/api/sys'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
@ -314,13 +357,52 @@ const viewType = [
{ {
label: t('formEditor'), label: t('formEditor'),
value: 'editor' value: 'editor'
},
{
label: t('formNumber'),
value: 'number'
} }
] ]
const verifyType = [
{
label: '无需验证',
value: ''
},
{
label: t('mobileVerify'),
value: 'mobile'
},
{
label: t('numberVerify'),
value: 'number'
},
{
label: t('idCardVerify'),
value: 'idCard'
},
{
label: t('emailVerify'),
value: 'email'
},
{
label: '',
value: 'max'
},
{
label: '',
value: 'min'
},
{
label: '',
value: 'between'
},
]
const addonList = ref<Array<any>>([]) const addonList = ref<Array<any>>([])
// //
const getaddonDevelopFn = (search: string) => { const getAddonDevelopFn = (search: string) => {
getaddonDevelop({ search }).then(res => { getAddonDevelop({ search }).then(res => {
addonList.value = res.data addonList.value = res.data
}) })
} }
@ -345,7 +427,7 @@ const rowDrop = () => {
} }
onMounted(() => { onMounted(() => {
rowDrop() rowDrop()
getaddonDevelopFn('') getAddonDevelopFn('')
}) })
//change //change
const deleteTypeChange = (val: any) => { const deleteTypeChange = (val: any) => {
@ -377,6 +459,10 @@ const setFormData = async (id: number = 0) => {
Object.keys(data).forEach((key: string) => { Object.keys(data).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key] if (data[key] != undefined) formData[key] = data[key]
}) })
formData.table_column.forEach(el => {
el.betweenMin = cloneDeep(el.min_number);
el.betweenMax = cloneDeep(el.max_number);
})
if (formData.addon_name != '') getAddonMenuFn(formData.addon_name) if (formData.addon_name != '') getAddonMenuFn(formData.addon_name)
loading.value = false loading.value = false
} }
@ -409,6 +495,7 @@ const addonChange = async (val: any) => {
const associatedIndex = ref(0) const associatedIndex = ref(0)
const editDialog = ref() const editDialog = ref()
// //
const addEvent = (val: any, index: number) => { const addEvent = (val: any, index: number) => {
associatedIndex.value = index associatedIndex.value = index
editDialog.value.setFormData(val) editDialog.value.setFormData(val)
@ -426,13 +513,28 @@ const deleteEvent = (index: number) => {
formData.relations.splice(index, 1) formData.relations.splice(index, 1)
} }
const onSave = async (code: number) => { const onSave = async (code: number) => {
loading.value = true
const data = cloneDeep(formData) const data = cloneDeep(formData)
// if (data.table_column.some(el => { return ['select', 'radio', 'checkbox'].includes(el.view_type) && el.dict_type == '' })) {
// // ElMessage({
// // type: 'error',
// // message: t('dictTypePlaceholder'),
// // })
// // return false
// }
data.table_column = JSON.stringify(data.table_column.map(el => { data.table_column = JSON.stringify(data.table_column.map(el => {
if (!el.is_search) el.query_type = '' if (!el.is_search) el.query_type = ''
if (el.validate_type === 'between' || el.view_type === 'number') {
el.max_number = el.betweenMax
el.min_number = el.betweenMin
}
if (!['select', 'radio', 'checkbox'].includes(el.view_type)) el.dict_type = ''
return el return el
})) }))
console.log(JSON.parse(data.table_column))
data.relations = JSON.stringify(data.relations) data.relations = JSON.stringify(data.relations)
loading.value = true
editGenerateTable(data).then((res: any) => { editGenerateTable(data).then((res: any) => {
if (code === 3) { if (code === 3) {
generatorCheckFileFn() generatorCheckFileFn()
@ -496,6 +598,37 @@ const generateCreateFn = (generate_type: any) => {
loading.value = false loading.value = false
}) })
} }
const rowIndex = ref(0)
const editVerifyRef = ref(null)
const editViewTypeRef = ref(null)
/**
* 打开最大最小值设置
*/
const validatorBtn = (row: any, index: number) => {
if (['max', 'min', 'between'].includes(row.validate_type) || row.view_type === 'number') {
rowIndex.value = index
editVerifyRef.value?.setFormData(row)
}
}
const completeVerify = (row: any) => {
formData.table_column.splice(rowIndex.value, 1, row)
}
const viewTypeBtn = (row: any, index: number) => {
if (!['input', 'textarea'].includes(row.view_type)) row.validate_type = ''
if (['select', 'radio', 'checkbox'].includes(row.view_type)) {
rowIndex.value = index
editViewTypeRef.value?.setFormData(row)
} else if (row.view_type === 'number') {
validatorBtn(row, index)
}
}
const completeViewType = (row: any) => {
formData.table_column.splice(rowIndex.value, 1, row)
}
const back = () => { const back = () => {
router.push({ path: '/tools/code' }) router.push({ path: '/tools/code' })
} }

View File

@ -97,14 +97,20 @@
<el-tab-pane :label="t('codeList')" name="codeList"> <el-tab-pane :label="t('codeList')" name="codeList">
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never"> <el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="codeTableData.searchParam" ref="searchFormRef"> <el-form :inline="true" :model="codeTableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('addonName')" prop="addon_name">
<el-select v-model="codeTableData.searchParam.addon_name" placeholder="Select" filterable remote clearable :remote-method="getAddonDevelopFn">
<el-option label="全部" value="" />
<el-option label="系统" value="2" />
<el-option :label="item.title" :value="item.key" v-for="item in addonList"
:key="item.key" />
</el-select>
</el-form-item>
<el-form-item :label="t('tableName')" prop="table_name"> <el-form-item :label="t('tableName')" prop="table_name">
<el-input v-model="codeTableData.searchParam.table_name" <el-input v-model="codeTableData.searchParam.table_name"
:placeholder="t('tableNamePlaceholder')" /> :placeholder="t('tableNamePlaceholder')" />
</el-form-item> </el-form-item>
<el-form-item :label="t('tableContent')" prop="table_content">
<el-input v-model="codeTableData.searchParam.table_content"
:placeholder="t('tableContentPlaceholder')" />
</el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="loadGenerateTableList()">{{ t('search') }}</el-button> <el-button type="primary" @click="loadGenerateTableList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button> <el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
@ -120,6 +126,8 @@
<el-table-column prop="table_name" :show-overflow-tooltip="true" :label="t('tableName')" <el-table-column prop="table_name" :show-overflow-tooltip="true" :label="t('tableName')"
min-width="120" /> min-width="120" />
<el-table-column prop="title" :show-overflow-tooltip="true" :label="t('addonName')"
min-width="120" />
<el-table-column prop="table_content" :show-overflow-tooltip="true" :label="t('tableContent')" <el-table-column prop="table_content" :show-overflow-tooltip="true" :label="t('tableContent')"
min-width="120" /> min-width="120" />
@ -159,7 +167,7 @@
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<add-table ref="addCodeDialog" /> <add-table ref="addCodeDialog" />
<el-dialog v-model="dialogVisible" width="70%" title="代码预览"> <el-dialog v-model="dialogVisible" class="dialog-visible" width="70%" title="代码预览">
<div class="flex h-[50vh]" v-loading="codeLoading"> <div class="flex h-[50vh]" v-loading="codeLoading">
<el-scrollbar class="h-[100%] w-[270px]"> <el-scrollbar class="h-[100%] w-[270px]">
<el-tree v-if="treeData.length && treeKey != ''" :data="treeData" :props="{ label: 'name', value: 'key' }" <el-tree v-if="treeData.length && treeKey != ''" :data="treeData" :props="{ label: 'name', value: 'key' }"
@ -182,7 +190,7 @@
</el-scrollbar> </el-scrollbar>
<div class="ml-[20px]" style="width: calc(100% - 285px);"> <div class="ml-[20px]" style="width: calc(100% - 285px);">
<el-scrollbar class="h-[100%] w-[100%]"> <el-scrollbar class="h-[100%] w-[100%]">
<highlightjs autodetect :code="code" /> <highlightjs autodetect class="h-[100%]" :code="code" />
</el-scrollbar> </el-scrollbar>
</div> </div>
</div> </div>
@ -194,7 +202,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, onMounted } from 'vue' import { reactive, ref, onMounted } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getGenerateTableList, deleteGenerateTable, generateCreate, generatePreview, generatorCheckFile, } from '@/app/api/tools' import { getGenerateTableList, deleteGenerateTable, generateCreate, generatePreview, generatorCheckFile,getAddonDevelop } from '@/app/api/tools'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import AddTable from '@/app/views/tools/code/components/add-table.vue' import AddTable from '@/app/views/tools/code/components/add-table.vue'
@ -214,7 +222,8 @@ let codeTableData = reactive({
data: [], data: [],
searchParam: { searchParam: {
table_name: "", table_name: "",
table_content: "" table_content: "",
addon_name:""
} }
}) })
@ -253,8 +262,17 @@ const loadGenerateTableList = (page: number = 1) => {
}) })
} }
const addonList = ref<Array<any>>([])
//
const getAddonDevelopFn = (search: string) => {
getAddonDevelop({ search }).then(res => {
addonList.value = res.data
})
}
const addCodeDialog: Record<string, any> | null = ref(null) const addCodeDialog: Record<string, any> | null = ref(null)
/** /**
* 添加代码生成 * 添加代码生成
*/ */
@ -312,6 +330,7 @@ const generatorCheckFileFn = ((id: any) => {
codeTableData.loading = false codeTableData.loading = false
}) })
}) })
/** /**
* 同步or下载 * 同步or下载
*/ */
@ -333,6 +352,7 @@ const generateCreateFn = (id: any, generate_type: any) => {
codeTableData.loading = false codeTableData.loading = false
}) })
} }
/* /*
*代码预览 *代码预览
*/ */
@ -429,4 +449,11 @@ const listToTree = (arr) => {
height: 44px; height: 44px;
display: flex; display: flex;
justify-content: center; justify-content: center;
}</style> }
:deep(.dialog-visible .el-scrollbar__view), :deep(.dialog-visible .el-scrollbar__view .hljs.ruby){
height: 100%;
}
</style>
<style>
</style>

View File

@ -118,5 +118,12 @@
"indexSwitch": "切换首页", "indexSwitch": "切换首页",
"indexWarning": "你确定要切换首页吗?", "indexWarning": "你确定要切换首页吗?",
"appName": "应用名称", "appName": "应用名称",
"appNamePlaceholder": "请输入应用名称" "appNamePlaceholder": "请输入应用名称",
"generateMobile":"请输入正确的手机号格式",
"generateNumber":"请输入整数",
"generateIdCard":"请输入正确的身份证号",
"generateEmail":"请输入正确的邮箱号",
"generateMax":"超过最多输入字符数",
"generateMin":"少于最少输入字符数",
"generateBetween":"请输入正确的字符信息"
} }

View File

@ -1,76 +1,384 @@
<template> <template>
<div :class="['flex',{'two-type': sidebar == 'twoType'},{'three-type': sidebar == 'threeType'}]"> <div :class="['flex', { 'two-type': sidebar == 'twoType' }, { 'three-type': sidebar == 'threeType' }]" v-if="aaa">
<div class="w-[72px] overflow-hidden">
<el-aside :class="['h-screen layout-aside w-[93px] pr-[20px] pb-[30px] bg-[#F7F8FA] ease-in duration-200', { 'bright': !dark }]">
<!-- 一级菜单 -->
<div class="">
<el-header class="logo-wrap h-auto">
<div class="logo flex items-center m-auto max-w-[210px] h-[60px] justify-center">
<!-- <img class="w-[35px] h-[35px] rounded-full" src="@/app/assets/images/login_logo_ico.png" alt=""> -->
<span class="iconfont iconyun text-[#999] !text-[30px]"></span>
</div>
</el-header>
<div class="menu-wrap"> <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 pt-2 relative">
<!-- <el-header class="logo-wrap h-auto mb-[10px]">
<div class="logo flex items-center m-auto max-w-[210px] h-[60px] justify-center"#19233C>
<span class="iconfont iconyun text-[#999] !text-[36px]"></span>
</div>
</el-header> -->
<!-- <template v-for="(item, index) in applyList" :key="index">
<div v-if="item.type == 'app'" @click="appToLink(item.key)"
class=" flex items-center justify-center h-[45px] mb-[5px] cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] menu-item hover:text-color whitespace-nowrap">
<img :src="img(item.icon)" class="w-[35px] h-[35px] rounded-full" alt="" :title="item.title">
</div>
</template> -->
<div class=" flex items-center justify-center h-[45px] mb-[5px] cursor-pointer cut-style" @click="floatActive=!floatActive">
<span class="iconfont icontuodong !text-[30px] "></span>
</div>
<div class="mb-[20px]">
<template v-for="(item, index) in menus" :key="index"> <template v-for="(item, index) in menus" :key="index">
<div v-if="item.meta.show" @click="toLink(item)" <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 == matched.path || (matched.path == '/admin' && item.path == '/index') || (matched.meta.app && item.path == '/index')) }]"> :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" /> <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 v-if="!floatMenuStyle&&floatActive"
class="flex absolute bg-[#fff] w-[640px] px-[28px] py-[20px] flex-wrap left-0 top-[65px] z-10 box-border shadow-lg">
<template v-for="(item, index) in applyList" :key="index">
<div v-if="item.type == 'app'" @click="appToLink(item.key)"
class="flex items-center cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] whitespace-nowrap py-[10px] px-[15px]">
<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>
<!-- 二级菜单 -->
<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">
<div class="flex items-center">
<template v-if="floatMenuStyle">
<img v-if="appInfo.icon" :src="img(appInfo.icon)" class="w-[40px] h-[40px] mr-[8px]" alt="">
<div class="flex items-center justify-center w-[30px] h-[30px]"
v-else-if="Object.keys(appInfo).length">
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px" />
</div>
</template>
<span>{{ item.meta.app ? appInfo.title : item.meta.title }}</span>
</div>
<!-- 浮动样式的应用菜单 -->
<div v-if="floatMenuStyle"
class="hidden group-hover:flex absolute bg-[#fff] w-[640px] px-[28px] py-[20px] flex-wrap left-0 top-[65px] z-10 box-border shadow-lg">
<template v-for="(item, index) in applyList" :key="index">
<div v-if="item.type == 'app'" @click="appToLink(item.key)"
class="flex items-center justify-center cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] whitespace-nowrap py-[10px] px-[15px]">
<img :src="img(item.icon)" class="w-[44px] h-[44px] rounded-full mr-[5px]" alt=""
:title="item.title">
<span>{{ item.title }}</span>
</div> </div>
</template> </template>
</div> </div>
</div> </div>
</el-aside>
</div>
<!-- 二级菜单 --> <el-scrollbar class="overflow-y-auto menus-wrap">
<div v-if="matched.children.length" class="w-[189px] box-border border-r-[1px] border-solid second-menu"> <el-menu class="apply-menu !border-0" :router="true" unique-opened="true"
<div class="flex flex-col items-center justify-center h-[108px] border-b-[1px] border-solid second-head mx-[10px]"> :default-active="String(route.name)">
<div class="flex items-center justify-center w-[30px] h-[30px]" v-if="!matched.meta.app"> <template v-for="(twoMenu, twoIndex) in item.children">
<icon v-if="matched.meta.icon" :name="matched.meta.icon" class="!w-auto" size="24px" /> <el-sub-menu :index="String(twoMenu.meta.title)" v-if="twoMenu.children && twoMenu.meta.show">
</div>
<img v-else-if="matched.meta.app && appInfo.icon" :src="img(appInfo.icon)" class="w-[40px] h-[40px]" alt="">
<!-- <img v-else-if="matched.meta.app && !appInfo.icon" src="@/app/assets/images/login_logo_ico.png" class="w-[40px] h-[40px] rounded-full" alt=""> -->
<div class="flex items-center">
<span class=" mt-[2px]">{{ matched.meta.app ? appInfo.title : matched.meta.title }}</span>
<span class="text-color ml-2 !text-[20px] cursor-pointer iconfont iconqiehuan2" v-if="matched.meta.app && appInfo.icon" @click="switchAppFn()"></span>
</div>
</div>
<el-menu class="system-menu !border-0" :router="true" unique-opened="true" :default-active="String(route.name)">
<template v-for="(twoMenu, twoIndex) in matched.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-menu-item v-if="threeMenu.meta.show" class="!h-[52px] !pl-[64px]" :index="String(threeMenu.name)" @click="toLink(threeMenu)">
<template #title> <template #title>
<span class="text-[14px]">{{ threeMenu.meta.title }}</span> <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">
<icon v-if="threeMenu.meta.icon && floatMenuStyle"
: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>
<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> </template>
</el-menu-item> </el-menu-item>
</template> <div class="flex items-center !px-[25px] h-[56px] cursor-pointer text-[#333] el-menu-item"
</el-sub-menu> v-else-if="twoMenu.meta.show && twoMenu.meta.key == 'official_market'"
@click="toLink(twoMenu)">
<el-menu-item v-else-if="twoMenu.meta.show && twoMenu.meta.key != 'official_market'" class="!pl-[35px] text-[#333]" :index="String(twoMenu.name)" @click="toLink(twoMenu)"> <div v-if="twoMenu.meta.icon" class="w-[16px] h-[16px] relative flex items-center">
<template #title> <icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon" class="absolute !w-auto"
<div v-if="twoMenu.meta.icon" class="w-[16px] h-[16px] relative flex items-center"> size="18px" />
<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> </div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template> </template>
</el-menu-item>
<div class="flex items-center !px-[35px] 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"> <template
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon" class="absolute !w-auto" size="18px"/> v-if="applyTypeList.includes(localMenuKey) || otherTypeList.includes(localMenuKey) || floatMenuStyle">
</div> <div class="!border-0 !border-t-[1px] border-solid mx-[25px] bg-[#f7f7f7] my-[5px]"></div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span> <template v-for="(twoMenu, twoIndex) in menus">
</div> <el-sub-menu :index="String(twoMenu.meta.title)"
</template> v-if="twoMenu.meta.attr == 'system' && !twoMenu.meta.app && twoMenu.children">
</el-menu> <template #title>
</div> <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">
<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 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) && 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>
<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>
<!-- 浮动样式 -->
<template v-if="floatMenuStyle">
<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 == '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>
<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>
</template>
</el-menu>
</el-scrollbar>
</div>
</template>
</div> </div>
</template> </template>
@ -85,47 +393,124 @@ import storage from '@/utils/storage'
import { CollectionTag } from '@element-plus/icons-vue' import { CollectionTag } from '@element-plus/icons-vue'
import { findFirstValidRoute } from '@/router/routers' import { findFirstValidRoute } from '@/router/routers'
import { getAddonByKey } from '@/app/api/addon' import { getAddonByKey } from '@/app/api/addon'
import { getApply } from '@/app/api/apply'
const userStore = useUserStore() const userStore = useUserStore()
const systemStore = useSystemStore() const systemStore = useSystemStore()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
let globalAppKey = ref('') //
let localMenuKey = ref('') //
globalAppKey.value = storage.get('menuAppStorage')
localMenuKey.value = storage.get('menuAppStorage')
// start
let applyList = ref([]);
let applyTypeList = ref([]);
let otherTypeList = ref([]); // \\
let aaa = ref(false)
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', 'overview'])
aaa.value = true;
}
getApplelist()
const floatActive = ref(false)
const appLink = ref({})
const getAppLink = () => {
userStore.routers.forEach((item, index) => {
if (item.meta.app != '') {
if (item.children && item.children.length) {
appLink.value[item.meta.app] = findFirstValidRoute(item.children)
} else {
appLink.value[item.meta.app] = item.name
}
}
})
}
getAppLink()
const appToLink = (addon: string) => {
globalAppKey.value = addon;
localMenuKey.value = addon;
storage.set({ key: 'menuAppStorage', data: addon })
storage.set({ key: 'plugMenuTypeStorage', data: '' })
let data = userStore.appMenuList
if (!data.length) {
data.push(addon)
} else if (!data.includes(addon)) {
data.push(addon)
}
userStore.setAppMenuList(data)
floatActive.value = false
router.push({ name: appLink.value[addon] })
}
// end
const menus = computed(() => { const menus = computed(() => {
const menus = [] const menus = []
userStore.routers.forEach((item, index) => { userStore.routers.forEach((item, index) => {
if (item.meta.app == '') { if (item.children && item.children.length) {
if (item.children && item.children.length) { item.name = findFirstValidRoute(item.children)
item.name = findFirstValidRoute(item.children) menus.push(item)
menus.push(item) } else {
} else { menus.push(item)
menus.push(item)
}
} }
}) })
return menus return menus
}) })
let currMetaAppType = "";
const matched = computed(() => {
const data = route.matched[1]
if (data.meta.app && (!currMetaAppType || currMetaAppType != data.meta.app)){
appInfo.value = {};
currMetaAppType = data.meta.app;
getAppInfo(data.meta.app)
}
return route.matched[1]
})
const dark = computed(() => { const dark = computed(() => {
return systemStore.dark return systemStore.dark
}) })
//
const appInfo = ref({})
const getAppInfo = (type) => {
getAddonByKey(type).then(res => {
appInfo.value = res.data
})
}
let currMetaAppType = "";
let plugMenuType = ref(''); //
let currentRoute = ref(''); //
watch(route, () => { watch(route, () => {
plugMenuType.value = storage.get('plugMenuTypeStorage')
let data = route.matched[1]
currentRoute.value = route.matched[1];
localMenuKey.value = data.meta.key;
data.meta.app = !data.meta.app && !data.meta.attr ? 'member' : data.meta.app;
// applist
setTimeout(() => {
//
if (data.meta.app && (!currMetaAppType || currMetaAppType != data.meta.app)) {
appInfo.value = {};
currMetaAppType = data.meta.app;
let appInfoKey = otherTypeList.value.includes(data.meta.app) ? globalAppKey.value : data.meta.app;
getAppInfo(appInfoKey)
}
}, 800);
systemStore.$patch(state => { systemStore.$patch(state => {
state.menuDrawer = false state.menuDrawer = false
}) })
}) }, { immediate: true })
// //
const toLink = (data) => { const toLink = (data) => {
@ -136,28 +521,34 @@ const toLink = (data) => {
} }
} }
//
const switchAppFn = () => {
router.push({ path: '/index/index' })
}
//
const appInfo = ref({})
const getAppInfo = (type) => {
getAddonByKey(type).then(res => {
appInfo.value = res.data
})
}
// //
const sidebar = computed(() => { const sidebar = computed(() => {
return systemStore.sidebar return systemStore.sidebar
}) })
//
let 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 && !otherTypeList.value.includes(localMenuKey.value) && (item.meta.key == localMenuKey.value || item.meta.app == localMenuKey.value))
return bool;
}
</script> </script>
<style lang="scss"> <style lang="scss">
.layout-aside { .layout-aside {
border-right: 1px solid var(--el-border-color-lighter); // border-right: 1px solid var(--el-border-color-lighter);
&.bright { &.bright {
background-color: #F5F7F9; background-color: #F5F7F9;
@ -188,15 +579,21 @@ const sidebar = computed(() => {
} }
} }
.second-menu .el-sub-menu .el-sub-menu__title{ .second-menu .el-sub-menu .el-sub-menu__title {
padding-left: 35px !important; padding-left: 25px !important;
.el-icon.el-sub-menu__icon-arrow{ padding-right: 25px !important;
right: 35px;
.el-icon.el-sub-menu__icon-arrow {
right: 25px;
font-weight: bolder; font-weight: bolder;
font-size: 14px; font-size: 14px;
} }
} }
.three-menu.el-sub-menu .el-sub-menu__title {
padding-left: 45px !important;
}
.text-color { .text-color {
color: var(--el-color-primary); color: var(--el-color-primary);
} }
@ -207,10 +604,17 @@ const sidebar = computed(() => {
} }
} }
.cut-style {
color: #6d7278;
}
.cut-style.qx {
transform: translateX(-50%);
}
// //
.two-type{ .two-type {
.logo-wrap{ .logo-wrap {
.logo span{ .logo span {
width: 36px; width: 36px;
height: 36px; height: 36px;
border-radius: 50%; border-radius: 50%;
@ -222,40 +626,54 @@ const sidebar = computed(() => {
font-size: 25px !important; font-size: 25px !important;
} }
} }
.layout-aside{
background-color: #12192D; .layout-aside {
.menu-item{ // background-color: #12192D;
background-color: #2b303b;
.menu-item {
color: #fff; color: #fff;
&.menu-item-active, &:hover{
&.menu-item-active,
&:hover {
background-color: var(--el-color-primary); background-color: var(--el-color-primary);
color: #fff; color: #fff;
} }
} }
} }
.second-menu{
.second-menu {
background-color: #F5F7F9; background-color: #F5F7F9;
.el-menu{
.el-menu {
background-color: transparent; background-color: transparent;
.el-menu-item:hover { .el-menu-item:hover {
background-color: #fff; background-color: #fff;
color: var(--el-color-primary); color: var(--el-color-primary);
} }
.el-menu-item.is-active { .el-menu-item.is-active {
background-color: #fff; background-color: #fff;
} }
} }
.el-sub-menu__title:hover{
.el-sub-menu__title:hover {
background-color: #fff; background-color: #fff;
color: var(--el-color-primary); color: var(--el-color-primary);
} }
} }
.cut-style {
color: #FFF;
}
} }
// //
.three-type{ .three-type {
.logo-wrap{ .logo-wrap {
.logo span{ .logo span {
width: 36px; width: 36px;
height: 36px; height: 36px;
border-radius: 50%; border-radius: 50%;
@ -267,38 +685,58 @@ const sidebar = computed(() => {
font-size: 25px !important; font-size: 25px !important;
} }
} }
.layout-aside{
background-color: #12192D; .layout-aside {
.menu-item{ background-color: #2b303b;
.menu-item {
color: #fff; color: #fff;
&.menu-item-active, &:hover{
background-color: #19233C; &.menu-item-active,
&:hover {
background-color: #303848;
color: var(--el-color-primary); color: var(--el-color-primary);
} }
} }
} }
.second-menu{
background-color: #19233C; .second-menu {
.second-head{ background-color: #303848;
.second-head {
color: #fff; color: #fff;
border-color: #364059; border-color: #364059;
} }
.el-menu{
.el-menu {
background-color: transparent; background-color: transparent;
.el-menu-item{
.el-menu-item {
color: #fff; color: #fff;
} }
.el-menu-item:hover, .el-menu-item.is-active {
.el-menu-item:hover,
.el-menu-item.is-active {
background-color: var(--el-color-primary); background-color: var(--el-color-primary);
} }
} }
.el-sub-menu__title:hover{
background-color: var(--el-color-primary);; .el-sub-menu__title:hover {
background-color: var(--el-color-primary);
;
} }
.el-sub-menu__title{
.el-sub-menu__title {
color: #fff; color: #fff;
} }
} }
.cut-style {
color: #FFF;
}
} }
.menus-wrap {
height: calc(100vh - 64px);
}
</style> </style>

View File

@ -3,6 +3,10 @@
<el-row class="w-100 h-full w-full"> <el-row class="w-100 h-full w-full">
<el-col :span="12"> <el-col :span="12">
<div class="left-panel h-full flex items-center"> <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"> <div class="navbar-item flex items-center h-full cursor-pointer" @click="refreshRouter">
<icon name="element-Refresh" /> <icon name="element-Refresh" />
@ -172,6 +176,15 @@ const submitIndex = () => {
router.go(0) 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -77,7 +77,8 @@ interface Route {
}, },
children?: [], children?: [],
is_show: boolean, is_show: boolean,
addon: string addon: string,
menu_attr: string
} }
/** /**
@ -86,9 +87,10 @@ interface Route {
* @param parentRoute * @param parentRoute
*/ */
const createRoute = function (route: Route, parentRoute: RouteRecordRaw | null = null): RouteRecordRaw { const createRoute = function (route: Route, parentRoute: RouteRecordRaw | null = null): RouteRecordRaw {
// parentRoute ? Symbol(`${parentRoute.path}/${route.router_path}`) : Symbol(`/${route.router_path}`)
const record: RouteRecordRaw = { const record: RouteRecordRaw = {
path: parentRoute ? route.router_path : `/${route.router_path}`, path: parentRoute ? route.router_path : `/${route.router_path}`,
name: parentRoute ? Symbol(`${parentRoute.path}/${route.router_path}`) : Symbol(`/${route.router_path}`), name: route.menu_key,
meta: { meta: {
title: route.menu_name, title: route.menu_name,
icon: route.icon, icon: route.icon,
@ -96,7 +98,8 @@ const createRoute = function (route: Route, parentRoute: RouteRecordRaw | null =
show: route.is_show, show: route.is_show,
app: route.addon, app: route.addon,
view: route.view_path, view: route.view_path,
key: route.menu_key key: route.menu_key,
attr: route.menu_attr
} }
} }
if (route.menu_type == 0) { if (route.menu_type == 0) {

View File

@ -227,6 +227,7 @@ html.dark {
// /* 多行超出隐藏 */ // /* 多行超出隐藏 */
.multi-hidden { .multi-hidden {
white-space: normal;
word-break: break-all; word-break: break-all;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 3883393 */ font-family: "iconfont"; /* Project id 3883393 */
src: url('//at.alicdn.com/t/c/font_3883393_tcu9x1pe11k.woff2?t=1693640799122') format('woff2'), src: url('//at.alicdn.com/t/c/font_3883393_236yfsl6lh5.woff2?t=1694750730982') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_tcu9x1pe11k.woff?t=1693640799122') format('woff'), url('//at.alicdn.com/t/c/font_3883393_236yfsl6lh5.woff?t=1694750730982') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_tcu9x1pe11k.ttf?t=1693640799122') format('truetype'); url('//at.alicdn.com/t/c/font_3883393_236yfsl6lh5.ttf?t=1694750730982') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icondian:before {
content: "\ec1e";
}
.iconjiantou_xiangzuoliangci_o:before {
content: "\eb93";
}
.iconfenlei:before { .iconfenlei:before {
content: "\e6c3"; content: "\e6c3";
} }

View File

@ -26,7 +26,7 @@ class Request {
baseURL: import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`, baseURL: import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`,
timeout: 30000, timeout: 30000,
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded;', 'Content-Type': 'application/json;',
'lang': storage.get('lang') ?? 'zh-cn' 'lang': storage.get('lang') ?? 'zh-cn'
} }
}); });