This commit is contained in:
全栈小学生 2026-03-20 15:56:15 +08:00
parent 52e3ea6cc5
commit b2a30f1172
59 changed files with 4497 additions and 2599 deletions

View File

@ -35,7 +35,8 @@ onMounted(() => {
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
<style>
.el-page-header__header .el-page-header__left .el-page-header__content{ .el-page-header__header .el-page-header__left .el-page-header__content{
font-size: 14px !important; font-size: 14px !important;
font-weight: 500 !important; font-weight: 500 !important;

View File

@ -60,7 +60,7 @@ export function getSmsList() {
* @returns * @returns
*/ */
export function getSmsInfo(sms_type: string) { export function getSmsInfo(sms_type: string) {
return request.get(`notice/notice/sms/${ sms_type }`,) return request.get(`notice/notice/sms/${ sms_type }`)
} }
/** /**
@ -151,7 +151,7 @@ export function deleteSign(username: string, params: Record<string, any>) {
* @param params * @param params
*/ */
export function editAccount(username: string, params: Record<string, any>) { export function editAccount(username: string, params: Record<string, any>) {
return request.post(`notice/niusms/account/edit/${username}`, params, { showSuccessMessage: true }); return request.post(`notice/niusms/account/edit/${username}`, params, { showSuccessMessage: true })
} }
/** /**

View File

@ -162,7 +162,7 @@ export function refreshPrinterToken(printer_id: number) {
return request.put(`sys/printer/refreshtoken/${ printer_id }`, {}, { return request.put(`sys/printer/refreshtoken/${ printer_id }`, {}, {
showErrorMessage: true, showErrorMessage: true,
showSuccessMessage: true showSuccessMessage: true
}); })
} }
/** /**
@ -171,7 +171,7 @@ export function refreshPrinterToken(printer_id: number) {
* @returns * @returns
*/ */
export function testPrint(printer_id: number) { export function testPrint(printer_id: number) {
return request.put(`sys/printer/testprint/${ printer_id }`, {},{ showErrorMessage: true, showSuccessMessage: true }); return request.put(`sys/printer/testprint/${ printer_id }`, {}, { showErrorMessage: true, showSuccessMessage: true })
} }
/** /**

View File

@ -573,21 +573,21 @@ export function setPatConfig(params: Record<string, any>) {
/** /**
* *
*/ */
export function menuRefresh(params: Record<string, any>) { export function menuRefresh() {
return request.post(`sys/menu/refresh`, {}) return request.post(`sys/menu/refresh`, {})
} }
/** /**
* *
*/ */
export function clearSchemaCache(params: Record<string, any>) { export function clearSchemaCache() {
return request.post(`sys/schema/clear`, {}, { showSuccessMessage: true }) return request.post(`sys/schema/clear`, {}, { showSuccessMessage: true })
} }
/** /**
* *
*/ */
export function clearCache(params: Record<string, any>) { export function clearCache() {
return request.post(`sys/cache/clear`, {}, { showSuccessMessage: true }) return request.post(`sys/cache/clear`, {}, { showSuccessMessage: true })
} }
@ -734,3 +734,11 @@ export function getInstallConfig() {
export function getQrcode(params: Record<string, any>) { export function getQrcode(params: Record<string, any>) {
return request.get(`sys/qrcode`, { params, showErrorMessage: false }) return request.get(`sys/qrcode`, { params, showErrorMessage: false })
} }
/**
*
* @returns
*/
export function checkJobStatus() {
return request.get(`sys/job`)
}

View File

@ -377,7 +377,7 @@ const dialogCancel = () => {
} }
const cloudBuildCheckDirFn = () => { const cloudBuildCheckDirFn = () => {
window.open('https://doc.niucloud.com/v6.html?keywords=/chang-jian-wen-ti-chu-li/er-shi-wu-3001-sheng-7ea7-yun-bian-yi-mu-lu-du-xie-quan-xian-zhuang-tai-bu-tong-guo-ru-he-chu-li') window.open('https://doc.press.niucloud.com/php/v6-shop/use/chang-jian-wen-ti-chu-li/er-shi-wu-3001-sheng-7ea7-yun-bian-yi-mu-lu-du-xie-quan-xian-zhuang-tai-bu-tong-guo-ru-he-chu-li.html')
} }
watch(() => showDialog.value, () => { watch(() => showDialog.value, () => {

View File

@ -33,6 +33,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, defineProps, nextTick } from "vue" import { computed, ref, defineProps, nextTick } from "vue"
import { t } from "@/lang" import { t } from "@/lang"
import { ElMessage } from "element-plus"
import { getAppVersionList, getFrameworkVersionList } from "@/app/api/module" import { getAppVersionList, getFrameworkVersionList } from "@/app/api/module"
const props = defineProps({ const props = defineProps({

View File

@ -34,37 +34,14 @@
</div> </div>
</template> </template>
</template> </template>
<div v-if="step == 2"> <div v-show="step == 2">
<el-steps :active="numberOfSteps" align-center class="number-of-steps" process-status="process" v-if="!errorDialog && active != 'complete'"> <el-steps :active="numberOfSteps" align-center class="number-of-steps" process-status="process">
<el-step :title="t('testDirectoryPermissions')" /> <el-step :title="t('testDirectoryPermissions')" />
<el-step :title="t('upgrade.option')" /> <el-step :title="t('upgrade.option')" />
<el-step :title="t('startUpgrade')" /> <el-step :title="t('startUpgrade')" />
<el-step :title="t('upgradeEnd')" /> <el-step :title="t('upgradeEnd')" />
</el-steps> </el-steps>
<div class="h-[400px]" style="overflow: auto"> <div class="h-[400px]" style="overflow: auto">
<!-- <div class="time-dialog-wrap mt-[30px]" v-show="active == 'content'">-->
<!-- <el-timeline style="width: 100%">-->
<!-- <el-timeline-item v-for="(item, index) in upgradeContent.version_list" :key="index" placement="left">-->
<!-- <div class="relative">-->
<!-- <span class="text-[#333333] text-[16px] absolute">{{ timeSplit(item.release_time)[0] }}</span>-->
<!-- <br />-->
<!-- <span class="text-[#999999] text-[14px] w-[78px] block mt-[10px] absolute" style="text-align: right"> {{ timeSplit(item.release_time)[1] }}</span>-->
<!-- </div>-->
<!-- <el-collapse v-model="activeName" accordion>-->
<!-- <el-collapse-item :name="index">-->
<!-- <template #title>-->
<!-- <span class="text-[#333] text-[16px]"> v{{ item.version_no }} </span>-->
<!-- </template>-->
<!-- <div class="px-[20px] py-[20px] bg-overlay timeline-log-wrap whitespace-pre-wrap rounded-[4px]" style="background: rgba(25, 103, 249, 0.03);" v-if="item['upgrade_log']">-->
<!-- <div v-html="item['upgrade_log']"></div>-->
<!-- </div>-->
<!-- </el-collapse-item>-->
<!-- </el-collapse>-->
<!-- </el-timeline-item>-->
<!-- </el-timeline>-->
<!-- </div>-->
<!-- 判断文件权限 --> <!-- 判断文件权限 -->
<div v-show="active == 'upgrade'"> <div v-show="active == 'upgrade'">
<div class="flex flex-col" v-if="upgradeCheck && !upgradeTask"> <div class="flex flex-col" v-if="upgradeCheck && !upgradeTask">
@ -132,7 +109,6 @@
<div class="h-[370px] mt-[30px]" v-show="showTerminal && upgradeTask && !errorDialog"> <div class="h-[370px] mt-[30px]" v-show="showTerminal && upgradeTask && !errorDialog">
<terminal ref="terminalRef" :name="`upgrade-${terminalId}`" :context="upgradeTask ? upgradeTask.upgrade.app_key : ''" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd" /> <terminal ref="terminalRef" :name="`upgrade-${terminalId}`" :context="upgradeTask ? upgradeTask.upgrade.app_key : ''" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd" />
</div> </div>
</div> </div>
<!-- 是否备份选择 --> <!-- 是否备份选择 -->
<div class="flex flex-col" v-show="active == 'backup'"> <div class="flex flex-col" v-show="active == 'backup'">
@ -156,7 +132,51 @@
</div> </div>
</el-scrollbar> </el-scrollbar>
</div> </div>
<div class="mt-[20px] h-[370px]" v-show="errorDialog">
</div>
</div>
<div v-if="step == 3">
<div class="mt-[10px]" v-show="active == 'complete'">
<el-result icon="success" :title="t('upgrade.upgradeSuccess')">
<template #icon>
<img src="@/app/assets/images/success_icon.png" alt="">
</template>
<template #extra>
<div class="text-[16px] text-[#4F516D] mt-[-5px]" v-show="upgradeTask && upgradeTask.executed && !upgradeTask.executed.includes('cloudBuild')">{{ t('upgrade.upgradeCompleteTips') }}</div>
<!-- <div class="text-[16px] text-[#9699B6] mt-[10px]">本次升级用时{{ formatUpgradeDuration }}</div> -->
<div class="w-[750px]" v-if="upgradeTask.cloud_build_error">
<el-alert class="!w-[750px] border-warning !border-[1px] !rounded-[0px] border-solid" type="warning" :closable="false">
<template #title>
<span class="text-error">警告</span>
<span class="text-black">升级过程中发生云编译错误</span>
</template>
</el-alert>
<div class="text-left mt-[10px] leading-8">
<div class="font-bold">为了保证系统稳定建议您做以下处理:</div>
<div><span class="w-[6px] h-[6px] rounded-[6px] bg-black inline-block mr-[10px]"></span>如果您是开发者安装的框架或者插件二开过或者正在开发中可能是因为您的本地代码不完整导致的云编译失败需要自己调试并重新进行编译才算升级完成(云编译会把本地插件前端代码上传编译)</div>
<div><span class="w-[6px] h-[6px] rounded-[6px] bg-black inline-block mr-[10px]"></span>如果您没有二开过任何代码可能是本地插件存在兼容性问题请联系插件开发者或者官方客服解决</div>
<div><span class="w-[6px] h-[6px] rounded-[6px] bg-black inline-block mr-[10px]"></span>如果您的项目已经投入正式运营中请立即回滚</div>
</div>
<div class="text-left mt-[10px]">
<div class="font-bold">编译信息错误</div>
<div class="mt-[10px] text-secondary overflow-hidden line-clamp-4">
{{ upgradeTask.cloud_build_error }}
</div>
</div>
<div class="mt-[20px]">
<el-button @click="handleBack()" class="!w-[90px]">更多信息</el-button>
<el-button @click="showDialog=false" type="primary" plain class="!w-[90px]">我已知晓</el-button>
<el-button @click="cloudBuildError('cloud_build_error_rollback')" type="primary" class="!w-[90px]">回滚</el-button>
</div>
</div>
<div class="mt-[20px]" v-else>
<el-button @click="handleBack()" class="!w-[90px]">返回</el-button>
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
</div>
</template>
</el-result>
</div>
<div class="mt-[20px] h-[370px]" v-show="active == 'fail'">
<el-result icon="error" :title="t('升级失败')"> <el-result icon="error" :title="t('升级失败')">
<template #icon> <template #icon>
<img src="@/app/assets/images/error_icon.png" alt="" /> <img src="@/app/assets/images/error_icon.png" alt="" />
@ -170,23 +190,6 @@
</template> </template>
</el-result> </el-result>
</div> </div>
<div class="mt-[20px]" v-show="active == 'complete'">
<el-result icon="success" :title="t('upgrade.upgradeSuccess')">
<template #icon>
<img src="@/app/assets/images/success_icon.png" alt="">
</template>
<template #extra>
<div class="text-[16px] text-[#4F516D] mt-[-5px]" v-show="upgradeTask && upgradeTask.executed && !upgradeTask.executed.includes('cloudBuild')">{{ t('upgrade.upgradeCompleteTips') }}</div>
<div class="text-[16px] text-[#9699B6] mt-[10px]">本次升级用时{{ formatUpgradeDuration }}</div>
<div class="mt-[20px]">
<el-button @click="handleBack()" class="!w-[90px]">返回</el-button>
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
</div>
</template>
</el-result>
<!-- <el-alert :title="t('upgrade.upgradeCompleteTips')" type="error" :closable="false" v-show="upgradeTask && upgradeTask.executed && !upgradeTask.executed.includes('cloudBuild')"/> -->
</div>
</div>
</div> </div>
</template> </template>
<template #footer> <template #footer>
@ -226,6 +229,18 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog v-model="dialogUpgradePrompt" title="升级提示" width="500" :before-close="handleClose">
<p>您当前使用的{{applicationType == 'app' ? '应用' : '插件'}}版本是 {{applicationVersion}}若想升级到 {{newApplicationVersion}} 版本需要先把框架从 v{{frameworkVersion}} 升级到 v{{ newFrameworkVersion }} </p>
<div class="flex items-center">
<span>如需升级框架请点击升级框架操作</span>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="upgradePromptConfirm">升级框架</el-button>
<el-button @click="dialogUpgradePrompt = false">取消</el-button>
</div>
</template>
</el-dialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -354,8 +369,6 @@ const getUpgradeTaskFn = () => {
errorMsg.value = item errorMsg.value = item
} }
}) })
errorDialog.value = true
showTerminal.value = false
if (upgradeTimer) { if (upgradeTimer) {
clearInterval(upgradeTimer) clearInterval(upgradeTimer)
upgradeTimer = null upgradeTimer = null
@ -365,12 +378,17 @@ const getUpgradeTaskFn = () => {
// //
if (data.step == 'restoreComplete') { if (data.step == 'restoreComplete') {
flashInterval && clearInterval(flashInterval) flashInterval && clearInterval(flashInterval)
step.value = 3
active.value = 'fail'
return return
} }
// //
if (data.step == 'upgradeComplete') { if (data.step == 'upgradeComplete') {
if (data.cloud_build_error) {
terminalRef.value.pushMessage({ content: data.cloud_build_error, class: 'error' })
}
step.value = 3
active.value = 'complete' active.value = 'complete'
showTerminal.value = false
numberOfSteps.value = 4 numberOfSteps.value = 4
notificationEl && notificationEl.close() notificationEl && notificationEl.close()
emits('complete') emits('complete')
@ -379,22 +397,29 @@ const getUpgradeTaskFn = () => {
upgradeTimer = null upgradeTimer = null
} }
timeloading.value = false timeloading.value = false
clearUpgradeTask()
return return
} }
numberOfSteps.value = 2 numberOfSteps.value = 2
active.value = 'upgrade' active.value = 'upgrade'
executeUpgradeFn() executeUpgradeFn()
}).catch((err) => {
console.log(err)
}) })
} }
getUpgradeTaskFn() getUpgradeTaskFn()
const upgradePromptConfirm = ()=>{
dialogUpgradePrompt.value = false
open('niucloud-admin')
}
const isBack = ref(false) const isBack = ref(false)
const handleBack = () => { const handleBack = () => {
active.value = 'upgrade' active.value = 'upgrade'
isBack.value = true isBack.value = true
showTerminal.value = true showTerminal.value = true
errorDialog.value = false // errorDialog.value = false //
step.value = 2
} }
const formatUpgradeDuration = computed(() => { const formatUpgradeDuration = computed(() => {
@ -505,8 +530,11 @@ const upgradeAddonFn = () => {
loading.value = false loading.value = false
}) })
} }
const dialogUpgradePrompt = ref(false)
const open = (addonKey: string = '', callback = null) => { const applicationVersion = ref('')
const newApplicationVersion = ref('')
const applicationType = ref('app')
const open = (addonKey: string = '', callback = null, otherData: any = {}) => {
errorDialog.value = false // errorDialog.value = false //
if (upgradeTask.value) { if (upgradeTask.value) {
ElMessage({ message: '已有正在执行中的升级任务', type: 'error' }) ElMessage({ message: '已有正在执行中的升级任务', type: 'error' })
@ -517,7 +545,10 @@ const open = (addonKey: string = '', callback = null) => {
if (callback) callback() if (callback) callback()
} else { } else {
if (addonKey && addonKey.indexOf('niucloud-admin') == -1 && frameworkVersion.value != newFrameworkVersion.value) { if (addonKey && addonKey.indexOf('niucloud-admin') == -1 && frameworkVersion.value != newFrameworkVersion.value) {
ElMessage({ message: '存在新版本框架,请先升级框架', type: 'error' }) applicationVersion.value = otherData.install_info.version
newApplicationVersion.value = otherData.version
applicationType.value = otherData.type
dialogUpgradePrompt.value = true
if (callback) callback() if (callback) callback()
return return
} }
@ -646,6 +677,12 @@ const cloudBuildError = (event: string) => {
getUpgradeTaskFn() getUpgradeTaskFn()
}) })
break break
case 'cloud_build_error_rollback':
upgradeUserOperate(event).then(() => {
handleBack()
executeUpgradeFn()
})
break
} }
} }

