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'
export {}
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']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
@ -70,6 +69,7 @@ declare module '@vue/runtime-core' {
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']

View File

@ -5,7 +5,7 @@ import request from '@/utils/request'
* @returns
*/
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)
}
/**
*
* @param params
* @returns
*/
export function cloudInstallAddon(params: Record<string, any>) {
return request.post(`addon/cloudinstall/${params.addon}`, params)
}
/**
*
* @param params
* @returns
*/
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
*/
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
* @param key
* @returns
*
* @returns
*/
export function getAddonInstallTaskState(addon: string, key: string) {
return request.get(`addon/install/${addon}/status/${key}`)
export function getAddonInstalltask() {
return request.get('addon/installtask')
}
/**
*
* @param addon
*
* @param addon
* @returns
*/
export function executeInstall(addon: string) {
return request.post(`addon/install/execute/${addon}`, {})
export function getAddonCloudInstallLog(addon: string) {
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
*/
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
*/
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
*/
export function getaddonDevelop(params: Record<string, any>) {
export function getAddonDevelop(params: Record<string, any>) {
return request.get(`addon_develop`,{params});
}
/**
@ -19,7 +19,7 @@ export function getAddontype() {
* @returns
*/
export function getAddonDevelop(key:any) {
export function getAddonDevelopInfo(key:any) {
return request.get(`addon_develop/${key}`)
}
/**
@ -147,13 +147,13 @@ export function getSystem() {
*
*/
export function getGeneratorAllModel(params:any) {
return request.get(`generator/all_model`,params)
return request.get(`generator/all_model`,{params})
}
/**
*
*/
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": "插件版本",
"versionCode": "版本号",
"createTime": "发布时间",
"buyLabel": "已购买"
"buyLabel": "已购买",
"installTips": "安装后需手动更新插件引用的依赖和编译各个端口的前端源码",
"localInstall":"本地安装",
"cloudInstall": "一键云安装",
"cloudInstallTips": "云安装可实现一键安装,安装后无需手动更新依赖和编译前端源码",
"installingTips": "有插件正在安装中请等待安装完成之后再进行其他操作,点击查看",
"installPercent": "安装进度"
}

View File

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

View File

@ -18,6 +18,7 @@
"tableNamePlaceholder":"请输入表名",
"tableContentPlaceholder":"请输入描述",
"addonPlaceholder":"请选择插件",
"addonPlaceholder1":"/",
"moduleNamePlaceholder":"请输入模块名",
"classNamePlaceholder":"请输入类名",
"editTypePlaceholder":"请选择编辑方式",
@ -52,6 +53,7 @@
"isQuery":"查询",
"queryType":"搜索方式",
"formType": "表单类型",
"verifyType": "验证类型",
"formInput":"文本框",
"formTextarea":"文本域",
"formSelect":"下拉框",
@ -60,6 +62,7 @@
"formDateTime":"日期",
"formImageSelect":"图片上传",
"formEditor":"富文本",
"formNumber":"数字框",
"pk":"主键",
"status": "状态",
"string": "字符串",
@ -102,8 +105,32 @@
"foreignKeyPlaceholder":"请输入外键",
"addons":"关联应用",
"addonsPlaceholder":"请选择应用",
"saveAndSync":"保存并同步",
"saveAndDownload":"保存并下载",
"saveAndSync":"同步代码",
"saveAndDownload":"下载代码",
"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":"官方市场",
"saveAndSync":"同步代码",
"saveAndSyncText":"同步的代码与项目产生冲突,是否确认覆盖?",
"saveAndSyncText1":"同步的代码会加入到项目代码中,是否确认继续"
"saveAndSyncText1":"同步的代码会加入到项目代码中,是否确认继续",
"addonName": "所属插件"
}

View File

@ -227,10 +227,8 @@
<el-scrollbar max-height="50vh">
<div class="min-h-[150px]">
<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>
<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-col :span="12">
<span>{{ t('path') }}</span>
@ -277,45 +275,27 @@
</el-row>
</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>
</el-scrollbar>
<div class="flex justify-end">
<el-button type="primary" :disabled="!installCheckResult.is_pass" @click="handleInstall">{{ t('install')
}}</el-button>
<div class="flex justify-end" v-if="mode == 'development'">
<el-tooltip effect="dark" :content="t('installTips')" placement="top">
<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 v-show="installStep == 1" class="h-[50vh] mt-[20px]">
@ -334,17 +314,18 @@
</template>
<script lang="ts" setup>
import { ref, watch, computed } from 'vue'
import { ref, watch, computed, h } from 'vue'
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 { TabsPaneContext, ElMessageBox } from 'element-plus'
import { TabsPaneContext, ElMessageBox, ElNotification } from 'element-plus'
import { img } from '@/utils/common'
import { Terminal, api as terminalApi } from 'vue-web-terminal'
const activeName = ref('installed')
const loading = ref<Boolean>(false)
const showType = ref('large')
const mode = ref(import.meta.env.MODE)
const downEvent = (key: string) => {
downloadVersion(key).then(() => {
@ -417,102 +398,135 @@ const handleClick = (tab: TabsPaneContext, event: Event) => {
const currAddon = ref('')
//
const installShowDialog = ref(false)
//
let installTask: AnyObject = {}
//
let currTask: string = ''
//
let executedTask: string[] = []
//
const installStep = ref(0)
//
const installCheckResult = ref({})
//
const installWarning = ref<string[]>([])
//
let timer: null | any = null
/**
* 安装
* @param key
*/
const installAddonFn = (key: string) => {
currAddon.value = key
installAddon({ addon: key }).then(res => {
installStep.value = 0
executedTask = []
installWarning.value = []
installTask = makeIterator(Object.keys(res.data))
currTask = installTask.next().value
installShowDialog.value = true
installStep.value = 0
installWarning.value = []
installShowDialog.value = true
preInstallCheck(key).then(res => {
installCheckResult.value = res.data
}).catch(() => { })
}).catch(() => {
})
preInstallCheck(key).then(res => {
installCheckResult.value = res.data
}).catch(() => { })
}
/**
* 创建遍历器
* @param arr array
* 获取正在进行的安装任务
*/
const makeIterator = (arr: string[]) => {
let nextIndex = 0
return {
next: function () {
return nextIndex < arr.length
? { value: arr[nextIndex++] }
: { done: true }
let notificationEl = null
const getInstallTask = (first: boolean = true) => {
getAddonInstalltask().then(res => {
if (res.data) {
if (first) {
installLog = []
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 = () => {
if (!installCheckResult.value.is_pass) return
installStep.value += 1
if (!installCheckResult.value.is_pass || localInstalling.value) return
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)
.then(() => {
timer = setInterval(() => {
getAddonInstallTaskState(currAddon.value, currTask).then(({ data }) => {
if (!executedTask.includes(currTask)) {
terminalApi.execute('my-terminal', data.command)
executedTask.push(currTask)
}
switch (data.state) {
case 'success':
terminalApi.pushMessage('my-terminal', { content: `${data.desc}执行成功`, class: 'success' })
if (data.step == 'installComplete') {
clearInterval(timer)
installStep.value += 2
localListFn()
} else {
currTask = installTask.next().value
}
break
case 'fail':
terminalApi.pushMessage('my-terminal', { content: `${data.desc}执行失败`, class: 'error' })
terminalApi.pushMessage('my-terminal', { content: `失败原因:${data.error}` })
clearInterval(timer)
break
case 'warn':
terminalApi.pushMessage('my-terminal', { content: data.error, class: 'warning' })
installWarning.value.push(data.error)
break
const cloudInstalling = ref(false)
/**
* 云安装插件
*/
const handleCloudInstall = () => {
if (!installCheckResult.value.is_pass || cloudInstalling.value) return
cloudInstalling.value = true
cloudInstallAddon({ addon: currAddon.value }).then(res => {
installStep.value += 1
terminalApi.execute('my-terminal', 'clear')
terminalApi.pushMessage('my-terminal', { content: '开始安装插件', class: 'info' })
getInstallTask()
cloudInstalling.value = false
}).catch((res) => {
cloudInstalling.value = false
})
}
let installLog: string[] = []
const getCloudInstallLog = () => {
getAddonCloudInstallLog(currAddon.value)
.then(res => {
const data = res.data.data ?? []
if (data[0] && data[0].length && installShowDialog.value == true) {
data[0].forEach(item => {
if (!installLog.includes(item.action)) {
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) => {
installCheckResult.value = {}
})

View File

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

View File

@ -26,7 +26,12 @@
</template>
<el-table-column prop="version" :label="t('code')" 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 :label="t('operation')" fixed="right" align="right" min-width="120">
<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 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-if="appLink[item.key]" 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 class="flex py-5 items-center">
<div class="flex justify-center items-center">
<el-image class="w-[50px] h-[50px]" :src="img(item.icon)" fit="contain">
<template #error>
<div class="image-slot">
<img class="w-[50px] h-[50px]" src="@/app/assets/images/index/app_default.png" />
</div>
</template>
</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 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)" class="flex py-5 items-center">
<div class="flex justify-center items-center">
<el-image class="w-[40px] h-[40px]" :src="img(item.icon)" fit="contain">
<template #error>
<div class="image-slot">
<img class="w-[50px] h-[50px]" src="@/app/assets/images/index/app_default.png" />
</div>
</template>
</el-image>
</div>
<div class="border-t-[1px] border-[#e8e9eb] py-3">
<p class="app-text text-[14px] text-[#999] w-[200px]">{{ item.desc }}</p>
<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 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>
</div>
</div> -->
</div>
</div>
</div>
@ -58,6 +52,7 @@ import { findFirstValidRoute } from '@/router/routers'
import useUserStore from '@/stores/modules/user'
import { useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
const userStore = useUserStore()
const router = useRouter()
const applyList = reactive({
@ -89,6 +84,7 @@ const getAppLink = () => {
getAppLink()
const toLink = (addon: string) => {
storage.set({ key: 'plugMenuTypeStorage', data: addon })
let data = userStore.appMenuList
if(!data.length){
data.push(addon)

View File

@ -8,12 +8,13 @@
<img v-else src="@/app/assets/images/login/login_index_left.png" alt="">
</div>
<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>
<el-form :model="form" ref="formRef" :rules="formRules">
<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>
<icon name="element-User" />
</template>
@ -21,7 +22,9 @@
</el-form-item>
<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>
<icon name="element-Lock" />
</template>
@ -29,7 +32,8 @@
</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>
@ -38,7 +42,8 @@
</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-container>
</template>
@ -67,6 +72,9 @@ const setFormData = async (id: number = 0) => {
webSite.value = await (await getWebConfig()).data
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()
setWindowTitle(t('adminLogin'))
@ -117,8 +125,11 @@ const handleLogin = async (formEl: FormInstance | undefined) => {
const loginFn = (data = {}) => {
loading.value = true
userStore.login({ username: form.username, password: form.password, ...data }).then(res => {
const { query: { redirect } } = route
const path = typeof redirect === 'string' ? redirect : '/'
// const { query: { redirect } } = route
// 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)
}).catch(() => {
loading.value = false
@ -161,5 +172,4 @@ const loginFn = (data = {}) => {
.login-main-left {
display: none;
}
}
</style>
}</style>

View File

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

View File

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

View File

@ -44,7 +44,7 @@
import { ref, reactive, computed, toRaw, } from 'vue'
import { t } from '@/lang'
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'
const showDialog = ref(false)
@ -80,6 +80,9 @@ const formRules = computed(() => {
name: [
{ required: true, message: t('associatedNamePlaceholder'), trigger: 'blur' }
],
addon: [
{ required: true, message: t('addonsPlaceholder'), trigger: 'change' }
],
model: [
{ required: true, message: t('associatedModelPlaceholder'), trigger: 'change' }
],
@ -97,8 +100,8 @@ const formRules = computed(() => {
* 获取关联模型
*/
const modelList = ref([])
const getGeneratorAllModelFn = () => {
getGeneratorAllModel().then(res => {
const getGeneratorAllModelFn = (params:any) => {
getGeneratorAllModel(params).then(res => {
modelList.value = res.data
})
}
@ -114,13 +117,13 @@ const getGeneratorTableColumnFn = (key: any) => {
}
//
const addonLst = ref<Array<any>>([])
const getaddonDevelopFn = async () => {
let { data } = await getaddonDevelop({})
const getAddonDevelopFn = async () => {
let { data } = await getAddonDevelop({})
addonLst.value = [{ title: "系统", key: "system" }]
addonLst.value.push(...data)
getGeneratorAllModelFn({addon:'system'})
}
getaddonDevelopFn()
getAddonDevelopFn()
//
const addonChange =(val:any)=>{
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" />
</el-form-item>
<el-form-item :label="t('addon')">
<el-select class="input-width" :placeholder="t('addonPlaceholder')"
v-model="formData.addon_name" filterable remote clearable :remote-method="getaddonDevelopFn"
<el-select class="input-width" :placeholder="t('addonPlaceholder1')"
v-model="formData.addon_name" filterable remote clearable :remote-method="getAddonDevelopFn"
@change="addonChange">
<el-option :label="item.title" :value="item.key" v-for="item in addonList"
:key="item.key" />
@ -56,39 +56,39 @@
</template>
</el-table-column>
<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 }">
<el-input class="" v-model="row.column_comment"
:placeholder="t('columnCommentPlaceholder')" />
</template>
</el-table-column>
<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 }">
<el-checkbox v-model="row.is_pk" disabled :true-label="1" :false-label="0" />
</template>
</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 }">
<el-checkbox v-model="row.is_required" :true-label="1" :false-label="0" />
</template>
</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 }">
<el-checkbox v-model="row.is_insert" :true-label="1" :false-label="0" />
</template>
</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 }">
<el-checkbox v-model="row.is_update" :true-label="1" :false-label="0" />
</template>
</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 }">
<el-checkbox v-model="row.is_lists" :true-label="1" :false-label="0" />
</template>
</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 }">
<el-checkbox v-model="row.is_search" :true-label="1" :false-label="0" />
</template>
@ -100,19 +100,51 @@
</el-table-column> -->
<el-table-column :label="t('queryType')" prop="" min-width="170px">
<template #default="{ row }">
<el-select v-if="row.is_search" :placeholder="t('selectPlaceholder')"
v-model="row.query_type">
<el-option :label="item" :value="item" v-for="(item, index) in queryType"
:key="index" />
</el-select>
<div class="flex items-center">
<el-select class="" v-if="row.is_search" :placeholder="t('selectPlaceholder')"
v-model="row.query_type">
<el-option :label="item" :value="item" v-for="(item, index) in queryType"
:key="index" />
</el-select>
</div>
</template>
</el-table-column>
<el-table-column :label="t('formType')" prop="" min-width="170px">
<template #default="{ row }">
<el-select :placeholder="t('selectPlaceholder')" v-model="row.view_type">
<el-table-column :label="t('formType')" prop="" min-width="225px">
<template #default="{ row, $index }">
<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"
:key="index" />
</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>
</el-table-column>
<!-- <el-table-column :label="t('formValidation')" prop="" min-width="170px">
@ -135,15 +167,22 @@
</el-radio-group>
<p class="text-[12px] text-[#a9a9a9] leading-normal mt-[5px]">
物理删除从表中把记录移除软删除通过标识使得这条记录在系统逻辑层面上不可见</p>
</div>
</el-form-item>
<el-form-item prop="delete_column_name" :label="t('deleteField')" v-if="formData.is_delete">
<el-select class="input-width" :placeholder="t('deleteFieldPlaceholder')"
v-model="formData.delete_column_name">
<el-option :label="`${item.column_name}:${item.column_comment}`" :value="item.column_name"
v-for="(item, index) in formData.table_column " :key="index" />
</el-select>
<div>
<el-select class="input-width" :placeholder="t('deleteFieldPlaceholder')"
v-model="formData.delete_column_name">
<el-option :label="`${item.column_name}:${item.column_comment}`"
: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 :label="t('editType')">
@ -236,6 +275,8 @@
</el-card>
</div>
<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">
<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 { FormInstance, ElMessageBox, ElMessage } from 'element-plus'
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 { useRoute, useRouter } from 'vue-router'
import Sortable from 'sortablejs'
@ -314,13 +357,52 @@ const viewType = [
{
label: t('formEditor'),
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 getaddonDevelopFn = (search: string) => {
getaddonDevelop({ search }).then(res => {
const getAddonDevelopFn = (search: string) => {
getAddonDevelop({ search }).then(res => {
addonList.value = res.data
})
}
@ -345,7 +427,7 @@ const rowDrop = () => {
}
onMounted(() => {
rowDrop()
getaddonDevelopFn('')
getAddonDevelopFn('')
})
//change
const deleteTypeChange = (val: any) => {
@ -377,6 +459,10 @@ const setFormData = async (id: number = 0) => {
Object.keys(data).forEach((key: string) => {
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)
loading.value = false
}
@ -409,6 +495,7 @@ const addonChange = async (val: any) => {
const associatedIndex = ref(0)
const editDialog = ref()
//
const addEvent = (val: any, index: number) => {
associatedIndex.value = index
editDialog.value.setFormData(val)
@ -426,13 +513,28 @@ const deleteEvent = (index: number) => {
formData.relations.splice(index, 1)
}
const onSave = async (code: number) => {
loading.value = true
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 => {
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
}))
console.log(JSON.parse(data.table_column))
data.relations = JSON.stringify(data.relations)
loading.value = true
editGenerateTable(data).then((res: any) => {
if (code === 3) {
generatorCheckFileFn()
@ -496,6 +598,37 @@ const generateCreateFn = (generate_type: any) => {
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 = () => {
router.push({ path: '/tools/code' })
}

View File

@ -97,14 +97,20 @@
<el-tab-pane :label="t('codeList')" name="codeList">
<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-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-input v-model="codeTableData.searchParam.table_name"
:placeholder="t('tableNamePlaceholder')" />
</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-button type="primary" @click="loadGenerateTableList()">{{ t('search') }}</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')"
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')"
min-width="120" />
@ -159,7 +167,7 @@
</el-tab-pane>
</el-tabs>
<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">
<el-scrollbar class="h-[100%] w-[270px]">
<el-tree v-if="treeData.length && treeKey != ''" :data="treeData" :props="{ label: 'name', value: 'key' }"
@ -182,7 +190,7 @@
</el-scrollbar>
<div class="ml-[20px]" style="width: calc(100% - 285px);">
<el-scrollbar class="h-[100%] w-[100%]">
<highlightjs autodetect :code="code" />
<highlightjs autodetect class="h-[100%]" :code="code" />
</el-scrollbar>
</div>
</div>
@ -194,7 +202,7 @@
<script lang="ts" setup>
import { reactive, ref, onMounted } from 'vue'
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 { ElMessageBox, ElMessage } from 'element-plus'
import AddTable from '@/app/views/tools/code/components/add-table.vue'
@ -214,7 +222,8 @@ let codeTableData = reactive({
data: [],
searchParam: {
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)
/**
* 添加代码生成
*/
@ -312,6 +330,7 @@ const generatorCheckFileFn = ((id: any) => {
codeTableData.loading = false
})
})
/**
* 同步or下载
*/
@ -333,6 +352,7 @@ const generateCreateFn = (id: any, generate_type: any) => {
codeTableData.loading = false
})
}
/*
*代码预览
*/
@ -429,4 +449,11 @@ const listToTree = (arr) => {
height: 44px;
display: flex;
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": "切换首页",
"indexWarning": "你确定要切换首页吗?",
"appName": "应用名称",
"appNamePlaceholder": "请输入应用名称"
"appNamePlaceholder": "请输入应用名称",
"generateMobile":"请输入正确的手机号格式",
"generateNumber":"请输入整数",
"generateIdCard":"请输入正确的身份证号",
"generateEmail":"请输入正确的邮箱号",
"generateMax":"超过最多输入字符数",
"generateMin":"少于最少输入字符数",
"generateBetween":"请输入正确的字符信息"
}

View File

@ -1,76 +1,384 @@
<template>
<div :class="['flex',{'two-type': sidebar == 'twoType'},{'three-type': sidebar == 'threeType'}]">
<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="['flex', { 'two-type': sidebar == 'twoType' }, { 'three-type': sidebar == 'threeType' }]" v-if="aaa">
<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">
<div v-if="item.meta.show" @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')) }]">
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px" :title="item.meta.title" />
<div v-if="item.meta.app == '' && item.meta.attr == 'common'" @click="toLink(item)"
:class="['flex items-center justify-center h-[56px] cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] menu-item hover:text-color whitespace-nowrap', { 'bg-[#f1f2f6] text-color menu-item-active ': (item.path == currentRoute.path || (currentRoute.path == '/admin' && item.path == '/index') || (currentRoute.meta.app && item.path == '/index')) }]">
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px"
:title="item.meta.title" />
</div>
</template>
</div>
<a href="javascript:;"
class="absolute -bottom-[20px] left-[50%] cut-style iconfont icongengduo !text-[30px] qx"
@click="cutMenuStyleFn" title="切换"></a>
</div>
</el-aside>
</div>
<!-- 浮动样式的应用菜单 -->
<div 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>
</template>
</div>
</div>
</el-aside>
</div>
<!-- 二级菜单 -->
<div v-if="matched.children.length" class="w-[189px] box-border border-r-[1px] border-solid second-menu">
<div class="flex flex-col items-center justify-center h-[108px] border-b-[1px] border-solid second-head mx-[10px]">
<div class="flex items-center justify-center w-[30px] h-[30px]" v-if="!matched.meta.app">
<icon v-if="matched.meta.icon" :name="matched.meta.icon" class="!w-auto" size="24px" />
</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)">
<el-scrollbar class="overflow-y-auto menus-wrap">
<el-menu class="apply-menu !border-0" :router="true" unique-opened="true"
:default-active="String(route.name)">
<template v-for="(twoMenu, twoIndex) in item.children">
<el-sub-menu :index="String(twoMenu.meta.title)" v-if="twoMenu.children && twoMenu.meta.show">
<template #title>
<span class="text-[14px]">{{ 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>
</el-menu-item>
</template>
</el-sub-menu>
<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)">
<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 class="flex items-center !px-[25px] h-[56px] cursor-pointer text-[#333] el-menu-item"
v-else-if="twoMenu.meta.show && twoMenu.meta.key == 'official_market'"
@click="toLink(twoMenu)">
<div v-if="twoMenu.meta.icon" class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon" class="absolute !w-auto"
size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</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">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon" class="absolute !w-auto" size="18px"/>
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</div>
</template>
</el-menu>
</div>
<!-- 系统菜单 -->
<template
v-if="applyTypeList.includes(localMenuKey) || otherTypeList.includes(localMenuKey) || 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 == 'system' && !twoMenu.meta.app && twoMenu.children">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
class="absolute !w-auto" size="18px" />
</div>
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
</template>
<template v-for="(threeMenu, threeIndex) in twoMenu.children" :key="threeIndex">
<el-sub-menu :index="String(threeMenu.meta.title)"
v-if="threeMenu.meta.app && threeMenu.children">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center">
<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>
</template>
@ -85,47 +393,124 @@ import storage from '@/utils/storage'
import { CollectionTag } from '@element-plus/icons-vue'
import { findFirstValidRoute } from '@/router/routers'
import { getAddonByKey } from '@/app/api/addon'
import { getApply } from '@/app/api/apply'
const userStore = useUserStore()
const systemStore = useSystemStore()
const route = useRoute()
const router = useRouter()
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 = []
userStore.routers.forEach((item, index) => {
if (item.meta.app == '') {
if (item.children && item.children.length) {
item.name = findFirstValidRoute(item.children)
menus.push(item)
} else {
menus.push(item)
}
if (item.children && item.children.length) {
item.name = findFirstValidRoute(item.children)
menus.push(item)
} else {
menus.push(item)
}
})
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(() => {
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, () => {
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 => {
state.menuDrawer = false
})
})
}, { immediate: true })
//
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(() => {
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>
<style lang="scss">
.layout-aside {
border-right: 1px solid var(--el-border-color-lighter);
// border-right: 1px solid var(--el-border-color-lighter);
&.bright {
background-color: #F5F7F9;
@ -188,15 +579,21 @@ const sidebar = computed(() => {
}
}
.second-menu .el-sub-menu .el-sub-menu__title{
padding-left: 35px !important;
.el-icon.el-sub-menu__icon-arrow{
right: 35px;
.second-menu .el-sub-menu .el-sub-menu__title {
padding-left: 25px !important;
padding-right: 25px !important;
.el-icon.el-sub-menu__icon-arrow {
right: 25px;
font-weight: bolder;
font-size: 14px;
}
}
.three-menu.el-sub-menu .el-sub-menu__title {
padding-left: 45px !important;
}
.text-color {
color: var(--el-color-primary);
}
@ -207,10 +604,17 @@ const sidebar = computed(() => {
}
}
.cut-style {
color: #6d7278;
}
.cut-style.qx {
transform: translateX(-50%);
}
//
.two-type{
.logo-wrap{
.logo span{
.two-type {
.logo-wrap {
.logo span {
width: 36px;
height: 36px;
border-radius: 50%;
@ -222,40 +626,54 @@ const sidebar = computed(() => {
font-size: 25px !important;
}
}
.layout-aside{
background-color: #12192D;
.menu-item{
.layout-aside {
// background-color: #12192D;
background-color: #2b303b;
.menu-item {
color: #fff;
&.menu-item-active, &:hover{
&.menu-item-active,
&:hover {
background-color: var(--el-color-primary);
color: #fff;
}
}
}
.second-menu{
.second-menu {
background-color: #F5F7F9;
.el-menu{
.el-menu {
background-color: transparent;
.el-menu-item:hover {
background-color: #fff;
color: var(--el-color-primary);
}
.el-menu-item.is-active {
background-color: #fff;
}
}
.el-sub-menu__title:hover{
.el-sub-menu__title:hover {
background-color: #fff;
color: var(--el-color-primary);
}
}
.cut-style {
color: #FFF;
}
}
//
.three-type{
.logo-wrap{
.logo span{
.three-type {
.logo-wrap {
.logo span {
width: 36px;
height: 36px;
border-radius: 50%;
@ -267,38 +685,58 @@ const sidebar = computed(() => {
font-size: 25px !important;
}
}
.layout-aside{
background-color: #12192D;
.menu-item{
.layout-aside {
background-color: #2b303b;
.menu-item {
color: #fff;
&.menu-item-active, &:hover{
background-color: #19233C;
&.menu-item-active,
&:hover {
background-color: #303848;
color: var(--el-color-primary);
}
}
}
.second-menu{
background-color: #19233C;
.second-head{
.second-menu {
background-color: #303848;
.second-head {
color: #fff;
border-color: #364059;
}
.el-menu{
.el-menu {
background-color: transparent;
.el-menu-item{
.el-menu-item {
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);
}
}
.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;
}
}
.cut-style {
color: #FFF;
}
}
.menus-wrap {
height: calc(100vh - 64px);
}
</style>

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3883393 */
src: url('//at.alicdn.com/t/c/font_3883393_tcu9x1pe11k.woff2?t=1693640799122') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_tcu9x1pe11k.woff?t=1693640799122') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_tcu9x1pe11k.ttf?t=1693640799122') format('truetype');
src: url('//at.alicdn.com/t/c/font_3883393_236yfsl6lh5.woff2?t=1694750730982') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_236yfsl6lh5.woff?t=1694750730982') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_236yfsl6lh5.ttf?t=1694750730982') format('truetype');
}
.iconfont {
@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale;
}
.icondian:before {
content: "\ec1e";
}
.iconjiantou_xiangzuoliangci_o:before {
content: "\eb93";
}
.iconfenlei:before {
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}/`,
timeout: 30000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded;',
'Content-Type': 'application/json;',
'lang': storage.get('lang') ?? 'zh-cn'
}
});