View File

@ -32,5 +32,5 @@
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高", "safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高",
"wechatBaseUri": "借权域名", "wechatBaseUri": "借权域名",
"wechatBaseUriPlaceholder": "", "wechatBaseUriPlaceholder": "",
"wechatBaseUriTips": "默认留空填写后将替换https://open.weixin.gg.com/获取授权!" "wechatBaseUriTips": "默认留空填写后将替换https://open.weixin.qq.com/获取授权!"
} }

View File

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

View File

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

View File

@ -2,6 +2,5 @@
"isCaptcha": "是否启用验证码", "isCaptcha": "是否启用验证码",
"bgImg": "登录页广告图", "bgImg": "登录页广告图",
"admin": "平台端", "admin": "平台端",
"adminBgImgTip": "建议上传尺寸为450*400px", "adminBgImgTip": "建议上传尺寸为450*400px"
"siteBgImgTip": "建议上传尺寸为620*980px"
} }

View File

@ -17,7 +17,7 @@
"mobileOrUsernameNoEmpty": "普通注册方式至少需启用一种", "mobileOrUsernameNoEmpty": "普通注册方式至少需启用一种",
"loginPageSet": "界面设置", "loginPageSet": "界面设置",
"bgUrl": "背景图", "bgUrl": "背景图",
"bgUrlPlaceholder": "建议图片尺寸750*669像素图片格式jpg、png、jpeg", "bgUrlPlaceholder": "前台快捷登录/注册页的背景图,建议图片尺寸750*669像素图片格式jpg、png、jpeg",
"desc": "描述", "desc": "描述",
"descPlaceholder": "请输入描述" "descPlaceholder": "请输入描述"
} }

View File

@ -30,12 +30,8 @@
"customerServiceCode": "客服二维码", "customerServiceCode": "客服二维码",
"contactsTel": "联系电话", "contactsTel": "联系电话",
"contactsTelPlaceholder": "请输入联系电话", "contactsTelPlaceholder": "请输入联系电话",
"logoPlaceholder": "建议图片尺寸210*30像素图片格式jpg、png、jpeg。", "logoPlaceholder": "管理系统左上角的长方形Logo建议图片尺寸210*30像素图片格式jpg、png、jpeg。",
"iconPlaceholder": "建议图片尺寸100*100像素图片格式jpg、png、jpeg。", "iconPlaceholder": "管理系统左上角的正方形Logo建议图片尺寸100*100像素图片格式jpg、png、jpeg。",
"siteLoginLogo": "站点登录Logo",
"siteLoginLogoTips": "站点端登录Logo建议图片尺寸132*40像素图片格式jpg、png、jpeg。",
"siteLoginBgImg": "站点登录背景图",
"siteLoginBgImgTips": "站点端登录背景图建议图片尺寸1920*1280像素图片格式jpg、png、jpeg。",
"metaTitle": "Meta 标题", "metaTitle": "Meta 标题",
"MetaPlaceholder": "请输入Meta 标题", "MetaPlaceholder": "请输入Meta 标题",
"metaDescription": "Meta 描述", "metaDescription": "Meta 描述",

View File

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

View File

@ -6,7 +6,7 @@
<template v-if="Object.keys(marketingList).length"> <template v-if="Object.keys(marketingList).length">
<template v-for="(item, index) in marketingList" :key="index + 'b'"> <template v-for="(item, index) in marketingList" :key="index + 'b'">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center" v-if="item.list.length > 0">
<span class="text-page-title">{{ item.title }}</span> <span class="text-page-title">{{ item.title }}</span>
</div> </div>
@ -58,7 +58,7 @@ const loading = ref(true)
const getMarketingList = async () => { const getMarketingList = async () => {
loading.value = false loading.value = false
} }
getMarketingList() // getMarketingList()
const toLink = (item: any) => { const toLink = (item: any) => {
if (item.url) { if (item.url) {

View File

@ -24,10 +24,10 @@
<el-form-item :label="t('parentMenu')" prop="parent_key"> <el-form-item :label="t('parentMenu')" prop="parent_key">
<el-tree-select class="input-width" v-if="formData.addon != ''" v-model="formData.parent_key" <el-tree-select class="input-width" v-if="formData.addon != ''" v-model="formData.parent_key"
:props="{ label: 'menu_name', value: 'menu_key' }" :data="addonMenuList" check-strictly :props="{ label: 'menu_name', value: 'menu_key' }" :data="addonMenuListWithDisabled" check-strictly
:render-after-expand="false" /> :render-after-expand="false" />
<el-tree-select class="input-width" v-else v-model="formData.parent_key" <el-tree-select class="input-width" v-else v-model="formData.parent_key"
:props="{ label: 'menu_name', value: 'menu_key' }" :data="sysMenuList" check-strictly :props="{ label: 'menu_name', value: 'menu_key' }" :data="sysMenuListWithDisabled" check-strictly
:render-after-expand="false" /> :render-after-expand="false" />
</el-form-item> </el-form-item>
@ -183,6 +183,38 @@ const getAddonMenuFn = async (key: any) => {
addonMenuList.value = data addonMenuList.value = data
} }
const markMenuDisabled = (menuList: any[], menuType: number): any[] => {
return menuList.map(item => {
const newItem = { ...item }
//
if (item.menu_key === '') {
// sysMenuList
newItem.disabled = menuType === 2 //
} else {
if (menuType === 2) {
// menu_type === 1
newItem.disabled = item.menu_type !== 1
} else {
// / menu_type === 0
newItem.disabled = item.menu_type !== 0
}
}
// children
if (Array.isArray(item.children) && item.children.length > 0) {
newItem.children = markMenuDisabled(item.children, menuType)
}
return newItem
})
}
const sysMenuListWithDisabled = computed(() => {
return markMenuDisabled(sysMenuList.value, formData.menu_type)
})
const addonMenuListWithDisabled = computed(() => {
return markMenuDisabled(addonMenuList.value, formData.menu_type)
})
// //
const addonChange = async (val: any) => { const addonChange = async (val: any) => {
formData.parent_key = '' formData.parent_key = ''

View File

@ -54,8 +54,34 @@ let popTitle: string = ''
// //
const menus = ref<Record<string, any>[]>([]) const menus = ref<Record<string, any>[]>([])
getAuthMenus({ is_button: 0 }).then((res) => { getAuthMenus({ is_button: 0 }).then((res) => {
menus.value = res.data menus.value = mergeMenuKeyAndAuth(res.data)
}) })
const mergeMenuKeyAndAuth = (arr: any) => {
//
if (!Array.isArray(arr)) {
return arr;
}
//
return arr.map(item => {
//
const newItem = { ...item };
// 1. menu_key auth
if (newItem.menu_key && Array.isArray(newItem.auth)) {
// menu_key concat
newItem.menu_key = newItem.auth.concat(newItem.menu_key);
}
// 2. children
if (Array.isArray(newItem.children)) {
newItem.children = mergeMenuKeyAndAuth(newItem.children);
}
return newItem;
});
}
// //
const selectAll = ref(false) const selectAll = ref(false)
const checkStrictly = ref(false) const checkStrictly = ref(false)
@ -71,7 +97,7 @@ watch(selectAll, () => {
}) })
const handleCheckChange = debounce((e) => { const handleCheckChange = debounce((e) => {
formData.rules = treeRef.value.getCheckedKeys() formData.rules = treeRef.value.getCheckedKeys().flat(1)
}) })
const menuAction = () => { const menuAction = () => {
@ -145,7 +171,7 @@ const confirm = async (formEl: FormInstance | undefined) => {
loading.value = true loading.value = true
const data = Object.assign({}, formData) const data = Object.assign({}, formData)
data.rules = data.rules.concat(treeRef.value.getHalfCheckedKeys()) data.rules = data.rules.concat(treeRef.value.getHalfCheckedKeys().flat(1))
save(data).then(res => { save(data).then(res => {
loading.value = false loading.value = false
@ -190,7 +216,7 @@ const setFormData = async (row: any = null) => {
function checked (menuKey:string, data:any, newArr:any) { function checked (menuKey:string, data:any, newArr:any) {
Object.keys(data).forEach((key:string) => { Object.keys(data).forEach((key:string) => {
const item = data[key] const item = data[key]
if (item.menu_key == menuKey) { if (!Array.isArray(item.menu_key) && item.menu_key == menuKey || Array.isArray(item.menu_key) && item.menu_key.indexOf(menuKey) != -1) {
if (!item.children || item.children.length == 0) { if (!item.children || item.children.length == 0) {
newArr.push(item.menu_key) newArr.push(item.menu_key)
} }

View File

@ -9,7 +9,7 @@
<el-button type="primary" class="w-[100px]" @click="addEvent"> <el-button type="primary" class="w-[100px]" @click="addEvent">
{{ t('addMenu') }} {{ t('addMenu') }}
</el-button> </el-button>
<el-button class="w-[100px]" @click="refreshMenu"> <el-button class="w-[100px]" :loading="refreshLoading" @click="refreshMenu">
{{ t('initializeMenu') }} {{ t('initializeMenu') }}
</el-button> </el-button>
</div> </div>
@ -129,6 +129,7 @@ const getMenuList = () => {
} }
getMenuList() getMenuList()
// //
const refreshLoading = ref(false)
const refreshMenu = () => { const refreshMenu = () => {
ElMessageBox.confirm(h('div', null, [ ElMessageBox.confirm(h('div', null, [
h('p', null, t('initializeMenuTipsOne')), h('p', null, t('initializeMenuTipsOne')),
@ -140,9 +141,12 @@ const refreshMenu = () => {
// type: 'warning' // type: 'warning'
} }
).then(() => { ).then(() => {
refreshLoading.value = true
menuRefresh({}).then(res => { menuRefresh({}).then(res => {
location.reload() refreshLoading.value = false
}).catch(() => {}) }).catch(() => {
refreshLoading.value = false
})
}).catch(() => {}) }).catch(() => {})
} }

View File

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

View File

@ -30,8 +30,8 @@
<div class="w-[700px]"> <div class="w-[700px]">
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<!-- 多应用切换启动页 --> <!-- 多应用切换启动页 v-if="siteApps.length > 1" -->
<el-button type="primary" @click="showDialog = true" v-if="siteApps.length > 1">{{ t('changePage') }}</el-button> <el-button type="primary" @click="showDialog = true">{{ t('changePage') }}</el-button>
<el-button type="primary" @click="toDecorate()" v-show="page.use_template.action == 'decorate'" class="ml-[12px]">{{ t('decorate') }}</el-button> <el-button type="primary" @click="toDecorate()" v-show="page.use_template.action == 'decorate'" class="ml-[12px]">{{ t('decorate') }}</el-button>
</div> </div>
@ -59,7 +59,7 @@
</div> </div>
</div> </div>
<el-dialog v-model="showDialog" :title="t('pageSelectTips')" width="400px" :close-on-press-escape="false" :destroy-on-close="true" :close-on-click-modal="false"> <el-dialog v-model="showDialog" :title="t('pageSelectTips')" width="400px" :close-on-press-escape="true" :destroy-on-close="true" :close-on-click-modal="false">
<div class="flex items-start"> <div class="flex items-start">
<el-scrollbar class="pl-4 h-[300px] flex-1"> <el-scrollbar class="pl-4 h-[300px] flex-1">
<div class="flex flex-wrap"> <div class="flex flex-wrap">

View File

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

View File

@ -194,7 +194,7 @@ const searchFormRef = ref<FormInstance>()
// //
watch(() => siteAccountLogTable.searchParam.trade_no, (nval) => { watch(() => siteAccountLogTable.searchParam.trade_no, (nval) => {
siteAccountLogTable.searchParam.trade_no = nval.trim(); siteAccountLogTable.searchParam.trade_no = nval.trim()
}) })
/** /**

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,9 @@
</el-form-item> </el-form-item>
<el-form-item :label="t('adjustBalance')" prop="adjust"> <el-form-item :label="t('adjustBalance')" prop="adjust">
<div>
<el-input-number v-model="formData.adjust" clearable :min="0" :max="999999" :placeholder="t('adjustBalancePlaceholder')" @focus="formData.adjust = ''" class="!w-[200px]"/> <el-input-number v-model="formData.adjust" clearable :min="0" :max="999999" :placeholder="t('adjustBalancePlaceholder')" @focus="formData.adjust = ''" class="!w-[200px]"/>
</div>
</el-form-item> </el-form-item>
<el-form-item :label="t('memo')" prop="memo"> <el-form-item :label="t('memo')" prop="memo">
@ -70,7 +72,7 @@ const formRules = computed(() => {
callback(new Error(t('adjustBalancePlaceholder'))) callback(new Error(t('adjustBalancePlaceholder')))
} }
if (formData.adjust_type == -1 && (parseFloat(formData.balance) - adjust < 0)) { if (formData.adjust_type == -1 && (parseFloat(formData.balance) - adjust) < 0) {
callback(new Error(t('adjustBalanceMaxAccountMessage'))) callback(new Error(t('adjustBalanceMaxAccountMessage')))
} }

View File

@ -14,7 +14,9 @@
</el-form-item> </el-form-item>
<el-form-item :label="t('adjustPoint')" prop="adjust"> <el-form-item :label="t('adjustPoint')" prop="adjust">
<div>
<el-input-number v-model="formData.adjust" clearable :min="0" :max="999999" :placeholder="t('adjustPlaceholder')" @focus="formData.adjust = ''" class="!w-[200px]"/> <el-input-number v-model="formData.adjust" clearable :min="0" :max="999999" :placeholder="t('adjustPlaceholder')" @focus="formData.adjust = ''" class="!w-[200px]"/>
</div>
</el-form-item> </el-form-item>
<el-form-item :label="t('memo')" prop="memo"> <el-form-item :label="t('memo')" prop="memo">
@ -72,7 +74,7 @@ const formRules = computed(() => {
callback(new Error(t('adjustPointPlaceholder'))) callback(new Error(t('adjustPointPlaceholder')))
} }
if (formData.adjust_type == -1 && (parseFloat(formData.point) - adjust < 0)) { if (formData.adjust_type == -1 && (parseFloat(formData.point) - adjust) < 0) {
callback(new Error(t('adjustPointMaxAccountMessage'))) callback(new Error(t('adjustPointMaxAccountMessage')))
} }

View File

@ -279,6 +279,8 @@ const previewPoster = (data: any) => {
previewDialogVisible.value = true previewDialogVisible.value = true
} }
isRepeat.value = false isRepeat.value = false
}).catch(() => {
isRepeat.value = false
}) })
} }

View File

@ -11,24 +11,36 @@
</div> </div>
<div class="mb-[10px] flex items-center"> <div class="mb-[10px] flex items-center">
<el-checkbox v-model="toggleCheckbox" size="large" class="px-[14px]" @change="toggleChange" :indeterminate="isIndeterminate" /> <el-checkbox v-model="toggleCheckbox" size="large" class="px-[14px]" @change="toggleChange" :indeterminate="isIndeterminate" />
<el-button @click="batchDeleteEvent" size="small">{{t("批量删除")}}</el-button> <el-button @click="batchDeleteEvent" size="small">{{ t('批量删除') }}</el-button>
</div> </div>
<el-table :data="tableData.data" size="large" v-loading="tableData.loading" ref="smsSignListTableRef" @selection-change="handleSelectionChange"> <el-table
:data="tableData.data"
size="large"
v-loading="tableData.loading"
ref="smsSignListTableRef"
@selection-change="handleSelectionChange"
>
<template #empty> <template #empty>
<span>{{ !tableData.loading ? t("emptyData") : "" }}</span> <span>{{ !tableData.loading ? t('emptyData') : '' }}</span>
</template> </template>
<el-table-column type="selection" :selectable="checkSelectable" width="55" /> <el-table-column type="selection" :selectable="checkSelectable" width="55" />
<el-table-column prop="sign" :label="t('签名名称')" min-width="200" /> <el-table-column prop="sign" :label="t('签名名称')" min-width="200" />
<el-table-column prop="is_default" :label="t('使用状态')" min-width="120"> <el-table-column prop="is_default" :label="t('使用状态')" min-width="120">
<template #default="{ row }"> <template #default="{ row }">
<div>{{ row.is_default? t('使用中') : t('未使用') }}</div> <div>
{{ row.is_default ? t('使用中') : t('未使用') }}
</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="auditResultName" :label="t('审核状态')" min-width="200"> <el-table-column prop="auditResultName" :label="t('审核状态')" min-width="200">
<template #default="{ row }"> <template #default="{ row }">
<div> <div>
<div :class="[row.auditResult == 2 ? 'text-green-600' : '']">{{ row.auditResultName }}</div> <div :class="[row.auditResult == 2 ? 'text-green-600' : '']">
<div class="text-red-600" v-if="row.auditResult != 2">{{ row.auditMsg }}</div> {{ row.auditResultName }}
</div>
<div class="text-red-600" v-if="row.auditResult != 2">
{{ row.auditMsg }}
</div>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
@ -49,9 +61,9 @@
</template> </template>
<template #default="{ row }"> <template #default="{ row }">
<div class="flex gap-[5px]"> <div class="flex gap-[5px]">
<el-tag :type="row.realNameLt == 0 ? 'info' : row.realNameLt == 1 ? 'success' : 'danger'">{{ t("联通") }}</el-tag> <el-tag :type="row.realNameLt == 0 ? 'info' : row.realNameLt == 1 ? 'success' : 'danger'">{{ t('联通') }}</el-tag>
<el-tag :type="row.realNameYd == 0 ? 'info' : row.realNameYd == 1 ? 'success' : 'danger'">{{ t("移动") }}</el-tag> <el-tag :type="row.realNameYd == 0 ? 'info' : row.realNameYd == 1 ? 'success' : 'danger'">{{ t('移动') }}</el-tag>
<el-tag :type="row.realNameDx == 0 ? 'info' : row.realNameDx == 1 ? 'success' : 'danger'">{{ t("电信") }}</el-tag> <el-tag :type="row.realNameDx == 0 ? 'info' : row.realNameDx == 1 ? 'success' : 'danger'">{{ t('电信') }}</el-tag>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
@ -63,51 +75,116 @@
<el-table-column :label="t('操作')" fixed="right" align="right" min-width="120"> <el-table-column :label="t('操作')" fixed="right" align="right" min-width="120">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click="selectTemplate(row)" v-if="!row.is_default && row.auditResult == 2"> <el-button type="primary" link @click="selectTemplate(row)" v-if="!row.is_default && row.auditResult == 2">
{{ t("使用") }} {{ t('使用') }}
</el-button> </el-button>
<el-button type="primary" link @click="deleteTemplate(row)" v-if="!row.is_default"> <el-button type="primary" link @click="deleteTemplate(row)" v-if="!row.is_default">
{{ t("删除") }} {{ t('删除') }}
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="mt-[16px] flex justify-end"> <div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.limit" <el-pagination
layout="total, sizes, prev, pager, next, jumper" :total="tableData.total" @size-change="loadSignList()" @current-change="loadSignList" /> v-model:current-page="tableData.page"
v-model:page-size="tableData.limit"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total"
@size-change="loadSignList()"
@current-change="loadSignList"
/>
</div> </div>
<template #footer> <template #footer>
<el-button @click="visible = false">{{ t("cancel") }}</el-button> <el-button @click="visible = false">{{ t('cancel') }}</el-button>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog v-model="visibleAdd" :title="t('添加签名')" width="800px" destroy-on-close :close-on-click-modal="false"> <el-dialog v-model="visibleAdd" :title="t('添加签名')" width="800px" destroy-on-close :close-on-click-modal="false">
<div class="max-h-[600px] overflow-auto">
<el-form label-width="150px" :model="formData" ref="formRef" :rules="formRules" class="page-form ml-[20px]"> <el-form label-width="150px" :model="formData" ref="formRef" :rules="formRules" class="page-form ml-[20px]">
<el-form-item :label="t('短信签名')" prop="signature"> <el-form-item :label="t('短信签名')" prop="signature">
<el-input v-model="formData.signature" placeholder="请输入短信签名" class="input-width" maxlength="20" show-word-limit clearable /> <el-input
v-model="formData.signature"
placeholder="请输入短信签名"
class="input-width"
maxlength="20"
show-word-limit
clearable
/>
</el-form-item> </el-form-item>
<div class="ml-[150px] text-[12px] text-[#999] leading-[20px]">必须由包裹例如test</div> <div class="ml-[150px] text-[12px] text-[#999] leading-[20px]">必须由包裹例如test</div>
<div class="my-[5px] ml-[150px] text-[12px] text-[#999] leading-[20px]">字数要求在2-20个字符不能使用空格和特殊符号 - + = * & % # @ ~;</div> <div class="my-[5px] ml-[150px] text-[12px] text-[#999] leading-[20px]">
字数要求在2-20个字符不能使用空格和特殊符号 - + = * & % # @ ~;
</div>
<el-form-item :label="t('短信示例内容')" prop="contentExample"> <el-form-item :label="t('短信示例内容')" prop="contentExample">
<el-input v-model="formData.contentExample" placeholder="请输入短信示例内容" clearable maxlength="50" show-word-limit class="input-width" /> <el-input
v-model="formData.contentExample"
placeholder="请输入短信示例内容"
clearable
maxlength="50"
show-word-limit
class="input-width"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('企业名称')" prop="companyName"> <el-form-item :label="t('企业名称')" prop="companyName">
<el-input v-model="formData.companyName" placeholder="请输入企业名称" clearable maxlength="20" show-word-limit class="input-width" /> <el-input
v-model="formData.companyName"
placeholder="请输入企业名称"
clearable
maxlength="20"
show-word-limit
class="input-width"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('社会统一信用代码')" prop="creditCode"> <el-form-item :label="t('社会统一信用代码')" prop="creditCode">
<el-input v-model="formData.creditCode" placeholder="请输入社会统一信用代码" clearable maxlength="20" show-word-limit class="input-width" /> <el-input
v-model="formData.creditCode"
placeholder="请输入社会统一信用代码"
clearable
maxlength="20"
show-word-limit
class="input-width"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('法人姓名')" prop="legalPerson"> <el-form-item :label="t('法人姓名')" prop="legalPerson">
<el-input v-model="formData.legalPerson" placeholder="请输入法人姓名" clearable maxlength="20" show-word-limit class="input-width" /> <el-input
v-model="formData.legalPerson"
placeholder="请输入法人姓名"
clearable
maxlength="20"
show-word-limit
class="input-width"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('经办人姓名')" prop="principalName"> <el-form-item :label="t('责任人姓名')" prop="principalName">
<el-input v-model="formData.principalName" placeholder="请输入经办人姓名" clearable maxlength="20" show-word-limit class="input-width" /> <el-input
v-model="formData.principalName"
placeholder="请输入责任人姓名"
clearable
maxlength="20"
show-word-limit
class="input-width"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('经办人手机号')" prop="principalMobile"> <el-form-item :label="t('责任人手机号')" prop="principalMobile">
<el-input v-model="formData.principalMobile" placeholder="请输入经办人手机号" clearable maxlength="20" show-word-limit class="input-width" /> <el-input
v-model="formData.principalMobile"
placeholder="请输入责任人手机号"
clearable
maxlength="20"
show-word-limit
class="input-width"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('经办人身份证')" prop="principalIdCard"> <el-form-item :label="t('责任人身份证')" prop="principalIdCard">
<el-input v-model="formData.principalIdCard" placeholder="请输入经办人身份证" clearable maxlength="18" show-word-limit class="input-width" /> <el-input
v-model="formData.principalIdCard"
placeholder="请输入责任人身份证"
clearable
maxlength="18"
show-word-limit
class="input-width"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('签名来源')"> <el-form-item :label="t('签名来源')">
<el-radio-group v-model="formData.signSource"> <el-radio-group v-model="formData.signSource">
@ -119,11 +196,38 @@
<el-radio v-for="item in signConfig.signTypeList" :key="item.type" :label="item.type">{{ item.name }}</el-radio> <el-radio v-for="item in signConfig.signTypeList" :key="item.type" :label="item.type">{{ item.name }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item :label="t('上传图片')" prop="imgUrl"> <!-- <el-form-item :label="t('上传图片')" prop="imgUrl">
<upload-image v-model="formData.imgUrl" :limit="1" /> <upload-image v-model="formData.imgUrl" :limit="1" />
</el-form-item> </el-form-item>
<div class="ml-[150px] text-[12px] text-[#999] leading-[20px]">当签名来源为商标APP小程序事业单位简称或企业名称简称时需必填此字段</div> <div class="ml-[150px] text-[12px] text-[#999] leading-[20px]">
<div class="my-[5px] ml-[150px] text-[12px] text-[#999] leading-[20px]">当签名来源为事业单位全称或企业名称全称时选填此字段</div> 当签名来源为商标APP小程序事业单位简称或企业名称简称时需必填此字段
</div>
<div
class="my-[5px] ml-[150px] text-[12px] text-[#999] leading-[20px]"
>
当签名来源为事业单位全称或企业名称全称时选填此字段
</div> -->
<el-form-item :label="t('营业执照')" prop="bizLicenseUrl">
<upload-image v-model="formData.bizLicenseUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('企查查唯一性截图')" prop="qccUrl">
<upload-image v-model="formData.qccUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('中国商标网截图')" prop="tmnetUrl">
<upload-image v-model="formData.tmnetUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('移动ICP截图')" prop="mobileIcpUrl">
<upload-image v-model="formData.mobileIcpUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('应用商店 /小程序页面开发者截图')" prop="telecomAppstoreUrl">
<upload-image v-model="formData.telecomAppstoreUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('身份证正面(人像面)')" prop="idcardFrontUrl">
<upload-image v-model="formData.idcardFrontUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('身份证反面URL')" prop="idcardBackUrl">
<upload-image v-model="formData.idcardBackUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('是否默认')"> <el-form-item :label="t('是否默认')">
<el-radio-group v-model="formData.defaultSign"> <el-radio-group v-model="formData.defaultSign">
<el-radio :label="1"></el-radio> <el-radio :label="1"></el-radio>
@ -131,9 +235,10 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div>
<template #footer> <template #footer>
<el-button @click="visibleAdd = false">{{ t("cancel") }}</el-button> <el-button @click="visibleAdd = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="onSave()">{{ t("confirm") }}</el-button> <el-button type="primary" @click="onSave()">{{ t('confirm') }}</el-button>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
@ -150,12 +255,19 @@ const emit = defineEmits(['select'])
const props = defineProps({ const props = defineProps({
username: { username: {
type: String, type: String,
default: '' default: '',
} },
}) })
const initialFormData = { const initialFormData = {
defaultSign: 0, defaultSign: 0,
imgUrl: '', imgUrl: '',
bizLicenseUrl: '',
qccUrl: '',
tmnetUrl: '',
mobileIcpUrl: '',
telecomAppstoreUrl: '',
idcardFrontUrl: '',
idcardBackUrl: '',
contentExample: '', contentExample: '',
signType: '', signType: '',
signSource: '', signSource: '',
@ -165,16 +277,16 @@ const initialFormData = {
legalPerson: '', legalPerson: '',
creditCode: '', creditCode: '',
companyName: '', companyName: '',
signature: '' signature: '',
} }
const formData = reactive({ ...initialFormData }) const formData = reactive({ ...initialFormData })
const signConfig = reactive({ const signConfig = reactive({
signTypeList: [], signTypeList: [],
signSourceList: [] signSourceList: [],
}) })
const getSmsSignConfigFn = () => { const getSmsSignConfigFn = () => {
getSmsSignConfig().then(res => { getSmsSignConfig().then((res) => {
signConfig.signTypeList = res.data.sign_type_list signConfig.signTypeList = res.data.sign_type_list
signConfig.signSourceList = res.data.sign_source_list signConfig.signSourceList = res.data.sign_source_list
formData.signSource = res.data.sign_source_list[0].type formData.signSource = res.data.sign_source_list[0].type
@ -210,31 +322,27 @@ const formRules = computed(() => {
callback() callback()
}, },
trigger: 'blur' trigger: 'blur',
} },
], ],
principalMobile: [ principalMobile: [
{ required: true, message: '请输入经办人手机号', trigger: 'blur' }, { required: true, message: '请输入责任人手机号', trigger: 'blur' },
{ validator: phoneVerify, trigger: 'blur' } { validator: phoneVerify, trigger: 'blur' },
],
companyName: [
{ required: true, message: '请输入企业名称', trigger: 'blur' }
],
contentExample: [
{ required: true, message: '请输入短信示例内容', trigger: 'blur' }
], ],
companyName: [{ required: true, message: '请输入企业名称', trigger: 'blur' }],
contentExample: [{ required: true, message: '请输入短信示例内容', trigger: 'blur' }],
creditCode: [ creditCode: [
{ required: true, message: '请输入社会统一信用代码', trigger: 'blur' } {
], required: true,
legalPerson: [ message: '请输入社会统一信用代码',
{ required: true, message: '请输入法人姓名', trigger: 'blur' } trigger: 'blur',
], },
principalName: [
{ required: true, message: '请输入经办人姓名', trigger: 'blur' }
], ],
legalPerson: [{ required: true, message: '请输入法人姓名', trigger: 'blur' }],
principalName: [{ required: true, message: '请输入责任人姓名', trigger: 'blur' }],
principalIdCard: [ principalIdCard: [
{ required: true, message: '请输入经办人身份证', trigger: 'blur' }, { required: true, message: '请输入责任人身份证', trigger: 'blur' },
{ validator: idCardVerify, trigger: 'blur' } { validator: idCardVerify, trigger: 'blur' },
], ],
imgUrl: [ imgUrl: [
{ {
@ -250,10 +358,86 @@ const formRules = computed(() => {
callback() // callback() //
} }
}, },
trigger: 'blur' trigger: 'blur',
},
],
bizLicenseUrl: [{ required: true, message: '请输入营业执照', trigger: 'blur' }],
qccUrl: [
{
validator: (rule, value, callback) => {
const needImage = [1, 2].includes(formData.signSource) || formData.signType === 1
if (needImage) {
if (!value || value.length === 0) {
callback(new Error('请上传企查查唯一性截图'))
} else {
callback()
} }
] } else {
callback() //
}
},
trigger: 'blur',
},
],
tmnetUrl: [
{
validator: (rule, value, callback) => {
const needImage = [3].includes(formData.signSource)
if (needImage) {
if (!value || value.length === 0) {
callback(new Error('请上传中国商标网截图'))
} else {
callback()
}
} else {
callback() //
}
},
trigger: 'blur',
},
],
mobileIcpUrl: [
{
validator: (rule, value, callback) => {
const needImage = [4, 5].includes(formData.signSource)
if (needImage) {
if (!value || value.length === 0) {
callback(new Error('请上传移动ICP截图'))
} else {
callback()
}
} else {
callback() //
}
},
trigger: 'blur',
},
],
telecomAppstoreUrl: [
{
validator: (rule, value, callback) => {
const needImage = [4, 5].includes(formData.signSource)
if (needImage) {
if (!value || value.length === 0) {
callback(new Error('请上传应用商店/小程序页面开发者截图'))
} else {
callback()
}
} else {
callback() //
}
},
trigger: 'blur',
},
],
idcardFrontUrl: [
{
required: true,
message: '请输入身份证正面(人像面)',
trigger: 'blur',
},
],
idcardBackUrl: [{ required: true, message: '请输入身份证反面', trigger: 'blur' }],
} }
}) })
@ -293,7 +477,7 @@ const tableData = reactive({
total: 0, total: 0,
loading: false, loading: false,
data: [], data: [],
searchParam: {} searchParam: {},
}) })
const open = () => { const open = () => {
@ -306,13 +490,15 @@ const loadSignList = () => {
const params = { const params = {
page: tableData.page, page: tableData.page,
limit: tableData.limit, limit: tableData.limit,
...tableData.searchParam ...tableData.searchParam,
} }
getSignList(props.username, params).then((res) => { getSignList(props.username, params)
.then((res) => {
tableData.loading = false tableData.loading = false
tableData.data = res.data.data tableData.data = res.data.data
tableData.total = res.data.total tableData.total = res.data.total
}).catch(() => { })
.catch(() => {
tableData.loading = false tableData.loading = false
}) })
} }
@ -328,7 +514,7 @@ const deleteTemplate = (row:any) => {
ElMessageBox.confirm(t('确定删除该签名吗?'), t('提示'), { ElMessageBox.confirm(t('确定删除该签名吗?'), t('提示'), {
confirmButtonText: t('确定'), confirmButtonText: t('确定'),
cancelButtonText: t('取消'), cancelButtonText: t('取消'),
type: 'warning' type: 'warning',
}).then(() => { }).then(() => {
deleteSign(props.username, { signatures: [row.sign] }).then((res) => { deleteSign(props.username, { signatures: [row.sign] }).then((res) => {
// loadSignList() // loadSignList()
@ -381,7 +567,7 @@ const batchDeleteEvent = () => {
if (multipleSelection.value.length == 0) { if (multipleSelection.value.length == 0) {
ElMessage({ ElMessage({
type: 'warning', type: 'warning',
message: `${t('请选择要删除的签名')}` message: `${t('请选择要删除的签名')}`,
}) })
return return
} }
@ -389,7 +575,7 @@ const batchDeleteEvent = () => {
ElMessageBox.confirm(t('确定删除选中的签名吗?'), t('warning'), { ElMessageBox.confirm(t('确定删除选中的签名吗?'), t('warning'), {
confirmButtonText: t('confirm'), confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'), cancelButtonText: t('cancel'),
type: 'warning' type: 'warning',
}).then(() => { }).then(() => {
const signatures: any = [] const signatures: any = []
multipleSelection.value.forEach((item: any) => { multipleSelection.value.forEach((item: any) => {
@ -397,14 +583,15 @@ const batchDeleteEvent = () => {
}) })
deleteSign(props.username, { deleteSign(props.username, {
signatures signatures,
}).then(() => { })
.then(() => {
tableData.loading = true tableData.loading = true
setTimeout(() => { setTimeout(() => {
loadSignList() loadSignList()
}, 1000) }, 1000)
}).catch(() => {
}) })
.catch(() => {})
}) })
} }

View File

@ -43,6 +43,16 @@
<el-form-item :label="t('icon')"> <el-form-item :label="t('icon')">
<upload-image v-model="formData.front_end_icon" /> <upload-image v-model="formData.front_end_icon" />
</el-form-item> </el-form-item>
<el-form-item :label="t('metaTitle')">
<el-input v-model.trim="formData.meta_title" :placeholder="t('MetaPlaceholder')" class="input-width" clearable maxlength="40" show-word-limit />
</el-form-item>
<el-form-item :label="t('metaDescription')">
<el-input v-model.trim="formData.meta_desc" :placeholder="t('metaDescriptionPlaceholder')" class="input-width" clearable maxlength="200" show-word-limit />
</el-form-item>
<el-form-item :label="t('metaKeywords')">
<el-input v-model.trim="formData.meta_keyword" :placeholder="t('metaKeywordsPlaceholder')" class="input-width" clearable maxlength="200" show-word-limit />
</el-form-item>
</el-card> </el-card>
<el-card class="box-card mt-[15px] !border-none" shadow="never"> <el-card class="box-card mt-[15px] !border-none" shadow="never">
@ -75,13 +85,10 @@ import { setWebsite, getWebsite, getService } from '@/app/api/sys'
import { FormInstance, FormRules } from 'element-plus' import { FormInstance, FormRules } from 'element-plus'
import { getAppType } from '@/utils/common' import { getAppType } from '@/utils/common'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import useUserStore from '@/stores/modules/user'
import useSystemStore from '@/stores/modules/system';
const route = useRoute() const route = useRoute()
const pageName = route.meta.title const pageName = route.meta.title
const loading = ref(true) const loading = ref(true)
const appType = ref(getAppType())
const formData = reactive<Record<string, string>>({ const formData = reactive<Record<string, string>>({
site_name: '', site_name: '',
logo: '', logo: '',
@ -102,7 +109,10 @@ const formData = reactive<Record<string, string>>({
icon: '', icon: '',
wechat_code: '', wechat_code: '',
enterprise_wechat: '', enterprise_wechat: '',
tel: '' tel: '',
meta_title: '',
meta_desc: '',
meta_keyword: ''
}) })
const setFormData = async () => { const setFormData = async () => {

View File

@ -83,7 +83,7 @@
温馨提示 温馨提示
</span> </span>
<span class="text-[12px] text-[#9699B6] ml-[10px]">运行环境要求需预先配置 Nodejs 环境</span> <span class="text-[12px] text-[#9699B6] ml-[10px]">运行环境要求需预先配置 Nodejs 环境</span>
<span class="text-[14px] text-primary cursor-pointer ml-[10px] border-b-[1px] border-solid border-primary" @click="linkEvent('https://doc.niucloud.com/saas.html?keywords=/di-san-fang-yun-bian-yi-pei-zhi')">搭建教程</span> <span class="text-[14px] text-primary cursor-pointer ml-[10px] border-b-[1px] border-solid border-primary" @click="linkEvent('https://doc.press.niucloud.com/php/saas-framework/use/other/third-party-cloud-compilation.html')">搭建教程</span>
</div> </div>
<div class="ml-[40px] text-[14px] text-[#4F516D] mb-[18px]"> <div class="ml-[40px] text-[14px] text-[#4F516D] mb-[18px]">
<span>1下载第三方云编译服务器搭建程序包</span><span class="text-primary cursor-pointer " @click="linkEvent('https://gitee.com/niucloud-team/niucloud-compile-server')"> niucloud-compile-server</span> <span>1下载第三方云编译服务器搭建程序包</span><span class="text-primary cursor-pointer " @click="linkEvent('https://gitee.com/niucloud-team/niucloud-compile-server')"> niucloud-compile-server</span>
@ -139,7 +139,7 @@
</div> </div>
<div> <div>
<div class="ml-[40px] text-[#374151] text-[14px] italic"> <div class="ml-[40px] text-[#374151] text-[14px] italic">
<span class="text-[16px] italic">#安装依赖</span> <span class="text-[16px] italic font-500">#安装依赖</span>
<span class="italic">进入admin端与uniapp端以及web端目录都可执行</span> <span class="italic">进入admin端与uniapp端以及web端目录都可执行</span>
</div> </div>
<div class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]"> <div class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]">
@ -149,9 +149,9 @@
</div> </div>
<div class="mt-[21px]"> <div class="mt-[21px]">
<div class="ml-[40px] text-[14px] text-[#374151] italic"> <div class="ml-[40px] text-[14px] text-[#374151] italic">
<span class="text-[16px] italic">#后台admin端口打包</span> <span class="text-[16px] italic font-500">#后台admin端口打包</span>
<span>进入admin目录下执行执行后编译代码默认移动到系统的niucloud下的</span> <span>进入admin目录下执行执行后编译代码默认移动到系统的niucloud下的</span>
<span class="text-[#F09000] mx-[3px]">public/admin</span> <span class="text-[#F09000] mx-[3px] font-bold">public/admin</span>
<span>目录下</span> <span>目录下</span>
</div> </div>
<div class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]"> <div class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]">
@ -161,9 +161,9 @@
</div> </div>
<div class="mt-[21px]"> <div class="mt-[21px]">
<div class="ml-[40px] text-[14px] text-[#374151] italic"> <div class="ml-[40px] text-[14px] text-[#374151] italic">
<span class="text-[16px] italic">#使用uniapp打包H5</span> <span class="text-[16px] italic font-500">#使用uniapp打包H5</span>
<span>进入uniapp目录下执行执行后编译代码默认移动到系统niucloud下的</span> <span>进入uniapp目录下执行执行后编译代码默认移动到系统niucloud下的</span>
<span class="text-[#F09000] mx-[3px]">public/wap</span> <span class="text-[#F09000] mx-[3px] font-bold">public/wap</span>
<span>目录下</span> <span>目录下</span>
</div> </div>
<div class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]"> <div class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]">
@ -173,9 +173,9 @@
</div> </div>
<div class="mt-[21px]"> <div class="mt-[21px]">
<div class="ml-[40px] text-[14px] text-[#374151] italic"> <div class="ml-[40px] text-[14px] text-[#374151] italic">
<span class="text-[16px] italic">#使用uniapp打包微信小程序</span> <span class="text-[16px] italic font-500">#使用uniapp打包微信小程序</span>
<span>进入uniapp目录下执行执行后编译代码默认移动到系统niucloud下的</span> <span>进入uniapp目录下执行执行后编译代码默认移动到系统niucloud下的</span>
<span class="text-[#F09000] mx-[3px]">uni-app/dist/build/mp-weixin</span> <span class="text-[#F09000] mx-[3px] font-bold">uni-app/dist/build/mp-weixin</span>
<span>目录</span> <span>目录</span>
</div> </div>
<div class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]"> <div class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]">
@ -185,9 +185,9 @@
</div> </div>
<div class="mt-[21px]"> <div class="mt-[21px]">
<div class="ml-[40px] text-[14px] text-[#374151] italic"> <div class="ml-[40px] text-[14px] text-[#374151] italic">
<span class="text-[16px] italic">#前台web(pc)端打包:</span> <span class="text-[16px] italic font-500">#前台web(pc)端打包:</span>
<span>进入web目录下执行执行后编译代码默认移动到系统niucloud下的</span> <span>进入web目录下执行执行后编译代码默认移动到系统niucloud下的</span>
<span class="text-[#F09000] mx-[3px]">public/web</span> <span class="text-[#F09000] mx-[3px] font-bold">public/web</span>
<span>目录下</span> <span>目录下</span>
</div> </div>
<div class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]"> <div class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]">

View File

@ -264,7 +264,7 @@ const deleteEvent = (id: number) => {
} }
).then(() => { ).then(() => {
deleteGenerateTable(id).then(() => { deleteGenerateTable(id).then(() => {
loadGenerateTableList() loadGenerateTableList(getTablePageStorage(codeTableData.searchParam).page)
}).catch(() => { }).catch(() => {
}) })
}) })
@ -314,7 +314,7 @@ const generateCreateFn = (id: any, generate_type: any) => {
codeTableData.loading = false codeTableData.loading = false
window.open(img(res.data.file), '_blank') window.open(img(res.data.file), '_blank')
} else { } else {
loadGenerateTableList() loadGenerateTableList(getTablePageStorage(codeTableData.searchParam).page)
} }
}).catch(() => { }).catch(() => {
codeTableData.loading = false codeTableData.loading = false
@ -368,7 +368,7 @@ const listToTree = (arr:any) => {
} }
} }
if (!obj) { if (!obj) {
obj = { name: name, path: name.indexOf('.') < 0 ? '' : arr[i], key: 'k' + i + j + k } obj = { name, path: name.indexOf('.') < 0 ? '' : arr[i], key: 'k' + i + j + k }
if (name.indexOf('.') < 0) obj.children = [] if (name.indexOf('.') < 0) obj.children = []
if (obj.path === arr[0]) treeKey.value = obj.key if (obj.path === arr[0]) treeKey.value = obj.key
_ret.push(obj) _ret.push(obj)

View File

@ -60,11 +60,11 @@
<el-input v-model="attachmentParam.real_name" class="m-0 !w-[200px]" clearable :placeholder="t('upload.placeholder' + type + 'Name')" prefix-icon="Search" @input="getAttachmentList()" /> <el-input v-model="attachmentParam.real_name" class="m-0 !w-[200px]" clearable :placeholder="t('upload.placeholder' + type + 'Name')" prefix-icon="Search" @input="getAttachmentList()" />
</el-col> </el-col>
</el-row> </el-row>
<div class="flex-1 my-[15px] h-0" v-loading="attachment.loading"> <div class="flex-1 mb-[15px] mt-[10px] h-0" v-loading="attachment.loading">
<el-scrollbar> <el-scrollbar>
<!-- 选择弹出框 --> <!-- 选择弹出框 -->
<div class="flex flex-wrap" v-if="attachment.data.length && (operate === true || scene != 'attachment')"> <div class="flex flex-wrap" v-if="attachment.data.length && (operate === true || scene != 'attachment')">
<div class="attachment-item mr-[10px]" :class="scene == 'select' ? 'w-[100px]' : 'w-[120px]'" v-for="(item, index) in attachment.data" :key="index"> <div class="attachment-item relative mr-[10px] mt-[6px]" :class="scene == 'select' ? 'w-[100px]' : 'w-[120px]'" v-for="(item, index) in attachment.data" :key="index">
<div class="attachment-wrap w-full rounded cursor-pointer overflow-hidden relative flex items-center justify-center" :class="scene == 'select' ? 'h-[100px]' : 'h-[120px]'" @click="selectFile(item)"> <div class="attachment-wrap w-full rounded cursor-pointer overflow-hidden relative flex items-center justify-center" :class="scene == 'select' ? 'h-[100px]' : 'h-[120px]'" @click="selectFile(item)">
<el-image :src="img(item.thumb)" fit="contain" v-if="type == 'image'"/> <el-image :src="img(item.thumb)" fit="contain" v-if="type == 'image'"/>
@ -76,9 +76,10 @@
<span class="absolute bottom-[2px] right-[2px] text-white z-[2] leading-none">{{ getFileIndex(item.att_id) }}</span> <span class="absolute bottom-[2px] right-[2px] text-white z-[2] leading-none">{{ getFileIndex(item.att_id) }}</span>
</div> </div>
</div> </div>
</div> </div>
<icon name="element CircleCloseFilled" v-if="type != 'icon' && !operate" @click="deleteAttachmentEvent(index)" class="top-[-6px] right-[-6px] absolute z-[3] del-icon hidden cursor-pointer opacity-[0.8]" />
<div class="flex items-center"> <div class="flex items-center">
<el-tooltip placement="top"> <el-tooltip placement="top">
<template #content>{{ item.real_name }}</template> <template #content>{{ item.real_name }}</template>
@ -110,7 +111,7 @@
<!-- 素材管理 --> <!-- 素材管理 -->
<div class="flex flex-wrap" v-else-if="attachment.data.length && operate === false"> <div class="flex flex-wrap" v-else-if="attachment.data.length && operate === false">
<div class="attachment-item mr-[10px] w-[120px]" v-for="(item, index) in attachment.data" :key="index"> <div class="attachment-item relative mr-[10px] mt-[6px] w-[120px]" v-for="(item, index) in attachment.data" :key="index">
<div class="attachment-wrap w-full rounded cursor-pointer overflow-hidden relative flex items-center justify-center h-[120px]"> <div class="attachment-wrap w-full rounded cursor-pointer overflow-hidden relative flex items-center justify-center h-[120px]">
<el-image :src="img(item.thumb)" fit="contain" v-if="type == 'image'" :preview-src-list="item.image_list"/> <el-image :src="img(item.thumb)" fit="contain" v-if="type == 'image'" :preview-src-list="item.image_list"/>
<video :src="img(item.thumb)" v-else-if="type == 'video'" @click="previewVideo(index)"></video> <video :src="img(item.thumb)" v-else-if="type == 'video'" @click="previewVideo(index)"></video>
@ -122,6 +123,7 @@
<div class="truncate my-[10px] cursor-pointer text-base flex-1 text-center">{{ item.real_name }}</div> <div class="truncate my-[10px] cursor-pointer text-base flex-1 text-center">{{ item.real_name }}</div>
</el-tooltip> </el-tooltip>
</div> </div>
<icon name="element CircleCloseFilled" v-if="type != 'icon'" @click="deleteAttachmentEvent(index)" class="top-[-6px] right-[-6px] absolute z-[3] del-icon hidden cursor-pointer opacity-[0.8]" />
</div> </div>
</div> </div>
<div class="flex absolute top-0 left-0 right-0 bottom-0 items-center justify-center" v-else> <div class="flex absolute top-0 left-0 right-0 bottom-0 items-center justify-center" v-else>
@ -668,6 +670,9 @@ defineExpose({
.attachment-action { .attachment-action {
display: block; display: block;
} }
.del-icon {
display: block;
}
} }
.attachment-list-wrap { .attachment-list-wrap {

View File

@ -11,7 +11,7 @@
<icon name="element Delete" color="#fff" size="18px" @click.stop="removeImage" /> <icon name="element Delete" color="#fff" size="18px" @click.stop="removeImage" />
</div> </div>
</div> </div>
<upload-attachment :limit="limit" @confirm="confirmSelect" v-else> <upload-attachment :limit="uploadImgNum" @confirm="confirmSelect" v-else>
<div class="w-full h-full flex items-center justify-center flex-col content-wrap"> <div class="w-full h-full flex items-center justify-center flex-col content-wrap">
<icon name="element Plus" size="20px" color="var(--el-text-color-secondary)" /> <icon name="element Plus" size="20px" color="var(--el-text-color-secondary)" />
<div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText || t('upload.root') }}</div> <div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText || t('upload.root') }}</div>
@ -34,7 +34,7 @@
</div> </div>
</div> </div>
<div class="rounded cursor-pointer overflow-hidden relative border border-dashed border-color" :style="style" v-if="images.data.length < limit"> <div class="rounded cursor-pointer overflow-hidden relative border border-dashed border-color" :style="style" v-if="images.data.length < limit">
<upload-attachment :limit="limit" @confirm="confirmSelect"> <upload-attachment :limit="uploadImgNum" @confirm="confirmSelect">
<div class="w-full h-full flex items-center justify-center flex-col content-wrap"> <div class="w-full h-full flex items-center justify-center flex-col content-wrap">
<icon name="element Plus" size="20px" color="var(--el-text-color-secondary)" /> <icon name="element Plus" size="20px" color="var(--el-text-color-secondary)" />
<div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText || t('upload.root') }}</div> <div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText || t('upload.root') }}</div>
@ -105,6 +105,11 @@ const setValue = () => {
previewImageList = toRaw(images.data).map((url: string) => { return url.indexOf('data:image') != -1 ? url : img(url) }) previewImageList = toRaw(images.data).map((url: string) => { return url.indexOf('data:image') != -1 ? url : img(url) })
} }
const uploadImgNum = computed(() => {
const num = prop.limit - images.data.length
return num
})
watch(() => value.value, () => { watch(() => value.value, () => {
if (value.value.indexOf('data:image') != -1) { if (value.value.indexOf('data:image') != -1) {
images.data = [value.value] images.data = [value.value]

File diff suppressed because one or more lines are too long

View File

@ -105,38 +105,38 @@
setup (props, context) { setup (props, context) {
const { mode, captchaType, vSpace, imgSize, barSize, type, blockSize, explain } = toRefs(props) const { mode, captchaType, vSpace, imgSize, barSize, type, blockSize, explain } = toRefs(props)
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const secretKey = ref(''), // ase const secretKey = ref('') // ase
passFlag = ref(''), // const passFlag = ref('') //
backImgBase = ref(''), // const backImgBase = ref('') //
blockBackImgBase = ref(''), // const blockBackImgBase = ref('') //
backToken = ref(''), // token const backToken = ref('') // token
startMoveTime = ref(''), // const startMoveTime = ref('') //
endMoveTime = ref(''), // const endMoveTime = ref('') //
tipsBackColor = ref(''), // const tipsBackColor = ref('') //
tipWords = ref(''), const tipWords = ref('')
text = ref(''), const text = ref('')
finishText = ref(''), const finishText = ref('')
setSize = reactive({ const setSize = reactive({
imgHeight: 0, imgHeight: 0,
imgWidth: 0, imgWidth: 0,
barHeight: 0, barHeight: 0,
barWidth: 0 barWidth: 0
}), })
top = ref(0), const top = ref(0)
left = ref(0), const left = ref(0)
moveBlockLeft = ref(undefined), const moveBlockLeft = ref(undefined)
leftBarWidth = ref(undefined), const leftBarWidth = ref(undefined)
// //
moveBlockBackgroundColor = ref(undefined), const moveBlockBackgroundColor = ref(undefined)
leftBarBorderColor = ref('#ddd'), const leftBarBorderColor = ref('#ddd')
iconColor = ref(undefined), const iconColor = ref(undefined)
iconClass = ref('icon-right'), const iconClass = ref('icon-right')
status = ref(false), // const status = ref(false) //
isEnd = ref(false), // const isEnd = ref(false) //
showRefresh = ref(true), const showRefresh = ref(true)
transitionLeft = ref(''), const transitionLeft = ref('')
transitionWidth = ref(''), const transitionWidth = ref('')
startLeft = ref(0) const startLeft = ref(0)
const barArea = computed(() => { const barArea = computed(() => {
return proxy.$el.querySelector('.verify-bar-area') return proxy.$el.querySelector('.verify-bar-area')
@ -146,7 +146,7 @@
text.value = explain.value text.value = explain.value
getPictrue() getPictrue()
nextTick(() => { nextTick(() => {
let { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy) const { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)
setSize.imgHeight = imgHeight setSize.imgHeight = imgHeight
setSize.imgWidth = imgWidth setSize.imgWidth = imgWidth
setSize.barHeight = barHeight setSize.barHeight = barHeight
@ -225,8 +225,8 @@
} else { // } else { //
var x = e.touches[0].pageX var x = e.touches[0].pageX
} }
var bar_area_left = barArea.value.getBoundingClientRect().left const bar_area_left = barArea.value.getBoundingClientRect().left
var move_block_left = x - bar_area_left // left let move_block_left = x - bar_area_left // left
if (move_block_left >= barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2) { if (move_block_left >= barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2) {
move_block_left = barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2 move_block_left = barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2
} }
@ -244,15 +244,17 @@
endMoveTime.value = +new Date() endMoveTime.value = +new Date()
// //
if (status.value && isEnd.value == false) { if (status.value && isEnd.value == false) {
var moveLeftDistance = parseInt(( moveBlockLeft.value || '' ).replace('px', '')) let moveLeftDistance = parseInt((moveBlockLeft.value || '').replace('px', ''))
moveLeftDistance = moveLeftDistance * 310 / parseInt(setSize.imgWidth) moveLeftDistance = moveLeftDistance * 310 / parseInt(setSize.imgWidth)
const data = { const data = {
captchaType: captchaType.value, captchaType: captchaType.value,
'captcha_code': secretKey.value ? aesEncrypt(JSON.stringify({ captcha_code: secretKey.value
? aesEncrypt(JSON.stringify({
x: moveLeftDistance, x: moveLeftDistance,
y: 5.0 y: 5.0
}), secretKey.value) : JSON.stringify({ x: moveLeftDistance, y: 5.0 }), }), secretKey.value)
'captcha_key': backToken.value : JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
captcha_key: backToken.value
} }
reqCheck(data).then(res => { reqCheck(data).then(res => {
if (res.code == 1) { if (res.code == 1) {
@ -270,10 +272,12 @@
} }
passFlag.value = true passFlag.value = true
tipWords.value = `${((endMoveTime.value - startMoveTime.value) / 1000).toFixed(2)}s验证成功` tipWords.value = `${((endMoveTime.value - startMoveTime.value) / 1000).toFixed(2)}s验证成功`
var captchaVerification = secretKey.value ? aesEncrypt(backToken.value + '---' + JSON.stringify({ const captchaVerification = secretKey.value
? aesEncrypt(backToken.value + '---' + JSON.stringify({
x: moveLeftDistance, x: moveLeftDistance,
y: 5.0 y: 5.0
}), secretKey.value) : backToken.value + '---' + JSON.stringify({ }), secretKey.value)
: backToken.value + '---' + JSON.stringify({
x: moveLeftDistance, x: moveLeftDistance,
y: 5.0 y: 5.0
}) })

View File

@ -181,9 +181,6 @@ const handleJump = (routeName: string) => {
storage.remove('activeAppKey') storage.remove('activeAppKey')
} else { } else {
} }
const processedSpecialMenus = handleSpecialMenus();
specialMenuNamesLevel1.value = collectSpecialMenuNamesLevel1(processedSpecialMenus)
// //
if (specialMenuNamesLevel1.value.includes(routeName)) { if (specialMenuNamesLevel1.value.includes(routeName)) {
routeName = 'addon_list' routeName = 'addon_list'

View File

@ -60,7 +60,6 @@ const addonRouters: Record<string, any> = {}
// storage.set({ key: 'specialAppList', data: specialList.value }) // storage.set({ key: 'specialAppList', data: specialList.value })
// } // }
onMounted(() => { onMounted(() => {
getWebConfig().then(({ data }) => { getWebConfig().then(({ data }) => {
webSite.value = data webSite.value = data
@ -95,17 +94,17 @@ routers.forEach(item => {
} }
// , // ,
// menuData.value.sort((a, b) => { menuData.value.sort((a, b) => {
// if (a.meta.sort && b.meta.sort) { if (a.meta.sort && b.meta.sort) {
// return b.meta.sort - a.meta.sort return b.meta.sort - a.meta.sort
// } else if (a.meta.sort) { } else if (a.meta.sort) {
// return -1 return -1
// } else if (b.meta.sort) { } else if (b.meta.sort) {
// return 1 return 1
// } else { } else {
// return 0 return 0
// } }
// }) })
}) })
// //
@ -120,17 +119,17 @@ if (systemStore?.apps.length > 1) {
menuData.value.unshift(...routers) menuData.value.unshift(...routers)
// , // ,
// menuData.value.sort((a, b) => { menuData.value.sort((a, b) => {
// if (a.meta.sort && b.meta.sort) { if (a.meta.sort && b.meta.sort) {
// return b.meta.sort - a.meta.sort return b.meta.sort - a.meta.sort
// } else if (a.meta.sort) { } else if (a.meta.sort) {
// return -1 return -1
// } else if (b.meta.sort) { } else if (b.meta.sort) {
// return 1 return 1
// } else { } else {
// return 0 return 0
// } }
// }) })
} }
</script> </script>

View File

@ -258,4 +258,8 @@ const toPreview = () => {
font-weight: inherit !important; font-weight: inherit !important;
color: var(--el-text-color-regular) !important; color: var(--el-text-color-regular) !important;
} }
:deep(.inter .el-breadcrumb__inner){
font-weight: inherit !important;
color: var(--el-text-color-regular) !important;
}
</style> </style>

View File

@ -1,15 +1,15 @@
<template> <template>
<div class="tab-wrap w-full px-[16px]"> <div class="tab-wrap w-full px-[16px]" v-show="systemStore.tab">
<el-tabs :closable="tabbarStore.tabLength > 1" :model-value="route.path" @tab-click="tabClick" @tab-remove="removeTab"> <el-tabs :closable="tabbarStore.tabLength > 1" :model-value="route.name" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane v-for="(tab, key, index) in tabbarStore.tabs" :name="tab.path" :key="index"> <el-tab-pane v-for="(tab, key, index) in tabbarStore.tabs" :name="tab.name" :key="index">
<template #label> <template #label>
<el-dropdown trigger="contextmenu" placement="bottom-start"> <el-dropdown trigger="contextmenu" placement="bottom-start">
<span :class="{ 'text-primary': route.path == tab.path }" class="tab-name">{{ tab.title }}</span> <span :class="{ 'text-primary': route.name == tab.name }" class="tab-name">{{ tab.title }}</span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item icon="Back" :disabled="index == 0" @click="closeLeft(tab.path)">{{t('tabs.closeLeft') }}</el-dropdown-item> <el-dropdown-item icon="Back" :disabled="index == 0" @click="closeLeft(tab.name)">{{t('tabs.closeLeft') }}</el-dropdown-item>
<el-dropdown-item icon="Right" :disabled="index == (tabbarStore.tabLength - 1)" @click="closeRight(tab.path)">{{t('tabs.closeRight') }}</el-dropdown-item> <el-dropdown-item icon="Right" :disabled="index == (tabbarStore.tabLength - 1)" @click="closeRight(tab.name)">{{t('tabs.closeRight') }}</el-dropdown-item>
<el-dropdown-item icon="Close" :disabled="tabbarStore.tabLength == 1" @click="closeOther(tab.path)">{{t('tabs.closeOther') }}</el-dropdown-item> <el-dropdown-item icon="Close" :disabled="tabbarStore.tabLength == 1" @click="closeOther(tab.name)">{{t('tabs.closeOther') }}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@ -22,10 +22,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { watch, onMounted } from 'vue' import { watch, onMounted } from 'vue'
import useTabbarStore from '@/stores/modules/tabbar' import useTabbarStore from '@/stores/modules/tabbar'
import useSystemStore from '@/stores/modules/system'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang' import { t } from '@/lang'
const tabbarStore = useTabbarStore() const tabbarStore = useTabbarStore()
const systemStore = useSystemStore()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -43,7 +45,7 @@ watch(route, (nval: any) => {
*/ */
const tabClick = (content: any) => { const tabClick = (content: any) => {
const tabRoute = tabbarStore.tabs[content.props.name] const tabRoute = tabbarStore.tabs[content.props.name]
router.push({ path: tabRoute.path, query: tabRoute.query }) router.push({ name: tabRoute.name, query: tabRoute.query })
} }
/** /**
@ -51,45 +53,49 @@ const tabClick = (content: any) => {
* @param content * @param content
*/ */
const removeTab = (content: any) => { const removeTab = (content: any) => {
if (route.path == content) { if (route.name == content) {
const tabs = Object.keys(tabbarStore.tabs) const tabs = Object.keys(tabbarStore.tabs)
router.push({ path: tabs[tabs.indexOf(content) - 1] }) if (tabs.indexOf(content) == 0) {
router.push({ name: tabs[1] })
} else {
router.push({ name: tabs[tabs.indexOf(content) - 1] })
}
} }
tabbarStore.removeTab(content) tabbarStore.removeTab(content)
} }
/** /**
* 关闭左侧 * 关闭左侧
* @param path * @param name
*/ */
const closeLeft = (path: string) => { const closeLeft = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs) const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) - 1; i >= 0; i--) { for (let i = tabs.indexOf(name) - 1; i >= 0; i--) {
delete tabbarStore.tabs[tabs[i]] delete tabbarStore.tabs[tabs[i]]
} }
router.push({ path }) router.push({ name })
} }
/** /**
* 关闭右侧 * 关闭右侧
* @param path * @param name
*/ */
const closeRight = (path: string) => { const closeRight = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs) const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) + 1; i < tabs.length; i++) { for (let i = tabs.indexOf(name) + 1; i < tabs.length; i++) {
delete tabbarStore.tabs[tabs[i]] delete tabbarStore.tabs[tabs[i]]
} }
router.push({ path }) router.push({ name })
} }
/** /**
* 关闭其他 * 关闭其他
* @param path * @param name
*/ */
const closeOther = (path: string) => { const closeOther = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs) const tabs = Object.keys(tabbarStore.tabs)
tabs.forEach((key: string) => { key != path && delete tabbarStore.tabs[key] }) tabs.forEach((key: string) => { key != name && delete tabbarStore.tabs[key] })
router.push({ path }) router.push({ name })
} }
</script> </script>

View File

@ -11,12 +11,14 @@
</el-header> </el-header>
<!-- 顶部 end --> <!-- 顶部 end -->
<layout-tab />
<!-- 主体 --> <!-- 主体 -->
<el-main class="h-full p-0 bg-page"> <el-main class="h-full p-0 bg-page">
<el-scrollbar> <el-scrollbar>
<div class="p-[15px]"> <div class="p-[15px]">
<router-view v-slot="{ Component, route }" v-if="appStore.routeRefreshTag"> <router-view v-slot="{ Component, route }" v-if="appStore.routeRefreshTag">
<keep-alive :include="tabbarStore.tabNames"> <keep-alive :include="tabbarStore.tabNames" :max="15">
<component :is="Component" :key="route.fullPath" /> <component :is="Component" :key="route.fullPath" />
</keep-alive> </keep-alive>
</router-view> </router-view>
@ -32,6 +34,7 @@
import { computed } from 'vue' import { computed } from 'vue'
import layoutHeader from './components/header/index.vue' import layoutHeader from './components/header/index.vue'
import layoutAside from './components/aside/index.vue' import layoutAside from './components/aside/index.vue'
import layoutTab from './components/tabs.vue'
import useAppStore from '@/stores/modules/app' import useAppStore from '@/stores/modules/app'
import useTabbarStore from '@/stores/modules/tabbar' import useTabbarStore from '@/stores/modules/tabbar'
import useSystemStore from '@/stores/modules/system' import useSystemStore from '@/stores/modules/system'

View File

@ -79,8 +79,12 @@ router.beforeEach(async (to: any, from, next) => {
// 设置首页路由 // 设置首页路由
let firstRoute: symbol | string | undefined = findFirstValidRoute(userStore.routers) let firstRoute: symbol | string | undefined = findFirstValidRoute(userStore.routers)
if (systemStore.apps[0]) { for (let i = 0; i < systemStore.apps.length; i++) {
firstRoute = userStore.addonIndexRoute[ systemStore.apps[0].key ] const item = systemStore.apps[i]
if (userStore.addonIndexRoute[item.key]) {
firstRoute = userStore.addonIndexRoute[item.key]
break
}
} }
ROOT_ROUTER.redirect = { name: firstRoute } ROOT_ROUTER.redirect = { name: firstRoute }

View File

@ -1,17 +1,20 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import storage from '@/utils/storage' import storage from '@/utils/storage'
import { useCssVar } from '@vueuse/core' import { useCssVar } from '@vueuse/core'
import { useCssVar } from '@vueuse/core'
import { getInstalledAddonList } from '@/app/api/addon' import { getInstalledAddonList } from '@/app/api/addon'
interface System { interface System {
menuIsCollapse: boolean, menuIsCollapse: boolean,
menuDrawer: boolean,
dark: boolean, dark: boolean,
theme: string, theme: string,
lang: string, lang: string,
apps: Record<string, any>[], apps: Record<string, any>[],
addons: Record<string, any>[] addons: Record<string, any>[]
sidebar: string,
sidebarStyle: string,
currHeadMenuName: any,
tab: Boolean
} }
const theme = storage.get('theme') ?? {} const theme = storage.get('theme') ?? {}
@ -20,14 +23,22 @@ const useSystemStore = defineStore('system', {
state: (): System => { state: (): System => {
return { return {
menuIsCollapse: false, menuIsCollapse: false,
menuDrawer: false,
dark: theme.dark ?? false, dark: theme.dark ?? false,
theme: theme.theme ?? '#273de3', theme: theme.theme ?? '#273de3',
sidebar: theme.sidebar ?? 'oneType',
lang: storage.get('lang') ?? 'zh-cn', lang: storage.get('lang') ?? 'zh-cn',
apps: [], apps: [],
addons: [] addons: [],
sidebarStyle: theme.sidebarStyle ?? 'threeType',
currHeadMenuName: '',
tab: storage.get('tab') ?? false
} }
}, },
actions: { actions: {
setHeadMenu(value: any) {
this.currHeadMenuName = value
},
setTheme(state: string, value: any) { setTheme(state: string, value: any) {
this[state] = value this[state] = value
theme[state] = value theme[state] = value

View File

@ -1,10 +1,11 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { RouteLocationNormalizedLoaded } from 'vue-router' import type { RouteLocationNormalizedLoaded, RouteRecordName } from 'vue-router'
import useSystemStore from '@/stores/modules/system'
interface Tabbar { interface Tabbar {
curr: string, curr: string,
tabs: { tabs: {
[key: string]: any [key: RouteRecordName]: any
} }
} }
@ -16,17 +17,18 @@ const useTabbarStore = defineStore('tabbar', {
} }
}, },
actions: { actions: {
addTab(roter: RouteLocationNormalizedLoaded) { addTab(router: RouteLocationNormalizedLoaded) {
if (roter.meta && roter.meta.type != 1) return if (router.meta && router.meta.type != 1) return
if (this.tabs[roter.path]) { if (this.tabs[router.name]) {
this.tabs[roter.path].query = roter.query || {} this.tabs[router.name].query = router.query || {}
return return
} }
this.tabs[roter.path] = { this.tabs[router.name] = {
path: roter.path, path: router.path,
title: roter.meta ? roter.meta.title : '', title: router.meta ? router.meta.title : '',
name: roter.name, name: router.name,
query: roter.query || {} query: router.query || {},
componentName: router.matched.at(-1)?.components.default.__name
} }
}, },
removeTab(path: string) { removeTab(path: string) {
@ -40,8 +42,9 @@ const useTabbarStore = defineStore('tabbar', {
tabLength: (state) => Object.keys(state.tabs).length, tabLength: (state) => Object.keys(state.tabs).length,
tabNames: (state) => { tabNames: (state) => {
const name: any[] = [] const name: any[] = []
if (!useSystemStore().tab) return name
Object.keys(state.tabs).forEach(key => { Object.keys(state.tabs).forEach(key => {
name.push(state.tabs[key].name) name.push(state.tabs[key].componentName)
}) })
return name return name
} }

View File

@ -53,7 +53,9 @@ const useUserStore = defineStore('user', {
logout() logout()
// 清除tabbar // 清除tabbar
useTabbarStore().clearTab() useTabbarStore().clearTab()
router.push('/login') const login = '/login'
// router.push(login)
location.href = `${location.origin}${login}`
}, },
getAuthMenusFn() { getAuthMenusFn() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

Binary file not shown.

View File

@ -1 +1,2 @@
/* addon-iconfont.css */ @import "addon/o2o/iconfont.css";
@import "addon/tourism/iconfont.css";

View File

@ -0,0 +1,38 @@
@font-face {
font-family: "o2o"; /* Project id 4412516 */
src: url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.woff2?t=1705720131974') format('woff2'),
url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.woff?t=1705720131974') format('woff'),
url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.ttf?t=1705720131974') format('truetype');
}
.o2o {
font-family: "o2o" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.o2o-icon-danhanghuadong:before {
content: "\e66f";
}
.o2o-icon-yuanjiao:before {
content: "\e6c0";
}
.o2o-icon-gl-square:before {
content: "\ea92";
}
.o2o-icon-sousuo12:before {
content: "\e699";
}
.o2o-icon-sousuo11:before {
content: "\e6d4";
}
.o2o-icon-jishi:before {
content: "\e600";
}

View File

@ -0,0 +1,51 @@
{
"id": "4412516",
"name": "上门服务",
"font_family": "o2o",
"css_prefix_text": "o2o-icon-",
"description": "",
"glyphs": [
{
"icon_id": "30621139",
"name": "单行滑动",
"font_class": "danhanghuadong",
"unicode": "e66f",
"unicode_decimal": 58991
},
{
"icon_id": "7149037",
"name": "圆角",
"font_class": "yuanjiao",
"unicode": "e6c0",
"unicode_decimal": 59072
},
{
"icon_id": "7594344",
"name": "20gl-square",
"font_class": "gl-square",
"unicode": "ea92",
"unicode_decimal": 60050
},
{
"icon_id": "10133070",
"name": "搜索",
"font_class": "sousuo12",
"unicode": "e699",
"unicode_decimal": 59033
},
{
"icon_id": "14652583",
"name": "搜索",
"font_class": "sousuo11",
"unicode": "e6d4",
"unicode_decimal": 59092
},
{
"icon_id": "6818781",
"name": "技师",
"font_class": "jishi",
"unicode": "e600",
"unicode_decimal": 58880
}
]
}

View File

@ -0,0 +1,58 @@
@font-face {
font-family: "tourism"; /* Project id 4137250 */
src: url('//at.alicdn.com/t/c/font_4137250_st1ha9l0k1e.woff2?t=1687685028672') format('woff2'),
url('//at.alicdn.com/t/c/font_4137250_st1ha9l0k1e.woff?t=1687685028672') format('woff'),
url('//at.alicdn.com/t/c/font_4137250_st1ha9l0k1e.ttf?t=1687685028672') format('truetype');
}
.tourism {
font-family: "tourism" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.tourism-icon-feiji:before {
content: "\e600";
}
.tourism-icon-lvyou:before {
content: "\e6a9";
}
.tourism-icon-lvyouchanpin:before {
content: "\e63b";
}
.tourism-icon-lvyou1:before {
content: "\e623";
}
.tourism-icon-lvyou2:before {
content: "\e601";
}
.tourism-icon-lvyou3:before {
content: "\e60c";
}
.tourism-icon-lvyoubaochedingdan:before {
content: "\e612";
}
.tourism-icon-lvyou4:before {
content: "\e653";
}
.tourism-icon-lvyou5:before {
content: "\e610";
}
.tourism-icon-lvyouguanguang:before {
content: "\e87e";
}
.tourism-icon-lvyou6:before {
content: "\e642";
}

View File

@ -0,0 +1,86 @@
{
"id": "4137250",
"name": "旅游业",
"font_family": "tourism",
"css_prefix_text": "tourism-icon-",
"description": "",
"glyphs": [
{
"icon_id": "1443",
"name": "飞机",
"font_class": "feiji",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "446824",
"name": "旅游",
"font_class": "lvyou",
"unicode": "e6a9",
"unicode_decimal": 59049
},
{
"icon_id": "1167173",
"name": "旅游产品",
"font_class": "lvyouchanpin",
"unicode": "e63b",
"unicode_decimal": 58939
},
{
"icon_id": "1354920",
"name": "旅游",
"font_class": "lvyou1",
"unicode": "e623",
"unicode_decimal": 58915
},
{
"icon_id": "1505555",
"name": "旅游",
"font_class": "lvyou2",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "2121726",
"name": "旅游",
"font_class": "lvyou3",
"unicode": "e60c",
"unicode_decimal": 58892
},
{
"icon_id": "2357494",
"name": "旅游包车订单",
"font_class": "lvyoubaochedingdan",
"unicode": "e612",
"unicode_decimal": 58898
},
{
"icon_id": "3944019",
"name": "旅游",
"font_class": "lvyou4",
"unicode": "e653",
"unicode_decimal": 58963
},
{
"icon_id": "4838220",
"name": "旅游",
"font_class": "lvyou5",
"unicode": "e610",
"unicode_decimal": 58896
},
{
"icon_id": "7444178",
"name": "旅游观光",
"font_class": "lvyouguanguang",
"unicode": "e87e",
"unicode_decimal": 59518
},
{
"icon_id": "9748082",
"name": "旅游",
"font_class": "lvyou6",
"unicode": "e642",
"unicode_decimal": 58946
}
]
}

View File

@ -1,9 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; font-family: "iconfont"; /* Project id 3883393 */
/* Project id 3883393 */ src: url('//at.alicdn.com/t/c/font_3883393_jquqyagjvf.woff2?t=1773643077865') format('woff2'),
src: url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.woff2?t=1762651161569') format('woff2'), url('//at.alicdn.com/t/c/font_3883393_jquqyagjvf.woff?t=1773643077865') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.woff?t=1762651161569') format('woff'), url('//at.alicdn.com/t/c/font_3883393_jquqyagjvf.ttf?t=1773643077865') format('truetype');
url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.ttf?t=1762651161569') format('truetype');
} }
.iconfont { .iconfont {
@ -14,6 +13,18 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.iconjiang-right:before {
content: "\e921";
}
.iconpeisongshezhi:before {
content: "\e920";
}
.iconai:before {
content: "\e91f";
}
.icona-zhulixiangqingpc30:before { .icona-zhulixiangqingpc30:before {
content: "\e914"; content: "\e914";
} }

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "nc-iconfont"; /* Project id 4567203 */ font-family: "nc-iconfont"; /* Project id 4567203 */
src: url('//at.alicdn.com/t/c/font_4567203_9qmfc2elgrt.woff2?t=1741345195504') format('woff2'), src: url('//at.alicdn.com/t/c/font_4567203_mq3zv60st5a.woff2?t=1767940264104') format('woff2'),
url('//at.alicdn.com/t/c/font_4567203_9qmfc2elgrt.woff?t=1741345195504') format('woff'), url('//at.alicdn.com/t/c/font_4567203_mq3zv60st5a.woff?t=1767940264104') format('woff'),
url('//at.alicdn.com/t/c/font_4567203_9qmfc2elgrt.ttf?t=1741345195504') format('truetype'); url('//at.alicdn.com/t/c/font_4567203_mq3zv60st5a.ttf?t=1767940264104') format('truetype');
} }
.nc-iconfont { .nc-iconfont {
@ -13,6 +13,578 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.nc-icon-huiyuangaikuang:before {
content: "\e91b";
}
.nc-icon-jiaoyigaikuang:before {
content: "\e91a";
}
.nc-icon-zhanghuruzhushenqing:before {
content: "\e919";
}
.nc-icon-jiesuanzhanghu1:before {
content: "\e918";
}
.nc-icon-zhanghuguanli:before {
content: "\e917";
}
.nc-icon-banbenhao:before {
content: "\e8ff";
}
.nc-icon-shangjiashoujiduan:before {
content: "\e8fd";
}
.nc-icon-xinxi:before {
content: "\e900";
}
.nc-icon-a-gongyingshangzhangdanpc30:before {
content: "\e771";
}
.nc-icon-a-jifenshangchengzhangdanpc30:before {
content: "\e772";
}
.nc-icon-a-shangchengzhangdanpc30:before {
content: "\e773";
}
.nc-icon-a-chongzhizhangdanpc30:before {
content: "\e774";
}
.nc-icon-gongzuotai:before {
content: "\e8ec";
}
.nc-icon-shoujiV6xx1:before {
content: "\e8ed";
}
.nc-icon-shoujiV6xxmm:before {
content: "\e8fe";
}
.nc-icon-xuanfuanniu:before {
content: "\e8e5";
}
.nc-icon-pintuanguanli:before {
content: "\e8e0";
}
.nc-icon-huodongliebiao1:before {
content: "\e8e1";
}
.nc-icon-pintuanliebiao:before {
content: "\e8e2";
}
.nc-icon-pintuandingdan:before {
content: "\e8e3";
}
.nc-icon-pintuanpeizhi:before {
content: "\e8e4";
}
.nc-icon-xia:before {
content: "\e8de";
}
.nc-icon-shang:before {
content: "\e8df";
}
.nc-icon-shoujiV6xx:before {
content: "\e7a1";
}
.nc-icon-ditumichi:before {
content: "\e8ca";
}
.nc-icon-qiehuanV6xx:before {
content: "\e8eb";
}
.nc-icon-dingdanbianhaoV6xx:before {
content: "\e8ea";
}
.nc-icon-dingdanbianhaoV6mm:before {
content: "\e8e9";
}
.nc-icon-dianpuzhandianV6xx2:before {
content: "\e8e6";
}
.nc-icon-huocheV6mm:before {
content: "\e8e7";
}
.nc-icon-huocheV6xx:before {
content: "\e8e8";
}
.nc-icon-wannengbiaodanV6xx:before {
content: "\e8d9";
}
.nc-icon-shujudaochuV6xx:before {
content: "\e8da";
}
.nc-icon-shujudaochuV6xx-1:before {
content: "\e8db";
}
.nc-icon-dayinjiV6xx:before {
content: "\e8dc";
}
.nc-icon-dayinV6xx:before {
content: "\e8dd";
}
.nc-icon-fenzuguanliV6xx:before {
content: "\e8d6";
}
.nc-icon-shangpinliebiaoV6xx:before {
content: "\e8d7";
}
.nc-icon-dingdanliebiaoV6xx:before {
content: "\e8d4";
}
.nc-icon-tuikuanV6xx2:before {
content: "\e8d8";
}
.nc-icon-jifenshangchengV6xx:before {
content: "\e8d5";
}
.nc-icon-chakandituV6xx:before {
content: "\e8d2";
}
.nc-icon-a-peisongguanliPC30:before {
content: "\e6ed";
}
.nc-icon-a-tongchengpeisongPC30:before {
content: "\e6ec";
}
.nc-icon-a-wuliupeisongPC30:before {
content: "\e6ee";
}
.nc-icon-a-shangjiapeisongPC30:before {
content: "\e6f2";
}
.nc-icon-xianshiV6mm:before {
content: "\e8ac";
}
.nc-icon-a-dianneifenrun34:before {
content: "\e811";
}
.nc-icon-xiaochengxu:before {
content: "\e8a8";
}
.nc-icon-qianV6xx:before {
content: "\e784";
}
.nc-icon-mimaV6xx:before {
content: "\e8a6";
}
.nc-icon-yanzhengmaV6xx:before {
content: "\e8a7";
}
.nc-icon-bianjiV6xx2:before {
content: "\e8a4";
}
.nc-icon-fenxiangV6mm-1:before {
content: "\e89c";
}
.nc-icon-a-Group1036V6xx:before {
content: "\e77b";
}
.nc-icon-shanchu-yuangaiV6xx:before {
content: "\e6eb";
}
.nc-icon-hezuogonghuoshangV6xx:before {
content: "\e88b";
}
.nc-icon-hezuodianpu:before {
content: "\e85b";
}
.nc-icon-gongyingshangxitong:before {
content: "\e852";
}
.nc-icon-shangchengxitong:before {
content: "\e858";
}
.nc-icon-fenzhangshezhi:before {
content: "\e843";
}
.nc-icon-xuanpinpingtai:before {
content: "\e845";
}
.nc-icon-a-Maskgroup:before {
content: "\e840";
}
.nc-icon-fenzhangguanli:before {
content: "\e84b";
}
.nc-icon-gongyingshangdingdan:before {
content: "\e84d";
}
.nc-icon-gongyingshangshangpin:before {
content: "\e84e";
}
.nc-icon-shuaxinV6mm2:before {
content: "\e889";
}
.nc-icon-yitiaohuo:before {
content: "\e888";
}
.nc-icon-shangchengdingdan:before {
content: "\e837";
}
.nc-icon-shangchengshangpin:before {
content: "\e83f";
}
.nc-icon-gouwudaiV6xx1:before {
content: "\e886";
}
.nc-icon-dianpuV6xx1:before {
content: "\e880";
}
.nc-icon-youhuiquanV6xx2:before {
content: "\e87a";
}
.nc-icon-yinhangka2:before {
content: "\e83e";
}
.nc-icon-huiyuantixian:before {
content: "\e839";
}
.nc-icon-a-Financing-tworongzi2:before {
content: "\e825";
}
.nc-icon-a-Weixin-marketweixindianshang:before {
content: "\e821";
}
.nc-icon-a-Repairbianji:before {
content: "\e818";
}
.nc-icon-a-Diskcipan:before {
content: "\e81c";
}
.nc-icon-a-Historylishijilu:before {
content: "\e81b";
}
.nc-icon-a-Deliveryjiaohuo:before {
content: "\e81a";
}
.nc-icon-dianpuV6xx:before {
content: "\e871";
}
.nc-icon-Frame:before {
content: "\e816";
}
.nc-icon-zhifujianshu1:before {
content: "\e815";
}
.nc-icon-jiagoushuliang:before {
content: "\e814";
}
.nc-icon-xiadanshu:before {
content: "\e813";
}
.nc-icon-shangpinliulanliang:before {
content: "\e812";
}
.nc-icon-fenxiaodengji:before {
content: "\e807";
}
.nc-icon-fenxiaoguanli:before {
content: "\e809";
}
.nc-icon-dianzimiandan:before {
content: "\e806";
}
.nc-icon-fenxiaodingdan:before {
content: "\e804";
}
.nc-icon-fenxiaoshang:before {
content: "\e808";
}
.nc-icon-hexiaoyuan:before {
content: "\e805";
}
.nc-icon-shangpintongji:before {
content: "\e80a";
}
.nc-icon-fenxiaogaikuang:before {
content: "\e802";
}
.nc-icon-fenxiaoshangpin:before {
content: "\e801";
}
.nc-icon-yingyongliebiao:before {
content: "\e80b";
}
.nc-icon-huodongliebiao:before {
content: "\e80c";
}
.nc-icon-hexiaojilu:before {
content: "\e80d";
}
.nc-icon-piliangfahuo:before {
content: "\e80e";
}
.nc-icon-fenxiaoshezhi:before {
content: "\e80f";
}
.nc-icon-dianpu:before {
content: "\e800";
}
.nc-icon-caozuorizhi1:before {
content: "\e7ef";
}
.nc-icon-dingdanliebiao:before {
content: "\e7f0";
}
.nc-icon-hexiaoguanli:before {
content: "\e7ee";
}
.nc-icon-fapiaoguanli:before {
content: "\e7ed";
}
.nc-icon-dingdanshezhi:before {
content: "\e7ea";
}
.nc-icon-huiyuanbiaoqian:before {
content: "\e7eb";
}
.nc-icon-huishouzhan:before {
content: "\e7db";
}
.nc-icon-jiesuanzhanghu:before {
content: "\e7f1";
}
.nc-icon-guanliyuan1:before {
content: "\e7e6";
}
.nc-icon-huiyuanliebiao:before {
content: "\e7da";
}
.nc-icon-shangpinfenlei:before {
content: "\e7dd";
}
.nc-icon-jiaoseguanli:before {
content: "\e7e3";
}
.nc-icon-shangpinliebiao:before {
content: "\e7e8";
}
.nc-icon-shangpinpingjia:before {
content: "\e7e9";
}
.nc-icon-dianpushezhi:before {
content: "\e7e7";
}
.nc-icon-sucaiguanli1:before {
content: "\e7f2";
}
.nc-icon-tixianjilu:before {
content: "\e7e4";
}
.nc-icon-xiaopiaodayin:before {
content: "\e7f3";
}
.nc-icon-shangjiadizhiku:before {
content: "\e7e1";
}
.nc-icon-shujudaochu:before {
content: "\e7de";
}
.nc-icon-shangpincanshu:before {
content: "\e7e0";
}
.nc-icon-tuikuanweiquan:before {
content: "\e7f4";
}
.nc-icon-weiyemian:before {
content: "\e7f5";
}
.nc-icon-shouyezhuangxiu:before {
content: "\e7d9";
}
.nc-icon-xianshizhekou:before {
content: "\e7d8";
}
.nc-icon-youhuiquan:before {
content: "\e7d7";
}
.nc-icon-zichangaikuang:before {
content: "\e7d6";
}
.nc-icon-yunfeimoban:before {
content: "\e7f6";
}
.nc-icon-daifukuandingdan:before {
content: "\e7fa";
}
.nc-icon-cangkushangpinshuliang:before {
content: "\e7f9";
}
.nc-icon-daishouhuodingdan:before {
content: "\e7f8";
}
.nc-icon-tuikuan:before {
content: "\e7f7";
}
.nc-icon-tuikuandingdan:before {
content: "\e7fb";
}
.nc-icon-chushoushangpinshuliang:before {
content: "\e7fc";
}
.nc-icon-daifahuodingdan:before {
content: "\e7fd";
}
.nc-icon-dingdanliang:before {
content: "\e7fe";
}
.nc-icon-xiaoshoue:before {
content: "\e7ff";
}
.nc-icon-paihangbangV6xx1:before {
content: "\e7e2";
}
.nc-icon-bangdanguanliV6xx:before {
content: "\e86d";
}
.nc-icon-bangdanshezhiV6xx:before {
content: "\e86e";
}
.nc-icon-zhuanzhangV6xx-1:before {
content: "\e86a";
}
.nc-icon-qingliV6xx:before { .nc-icon-qingliV6xx:before {
content: "\e865"; content: "\e865";
} }

View File

@ -127,14 +127,15 @@ export function isUrl(str: string): boolean {
* @returns * @returns
*/ */
export function img(path: string): string { export function img(path: string): string {
if (!path) return ''
let imgDomain = import.meta.env.VITE_IMG_DOMAIN || location.origin let imgDomain = import.meta.env.VITE_IMG_DOMAIN || location.origin
if (typeof path == 'string' && path.startsWith('/')) path = path.replace(/^\//, '') if (path.startsWith('/')) path = path.replace(/^\//, '')
if (typeof imgDomain == 'string' && imgDomain.endsWith('/')) imgDomain = imgDomain.slice(0, -1) if (imgDomain.endsWith('/')) imgDomain = imgDomain.slice(0, -1)
if(path){
return isUrl(path) ? path : `${imgDomain}/${path}` return isUrl(path) ? path : `${imgDomain}/${path}`
} }
}
/** /**
* asset img * asset img
@ -401,3 +402,37 @@ export function getTablePageStorage(where: any = {}) {
} }
return data; return data;
} }
// 距离显示
export function distance(distance: string | number): string {
const dist = typeof distance === 'string' ? parseFloat(distance) : distance;
if (isNaN(dist)) return distance.toString();
return dist < 1 ? parseInt((dist * 1000).toString()) + 'm' : dist.toFixed(1) + 'km'
}
// 获取图片尺寸的函数
export function getImageDimensions (url: string) {
return new Promise((resolve) => {
const imgObj = new Image()
imgObj.onload = () => {
// 成功加载
const size = {
width: imgObj.naturalWidth,
height: imgObj.naturalHeight
}
resolve(size)
}
imgObj.onerror = (err) => {
// 加载失败
resolve(null)
}
// 设置图片源,开始加载
// 注意如果图片跨域且服务器未设置CORS可能会触发onerror
imgObj.src = img(url)
})
}

View File

@ -134,6 +134,9 @@ export const createPolygon = (map: any, geometriesData: any) => {
* @param key * @param key
*/ */
export const deleteGeometry = (key: string) => { export const deleteGeometry = (key: string) => {
if (!geometry[key]) {
return
}
geometry[key].graphical.remove(key) geometry[key].graphical.remove(key)
geometry[key].editor.delete() geometry[key].editor.delete()
} }
@ -143,6 +146,9 @@ export const deleteGeometry = (key: string) => {
* @param key * @param key
*/ */
export const selectGeometry = (key: string) => { export const selectGeometry = (key: string) => {
if (!geometry[key] || !geometry[key].editor) {
return
}
geometry[key].editor.select([key]) geometry[key].editor.select([key])
} }

View File

@ -82,8 +82,9 @@ class Request {
} }
/** /**
* get请求 * post请求
* @param url * @param url
* @param data
* @param config * @param config
* @returns * @returns
*/ */
@ -92,8 +93,9 @@ class Request {
} }
/** /**
* get请求 * put请求
* @param url * @param url
* @param data
* @param config * @param config
* @returns * @returns
*/ */
@ -102,7 +104,7 @@ class Request {
} }
/** /**
* get请求 * delete请求
* @param url * @param url
* @param config * @param config
* @returns * @returns
@ -180,15 +182,27 @@ class Request {
} }
private messageCache = new Map(); private messageCache = new Map();
private readonly CACHE_EXPIRY = 5000; // 5秒内重复内容不再弹出可自定义过期时间
private readonly MAX_CACHE_SIZE = 100;
private showElMessage(options: MessageParams) { private showElMessage(options: MessageParams) {
const cacheKey = options.message const cacheKey = options.message
const now = Date.now()
const cachedMessage = this.messageCache.get(cacheKey); const cachedMessage = this.messageCache.get(cacheKey);
if (!cachedMessage || Date.now() - cachedMessage.timestamp > 5000) { // 5秒内重复内容不再弹出可自定义过期时间 if (!cachedMessage || now - cachedMessage.timestamp > this.CACHE_EXPIRY) {
this.messageCache.set(cacheKey, { timestamp: Date.now() }); this.messageCache.set(cacheKey, { timestamp: now });
ElMessage(options) ElMessage(options)
} }
// 定期清理过期缓存,防止内存泄漏
if (this.messageCache.size > this.MAX_CACHE_SIZE) {
for (const [key, value] of this.messageCache.entries()) {
if (now - value.timestamp > this.CACHE_EXPIRY) {
this.messageCache.delete(key)
}
}
}
} }
} }

View File

@ -34,10 +34,12 @@ class Storage {
*/ */
public get(key: string) { public get(key: string) {
try { try {
const json: any = window.localStorage.getItem(`${this.prefix}.${key}`) const value = window.localStorage.getItem(`${this.prefix}.${key}`)
return JSON.parse(json) if (value === null) return null
return JSON.parse(value)
} catch (error) { } catch (error) {
return window.localStorage.getItem(`${this.prefix}.${key}`) const value = window.localStorage.getItem(`${this.prefix}.${key}`)
return value !== null ? value : null
} }
} }