This commit is contained in:
全栈小学生 2025-05-23 14:31:53 +08:00
parent c337da9ea3
commit d98c2dacc1
137 changed files with 5696 additions and 2947 deletions

View File

@ -1,6 +1,5 @@
import request from '@/utils/request' import request from '@/utils/request'
/** /**
* *
* @param params * @param params
@ -24,14 +23,6 @@ export function getAuthMenus(params: Record<string, any>) {
return request.get('auth/authmenu', { params }) return request.get('auth/authmenu', { params })
} }
/**
*
* @returns
*/
export function getSiteInfo() {
return request.get('auth/site')
}
/** /**
* *
* @returns * @returns

View File

@ -19,6 +19,7 @@ export function getDiyFormPageList(params: Record<string, any>) {
export function getDiyFormList(params: Record<string, any>) { export function getDiyFormList(params: Record<string, any>) {
return request.get(`diy/form/list`, { params }) return request.get(`diy/form/list`, { params })
} }
/** /**
* *
* @param params * @param params

View File

@ -61,3 +61,11 @@ export function getFrameworkNewVersion() {
export function getFrameworkVersionList() { export function getFrameworkVersionList() {
return request.get(`niucloud/framework/version/list`) return request.get(`niucloud/framework/version/list`)
} }
/**
* /
* @param params
*/
export function getAppVersionList(params: Record<string, any>) {
return request.get(`niucloud/app_version/list`, { params })
}

View File

@ -54,6 +54,14 @@ export function getPayRefundInfo(refund_no: string) {
return request.get(`pay/refund/${refund_no}`) return request.get(`pay/refund/${refund_no}`)
} }
/**
* 退
* @param refund_no
*/
export function getRefundStatus() {
return request.get(`pay/refund/status`)
}
/** /**
* 退 * 退
*/ */

View File

@ -727,3 +727,11 @@ export function deleteExport(id: number) {
export function getInstallConfig() { export function getInstallConfig() {
return request.get('sys/install/config') return request.get('sys/install/config')
} }
/**
*
* @returns
*/
export function getQrcode(params: Record<string, any>) {
return request.get(`sys/qrcode`, { params, showErrorMessage: false })
}

View File

@ -85,6 +85,7 @@ export function addonDevelopBuild(key: any) {
export function addonDevelopDownload(key: any) { export function addonDevelopDownload(key: any) {
return request.post(`addon_develop/download/${ key }`, {}) return request.post(`addon_develop/download/${ key }`, {})
} }
/***************************************************** 代码生成 ****************************************************/ /***************************************************** 代码生成 ****************************************************/
/** /**

View File

@ -20,15 +20,15 @@ export function getUpgradeTask() {
* *
* @param addon * @param addon
*/ */
export function upgradeAddon(addon: string = '') { export function upgradeAddon(addon: string = '', params: Record<string, any> = {}) {
return request.post(addon ? `upgrade/${addon}` : 'upgrade') return request.post(addon ? `upgrade/${ addon }` : 'upgrade', params)
} }
/** /**
* *
*/ */
export function executeUpgrade() { export function executeUpgrade() {
return request.post('upgrade/execute', {}) return request.post('upgrade/execute', {}, { showErrorMessage: false })
} }
/** /**
@ -44,3 +44,96 @@ export function preUpgradeCheck(addon: string = '') {
export function clearUpgradeTask() { export function clearUpgradeTask() {
return request.post('upgrade/clear') return request.post('upgrade/clear')
} }
/**
*
* @param operate
*/
export function upgradeUserOperate(operate: string) {
return request.post(`upgrade/operate/${ operate }`)
}
/**
*
* @param params
* @returns
*/
export function getUpgradeRecords(params: Record<string, any>) {
return request.get(`upgrade/records`, { params })
}
/**
*
* @param params
*/
export function delUpgradeRecords(params: Record<string, any>) {
return request.delete(`upgrade/records`, { params })
}
/**
*
* @param params
* @returns
*/
export function getBackupRecords(params: Record<string, any>) {
return request.get(`backup/records`, { params })
}
/**
*
* @param params
*/
export function modifyBackupRemark(params: Record<string, any>) {
return request.put(`backup/remark`, params, { showSuccessMessage: true })
}
/**
*
*/
export function checkDirExist(params: Record<string, any>) {
return request.post('backup/check_dir', params)
}
/**
*
*/
export function checkPermission(params: Record<string, any>) {
return request.post('backup/check_permission', params)
}
/**
*
*/
export function restoreUpgradeBackup(params: Record<string, any>) {
return request.post('backup/restore', params)
}
/**
*
*/
export function deleteRecords(params: Record<string, any>) {
return request.post('backup/delete', params, { showSuccessMessage: true })
}
/**
*
*/
export function manualBackup(params: Record<string, any>) {
return request.post("backup/manual", params)
}
/**
*
* @param params
*/
export function performRecoveryTasks(params: Record<string, any>) {
return request.get("backup/restore_task", params)
}
/**
*
* @param params
*/
export function performBackupTasks(params: Record<string, any>) {
return request.get("backup/task", params)
}

View File

@ -66,3 +66,19 @@ export function addVerifier(params: Record<string, any>) {
export function deleteVerifier(id: number) { export function deleteVerifier(id: number) {
return request.delete(`verify/verifier/${ id }`, { showSuccessMessage: true }) return request.delete(`verify/verifier/${ id }`, { showSuccessMessage: true })
} }
/**
*
* @returns
*/
export function getVerifyInfo(id: number) {
return request.get(`verify/verifier/${ id }`)
}
/**
*
* @returns
*/
export function editVerifier(params: Record<string, any>) {
return request.post(`verify/verifier/${ params.id }`, params,{ showSuccessMessage: true })
}

View File

@ -33,6 +33,7 @@ export function getTemplateList() {
export function getBatchAcquisition(params: Record<string, any>) { export function getBatchAcquisition(params: Record<string, any>) {
return request.put('weapp/template/sync', params, { showSuccessMessage: true }) return request.put('weapp/template/sync', params, { showSuccessMessage: true })
} }
/** /**
* *
* @param params * @param params

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -84,7 +84,6 @@ const active = ref('build')
const cloudBuildCheck = ref<null | AnyObject>(null) const cloudBuildCheck = ref<null | AnyObject>(null)
const loading = ref(false) const loading = ref(false)
const terminalRef = ref(null) const terminalRef = ref(null)
const emits = defineEmits(['complete'])
let cloudBuildLog = [] let cloudBuildLog = []
/** /**
@ -127,7 +126,7 @@ const getCloudBuildLogFn = () => {
data[0].forEach(item => { data[0].forEach(item => {
if (!cloudBuildLog.includes(item.action)) { if (!cloudBuildLog.includes(item.action)) {
terminalRef.value.pushMessage({ content: `正在执行:${item.action}` }) terminalRef.value.pushMessage({ content: `${item.action}` })
cloudBuildLog.push(item.action) cloudBuildLog.push(item.action)
if (item.code == 0) { if (item.code == 0) {

View File

@ -0,0 +1,240 @@
<template>
<el-dialog v-model="dialogVisible" :title="t('gxx')" width="850" :destroy-on-close="true">
<el-card class="box-card !border-none" shadow="never" >
<div v-loading="loading">
<div class="text-page-title mb-[20px]">历史版本</div>
<div class="time-dialog h-[500px]" style="overflow: auto">
<el-scrollbar>
<el-timeline style="width: 100%" v-if="!loading">
<el-timeline-item v-for="(item, index) in frameworkVersionList" :key="index" placement="left" :color="color">
<div class="relative">
<span class="text-[#333333] text-[14px] absolute">{{ timeSplit(item.release_time)[0] }}</span>
<br />
<span class="text-[#999999] text-[14px] w-[78px] block mt-[10px] absolute text-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-[14px]"> 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>
</el-scrollbar>
</div>
</div>
</el-card>
</el-dialog>
</template>
<script lang="ts" setup>
import { computed, ref, defineProps, nextTick } from "vue"
import { t } from "@/lang"
import { getAppVersionList, getFrameworkVersionList } from "@/app/api/module"
const props = defineProps({
upgradeKey: {
type: String,
required: true
}
})
const frameworkVersionList = ref([])
const newVersion: any = computed(() => {
return frameworkVersionList.value.length ? frameworkVersionList.value[0] : null
})
const getAppVersionListFn = () => {
getAppVersionList({ app_key: props.upgradeKey }).then(({ data }) => {
loading.value = false
data.forEach((item: any, index) => {
if (index == 0) {
item.important = 1
} else {
item.important = 0
}
})
frameworkVersionList.value = data
})
}
const getFrameworkVersionListFn = () => {
getFrameworkVersionList().then(({ data }) => {
loading.value = false
data.forEach((item: any, index) => {
if (index == 0) {
item.important = 1
} else {
item.important = 0
}
})
frameworkVersionList.value = data
})
}
const activeName = ref(0)
//
const loading = ref(true)
const dialogVisible = ref(false)
const open = async () => {
nextTick(() => {
activeName.value = 0 //
if (props.upgradeKey) {
getAppVersionListFn()
} else {
getFrameworkVersionListFn()
}
dialogVisible.value = true
})
}
const timeSplit = (str: string) => {
const [date, time] = str.split(" ")
const [hours, minutes] = time.split(":")
return [date, `${ hours }:${ minutes }`]
}
defineExpose({
open
})
</script>
<style lang="scss" scoped></style>
<style scoped>
.el-timeline-item {
min-height: 75px;
}
.el-timeline-item >>> .el-timeline-item__node--normal {
left: 117px;
width: 18px;
height: 18px;
background: rgba(25, 103, 249, 0.12) !important;
border-radius: 50%;
/* 创建圆形 */
position: relative;
/* 用于定位伪元素 */
}
.el-timeline-item >>> .el-timeline-item__node--normal::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 8px;
/* 中心圆直径 */
height: 8px;
background-color: var(--el-color-primary);
/* 中心圆颜色 */
border-radius: 50%;
/* 中心圆为圆形 */
transform: translate(-50%, -50%);
/* 居中显示 */
}
.el-timeline-item >>> .el-timeline-item__tail {
left: 125px;
border-left-color: #dddddd;
border-left-style: dashed;
margin: 12px 0;
margin-top: 24px;
height: calc(100% - 24px - 12px);
}
.time-dialog >>> .el-dialog__header {
padding: 10px 20px;
height: 25px;
line-height: 25px;
text-align: left;
background: #fff;
border-bottom: solid 1px #e4e7ed;
}
.time-dialog >>> .el-card__body {
padding: 8px;
}
.time-dialog >>> .el-card.is-always-shadow {
box-shadow: none;
}
.time-dialog >>> .el-dialog__headerbtn .el-dialog__close {
color: #666;
}
.time-dialog >>> .el-dialog__headerbtn:hover .el-dialog__close {
color: #666;
}
.time-dialog >>> .el-dialog__headerbtn {
top: 14px;
}
.time-dialog >>> .el-collapse {
margin-left: 119px;
border: none;
margin-top: -22px;
}
.time-dialog >>> .el-collapse-item__header {
border: none;
line-height: 25px;
height: 25px;
position: relative;
z-index: 999;
}
.time-dialog >>> .el-collapse-item__wrap {
border: none;
}
.time-dialog >>> .el-collapse-item__content {
margin-top: 15px;
padding-bottom: 0px !important;
}
.time-dialog >>> .el-timeline-item__node--01 {
width: 18px !important;
height: 18px !important;
left: 117px !important;
}
</style>
<style>
.time-dialog .el-dialog {
margin: 0 auto !important;
max-height: 90%;
overflow: hidden;
top: 5%;
}
.time-dialog .el-dialog {
margin: 0 auto !important;
height: 65%;
overflow: hidden;
top: 10%;
}
.time-dialog .el-dialog__body {
position: absolute;
left: 0;
top: 46px;
bottom: 0;
right: 0;
z-index: 1;
overflow: hidden;
overflow-y: auto;
padding: 10px 20px 0 0;
}
.time-dialog .el-timeline-item__wrapper {
top: -20px !important;
}
.el-scrollbar__bar{
z-index:999;
}
</style>

View File

@ -1,58 +1,102 @@
<template> <template>
<el-dialog v-model="showDialog" :title="t('upgrade.title')" width="850px" :close-on-click-modal="false" :close-on-press-escape="false" :before-close="dialogClose"> <el-dialog v-model="showDialog" :title="t('upgrade.title')" width="850px" :close-on-click-modal="false" :close-on-press-escape="false" :before-close="dialogClose">
<div v-show="active == 'content'"> <template v-if="upgradeContent">
<!-- 检测服务是否到期 -->
<div class="h-[60vh] flex flex-col" v-if="upgradeContent"> <template v-if="step == 1">
<template v-for="(item, index) in upgradeContent.content">
<div class="text-lg"> <div class="text-lg">
本次升级将从<span class="font-bold">{{ upgradeContent.version }}</span>升级到<span class="font-bold">{{ upgradeContent.upgrade_version }}</span>版本 <template v-if="item.upgrade_version">
<span>{{ item.app.app_name }}本次升级将从</span>
<span class="font-bold px-[2px]">{{ item.version }}</span>
<span>升级到</span>
<span class="font-bold px-[2px]">{{ item.upgrade_version }}</span>
<span>版本</span>
</template>
<template v-else>
<template v-if="upgradeContent.content.length > 1">
<span>{{ item.app.app_name }}当前版本</span>
<span class="font-bold px-[2px]">{{ item.version }}</span>
</template>
<template v-else>
<span>当前版本</span>
<span class="font-bold px-[2px]">{{ item.version }}</span>
</template>
</template>
</div> </div>
<div class="mt-[10px]" v-if="upgradeContent.upgrade_version != upgradeContent.last_version"> <div class="mt-[10px]" :class="{ 'mb-[10px]' : (index + 1) < upgradeContent.content.length }" v-if="item.upgrade_version != item.last_version">
<el-alert type="info" show-icon> <el-alert type="info" show-icon :closable="false">
<template #title> <template #title>
当前最新版本为{{ upgradeContent.last_version }}您的服务{{ upgradeContent.expire_time ? `已于${upgradeContent.expire_time}到期` : '长期有效' }}如需升级到最新版可在<a class="text-primary" href="https://www.niucloud.com" target="_blank">niucloud-admin官网</a>购买相关服务后再进行升级 <span>当前最新版本为{{ item.last_version }}您的服务{{ item.expire_time ? `已于${item.expire_time}到期` : '长期有效' }}</span>
<span>如需升级到最新版可在<a class="text-primary" href="https://www.niucloud.com" target="_blank">niucloud-admin官网</a>购买相关服务后再进行升级</span>
</template> </template>
</el-alert> </el-alert>
</div> </div>
<el-scrollbar class="flex-1 h-0 mt-[20px]"> </template>
<div class="mt-[20px]" v-for="(item, index) in upgradeContent.version_list" :key="index"> </template>
<div class="font-bold text-lg">{{ item.version_no }}</div> <div v-if="step == 2">
<div class="mt-[5px]" v-if="item.release_time">{{ item.release_time }}</div> <el-steps :active="numberOfSteps" align-center class="number-of-steps" finish-status="success" process-status="process">
<div class="mt-[10px] p-[10px] rounded bg-[#f4f4f5] whitespace-pre-wrap !break-all" v-if="item.upgrade_log" v-html="item.upgrade_log"></div> <el-step :title="t('testDirectoryPermissions')" />
</div> <el-step :title="t('backupFiles')" />
</el-scrollbar> <el-step :title="t('startUpgrade')" />
</div> <el-step :title="t('upgradeEnd')" />
<div class="flex justify-end" v-if="upgradeContent.version_list.length"> </el-steps>
<el-button type="primary" @click="handleUpgrade" :loading="uploading">{{ t('upgrade.upgradeButton') }}</el-button> <div class="h-[400px]" style="overflow: auto">
</div> <!-- <div class="time-dialog-wrap mt-[30px]" v-show="active == 'content'">-->
</div> <!-- <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="h-[60vh] flex flex-col" v-if="upgradeCheck && !upgradeTask"> <div class="flex flex-col" v-if="upgradeCheck && !upgradeTask">
<el-scrollbar> <el-scrollbar>
<div class="bg-[#fff] my-3" v-if="upgradeCheck.dir"> <div class="bg-[#fff] my-3" v-if="upgradeCheck.dir">
<p class="pt-[20px] pl-[20px] ">{{ t('upgrade.dirPermission') }}</p>
<div class="px-[20px] pt-[10px] text-[14px] el-table"> <div class="px-[20px] pt-[10px] text-[14px] el-table">
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]"> <el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
<el-col :span="12"> <el-col :span="12">
<span>{{ t('upgrade.path') }}</span> <span>{{ t("upgrade.path") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('upgrade.demand') }}</span> <span>{{ t("upgrade.demand") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('status') }}</span> <span>{{ t("status") }}</span>
</el-col> </el-col>
</el-row> </el-row>
<div style="height: calc(300px); overflow: auto">
<el-row class="pb-[10px] items pl-[15px]" v-for="item in upgradeCheck.dir.is_readable"> <el-row class="pb-[10px] items pl-[15px]" v-for="item in upgradeCheck.dir.is_readable">
<el-col :span="12"> <el-col :span="12">
<span>{{ item.dir }}</span> <span>{{ item.dir }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('upgrade.readable') }}</span> <span>{{ t("upgrade.readable") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span> <span v-if="item.status">
<el-icon color="green">
<Select />
</el-icon>
</span>
<span v-else> <span v-else>
<el-icon color="red"> <el-icon color="red">
<CloseBold /> <CloseBold />
@ -65,10 +109,14 @@
<span>{{ item.dir }}</span> <span>{{ item.dir }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('upgrade.write') }}</span> <span>{{ t("upgrade.write") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span> <span v-if="item.status">
<el-icon color="green">
<Select />
</el-icon>
</span>
<span v-else> <span v-else>
<el-icon color="red"> <el-icon color="red">
<CloseBold /> <CloseBold />
@ -78,34 +126,89 @@
</el-row> </el-row>
</div> </div>
</div> </div>
</div>
</el-scrollbar> </el-scrollbar>
</div> </div>
<div class="h-[60vh]" v-show="upgradeTask"> <div class="h-[370px] mt-[30px]" v-show="upgradeTask">
<terminal ref="terminalRef" :context="upgradeTask ? upgradeTask.upgrade.app_key : ''" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd" /> <terminal ref="terminalRef" :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'">
<el-scrollbar>
<div class="bg-[#fff] my-3">
<div v-show="active == 'complete'"> <div class="px-[20px] pt-[10px] text-[14px] el-table" v-if="!upgradeContent.last_backup">
<div class="h-[60vh] flex flex-col"> <el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
<div class="flex-1 h-0"> <el-col :span="20">
<span>功能操作</span>
</el-col>
<el-col :span="4">
<span>状态</span>
</el-col>
</el-row>
<el-row class="pb-[10px] items pl-[15px]" v-for="item in excludeSteps">
<el-col :span="20">
<span>{{ item.name }}</span>
</el-col>
<el-col :span="4">
<span>
<el-icon color="green">
<Select />
</el-icon>
</span>
</el-col>
</el-row>
</div>
<div class="pl-[50px] pt-[50px]" v-else>
<el-checkbox v-model="isNeedBackup" :label="t('upgrade.isNeedBackup')" :true-value="true" :false-value="false" size="large" >
</el-checkbox>
<div class="backup">{{ t('upgrade.isNeedBackupTips') }}<el-button link type="primary" @click="toBackupRecord">{{ t('upgrade.isNeedBackupBtn') }}</el-button></div>
</div>
</div>
</el-scrollbar>
</div>
<div class="mt-[50px]" v-show="active == 'complete'">
<el-result icon="success" :title="t('upgrade.upgradeSuccess')"></el-result> <el-result icon="success" :title="t('upgrade.upgradeSuccess')"></el-result>
<el-alert :title="t('upgrade.upgradeCompleteTips')" type="error" :closable="false" /> <el-alert :title="t('upgrade.upgradeCompleteTips')" type="error" :closable="false" v-show="upgradeTask && upgradeTask.executed && !upgradeTask.executed.includes('cloudBuild')"/>
</div>
<div class="flex justify-end">
<el-button type="default" @click="showDialog = false">{{ t('upgrade.localBuild') }}</el-button>
<el-button type="primary" @click="handleCloudBuild">{{ t('upgrade.cloudBuild') }}</el-button>
</div> </div>
</div> </div>
</div> </div>
</template>
<template #footer>
<div class="dialog-footer">
<!-- 查看升级内容 -->
<el-button v-if="step == 1 && upgradeContent.content.length && isAllowUpgrade" @click="step = 2" type="primary">{{ t("upgrade.upgradeButton") }}</el-button>
<template v-if="step == 2">
<!-- <el-button v-if="active == 'content'" @click="showDialog = false">{{ t("return") }}</el-button>-->
<el-button type="primary" :disabled="!is_pass" v-if="active == 'upgrade' && !upgradeTask" @click="() => { active = 'backup'; numberOfSteps = 1 }">{{ t("nextStep") }}</el-button>
<el-button v-if="active == 'backup'" @click="() => { active = 'upgrade'; numberOfSteps = 1 } ">{{ t("prev") }}</el-button>
<el-button type="primary" v-if="active == 'backup'" :loading="loading" @click="() => { upgradeAddonFn() }">{{ t("nextStep") }}</el-button>
<el-button v-if="active == 'complete'" @click="showDialog = false">{{ t("complete") }}</el-button>
</template>
</div>
</template>
</el-dialog> </el-dialog>
<el-dialog v-model="upgradeTipsShowDialog" :title="t('warning')" width="500px" draggable> <el-dialog v-model="upgradeTipsShowDialog" :title="t('warning')" width="500px" draggable>
<span v-html="t('upgrade.upgradeTips')"></span> <span v-html="t('upgrade.upgradeTips')"></span>
<template #footer> <template #footer>
<div class="flex justify-end"> <div class="flex justify-end">
<el-button @click="upgradeTipsConfirm(true)" type="primary">{{ t('upgrade.knownToKnow') }}</el-button> <el-button @click="upgradeTipsConfirm(true)" type="primary">{{ t("upgrade.knownToKnow") }}</el-button>
<el-button @click="upgradeTipsConfirm()" type="primary" plain>{{ t('upgrade.upgradeButton') }}</el-button> <el-button @click="handleUpgrade()" type="primary" plain :loading="readyLoading">{{ t("upgrade.upgradeButton") }}</el-button>
<el-button @click="upgradeTipsShowDialog = false">{{ t('cancel') }}</el-button> <el-button @click="upgradeTipsShowDialog = false">{{ t("cancel") }}</el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="cloudBuildErrorTipsShowDialog" :title="t('warning')" width="500px" draggable :show-close="false">
<span v-html="t('upgrade.cloudBuildErrorTips')"></span>
<template #footer>
<div class="flex justify-end">
<el-button @click="cloudBuildError('local')" type="primary">{{ t("upgrade.localBuild") }}</el-button>
<el-button @click="cloudBuildError('retry')" type="primary">{{ t("upgrade.cloudBuild") }}{{ retrySecond }}S</el-button>
<el-button @click="cloudBuildError('rollback')" type="primary">{{ t("upgrade.rollback") }}</el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
@ -116,25 +219,51 @@ import { ref, h, watch } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getVersions } from '@/app/api/auth' import { getVersions } from '@/app/api/auth'
import { getFrameworkNewVersion } from '@/app/api/module' import { getFrameworkNewVersion } from '@/app/api/module'
import { getUpgradeContent, getUpgradeTask, upgradeAddon, executeUpgrade, preUpgradeCheck, clearUpgradeTask } from '@/app/api/upgrade' import {
getUpgradeContent,
getUpgradeTask,
upgradeAddon,
executeUpgrade,
preUpgradeCheck,
clearUpgradeTask, upgradeUserOperate
} from '@/app/api/upgrade'
import { Terminal, TerminalFlash } from 'vue-web-terminal' import { Terminal, TerminalFlash } from 'vue-web-terminal'
import 'vue-web-terminal/lib/theme/dark.css' import 'vue-web-terminal/lib/theme/dark.css'
import { AnyObject } from '@/types/global' import { AnyObject } from '@/types/global'
import { ElNotification, ElMessage, ElMessageBox } from 'element-plus' import { ElNotification, ElMessage, ElMessageBox } from 'element-plus'
import Storage from '@/utils/storage' import Storage from '@/utils/storage'
import { useRouter } from 'vue-router'
const router = useRouter()
const showDialog = ref<boolean>(false) const showDialog = ref<boolean>(false)
const upgradeContent = ref<null | AnyObject>(null) const upgradeContent = ref<null | AnyObject>(null)
const isAllowUpgrade = ref(true) //
const upgradeTask = ref<null | AnyObject>(null) const upgradeTask = ref<null | AnyObject>(null)
const active = ref('content') const active = ref('upgrade')
const step = ref(1)
const upgradeCheck = ref<null | AnyObject>(null) const upgradeCheck = ref<null | AnyObject>(null)
const uploading = ref(false) const loading = ref(false)
const terminalRef: any = ref(null) const terminalRef: any = ref(null)
const emits = defineEmits(['complete', 'cloudbuild']) const emits = defineEmits(['complete', 'cloudbuild'])
const upgradeTipsShowDialog = ref<boolean>(false) const upgradeTipsShowDialog = ref<boolean>(false)
let upgradeLog: any = [] let upgradeLog: any = []
let errorLog: any = [] let errorLog: any = []
const cloudBuildErrorTipsShowDialog = ref<boolean>(false)
const retrySecond = ref(30)
let retrySecondInterval: any = null
const isNeedBackup = ref(true)
// backupCode backupSql
const excludeSteps: any = ref([
{
name: '备份源码',
code: 'backupCode'
},
{
name: '备份数据库',
code: 'backupSql'
}
])
/** /**
* 查询升级任务 * 查询升级任务
*/ */
@ -142,6 +271,24 @@ const getUpgradeTaskFn = () => {
getUpgradeTask().then(({ data }) => { getUpgradeTask().then(({ data }) => {
if (!data) return if (!data) return
if (!upgradeContent.value) {
upgradeContent.value = data.upgrade_content
let upgradeCount = 0
let failUpgradeCount = 0
for (let i = 0; i < upgradeContent.value.content.length; i++) {
if (upgradeContent.value.content[i].version_list.length) {
upgradeCount++
} else {
failUpgradeCount++
}
}
if (upgradeContent.value.content.length == upgradeCount) {
isAllowUpgrade.value = true
} else if (upgradeContent.value.content.length == failUpgradeCount) {
isAllowUpgrade.value = false
}
}
// //
if (!showDialog.value) { if (!showDialog.value) {
showElNotification() showElNotification()
@ -151,15 +298,18 @@ const getUpgradeTaskFn = () => {
terminalRef.value.execute('clear') terminalRef.value.execute('clear')
terminalRef.value.execute('开始升级') terminalRef.value.execute('开始升级')
} }
data.log.forEach(item => {
upgradeTask.value = data
data.log.forEach((item) => {
if (!upgradeLog.includes(item)) { if (!upgradeLog.includes(item)) {
terminalRef.value.pushMessage({content: `正在执行:${item}`}) terminalRef.value.pushMessage({ content: `${item}` })
upgradeLog.push(item) upgradeLog.push(item)
} }
}) })
// //
if (data.error) { if (data.error) {
data.error.forEach(item => { data.error.forEach((item) => {
if (!errorLog.includes(item)) { if (!errorLog.includes(item)) {
terminalRef.value.pushMessage({ content: item, class: 'error' }) terminalRef.value.pushMessage({ content: item, class: 'error' })
errorLog.push(item) errorLog.push(item)
@ -168,26 +318,43 @@ const getUpgradeTaskFn = () => {
} }
// //
if (data.step == 'restoreComplete') { if (data.step == 'restoreComplete') {
flashInterval && clearInterval(flashInterval)
return return
} }
// //
if (data.step == 'upgradeComplete') { if (data.step == 'upgradeComplete') {
active.value = 'complete' active.value = 'complete'
numberOfSteps.value = 4
notificationEl && notificationEl.close() notificationEl && notificationEl.close()
emits('complete') emits('complete')
clearUpgradeTask() clearUpgradeTask()
return return
} }
upgradeTask.value = data numberOfSteps.value = 2
active.value = 'upgrade'
executeUpgradeFn() executeUpgradeFn()
}).catch() })
} }
getUpgradeTaskFn() getUpgradeTaskFn()
const executeUpgradeFn = () => { const executeUpgradeFn = () => {
executeUpgrade().then(() => { executeUpgrade().then(() => {
getUpgradeTaskFn() getUpgradeTaskFn()
}).catch() }).catch((err) => {
if (err.message.indexOf('队列') != -1) {
retrySecond.value = 30
retrySecondInterval = setInterval(() => {
retrySecond.value--
if (retrySecond.value == 0) {
cloudBuildError('retry')
}
}, 1000)
cloudBuildErrorTipsShowDialog.value = true
} else {
ElMessage({ message: err.message, type: 'error' })
}
})
} }
let notificationEl: any = null let notificationEl: any = null
@ -198,10 +365,10 @@ const showElNotification = () => {
notificationEl = ElNotification.success({ notificationEl = ElNotification.success({
title: t('warning'), title: t('warning'),
dangerouslyUseHTMLString: true, dangerouslyUseHTMLString: true,
message: h('div', {}, [ message: h('div', {}, [t('upgrade.upgradingTips'), h('span', {
t('upgrade.upgradingTips'), class: 'text-primary cursor-pointer',
h('span', { class: 'text-primary cursor-pointer', onClick: elNotificationClick }, [t('upgrade.clickView')]) onClick: elNotificationClick
]), }, [t('upgrade.clickView')])]),
duration: 0, duration: 0,
showClose: false showClose: false
}) })
@ -209,16 +376,18 @@ const showElNotification = () => {
const elNotificationClick = () => { const elNotificationClick = () => {
showDialog.value = true showDialog.value = true
active.value = 'upgrade'
getUpgradeTaskFn() getUpgradeTaskFn()
step.value = 2
numberOfSteps.value = 3
active.value = 'upgrade'
notificationEl && notificationEl.close() notificationEl && notificationEl.close()
} }
const frameworkVersion = ref('') const frameworkVersion = ref('')
getVersions().then(res => { getVersions().then((res) => {
frameworkVersion.value = res.data.version.version frameworkVersion.value = res.data.version.version
}) })
const newFrameworkVersion = ref("") const newFrameworkVersion = ref('')
getFrameworkNewVersion().then(({ data }) => { getFrameworkNewVersion().then(({ data }) => {
newFrameworkVersion.value = data.last_version newFrameworkVersion.value = data.last_version
}) })
@ -226,44 +395,90 @@ getFrameworkNewVersion().then(({ data }) => {
/** /**
* 执行升级 * 执行升级
*/ */
const handleUpgrade = async () => { const is_pass = ref(false)
if (uploading.value) return const repeat = ref(false)
uploading.value = true const readyLoading = ref(false)
const appKey = upgradeContent.value?.app.app_key != 'niucloud-admin' ? upgradeContent.value?.app.app_key : '' const handleUpgrade = async () => {
if (repeat.value) return
repeat.value = true
readyLoading.value = true
const appKey = upgradeContent.value?.upgrade_apps.join(',') != 'niucloud-admin' ? upgradeContent.value?.upgrade_apps.join(',') : ''
await preUpgradeCheck(appKey).then(async ({ data }) => { await preUpgradeCheck(appKey).then(async ({ data }) => {
if (data.is_pass) { upgradeCheck.value = data
await upgradeAddon(appKey).then(() => { is_pass.value = data.is_pass
active.value = 'upgrade'
!upgradeTask.value ? (numberOfSteps.value = 0) : numberOfSteps.value
upgradeTipsShowDialog.value = false
showDialog.value = true
repeat.value = false
readyLoading.value = false
}).catch(() => {
repeat.value = false
readyLoading.value = false
})
}
const upgradeAddonFn = () => {
if (!is_pass.value) return
if (loading.value) return
loading.value = true
const appKey = upgradeContent.value?.upgrade_apps.join(',') != 'niucloud-admin' ? upgradeContent.value?.upgrade_apps.join(',') : ''
upgradeAddon(appKey, { is_need_backup: isNeedBackup.value }).then(() => {
getUpgradeTaskFn() getUpgradeTaskFn()
}).catch(() => { }).catch(() => {
uploading.value = false loading.value = false
}) })
} else {
upgradeCheck.value = data
}
}).catch()
if (uploading.value) active.value = 'upgrade'
} }
const open = (addonKey: string = '') => { const open = (addonKey: string = '', callback = null) => {
if (upgradeTask.value) { if (upgradeTask.value) {
ElMessage({ message: '已有正在执行中的升级任务', type: 'error' }) ElMessage({ message: '已有正在执行中的升级任务', type: 'error' })
showDialog.value = true showDialog.value = true
step.value = 2
numberOfSteps.value = 3
active.value = 'upgrade'
if (callback) callback()
} else { } else {
if (addonKey && frameworkVersion.value != newFrameworkVersion.value) { if (addonKey && frameworkVersion.value != newFrameworkVersion.value) {
ElMessage({ message: '存在新版本框架,请先升级框架', type: 'error' }) ElMessage({ message: "存在新版本框架,请先升级框架", type: "error" })
if (callback) callback()
return return
} }
if (loading.value) return
loading.value = true
getUpgradeContent(addonKey).then(({ data }) => { getUpgradeContent(addonKey).then(({ data }) => {
loading.value = false
upgradeContent.value = data upgradeContent.value = data
let upgradeCount = 0
let failUpgradeCount = 0
for (let i = 0; i < upgradeContent.value.content.length; i++) {
if (upgradeContent.value.content[i].version_list.length) {
upgradeCount++
} else {
failUpgradeCount++
}
}
if (upgradeContent.value.content.length == upgradeCount) {
isAllowUpgrade.value = true
} else if (upgradeContent.value.content.length == failUpgradeCount) {
isAllowUpgrade.value = false
}
if (Storage.get('upgradeTipsLock')) { if (Storage.get('upgradeTipsLock')) {
showDialog.value = true handleUpgrade()
} else { } else {
upgradeTipsShowDialog.value = true upgradeTipsShowDialog.value = true
} }
}).catch() if (callback) callback()
}).catch(() => {
loading.value = false
if (callback) callback()
})
} }
} }
@ -283,10 +498,10 @@ const onExecCmd = (key, command, success, failed, name) => {
} }
const makeIterator = (array: string[]) => { const makeIterator = (array: string[]) => {
var nextIndex = 0 let nextIndex = 0
return { return {
next () { next () {
if ((nextIndex + 1) == array.length) { if (nextIndex + 1 == array.length) {
nextIndex = 0 nextIndex = 0
} }
return { value: array[nextIndex++] } return { value: array[nextIndex++] }
@ -295,45 +510,71 @@ const makeIterator = (array: string[]) => {
} }
const dialogClose = (done: () => {}) => { const dialogClose = (done: () => {}) => {
if (active.value == 'upgrade' && upgradeTask.value && !upgradeTask.value.error) { if (active.value == 'upgrade' && upgradeTask.value && ['upgradeComplete', 'restoreComplete'].includes(upgradeTask.value.step) === false) {
ElMessageBox.confirm( ElMessageBox.confirm(t('upgrade.showDialogCloseTips'), t('warning'), {
t('upgrade.showDialogCloseTips'),
t('warning'),
{
confirmButtonText: t('confirm'), confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'), cancelButtonText: t('cancel'),
type: 'warning' type: 'warning'
} }).then(() => {
).then(() => {
done() done()
}).catch(() => { }) })
} else { } else {
done() done()
} }
} }
watch(() => showDialog.value, () => { watch(
() => showDialog.value,
() => {
if (!showDialog.value) { if (!showDialog.value) {
clearUpgradeTaskFn() clearUpgradeTaskFn()
} }
}) }
)
const clearUpgradeTaskFn = () => { const clearUpgradeTaskFn = () => {
active.value = 'content' active.value = 'upgrade'
uploading.value = false loading.value = false
upgradeTask.value = null upgradeTask.value = null
upgradeLog = [] upgradeLog = []
errorLog = [] errorLog = []
numberOfSteps.value = 0
flashInterval && clearInterval(flashInterval) flashInterval && clearInterval(flashInterval)
clearUpgradeTask().then(() => {}).catch() retrySecondInterval && clearInterval(retrySecondInterval)
isNeedBackup.value = true
step.value = 1
clearUpgradeTask().then(() => {
})
} }
/** /**
* 云编译 * 云编译队列不足操作
* @param event
*/ */
const handleCloudBuild = () => { const cloudBuildError = (event: string) => {
showDialog.value = false cloudBuildErrorTipsShowDialog.value = false
emits('cloudbuild') switch (event) {
case 'local':
upgradeUserOperate(event).then(() => {
getUpgradeTaskFn()
})
break
case 'retry':
executeUpgradeFn()
retrySecondInterval && clearInterval(retrySecondInterval)
break
case 'rollback':
upgradeUserOperate(event).then(() => {
getUpgradeTaskFn()
})
break
}
}
const timeSplit = (str: string) => {
const [date, time] = str.split(' ')
const [hours, minutes] = time.split(':')
return [date, `${hours}:${minutes}`]
} }
const upgradeTipsConfirm = (isLock: boolean = false) => { const upgradeTipsConfirm = (isLock: boolean = false) => {
@ -341,9 +582,19 @@ const upgradeTipsConfirm = (isLock: boolean = false) => {
upgradeTipsShowDialog.value = false upgradeTipsShowDialog.value = false
!isLock && (showDialog.value = true) !isLock && (showDialog.value = true)
} }
const activeName = ref(0)
const numberOfSteps = ref(0)
const toBackupRecord = () => {
const routeUrl = router.resolve({
path: '/tools/backup_records'
})
window.open(routeUrl.href, '_blank')
}
defineExpose({ defineExpose({
open open,
loading
}) })
</script> </script>
@ -351,7 +602,187 @@ defineExpose({
.table-head-bg { .table-head-bg {
background-color: var(--el-table-header-bg-color); background-color: var(--el-table-header-bg-color);
} }
:deep(.terminal .t-log-box span) { :deep(.terminal .t-log-box span) {
white-space: pre-wrap; white-space: pre-wrap;
} }
::v-deep .number-of-steps {
.el-step__line {
margin: 0 25px;
background: #dddddd;
}
.el-step__head {
margin-top: 10px;
}
.is-success {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
.el-step__icon {
background: var(--el-color-primary);
box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
i {
color: #fff;
}
}
.el-step__line {
margin: 0 25px;
background: var(--el-color-primary);
}
}
.is-process {
color: var(--el-color-primary);
font-weight: inherit;
// font-size: 18px;
.el-step__icon {
padding: 10px;
border: 1px solid var(--el-color-primary);
box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
}
}
.is-wait {
color: #333;
}
}
</style>
<style scoped>
.el-timeline-item {
min-height: 100px;
}
.el-timeline-item >>> .el-timeline-item__node--normal {
left: 117px;
width: 18px;
height: 18px;
background: rgba(25, 103, 249, 0.12) !important;
border-radius: 50%;
/* 创建圆形 */
position: relative;
/* 用于定位伪元素 */
}
.el-timeline-item >>> .el-timeline-item__node--normal::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 8px;
/* 中心圆直径 */
height: 8px;
background-color: var(--el-color-primary);
/* 中心圆颜色 */
border-radius: 50%;
/* 中心圆为圆形 */
transform: translate(-50%, -50%);
/* 居中显示 */
}
.el-timeline-item >>> .el-timeline-item__tail {
left: 125px;
border-left-color: #dddddd;
border-left-style: dashed;
margin: 12px 0;
margin-top: 24px;
height: calc(100% - 24px - 12px);
}
.time-dialog-wrap >>> .el-dialog__header {
padding: 10px 20px;
height: 25px;
line-height: 25px;
text-align: left;
background: #fff;
border-bottom: solid 1px #e4e7ed;
}
.time-dialog-wrap >>> .el-card__body {
padding: 8px;
}
.time-dialog-wrap >>> .el-card.is-always-shadow {
box-shadow: none;
}
.time-dialog-wrap >>> .el-dialog__headerbtn .el-dialog__close {
color: #666;
}
.time-dialog-wrap >>> .el-dialog__headerbtn:hover .el-dialog__close {
color: #666;
}
.time-dialog-wrap >>> .el-dialog__headerbtn {
top: 14px;
}
.time-dialog-wrap >>> .el-collapse {
margin-left: 119px;
border: none;
margin-top: -22px;
}
.time-dialog-wrap >>> .el-collapse-item__header {
border: none;
line-height: 25px;
height: 25px;
position: relative;
z-index: 999;
}
.time-dialog-wrap >>> .el-collapse-item__wrap {
border: none;
}
.time-dialog-wrap >>> .el-collapse-item__content {
margin-top: 15px;
padding-bottom: 0 !important;
}
.time-dialog-wrap >>> .el-timeline-item__node--01 {
width: 18px !important;
height: 18px !important;
left: 117px !important;
}
</style>
<style>
.time-dialog-wrap .el-dialog {
margin: 0 auto !important;
max-height: 90%;
overflow: hidden;
top: 5%;
}
.time-dialog-wrap .el-dialog {
margin: 0 auto !important;
height: 65%;
overflow: hidden;
top: 10%;
}
.time-dialog-wrap .el-dialog__body {
position: absolute;
left: 0;
top: 46px;
bottom: 0px;
right: 0;
z-index: 1;
overflow: hidden;
overflow-y: auto;
padding: 10px 20px 0 0;
}
.time-dialog-wrap .el-timeline-item__wrapper {
top: -20px !important;
}
</style> </style>

View File

@ -25,11 +25,9 @@ import { t } from '@/lang'
import type { FormInstance } from 'element-plus' import type { FormInstance } from 'element-plus'
import { deepClone } from '@/utils/common' import { deepClone } from '@/utils/common'
import { getUserInfo, setUserInfo } from '@/app/api/personal' import { getUserInfo, setUserInfo } from '@/app/api/personal'
import { useRouter } from 'vue-router'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter()
// //
const saveInfo: any = reactive({}) const saveInfo: any = reactive({})
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()

View File

@ -1,25 +0,0 @@
{
"todayData": "today's data",
"memberNumb": "number of member",
"numberOfSites": "number of sites",
"numberOfVisitors": "number of visitors",
"commonlyUsedFunction": "commonly used function",
"articleList": "article list",
"memberManagement": "member management",
"balanceAccount": "balance account",
"administrator": "administrator",
"WebDecoration": "website decoration",
"accessMessage": "access message",
"memberDistribution": "membership distribution",
"systemInfo": "system environment",
"os": "os",
"phpVersions": "php version number",
"productionEnvironment": "production environment",
"versionsInfo": "version information",
"versions": "current version",
"frame": "framework based",
"channel": "access channel",
"serviceSupport": "service support",
"officialWbsite": "official website",
"pageView": "page view"
}

View File

@ -0,0 +1,18 @@
{
"companyName": "授权主体",
"siteAddress": "授权域名",
"contactName": "授权联系人",
"authCode": "授权码",
"authSecret": "授权秘钥",
"createTime": "授权时间",
"expireTime": "到期时间",
"authApp": "授权应用",
"authAppKey": "应用标识",
"siteAddressTips": "授权域名不匹配",
"authCodePlaceholder": "请输入授权码",
"authSecretPlaceholder": "请输入授权秘钥",
"updateCode": "重新绑定",
"notHaveAuth": "还没有授权?去购买",
"authInfoTips": "授权码和授权秘钥可在Niucloud官网我的授权 授权详情中查看",
"versionTips": "已经升级到最新版本"
}

View File

@ -50,7 +50,7 @@
"cashOutInfo":"收款方信息", "cashOutInfo":"收款方信息",
"transferCode":"收款码", "transferCode":"收款码",
"realname":"真实姓名", "realname":"真实姓名",
"account":"账号", "account":"收款账号",
"bankRealname":"持卡人姓名", "bankRealname":"持卡人姓名",
"remark":"备注", "remark":"备注",
"remarkPlaceholder":"请输入备注", "remarkPlaceholder":"请输入备注",

View File

@ -1,52 +0,0 @@
{
"dataSummarize": "数据概况",
"todayData": "今日数据",
"memberNumb": "新增会员数",
"orderMoney": "订单金额",
"numberOfVisitors": "今日访客数",
"commonlyUsedFunction": "常用功能",
"articleList": "文章列表",
"memberManagement": "会员管理",
"balanceAccount": "余额账户",
"administrator": "站点用户",
"WebDecoration": "网站装修",
"accessMessage": "访问消息",
"memberDistribution": "会员分布",
"systemInfo": "系统环境",
"os": "操作系统:",
"phpVersions": "PHP版本号:",
"productionEnvironment": "生产环境:",
"versionsInfo": "版本信息",
"versions": "当前版本",
"frame": "基于框架",
"channel": "获取渠道",
"serviceSupport": "官方客服",
"officialWbsite": "官网",
"pageView": "访问量",
"siteInfo":"站点信息",
"siteName":"站点名称",
"groupName":"站点套餐",
"expireTime":"过期时间",
"permanent":"永久",
"statusName":"站点状态",
"newSiteSum": "新增站点数",
"total": "总计",
"newMemberSum": "新增用户数",
"siteList": "站点列表",
"sitePackage": "站点套餐",
"newSite": "新增站点",
"appMarketplace": "应用市场",
"siteDistribution": "站点分布",
"addUser": "新增用户",
"normalSiteSum": "正常站点(个)",
"weekExpireSiteCount":"即将到期站点(个)",
"expireSiteSum": "过期站点(个)",
"noInstallAppSun": "未安装应用(个)",
"installAppSun": "已安装应用(个)",
"officialAccount": "Niucloud官方公众号",
"officialAccountDesc": "微信扫码关注",
"WeCom": "客服二维码",
"WeComDesc": "扫码联系客服",
"tel": "服务热线:",
"newVersion": "最新版本"
}

View File

@ -1,67 +0,0 @@
{
"todayData": "实时概况",
"memberNumb": "新增会员数(人)",
"orderMoney": "订单金额(元)",
"numberOfSites": "站点数量",
"numberOfVisitors": "今日访客数(人)",
"commonlyUsedFunction": "常用功能",
"articleList": "文章列表",
"memberManagement": "会员管理",
"balanceAccount": "余额账户",
"administrator": "管理员",
"WebDecoration": "网站装修",
"accessMessage": "访问消息",
"memberDistribution": "会员分布",
"systemInfo": "系统环境",
"os": "操作系统",
"phpVersions": "PHP版本号",
"productionEnvironment": "生产环境",
"versionsInfo": "版本信息",
"versions": "当前版本",
"frame": "基于框架",
"channel": "获取渠道",
"serviceSupport": "服务支持",
"officialWbsite": "官网",
"pageView": "访客数(人)",
"siteInfo":"站点信息",
"siteName":"站点名称",
"groupName":"站点套餐",
"expireTime":"过期时间",
"permanent":"永久",
"statusName":"站点状态",
"orderNumber": "订单数(笔)",
"wechatCode": "公众号二维码",
"wechatCodeDesc": "微信扫码关注",
"enterpriseWechatCode": "客服二维码",
"enterpriseWechatCodeDesc": "扫码联系客服",
"tel": "服务热线:",
"message": "请联系客服",
"messageTitle": "提示",
"accumulative":"累计",
"officialAccount": "Niucloud官方公众号",
"officialAccountDesc": "微信扫码关注",
"WeCom": "添加企业微信群",
"path": "地址",
"menuName": "名称",
"menuNamePlaceholder": "模版名称",
"menuBgColor": "背景颜色",
"menuImg": "选择图标",
"menuDesc": "描述",
"addShortcutMenu": "添加快捷模版",
"appTemplate": "应用模块",
"siteType": "站点类型",
"periodTime": "有效期",
"renew": "续费",
"selectModel": "选择模块",
"addMenu": "添加模块",
"shortcutLink": "模版",
"emptyMenu": "暂无快捷模块",
"select": "选择",
"custom": "自定义",
"accessSite": "访问站点",
"pathSelect": "选择模块",
"bgColorPlaceholder": "请选择背景色",
"iconPlaceholder": "请选择图标",
"pathPlaceholder": "请选择链接",
"descPlaceholder": "输入描述语…"
}

View File

@ -43,6 +43,7 @@
"versionCode": "版本号", "versionCode": "版本号",
"createTime": "发布时间", "createTime": "发布时间",
"buyLabel": "已购买", "buyLabel": "已购买",
"recentlyUpdated": "最近更新",
"installTips": "安装后需手动更新插件引用的依赖和编译各个端口的前端源码", "installTips": "安装后需手动更新插件引用的依赖和编译各个端口的前端源码",
"localInstall": "本地安装", "localInstall": "本地安装",
"cloudInstall": "一键云安装", "cloudInstall": "一键云安装",
@ -58,6 +59,7 @@
"link": "官方应用市场", "link": "官方应用市场",
"descriptionRight": "逛逛", "descriptionRight": "逛逛",
"installed-empty": "暂未安装任何应用", "installed-empty": "暂未安装任何应用",
"recentlyUpdatedEmpty": "暂无最近更新应用",
"siteAddressTips": "授权域名不匹配", "siteAddressTips": "授权域名不匹配",
"authCodePlaceholder": "请输入授权码", "authCodePlaceholder": "请输入授权码",
"authSecretPlaceholder": "请输入授权秘钥", "authSecretPlaceholder": "请输入授权秘钥",
@ -68,9 +70,10 @@
"appIdentification": "应用标识", "appIdentification": "应用标识",
"tipText": "标识指开发应用或插件的文件夹名称", "tipText": "标识指开发应用或插件的文件夹名称",
"uninstallTips": "是否要卸载该插件?", "uninstallTips": "是否要卸载该插件?",
"upgrade": "升级", "upgrade": "一键升级",
"newVersion": "最新版本", "newVersion": "最新版本",
"cloudBuild": "云编译", "cloudBuild": "云编译",
"cloudBuildTips": "是否要进行云编译该操作可能会影响到正在访问的客户是否要继续操作?", "cloudBuildTips": "是否要进行云编译该操作可能会影响到正在访问的客户是否要继续操作?",
"deleteAddonTips": "删除插件会把插件目录连同文件全部删除,确定要删除吗?" "deleteAddonTips": "删除插件会把插件目录连同文件全部删除,确定要删除吗?",
"batchUpgrade": "批量升级"
} }

View File

@ -3,7 +3,6 @@
"logging": "登录中", "logging": "登录中",
"platform": "管理端", "platform": "管理端",
"login" : "登录", "login" : "登录",
"siteLogin": "站点登录",
"adminLogin": "平台登录", "adminLogin": "平台登录",
"userPlaceholder": "请输入您的账号", "userPlaceholder": "请输入您的账号",
"passwordPlaceholder": "请输入您的密码", "passwordPlaceholder": "请输入您的密码",

View File

@ -29,6 +29,7 @@
"continueSign": "连签天数", "continueSign": "连签天数",
"continueSignFormatError": "连签天数格式错误", "continueSignFormatError": "连签天数格式错误",
"continueSignBerweenDays": "连签天数为2-365天", "continueSignBerweenDays": "连签天数为2-365天",
"continueSignMustLessThanSignPeriod": "连签天数不能大于签到周期",
"receiveLimit": "领取限制", "receiveLimit": "领取限制",
"noLimit": "不限制", "noLimit": "不限制",
"everyOneLimit": "每人限领", "everyOneLimit": "每人限领",

View File

@ -15,7 +15,7 @@
"searchValueEmptyTips": "请输入搜索内容", "searchValueEmptyTips": "请输入搜索内容",
"verify": "核销", "verify": "核销",
"buyInfo": "预订信息", "buyInfo": "预订信息",
"orderRefunding": "该订单正在维权中不能进行核销", "orderRefunding": "该订单正在售后中不能进行核销",
"verifyTips": "是否要核销该订单?", "verifyTips": "是否要核销该订单?",
"toOrder": "查看订单", "toOrder": "查看订单",
"verifyType": "核销类型", "verifyType": "核销类型",
@ -27,5 +27,5 @@
"memberInfo": "会员信息", "memberInfo": "会员信息",
"memberIdPlaceholder": "请选择会员", "memberIdPlaceholder": "请选择会员",
"member": "会员", "member": "会员",
"searchPlaceholder": "请输入会员昵称搜索" "searchPlaceholder": "请输入会员编号/昵称/手机号"
} }

View File

@ -15,7 +15,7 @@
"searchValueEmptyTips": "请输入搜索内容", "searchValueEmptyTips": "请输入搜索内容",
"verify": "核销", "verify": "核销",
"buyInfo": "预订信息", "buyInfo": "预订信息",
"orderRefunding": "该订单正在维权中不能进行核销", "orderRefunding": "该订单正在售后中不能进行核销",
"verifyTips": "是否要核销该订单?", "verifyTips": "是否要核销该订单?",
"toOrder": "查看订单", "toOrder": "查看订单",
"verifyType": "核销类型", "verifyType": "核销类型",

View File

@ -3,6 +3,6 @@
"type": "协议类型", "type": "协议类型",
"titlePlaceholder": "请输入协议标题", "titlePlaceholder": "请输入协议标题",
"contentPlaceholder": "请填写协议内容", "contentPlaceholder": "请填写协议内容",
"contentMaxTips": "协议内容字符数应在5100000之间", "contentMaxTips": "协议内容字符数应在510000之间",
"content": "内容" "content": "内容"
} }

View File

@ -27,6 +27,8 @@
"appPublicCertPathTips": "上传appCertPublicKey文件", "appPublicCertPathTips": "上传appCertPublicKey文件",
"alipayPublicCertPathTips": "上传alipayCertPublicKey文件", "alipayPublicCertPathTips": "上传alipayCertPublicKey文件",
"alipayRootCertPathTips": "上传alipayRootCert文件", "alipayRootCertPathTips": "上传alipayRootCert文件",
"operationTip": "温馨提示:打款设置用于会员提现转账,发放红包等场景" "operationTip": "温馨提示:打款设置用于会员提现转账,发放红包等场景",
"transferTips":"注意:应微信方规定,在2025年1月15日前开通商家转账到零钱服务的商户号可正常使用转账功能,之后开通的不支持使用转账到零钱服务",
"wechatpayPublicCert": "微信支付公钥",
"wechatpayPublicCertId": "微信支付公钥ID"
} }

View File

@ -0,0 +1,28 @@
{
"manualBackup": "手动备份",
"manualBackupTips": "即将开始手动备份您的源码和数据库,为确保备份过程中顺利进行以及数据的完整性,建议您暂停所有相关操作,避免因数据写入或修改导致备份不一致,是否确定继续?",
"id": "编号",
"content": "内容",
"currentVersion": "备份版本",
"contentPlaceholder": "请输入内容",
"batchDelete": "批量删除",
"backupDir": "备份路径",
"completeTime": "备份时间",
"restore": "恢复",
"restoreTips": "此操作将恢复备份的源码和数据库,并且覆盖当前数据。为避免意外损失,请确认已了解此操作的影响,并确保已备份重要信息,是否确定继续?",
"deleteTips": "删除记录将会同步删除其备份文件,确定要操作吗?",
"batchEmptySelectedTips": "请选择需要批量删除的记录",
"restoreTitle": "恢复备份",
"backupCompleteTips": "备份成功",
"restoreCompleteTips": "恢复成功",
"showDialogCloseTips": "任务尚未完成,关闭将会造成数据丢失或系统损坏的影响,是否要继续关闭?",
"manualBackupTitle": "手动备份",
"checkDirectoryPermissions": "检测目录权限",
"backupFiles": "备份文件",
"startUpgrade": "开始恢复",
"upgradeEnd": "恢复完成",
"startBackUp": "开始备份",
"backUpEnd": "备份完成",
"remark": "备注",
"remarkEmpty": "无"
}

View File

@ -0,0 +1,12 @@
{
"id": "编号",
"upgradeName": "内容",
"prevVersion": "前一版本",
"currentVersion": "版本",
"upgradeNamePlaceholder": "请输入内容",
"completeTime": "升级时间",
"status": "状态",
"failReason": "失败原因",
"batchDelete": "批量删除",
"deleteTips": "确定要删除吗?"
}

View File

@ -2,47 +2,31 @@
<!--授权信息--> <!--授权信息-->
<div class="main-container"> <div class="main-container">
<el-card class="box-card !border-none" shadow="never" v-if="!loading"> <el-card class="box-card !border-none" shadow="never" v-if="!loading">
<div>
<div class="flex"> <div class="text-[#333] text-[18px]">授权信息</div>
<div class="w-[450px] mr-[20px] p-[50px] bg-[var(--el-color-info-light-9)]"> <div class="ml-[50px] mt-[40px]">
<div class="flex items-center justify-between">
<span class="text-page-title">版本信息</span>
<div class="flex-1 w-0 flex justify-end">
<el-button class="text-[#4C4C4C] w-[78px] h-[32px] !bg-transparent" @click="getFrameworkVersionListFn" v-if="!newVersion || (newVersion && newVersion.version_no == versions)">检测更新</el-button>
<el-button class="text-[#4C4C4C] w-[78px] h-[32px]" type="primary" @click="handleUpgrade" v-else>一键升级</el-button>
<el-button class="text-[#4C4C4C] w-[78px] h-[32px]" type="primary" @click="handleCloudBuild" :loading="cloudBuildRef?.loading">云编译</el-button>
</div>
</div>
<div class="mt-[30px] flex items-center text-[14px] text-[#797979]">
<span>当前版本</span>
<span class="text-[26px] ml-[15px] mr-[10px] text-[#656668]">{{versions}}</span>
<em class="text-[12px]" v-if="!newVersion || (newVersion && newVersion.version_no == versions)">(当前已是最新版本)</em>
<em class="text-[12px] text-[red]" v-else>(最新版本{{ newVersion.version_no }})</em>
</div>
</div>
<div class="flex flex-1 justify-between items-center p-[50px] bg-[var(--el-color-info-light-9)]">
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex flex-wrap items-center"> <div class="flex flex-wrap items-center">
<p class="text-page-title mr-[20px]">授权信息</p> <span class="mr-[6px] text-[14px] text-[#666666] w-[70px] text-right">授权公司</span>
<span class="text-[14px] text-[#666]">{{ authinfo.company_name || '--' }}</span> <span class="text-[14px] text-[#333]">{{ authinfo.company_name || "--" }}</span>
</div> </div>
<div class="mt-[46px] ml-[40px] flex flex-wrap"> <div class="flex flex-wrap items-center mt-[20px]">
<span class="text-[14px] mr-[84px]">授权域名<em class="ml-[12px] text-[12px]">{{ authinfo.site_address || '--' }}</em></span> <span class="mr-[6px] text-[14px] text-[#666666] w-[70px] text-right">授权域名</span>
<span class="text-[14px] flex items-center"> <span class="text-[14px] text-[#333]">{{ authinfo.site_address || "--" }}</span>
<span>授权码</span> </div>
<em class="ml-[12px] mr-[10px] text-[12px]">{{ authinfo.auth_code ? (isCheck ? authinfo.auth_code : hideAuthCode(authinfo.auth_code)) : '--' }}</em> <div class="flex flex-wrap items-center mt-[20px]">
<el-icon v-if="!isCheck" @click="isCheck = !isCheck" class="text-[12px] cursor-pointer"> <span class="mr-[6px] text-[14px] text-[#666666] w-[70px] text-right">授权码</span>
<span class="text-[14px] text-[#333]">
<span class="mr-[10px]">{{ authinfo.auth_code ? (isCheck ? authinfo.auth_code : hideAuthCode(authinfo.auth_code)) : "--" }}</span>
<el-icon v-if="!isCheck" @click="isCheck = !isCheck" class="text-[12px] cursor-pointer text-[#4383F9]">
<View /> <View />
</el-icon> </el-icon>
<el-icon v-else @click="isCheck = !isCheck" class="text-[12px] cursor-pointer"> <el-icon v-else @click="isCheck = !isCheck" class="text-[12px] cursor-pointer text-[#4383F9]"> <Hide /> </el-icon>
<Hide />
</el-icon>
</span> </span>
</div> </div>
</div> </div>
<div class="flex flex-1 flex-wrap justify-end relative"> <div class="mt-[60px] mb-[50px]">
<el-button class="w-[154px] !h-[48px] mt-[8px]" type="primary" @click="authCodeApproveFn">授权码认证</el-button> <el-button class="w-[150px] !h-[46px] mt-[8px]" type="primary" @click="authCodeApproveFn">授权码认证</el-button>
<el-popover ref="getAuthCodeDialog" placement="bottom-start" :width="478" trigger="click" class="mt-[8px]"> <el-popover ref="getAuthCodeDialog" placement="bottom-start" :width="478" trigger="click" class="mt-[8px]">
<div class="px-[18px] py-[8px]"> <div class="px-[18px] py-[8px]">
<p class="leading-[32px] text-[14px]">您在官方应用市场购买任意一款应用即可获得授权码输入正确授权码认证通过后即可支持在线升级和其它相关服务</p> <p class="leading-[32px] text-[14px]">您在官方应用市场购买任意一款应用即可获得授权码输入正确授权码认证通过后即可支持在线升级和其它相关服务</p>
@ -52,7 +36,7 @@
</div> </div>
</div> </div>
<template #reference> <template #reference>
<el-button class="w-[154px] !h-[48px] mt-[8px] !text-[var(--el-color-primary)] hover:!text-[var(--el-color-primary)] !bg-transparent" plain type="primary">如何获取授权码?</el-button> <el-button class="w-[150px] !h-[46px] mt-[8px] !text-[var(--el-color-primary)] hover:!text-[var(--el-color-primary)] !bg-transparent" plain type="primary">如何获取授权码?</el-button>
</template> </template>
</el-popover> </el-popover>
</div> </div>
@ -70,13 +54,13 @@
</el-form-item> </el-form-item>
</div> </div>
<div class="text-sm mt-[10px] text-info">{{ t('authInfoTips') }}</div> <div class="text-sm mt-[10px] text-info">{{ t("authInfoTips") }}</div>
<div class="mt-[20px]"> <div class="mt-[20px]">
<el-button type="primary" class="w-full" size="large" :loading="saveLoading" @click="save(formRef)">{{ t('confirm') }}</el-button> <el-button type="primary" class="w-full" size="large" :loading="saveLoading" @click="save(formRef)">{{ t("confirm") }}</el-button>
</div> </div>
<div class="mt-[10px] text-right"> <div class="mt-[10px] text-right">
<el-button type="primary" link @click="market">{{ t('notHaveAuth') }}</el-button> <el-button type="primary" link @click="market">{{ t("notHaveAuth") }}</el-button>
</div> </div>
</el-card> </el-card>
</el-form> </el-form>
@ -84,73 +68,24 @@
</div> </div>
</div> </div>
</el-card> </el-card>
<el-card class="box-card !border-none " shadow="never" v-if="!loading">
<div class="text-page-title mb-[20px]">历史版本</div>
<el-timeline>
<el-timeline-item :timestamp="item['release_time'] + ' 版本:' + item['version_no']" v-for="(item,index) in frameworkVersionList" type="primary" :hollow="true" placement="top" :key="index">
<div class="mt-[10px] p-[20px] bg-overlay rounded-md timeline-log-wrap whitespace-pre-wrap" v-if="item['upgrade_log']">
<div v-html="item['upgrade_log']"></div>
</div>
</el-timeline-item>
</el-timeline>
</el-card>
<upgrade ref="upgradeRef" />
<cloud-build ref="cloudBuildRef" />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, computed } from 'vue' import { reactive, ref } from "vue"
import { t } from '@/lang' import { t } from "@/lang"
import { getVersions } from '@/app/api/auth' import { getVersions } from "@/app/api/auth"
import { getInstallConfig } from '@/app/api/sys' import { getAuthInfo, setAuthInfo } from "@/app/api/module"
import { getAuthInfo, setAuthInfo, getFrameworkVersionList } from '@/app/api/module' import { FormInstance, FormRules } from "element-plus"
import { ElMessageBox, FormInstance, FormRules, ElMessage } from 'element-plus' import { cloneDeep } from "lodash-es"
import Upgrade from '@/app/components/upgrade/index.vue'
import CloudBuild from '@/app/components/cloud-build/index.vue'
import { cloneDeep } from 'lodash-es'
const upgradeRef = ref<any>(null)
const cloudBuildRef = ref<any>(null)
const getAuthCodeDialog: Record<string, any> | null = ref(null) const getAuthCodeDialog: Record<string, any> | null = ref(null)
const authCodeApproveDialog = ref(false) const authCodeApproveDialog = ref(false)
const isCheck = ref(false) const isCheck = ref(false)
const frameworkVersionList = ref([])
const installPhpConfig = ref(null)
getInstallConfig().then(({ data }) => {
installPhpConfig.value = data
}).catch()
const checkVersion = ref(false)
const getFrameworkVersionListFn = () => {
getFrameworkVersionList().then(({ data }) => {
frameworkVersionList.value = data
if (checkVersion.value) {
if (!newVersion.value || (newVersion.value && newVersion.value.version_no == versions.value)) {
ElMessage({
message: t('versionTips'),
type: 'success'
})
}
} else {
checkVersion.value = true
}
})
}
getFrameworkVersionListFn()
const newVersion:any = computed(() => {
return frameworkVersionList.value.length ? frameworkVersionList.value[0] : null
})
const hideAuthCode = (res: any) => { const hideAuthCode = (res: any) => {
const authCode = cloneDeep(res) const authCode = cloneDeep(res)
const data = authCode.slice(0, authCode.length / 2) + authCode.slice(authCode.length / 2, authCode.length - 1).replace(/./g, '*') const data = authCode.slice(0, authCode.length / 2) + authCode.slice(authCode.length / 2, authCode.length - 1).replace(/./g, "*")
return data return data
} }
@ -159,15 +94,15 @@ const authCodeApproveFn = () => {
} }
interface AuthInfo { interface AuthInfo {
company_name: string, company_name: string
site_address: string, site_address: string
auth_code: string auth_code: string
} }
const authinfo = ref<AuthInfo>({ const authinfo = ref<AuthInfo>({
company_name: '', company_name: "",
site_address: '', site_address: "",
auth_code: '' auth_code: ""
}) })
const loading = ref(true) const loading = ref(true)
const saveLoading = ref(false) const saveLoading = ref(false)
@ -187,19 +122,15 @@ const checkAppMange = () => {
checkAppMange() checkAppMange()
const formData = reactive<Record<string, string>>({ const formData = reactive<Record<string, string>>({
auth_code: '', auth_code: "",
auth_secret: '' auth_secret: ""
}) })
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
// //
const formRules = reactive<FormRules>({ const formRules = reactive<FormRules>({
auth_code: [ auth_code: [{ required: true, message: t("authCodePlaceholder"), trigger: "blur" }],
{ required: true, message: t('authCodePlaceholder'), trigger: 'blur' } auth_secret: [{ required: true, message: t("authSecretPlaceholder"), trigger: "blur" }]
],
auth_secret: [
{ required: true, message: t('authSecretPlaceholder'), trigger: 'blur' }
]
}) })
const save = async(formEl: FormInstance | undefined) => { const save = async(formEl: FormInstance | undefined) => {
@ -221,47 +152,16 @@ const save = async (formEl: FormInstance | undefined) => {
} }
const market = () => { const market = () => {
window.open(installPhpConfig.value?.website_url) window.open("https://www.niucloud.com/app")
} }
const versions = ref('') const versions = ref("")
const getVersionsInfo = () => { const getVersionsInfo = () => {
getVersions().then(res => { getVersions().then((res) => {
versions.value = res.data.version.version versions.value = res.data.version.version
}) })
} }
getVersionsInfo() getVersionsInfo()
/**
* 升级
*/
const handleUpgrade = () => {
if (!authinfo.value.auth_code) {
authCodeApproveFn()
return
}
upgradeRef.value?.open()
}
const handleCloudBuild = () => {
if (!authinfo.value.auth_code) {
authCodeApproveFn()
return
}
if (cloudBuildRef.value.cloudBuildTask) {
cloudBuildRef.value?.open()
return
}
ElMessageBox.confirm(t('cloudBuildTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
cloudBuildRef.value?.open()
})
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -59,10 +59,6 @@ const appList = ref<Record<string, any>[]>([])
const loading = ref(true) const loading = ref(true)
const getAppList = async () => { const getAppList = async () => {
// const res = await getSiteAddons()
// appList.value = res.data
// loading.value = false
const res = await getShowApp() const res = await getShowApp()
appList.value = res.data appList.value = res.data
loading.value = false loading.value = false
@ -70,7 +66,6 @@ const getAppList = async () => {
getAppList() getAppList()
const toLink = (item: any) => { const toLink = (item: any) => {
console.log('tol', item)
if (item.url) { if (item.url) {
router.push(item.url) router.push(item.url)
} else { } else {

View File

@ -0,0 +1,195 @@
<template>
<!--授权信息-->
<div class="main-container">
<el-card class="box-card !border-none" shadow="never" v-if="newVersion">
<div>
<div class="mx-[20px] my-[20px]">
<div class="title text-[18px]">版本信息</div>
<div class="text-[18px] text-center mb-[7px] mt-[40px]">系统当前版本v{{ version }}{{ versionCode }}</div>
<div class="text-center text-[#666] text-[14px]" v-if="!newVersion || (newVersion && newVersion.version_no == version)">
<span>当前已是最新版本无需升级</span>
<span class="text-[14px] text-primary ml-[10px] cursor-pointer" @click="openUpgrade">更新说明</span>
</div>
<div class="text-[#666] text-[14px] text-center" v-else>
当前系统最新版本为 <span class="text-[18px] text-[#FF4D01]">v{{ newVersion.version_no }}</span>
<span class="text-[14px] text-primary ml-[10px]" style="cursor: pointer" @click="openUpgrade">更新说明</span>
</div>
<div class="mt-[30px] flex justify-center items-center">
<el-button class="text-[#4C4C4C] w-[150px] !h-[44px]" type="primary" :loading="loading" @click="handleUpgrade" v-if="!(!newVersion || (newVersion && newVersion.version_no == version))">一键升级</el-button>
<el-button class="text-[#4C4C4C] w-[130px] !h-[44px]" @click="upgradeRecord">升级记录</el-button>
</div>
</div>
</div>
</el-card>
<el-dialog v-model="authCodeApproveDialog" title="授权码认证" width="400px">
<el-form :model="formData" label-width="0" ref="formRef" :rules="formRules" class="page-form">
<el-card class="box-card !border-none" shadow="never">
<el-form-item prop="auth_code">
<el-input v-model.trim="formData.auth_code" :placeholder="t('authCodePlaceholder')" class="input-width" clearable size="large" />
</el-form-item>
<div class="mt-[20px]">
<el-form-item prop="auth_secret">
<el-input v-model.trim="formData.auth_secret" clearable :placeholder="t('authSecretPlaceholder')" class="input-width" size="large" />
</el-form-item>
</div>
<div class="text-sm mt-[10px] text-info">{{ t("authInfoTips") }}</div>
<div class="mt-[20px]">
<el-button type="primary" class="w-full" size="large" :loading="saveLoading" @click="save(formRef)">{{ t("confirm") }}</el-button>
</div>
<div class="mt-[10px] text-right">
<el-button type="primary" link @click="market">{{ t("notHaveAuth") }}</el-button>
</div>
</el-card>
</el-form>
</el-dialog>
<upgrade ref="upgradeRef" />
<upgrade-log ref="upgradeLogRef" />
</div>
</template>
<script lang="ts" setup>
import { ref, computed, reactive } from "vue"
import { t } from "@/lang"
import { getVersions } from "@/app/api/auth"
import { getAuthInfo, getFrameworkVersionList, setAuthInfo } from "@/app/api/module"
import { ElMessage, FormInstance, FormRules } from "element-plus"
import { useRouter } from "vue-router"
import Upgrade from "@/app/components/upgrade/index.vue"
import UpgradeLog from "@/app/components/upgrade-log/index.vue"
const upgradeRef = ref<any>(null)
const upgradeLogRef = ref<any>(null)
const authCodeApproveDialog = ref(false)
const frameworkVersionList = ref([])
const checkVersion = ref(false)
const formData = reactive<Record<string, string>>({
auth_code: '',
auth_secret: ''
})
const formRef = ref<FormInstance>()
//
const formRules = reactive<FormRules>({
auth_code: [{ required: true, message: t('authCodePlaceholder'), trigger: 'blur' }],
auth_secret: [{ required: true, message: t('authSecretPlaceholder'), trigger: 'blur' }]
})
const saveLoading = ref(false)
const save = async(formEl: FormInstance | undefined) => {
if (saveLoading.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
saveLoading.value = true
setAuthInfo(formData).then(() => {
saveLoading.value = false
checkAppMange()
}).catch(() => {
saveLoading.value = false
authCodeApproveDialog.value = false
})
}
})
}
const getFrameworkVersionListFn = () => {
getFrameworkVersionList().then(({ data }) => {
frameworkVersionList.value = data
if (checkVersion.value) {
if (!newVersion.value || (newVersion.value && newVersion.value.version_no == version.value)) {
ElMessage({
message: t('versionTips'),
type: 'success'
})
}
} else {
checkVersion.value = true
}
})
}
getFrameworkVersionListFn()
const newVersion: any = computed(() => {
return frameworkVersionList.value.length ? frameworkVersionList.value[0] : null
})
const authCodeApproveFn = () => {
authCodeApproveDialog.value = true
}
const version = ref('')
const versionCode = ref('')
const getVersionsInfo = () => {
getVersions().then((res) => {
version.value = res.data.version.version
versionCode.value = res.data.version.code
})
}
getVersionsInfo()
interface AuthInfo {
company_name: string
site_address: string
auth_code: string
}
const authInfo = ref<AuthInfo>({
company_name: '',
site_address: '',
auth_code: ''
})
const repeat = ref(false)
const loading = ref(false)
/**
* 升级
*/
const handleUpgrade = () => {
if (!authInfo.value.auth_code) {
authCodeApproveFn()
return
}
if (repeat.value) return
repeat.value = true
loading.value = true
upgradeRef.value?.open('', () => {
repeat.value = false
loading.value = false;
})
}
const checkAppMange = () => {
getAuthInfo().then((res) => {
if (res.data.data && res.data.data.length != 0) {
authInfo.value = res.data.data
authCodeApproveDialog.value = false
}
}).catch(() => {
authCodeApproveDialog.value = false
})
}
checkAppMange()
const router = useRouter()
const upgradeRecord = () => {
router.push('/admin/tools/upgrade_records')
}
const openUpgrade = () => {
upgradeLogRef.value?.open()
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,5 +1,5 @@
<template> <template>
<!--站点菜单--> <!--平台菜单-->
<div class="main-container"> <div class="main-container">
<el-card class="box-card !border-none" shadow="never"> <el-card class="box-card !border-none" shadow="never">

View File

@ -102,7 +102,6 @@ const router = useRouter()
const pageName = route.meta.title const pageName = route.meta.title
const activeName = ref('/channel/aliapp') const activeName = ref('/channel/aliapp')
const active = ref(2)
const qrCode = ref<string>('') const qrCode = ref<string>('')
onMounted(async () => { onMounted(async () => {
const res = await getAliappConfig() const res = await getAliappConfig()

View File

@ -24,11 +24,7 @@
</div> </div>
<div class="py-[20px] px-[30px] h-[350px]"> <div class="py-[20px] px-[30px] h-[350px]">
<div v-if="formData.msgtype == 'text'"> <div v-if="formData.msgtype == 'text'">
<el-input <el-input v-model.trim="formData.text.content" :rows="5" type="textarea" placeholder="" maxlength="600" :show-word-limit="true" resize="none" input-style="box-shadow: none;height:300px" />
v-model.trim="formData.text.content" :rows="5" type="textarea" placeholder="" maxlength="600" :show-word-limit="true"
resize="none"
input-style="box-shadow: none;height:300px"
/>
</div> </div>
<div v-if="formData.msgtype == 'image'" class="flex w-full h-full justify-center items-center image-media"> <div v-if="formData.msgtype == 'image'" class="flex w-full h-full justify-center items-center image-media">
<div class="w-full h-full" v-if="formData.image.url"> <div class="w-full h-full" v-if="formData.image.url">

View File

@ -14,7 +14,9 @@
</upload-media> </upload-media>
</div> </div>
<div class="flex" v-else> <div class="flex" v-else>
<el-button type="primary" :loading="syncLoading" @click="syncWechatNews">{{ syncLoading ? '同步中' : '同步微信图文'}}</el-button> <el-button type="primary" :loading="syncLoading" @click="syncWechatNews">
{{ syncLoading ? '同步中' : '同步微信图文' }}
</el-button>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
@ -23,8 +25,10 @@
<!-- 素材管理 --> <!-- 素材管理 -->
<div v-if="attachment.data.length"> <div v-if="attachment.data.length">
<div class="flex flex-wrap" v-if="prop.type != 'news'"> <div class="flex flex-wrap" v-if="prop.type != 'news'">
<div class="attachment-item mr-[10px] mb-[10px] w-[120px]" v-for="(item, index) in attachment.data" :key="index" @click="selectedFile = item"> <div class="attachment-item mr-[10px] mb-[10px] w-[120px]"
<div class="attachment-wrap w-full rounded cursor-pointer overflow-hidden relative flex items-center justify-center h-[120px]"> v-for="(item, index) in attachment.data" :key="index" @click="selectedFile = item">
<div
class="attachment-wrap w-full rounded cursor-pointer overflow-hidden relative flex items-center justify-center h-[120px]">
<el-image :src="img(item.value)" fit="contain" v-if="type == 'image'" :preview-src-list="item.image_list" /> <el-image :src="img(item.value)" fit="contain" v-if="type == 'image'" :preview-src-list="item.image_list" />
<video :src="img(item.value)" v-else-if="type == 'video'"></video> <video :src="img(item.value)" v-else-if="type == 'video'"></video>
<div class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60" v-show="selectedFile.id == item.id"> <div class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60" v-show="selectedFile.id == item.id">
@ -34,7 +38,9 @@
</div> </div>
</div> </div>
<div class="relative" ref="waterfallContainerRef" v-else> <div class="relative" ref="waterfallContainerRef" v-else>
<div ref="waterfallItemRef" class="absolute attachment-item mr-[10px] mb-[10px] w-[280px] rounded-lg overflow-hidden border border-color" v-for="(item, index) in attachment.data" <div ref="waterfallItemRef"
class="absolute attachment-item mr-[10px] mb-[10px] w-[280px] rounded-lg overflow-hidden border border-color"
v-for="(item, index) in attachment.data"
:style="{ left: listPosition[index] ? listPosition[index].left : '', top: listPosition[index] ? listPosition[index].top : '' }" :style="{ left: listPosition[index] ? listPosition[index].left : '', top: listPosition[index] ? listPosition[index].top : '' }"
:key="index" @click="selectedFile = item"> :key="index" @click="selectedFile = item">
<div class="relative"> <div class="relative">
@ -47,18 +53,14 @@
<div v-if="item.value.news_item.length > 1"> <div v-if="item.value.news_item.length > 1">
<template v-for="(newsItem, newsIndex) in item.value.news_item"> <template v-for="(newsItem, newsIndex) in item.value.news_item">
<div class="px-[15px] py-[10px] flex" :class="{'border-b border-color' : newsIndex < item.value.news_item.length - 1 }" v-if="newsIndex > 0"> <div class="px-[15px] py-[10px] flex" :class="{'border-b border-color' : newsIndex < item.value.news_item.length - 1 }" v-if="newsIndex > 0">
<div class="flex-1 w-0 truncate"> <div class="flex-1 w-0 truncate">{{ newsItem.title }}</div>
{{ newsItem.title }}
</div>
<div class="w-[50px] h-[50px] ml-[10px]"> <div class="w-[50px] h-[50px] ml-[10px]">
<el-image :src="newsItem.thumb_url" class="w-full h-full" /> <el-image :src="newsItem.thumb_url" class="w-full h-full" />
</div> </div>
</div> </div>
</template> </template>
</div> </div>
<div class="px-[15px] py-[10px]" v-else> <div class="px-[15px] py-[10px]" v-else>{{ item.value.news_item[0].title }}</div>
{{ item.value.news_item[0].title }}
</div>
<div class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60" v-show="selectedFile.id == item.id"> <div class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60" v-show="selectedFile.id == item.id">
<icon name="element Select" color="#fff" size="40px" /> <icon name="element Select" color="#fff" size="40px" />
</div> </div>

View File

@ -83,8 +83,7 @@
<el-form-item :label="t('businessDomain')"> <el-form-item :label="t('businessDomain')">
<el-input :model-value="wechatStatic.business_domain" placeholder="Please input" class="input-width" :readonly="true"> <el-input :model-value="wechatStatic.business_domain" placeholder="Please input" class="input-width" :readonly="true">
<template #append> <template #append>
<div class="cursor-pointer" @click="copyEvent(wechatStatic.business_domain)">{{ t('copy') }} <div class="cursor-pointer" @click="copyEvent(wechatStatic.business_domain)">{{ t('copy') }}</div>
</div>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@ -92,8 +91,7 @@
<el-form-item :label="t('jsSecureDomain')"> <el-form-item :label="t('jsSecureDomain')">
<el-input :model-value="wechatStatic.js_secure_domain" placeholder="Please input" class="input-width" :readonly="true"> <el-input :model-value="wechatStatic.js_secure_domain" placeholder="Please input" class="input-width" :readonly="true">
<template #append> <template #append>
<div class="cursor-pointer" @click="copyEvent(wechatStatic.business_domain)">{{ t('copy') }} <div class="cursor-pointer" @click="copyEvent(wechatStatic.business_domain)">{{ t('copy') }}</div>
</div>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@ -101,8 +99,7 @@
<el-form-item :label="t('webAuthDomain')"> <el-form-item :label="t('webAuthDomain')">
<el-input :model-value="wechatStatic.web_auth_domain" placeholder="Please input" class="input-width" :readonly="true"> <el-input :model-value="wechatStatic.web_auth_domain" placeholder="Please input" class="input-width" :readonly="true">
<template #append> <template #append>
<div class="cursor-pointer" @click="copyEvent(wechatStatic.business_domain)">{{ t('copy') }} <div class="cursor-pointer" @click="copyEvent(wechatStatic.business_domain)">{{ t('copy') }}</div>
</div>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>

View File

@ -12,9 +12,7 @@
<span class="flex justify-center items-center block w-[40px] h-[40px] border-[1px] border-primary rounded-[999px] text-primary">1</span> <span class="flex justify-center items-center block w-[40px] h-[40px] border-[1px] border-primary rounded-[999px] text-primary">1</span>
</div> </div>
<div> <div>
<p class="flex items-center text-[14px]">{{ t('writingTipsOne1') }}--<el-button link type="primary" <p class="flex items-center text-[14px]">{{ t('writingTipsOne1') }}--<el-button link type="primary" @click="linkEvent">{{ t('writingTipsOne2') }}</el-button>, {{ t('writingTipsOne3') }}<span class="text-primary">URL / Token / EncondingAESKey</span>{{ t('writingTipsOne4') }}</p>
@click="linkEvent">{{ t('writingTipsOne2') }}</el-button>, {{ t('writingTipsOne3') }}<span
class="text-primary">URL / Token / EncondingAESKey</span>{{ t('writingTipsOne4') }}</p>
<div class="w-[100%] mt-[10px]"> <div class="w-[100%] mt-[10px]">
<img class="w-[100%]" src="@/app/assets/images/setting/wechat_1.png" /> <img class="w-[100%]" src="@/app/assets/images/setting/wechat_1.png" />
</div> </div>

View File

@ -83,7 +83,7 @@
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getKeywordsReplyInfo, editKeywordsReply, addKeywordsReply } from '@/app/api/wechat' import { getKeywordsReplyInfo, editKeywordsReply, addKeywordsReply } from '@/app/api/wechat'
import { ElMessage, FormInstance, FormRules } from 'element-plus' import { FormInstance, FormRules } from 'element-plus'
import { ArrowLeft } from '@element-plus/icons-vue' import { ArrowLeft } from '@element-plus/icons-vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import ReplyForm from '@/app/views/channel/wechat/components/reply-form.vue' import ReplyForm from '@/app/views/channel/wechat/components/reply-form.vue'

View File

@ -110,60 +110,6 @@ const setFormData = async (row: any = null) => {
loading.value = false loading.value = false
} }
//
const mobileVerify = (rule: any, value: any, callback: any) => {
if (value && !/^1[3-9]\d{9}$/.test(value)) {
callback(new Error(t('generateMobile')))
} else {
callback()
}
}
//
const idCardVerify = (rule: any, value: any, callback: any) => {
if (value && !/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value)) {
callback(new Error(t('generateIdCard')))
} else {
callback()
}
}
//
const emailVerify = (rule: any, value: any, callback: any) => {
if (value && !/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)) {
callback(new Error(t('generateEmail')))
} else {
callback()
}
}
// 1
const minInputVerify = (rule: any, value: any, callback: any) => {
if (value && !/^\d{0,}$/.test(value)) {
callback(new Error(t('generateMin')))
} else {
callback()
}
}
// 150
const maxInputVerify = (rule: any, value: any, callback: any) => {
if (value && !/^\d{0,150}$/.test(value)) {
callback(new Error(t('generateMax')))
} else {
callback()
}
}
//
const numberVerify = (rule: any, value: any, callback: any) => {
if (!Number.isInteger(value)) {
callback(new Error(t('generateNumber')))
} else {
callback()
}
}
defineExpose({ defineExpose({
showDialog, showDialog,
setFormData setFormData

View File

@ -27,10 +27,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive } from 'vue' import { ref, reactive, computed } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import type { FormInstance } from 'element-plus' import type { FormInstance } from 'element-plus'
import { FormRules } from 'element-plus'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import useDiyStore from '@/stores/modules/diy' import useDiyStore from '@/stores/modules/diy'
const diyStore = useDiyStore() const diyStore = useDiyStore()
@ -70,12 +69,23 @@ const open = (option:any) => {
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
// //
const formRules = reactive<FormRules>({ const formRules = computed(() => {
return {
title: [ title: [
{ required: true, message: "请输入颜色名称", trigger: 'blur' } { required: true, message: "请输入颜色名称", trigger: 'blur' }
], ],
value: [ value: [
{ required: true, message: "请输入颜色value值", trigger: 'blur' } {
required: true,
validator: (rule: any, value: any, callback: any) => {
if (!value) {
callback('请输入颜色value值')
} else{
callback();
}
},
trigger: ['blur', 'change']
}
], ],
label: [ label: [
{ required: true, message: "请输入颜色key值", trigger: 'blur' }, { required: true, message: "请输入颜色key值", trigger: 'blur' },
@ -93,14 +103,16 @@ const formRules = reactive<FormRules>({
trigger: 'blur' trigger: 'blur'
} }
] ]
}
}) })
const confirmFn = async (formEl: FormInstance | undefined) => { const confirmFn = async (formEl: FormInstance | undefined) => {
if (confirmRepeat.value || !formEl) return if (confirmRepeat.value) return
await formEl.validate(async (valid) => { await formRef.value?.validate(async (valid) => {
if (confirmRepeat.value) return if (confirmRepeat.value) return
confirmRepeat.value = true confirmRepeat.value = true
if (valid) { if (valid) {
confirmRepeat.value = false
emit('confirm', cloneDeep(formData)); emit('confirm', cloneDeep(formData));
dialogThemeVisible.value = false; dialogThemeVisible.value = false;
} }

View File

@ -32,7 +32,9 @@
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<template v-for="(item,index) in titleStyleList" :key="index"> <template v-for="(item,index) in titleStyleList" :key="index">
<div :class="{ 'border-primary': selectTitleStyle.value == item.value }" @click="changeTitleStyle(item)" class="flex items-center justify-center overflow-hidden w-[200px] h-[100px] mr-[12px] mb-[12px] cursor-pointer border bg-[#eee]"> <div :class="{ 'border-primary': selectTitleStyle.value == item.value }"
@click="changeTitleStyle(item)"
class="flex items-center justify-center overflow-hidden w-[200px] h-[100px] mr-[12px] mb-[12px] cursor-pointer border bg-[#eee]">
<img :src="img(item.url)" /> <img :src="img(item.url)" />
</div> </div>
</template> </template>
@ -53,7 +55,8 @@
<el-form label-width="90px" class="px-[10px]"> <el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('selectStyle')" class="flex"> <el-form-item :label="t('selectStyle')" class="flex">
<span class="text-primary flex-1 cursor-pointer" @click="showBlockStyle">{{ diyStore.editComponent.blockStyle.title }}</span> <span class="text-primary flex-1 cursor-pointer"
@click="showBlockStyle">{{ diyStore.editComponent.blockStyle.title }}</span>
<el-icon> <el-icon>
<ArrowRight /> <ArrowRight />
</el-icon> </el-icon>
@ -62,7 +65,9 @@
<el-dialog v-model="showListDialog" :title="t('selectStyle')" width="600px"> <el-dialog v-model="showListDialog" :title="t('selectStyle')" width="600px">
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<template v-for="(item,index) in blockStyleList" :key="index"> <template v-for="(item,index) in blockStyleList" :key="index">
<div :class="{ 'border-primary': selectBlockStyle.value == item.value }" @click="changeBlockStyle(item)" class="flex items-center justify-center overflow-hidden w-[250px] h-[150px] mr-[12px] mb-[12px] cursor-pointer border bg-[#eee]"> <div :class="{ 'border-primary': selectBlockStyle.value == item.value }"
@click="changeBlockStyle(item)"
class="flex items-center justify-center overflow-hidden w-[250px] h-[150px] mr-[12px] mb-[12px] cursor-pointer border bg-[#eee]">
<img :src="img(item.url)" /> <img :src="img(item.url)" />
</div> </div>
</template> </template>
@ -81,13 +86,15 @@
<p class="text-sm text-gray-400 mb-[10px]">{{ t('dragMouseAdjustOrder') }}</p> <p class="text-sm text-gray-400 mb-[10px]">{{ t('dragMouseAdjustOrder') }}</p>
<div ref="blockBoxRef"> <div ref="blockBoxRef">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]"> <div v-for="(item,index) in diyStore.editComponent.list" :key="item.id"
class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('image')"> <el-form-item :label="t('image')">
<upload-image v-model="item.imageUrl" :limit="1" /> <upload-image v-model="item.imageUrl" :limit="1" />
</el-form-item> </el-form-item>
<el-form-item :label="t('activeCubeTitle')"> <el-form-item :label="t('activeCubeTitle')">
<el-input v-model.trim="item.title.text" :placeholder="t('activeCubeTitlePlaceholder')" clearable maxlength="4" show-word-limit/> <el-input v-model.trim="item.title.text" :placeholder="t('activeCubeTitlePlaceholder')"
clearable maxlength="4" show-word-limit />
</el-form-item> </el-form-item>
<el-form-item :label="t('activeCubeSubTitleTextColor')" v-show="diyStore.editComponent.blockStyle.value == 'style-3'"> <el-form-item :label="t('activeCubeSubTitleTextColor')" v-show="diyStore.editComponent.blockStyle.value == 'style-3'">
@ -115,7 +122,8 @@
<el-color-picker v-model="item.listFrame.endColor" show-alpha :predefine="diyStore.predefineColors" /> <el-color-picker v-model="item.listFrame.endColor" show-alpha :predefine="diyStore.predefineColors" />
</el-form-item> </el-form-item>
<div v-show="diyStore.editComponent.blockStyle.value != 'style-4' && diyStore.editComponent.blockStyle.value != 'style-3'"> <div
v-show="diyStore.editComponent.blockStyle.value != 'style-4' && diyStore.editComponent.blockStyle.value != 'style-3'">
<el-form-item :label="t('activeCubeButton')"> <el-form-item :label="t('activeCubeButton')">
<el-input v-model.trim="item.moreTitle.text" :placeholder="t('activeCubeButtonPlaceholder')" clearable maxlength="3" show-word-limit /> <el-input v-model.trim="item.moreTitle.text" :placeholder="t('activeCubeButtonPlaceholder')" clearable maxlength="3" show-word-limit />
</el-form-item> </el-form-item>
@ -131,13 +139,17 @@
<diy-link v-model="item.link" /> <diy-link v-model="item.link" />
</el-form-item> </el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.list.length > 1" @click="diyStore.editComponent.list.splice(index,1)"> <div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]"
v-show="diyStore.editComponent.list.length > 1"
@click="diyStore.editComponent.list.splice(index,1)">
<icon name="element CircleCloseFilled" color="#bbb" size="20px" /> <icon name="element CircleCloseFilled" color="#bbb" size="20px" />
</div> </div>
</div> </div>
</div> </div>
<el-button v-show="diyStore.editComponent.list.length < 10" class="w-full" @click="addItem">{{ t('activeCubeAddItem') }}</el-button> <el-button v-show="diyStore.editComponent.list.length < 10" class="w-full" @click="addItem">
{{ t('activeCubeAddItem') }}
</el-button>
</el-form> </el-form>
</div> </div>
@ -160,7 +172,8 @@
<h3 class="mb-[10px]">{{ t('subTitleStyle') }}</h3> <h3 class="mb-[10px]">{{ t('subTitleStyle') }}</h3>
<el-form label-width="90px" class="px-[10px]"> <el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('textColor')"> <el-form-item :label="t('textColor')">
<el-color-picker v-model="diyStore.editComponent.subTitle.textColor" show-alpha :predefine="diyStore.predefineColors"/> <el-color-picker v-model="diyStore.editComponent.subTitle.textColor" show-alpha
:predefine="diyStore.predefineColors" />
</el-form-item> </el-form-item>
<el-form-item :label="t('subTextBgColor')"> <el-form-item :label="t('subTextBgColor')">
<el-color-picker v-model="diyStore.editComponent.subTitle.startColor" show-alpha :predefine="diyStore.predefineColors" /> <el-color-picker v-model="diyStore.editComponent.subTitle.startColor" show-alpha :predefine="diyStore.predefineColors" />

View File

@ -82,7 +82,8 @@
<p class="text-sm text-gray-400 mb-[10px]">{{ t('dragMouseAdjustOrder') }}</p> <p class="text-sm text-gray-400 mb-[10px]">{{ t('dragMouseAdjustOrder') }}</p>
<div ref="searchHotWordTabBoxRef"> <div ref="searchHotWordTabBoxRef">
<div v-for="(item,index) in diyStore.editComponent.search.hotWord.list" :key="item.id" class="item-wrap p-[10px] relative border border-dashed border-gray-300 mb-[16px]"> <div v-for="(item,index) in diyStore.editComponent.search.hotWord.list" :key="item.id"
class="item-wrap p-[10px] relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('carouselSearchHotWordText')" class="!mb-0"> <el-form-item :label="t('carouselSearchHotWordText')" class="!mb-0">
<el-input v-model.trim="item.text" :placeholder="t('carouselSearchHotWordTextPlaceholder')" clearable maxlength="4" show-word-limit /> <el-input v-model.trim="item.text" :placeholder="t('carouselSearchHotWordTextPlaceholder')" clearable maxlength="4" show-word-limit />
@ -131,7 +132,9 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.tab.list.length > 1" @click="diyStore.editComponent.tab.list.splice(index,1)"> <div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]"
v-show="diyStore.editComponent.tab.list.length > 1"
@click="diyStore.editComponent.tab.list.splice(index,1)">
<icon name="element CircleCloseFilled" color="#bbb" size="20px" /> <icon name="element CircleCloseFilled" color="#bbb" size="20px" />
</div> </div>
@ -141,7 +144,9 @@
<!-- 选择微页面弹出框 --> <!-- 选择微页面弹出框 -->
<el-dialog v-model="diyPageShowDialog" :title="t('selectSourcesDiyPage')" width="1000px" :close-on-press-escape="true" :destroy-on-close="true" :close-on-click-modal="false"> <el-dialog v-model="diyPageShowDialog" :title="t('selectSourcesDiyPage')" width="1000px" :close-on-press-escape="true" :destroy-on-close="true" :close-on-click-modal="false">
<el-table :data="diyPageTable.data" ref="diyPageTableRef" size="large" v-loading="diyPageTable.loading" height="490px" @current-change="handleCurrentDiyPageChange" row-key="id" highlight-current-row> <el-table :data="diyPageTable.data" ref="diyPageTableRef" size="large"
v-loading="diyPageTable.loading" height="490px"
@current-change="handleCurrentDiyPageChange" row-key="id" highlight-current-row>
<template #empty> <template #empty>
<span>{{ !diyPageTable.loading ? t('emptyData') : '' }}</span> <span>{{ !diyPageTable.loading ? t('emptyData') : '' }}</span>
</template> </template>
@ -150,8 +155,10 @@
<el-table-column prop="type_name" :label="t('diyPageForAddon')" min-width="80" /> <el-table-column prop="type_name" :label="t('diyPageForAddon')" min-width="80" />
</el-table> </el-table>
<div class="mt-[16px] flex justify-end"> <div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="diyPageTable.page" v-model:page-size="diyPageTable.limit" <el-pagination v-model:current-page="diyPageTable.page"
layout="total, sizes, prev, pager, next, jumper" :total="diyPageTable.total" v-model:page-size="diyPageTable.limit"
layout="total, sizes, prev, pager, next, jumper"
:total="diyPageTable.total"
@size-change="loadDiyPageList" @current-change="loadDiyPageList" /> @size-change="loadDiyPageList" @current-change="loadDiyPageList" />
</div> </div>
<div class="flex items-center justify-end mt-[15px]"> <div class="flex items-center justify-end mt-[15px]">
@ -175,12 +182,15 @@
<div class="text-sm text-gray-400 mb-[10px]">{{ t('carouselSearchSwiperTips') }}</div> <div class="text-sm text-gray-400 mb-[10px]">{{ t('carouselSearchSwiperTips') }}</div>
<div ref="imageBoxRef"> <div ref="imageBoxRef">
<div v-for="(item,index) in diyStore.editComponent.swiper.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]"> <div v-for="(item,index) in diyStore.editComponent.swiper.list" :key="item.id"
class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('image')"> <el-form-item :label="t('image')">
<upload-image v-model="item.imageUrl" :limit="1" @change="selectImg" /> <upload-image v-model="item.imageUrl" :limit="1" @change="selectImg" />
</el-form-item> </el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.swiper.list.length > 1" @click="diyStore.editComponent.swiper.list.splice(index,1)"> <div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]"
v-show="diyStore.editComponent.swiper.list.length > 1"
@click="diyStore.editComponent.swiper.list.splice(index,1)">
<icon name="element CircleCloseFilled" color="#bbb" size="20px" /> <icon name="element CircleCloseFilled" color="#bbb" size="20px" />
</div> </div>
@ -190,7 +200,9 @@
</div> </div>
</div> </div>
<el-button v-show="diyStore.editComponent.swiper.list.length < 10" class="w-full" @click="addImageAd">{{ t('addImageAd') }}</el-button> <el-button v-show="diyStore.editComponent.swiper.list.length < 10" class="w-full"
@click="addImageAd">{{ t('addImageAd') }}
</el-button>
</el-form> </el-form>
</el-collapse-item> </el-collapse-item>
@ -270,10 +282,12 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item :label="t('topRounded')"> <el-form-item :label="t('topRounded')">
<el-slider v-model="diyStore.editComponent.swiper.topRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" /> <el-slider v-model="diyStore.editComponent.swiper.topRounded" show-input size="small"
class="ml-[10px] diy-nav-slider" :max="50" />
</el-form-item> </el-form-item>
<el-form-item :label="t('bottomRounded')"> <el-form-item :label="t('bottomRounded')">
<el-slider v-model="diyStore.editComponent.swiper.bottomRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" /> <el-slider v-model="diyStore.editComponent.swiper.bottomRounded" show-input size="small"
class="ml-[10px] diy-nav-slider" :max="50" />
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@ -415,7 +429,8 @@ diyStore.editComponent.swiper.list.forEach((item: any) => {
}) })
const activeNames = ref(['tab', 'swiper']) const activeNames = ref(['tab', 'swiper'])
const handleChange = (val: string[]) => {} const handleChange = (val: string[]) => {
}
onMounted(() => { onMounted(() => {
loadDiyPageList() loadDiyPageList()
@ -507,8 +522,7 @@ const diyPageTable = reactive({
total: 0, total: 0,
loading: true, loading: true,
data: [], data: [],
searchParam: { searchParam: {}
}
}) })
const diyPageTableRef = ref<InstanceType<typeof ElTable>>() const diyPageTableRef = ref<InstanceType<typeof ElTable>>()
@ -633,6 +647,7 @@ defineExpose({})
.select-diy-page-input .el-input__inner { .select-diy-page-input .el-input__inner {
cursor: pointer; cursor: pointer;
} }
.collapse-wrap { .collapse-wrap {
.el-collapse-item__header { .el-collapse-item__header {
font-size: 16px; font-size: 16px;

View File

@ -2,7 +2,7 @@
<el-dialog v-model="dialogThemeVisible" title="编辑色调" width="850px" align-center destroy-on-close="true"> <el-dialog v-model="dialogThemeVisible" title="编辑色调" width="850px" align-center destroy-on-close="true">
<el-form :model="openData" label-width="150px" :rules="formRules"> <el-form :model="openData" label-width="150px" :rules="formRules">
<el-form-item label="色调名称" prop="title"> <el-form-item label="色调名称" prop="title">
<el-input v-model="openData.title" placeholder="请输入色调名称" maxlength="15" class="!w-[250px]" :disabled="openData.id != ''" /> <el-input v-model="openData.title" placeholder="请输入色调名称" maxlength="15" class="!w-[250px]" :disabled="openData.id != ''" @keydown.enter.native.prevent />
</el-form-item> </el-form-item>
</el-form> </el-form>

View File

@ -238,8 +238,8 @@ route.query.title = route.query.title || ''
route.query.back = route.query.back || '/admin/diy/list' route.query.back = route.query.back || '/admin/diy/list'
const backPath = route.query.back const backPath = route.query.back
const template = ref(''); const template = ref('')
const oldTemplate = ref(''); const oldTemplate = ref('')
const wapUrl = ref('') const wapUrl = ref('')
const wapDomain = ref('') const wapDomain = ref('')
const wapPreview = ref('') const wapPreview = ref('')
@ -273,7 +273,7 @@ const originData = reactive({
const isChange = ref(true) // truefalse const isChange = ref(true) // truefalse
const goBack = () => { const goBack = () => {
if (isChange.value) { if (isChange.value) {
location.href = `${location.origin}${backPath}`; location.href = `${location.origin}${backPath}`
router.push(backPath) router.push(backPath)
} else { } else {
// //
@ -287,7 +287,7 @@ const goBack = () => {
autofocus: false autofocus: false
} }
).then(() => { ).then(() => {
location.href = `${location.origin}${backPath}`; location.href = `${location.origin}${backPath}`
}).catch(() => { }).catch(() => {
}) })
} }
@ -337,7 +337,7 @@ const changeTemplatePage = (value:any)=> {
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
diyStore.changeCurrentIndex(-99) diyStore.changeCurrentIndex(-99)
diyStore.init(); // diyStore.init() //
if (value) { if (value) {
let data = cloneDeep(templatePages[value].data); let data = cloneDeep(templatePages[value].data);
diyStore.global = data.global; diyStore.global = data.global;
@ -349,13 +349,13 @@ const changeTemplatePage = (value:any)=> {
} }
}).catch(() => { }).catch(() => {
// //
template.value = oldTemplate.value; template.value = oldTemplate.value
}); });
} else { } else {
diyStore.init(); // diyStore.init() //
if (value) { if (value) {
let data = cloneDeep(templatePages[value].data); let data = cloneDeep(templatePages[value].data)
diyStore.global = data.global; diyStore.global = data.global
if (data.value.length) { if (data.value.length) {
diyStore.value = data.value diyStore.value = data.value
} }
@ -444,7 +444,7 @@ initPage({
} }
} }
loadDiyTemplatePages(data.type); loadDiyTemplatePages(data.type)
// //
wapDomain.value = data.domain_url.wap_domain wapDomain.value = data.domain_url.wap_domain

View File

@ -34,7 +34,7 @@
</el-tooltip> </el-tooltip>
</div> </div>
</template> </template>
<el-switch v-model="diyStore.editComponent.field.privacyProtection" /> <el-switch v-model="diyStore.editComponent.field.privacyProtection" :disabled ="diyStore.editComponent.addressFormat != 'province/city/district/address'" />
<div class="text-sm text-gray-400">{{ t('提交后自动隐藏地址,仅管理员可查看') }}</div> <div class="text-sm text-gray-400">{{ t('提交后自动隐藏地址,仅管理员可查看') }}</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -55,7 +55,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { t } from '@/lang' import { t } from '@/lang'
import { ref } from 'vue' import { ref,watch } from 'vue'
import useDiyStore from '@/stores/modules/diy' import useDiyStore from '@/stores/modules/diy'
const diyStore = useDiyStore() const diyStore = useDiyStore()
@ -66,6 +66,15 @@ diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' } const res = { code: true, message: '' }
return res return res
} }
watch(
() => diyStore.editComponent.addressFormat,
(newVal) => {
if (newVal !== 'province/city/district/address') {
diyStore.editComponent.field.privacyProtection = false
}
},
{ immediate: true }
)
defineExpose({}) defineExpose({})

View File

@ -4,7 +4,147 @@
<!-- 表单组件 字段内容设置 --> <!-- 表单组件 字段内容设置 -->
<slot name="field"></slot> <slot name="field"></slot>
todo 此处编写表格组件的属性 <el-form label-width="100px" class="px-[10px]" @submit.prevent>
<el-form-item :label="t('列设置')">
<div ref="imageBoxRef">
<div v-for="(item, index) in diyStore.editComponent.columnList" :key="item.id"
class="border-b-[1px] border-[#e0e0e0] py-1">
<div class="flex items-center justify-between">
<div class="flex">
<span :class="['iconfont', 'ml-[5px]', 'cursor-pointer', getIconClass(item.type)]"></span>
<el-input v-model="item.name" class="input-style" :input-style="{ boxShadow: 'none' }"
:placeholder="t('请输入列名')" />
</div>
<div class="flex">
<span v-if="diyStore.editComponent.columnList.length > 1" @click="removeOption(index)"
class="cursor-pointer ml-[5px] nc-iconfont nc-icon-shanchu-yuangaizhiV6xx"></span>
<span class="cursor-pointer ml-[5px] nc-iconfont nc-icon-xiaV6xx"></span>
</div>
</div>
<div v-if="item.type == 'radio'" class="flex">
<div class="text-[#999] mr-3" >{{ item.options?.length || 0 }}个选项</div>
<span class="text-primary cursor-pointer mr-[10px]" @click="openRadioDialog(item, index)">{{ t('编辑') }}</span>
</div>
<div v-if="item.type == 'date'" class="flex">
<span class="text-primary cursor-pointer mr-[10px]" @click="openRadioDialog(item, index)">{{ t('设置日期格式') }}</span>
</div>
<div v-if="item.type == 'address'" class="flex">
<div class="text-[#999] mr-3">精确到详细地址</div>
<span class="text-primary cursor-pointer mr-[10px]" @click="openRadioDialog(item, index)">{{ t('设置') }}</span>
</div>
</div>
</div>
<el-popover placement="bottom" :width="50" trigger="hover">
<template #reference>
<span class="text-primary cursor-pointer mr-[10px]">{{ t('添加') }}</span>
</template>
<div v-for="(item, index) in columnTypeOptions" :key="index" @click="addOption(item)"
class="cursor-pointer hover:bg-[#d1e1ff] rounded text-center">
<div class="py-1 text-[var(--el-text-color-primary]">{{ item.label }}</div>
</div>
</el-popover>
</el-form-item>
<el-form-item :label="t('是否自增')">
<el-switch v-model="diyStore.editComponent.autoIncrementControl" />
</el-form-item>
<el-form-item :label="t('填写限制')" v-if="diyStore.editComponent.autoIncrementControl">
<div class="flex items-center">
<span>默认显示</span>
<el-input v-model="diyStore.editComponent.writeLimit.default" class="input-short" :placeholder="t('')" />
<span></span>
</div>
<div class="flex items-center my-1">
<span>最少填写</span>
<el-input v-model="diyStore.editComponent.writeLimit.min" class="input-short" :placeholder="t('')" />
<span></span>
</div>
<div class="flex items-center">
<span>最多填写</span>
<el-input v-model="diyStore.editComponent.writeLimit.max" class="input-short" :placeholder="t('')" />
<span></span>
</div>
</el-form-item>
<el-form-item :label="t('按钮名称')" v-if="diyStore.editComponent.autoIncrementControl">
<el-input v-model="diyStore.editComponent.btnText" :placeholder="t('请输入按钮名称')" />
</el-form-item>
</el-form>
<!-- 单选项 -->
<!-- <el-dialog v-model="radioDialogVisible" :title="t('设置单选项')" width="500">
<div v-if="activeColumnTemp.type == 'radio'">
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('选项名称')">
<el-input v-model="activeColumnTemp.name" :input-style="{ boxShadow: 'none' }" />
</el-form-item>
<el-form-item :label="t('设置选项')">
<div ref="radioBoxRef">
<div v-for="(opt, idx) in activeColumnTemp.options" :key="opt.id">
<div class="flex items-center justify-between mb-2">
<div class="flex-1">
<el-input v-model="opt.label" :input-style="{ boxShadow: 'none' }"
:placeholder="t('请输入')" />
</div>
<span v-if="activeColumnTemp.options.length > 1" @click="removeOptionItem(idx)"
class="cursor-pointer ml-[5px] nc-iconfont nc-icon-shanchu-yuangaizhiV6xx"></span>
<span class="cursor-pointer ml-[5px] nc-iconfont nc-icon-iconpaixu1"></span>
</div>
</div>
<span class="text-primary cursor-pointer mr-[10px]" @click="addOptionItem">{{ t('添加选项') }}</span>
<span class="text-primary cursor-pointer mr-[10px]" @click="addOtherOption">{{ t('添加其它项') }}</span>
<el-popover :visible="visible" placement="bottom" :width="300">
<p class="mb-[5px]">{{ t('addMultipleOption') }}</p>
<p class="text-[#888] text-[12px] mb-[5px]">{{ t('addOptionTips') }}</p>
<el-input v-model.trim="optionsValue" type="textarea" clearable maxlength="200"
show-word-limit />
<div class="mt-[10px] text-right">
<el-button size="small" text @click="visible = false">{{ t('cancel') }}</el-button>
<el-button size="small" type="primary" @click="batchAddOptions">{{t('confirm')}}</el-button>
</div>
<template #reference>
<span class="text-primary cursor-pointer"
@click="visible = true">{{ t('addMultipleOption') }}</span>
</template>
</el-popover>
</div>
</el-form-item>
</el-form>
</div>
<div v-else-if="activeColumnTemp.type == 'date'">
<el-form>
<el-form-item :label="t('dataFormat')">
<el-radio-group v-model="activeColumnTemp.dateFormat" class="!block">
<el-radio class="!block" label="YYYY年M月D日">{{ dateFormat.format1 }}</el-radio>
<el-radio class="!block" label="YYYY-MM-DD">{{ dateFormat.format2 }}</el-radio>
<el-radio class="!block" label="YYYY/MM/DD">{{ dateFormat.format3 }}</el-radio>
<el-radio class="!block" label="YYYY-MM-DD HH:mm">{{ dateFormat.format4 }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<div v-else-if="activeColumnTemp.type == 'address'">
<el-form>
<el-form-item :label="t('地址格式')">
<el-radio-group v-model="activeColumnTemp.addressFormat" class="!block">
<el-radio class="!block" label="province/city/district/address">{{ t('省/市/区/街道/详细地址') }}</el-radio>
<el-radio class="!block" label="province/city/district/street">{{ t('省/市/区/街道(镇)') }}</el-radio>
<el-radio class="!block" label="province/city/district">{{ t('省/市/区(县)') }}</el-radio>
<el-radio class="!block" label="province/city">{{ t('省/市') }}</el-radio>
<el-radio class="!block" label="province">{{ t('省') }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="radioDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleDialogConfirm">确定</el-button>
</div>
</template>
</el-dialog> -->
<div>
</div>
<!-- 表单组件 其他设置 --> <!-- 表单组件 其他设置 -->
<slot name="other"></slot> <slot name="other"></slot>
@ -25,20 +165,248 @@
<script lang="ts" setup> <script lang="ts" setup>
import { t } from '@/lang' import { t } from '@/lang'
import { ref } from 'vue' import Sortable from 'sortablejs'
import { ref, watch, onMounted, nextTick ,reactive, computed} from 'vue'
import useDiyStore from '@/stores/modules/diy' import useDiyStore from '@/stores/modules/diy'
import { range } from 'lodash-es'
const diyStore = useDiyStore() const diyStore = useDiyStore()
diyStore.editComponent.ignore = ['componentBgUrl'] // diyStore.editComponent.ignore = ['componentBgUrl'] //
// //
diyStore.editComponent.verify = (index: number) => { diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' } const res = { code: true, message: '' }
// todo // todo
return res return res
} }
//
const columnTypeOptions = ref([
{ label: '单选项', value: 'radio' },
{ label: '文本', value: 'text' },
{ label: '数字', value: 'number' },
{ label: '手机号', value: 'mobile' },
{ label: '地址', value: 'address' },
{ label: '身份证', value: 'idcard' },
{ label: '性别', value: 'gender' },
{ label: '日期', value: 'date' }
])
const getIconClass = (type:any) => {
switch (type) {
case 'radio':
return 'icona-duihaopc30'
case 'text':
return 'icona-danhangwenben-1pc30'
case 'number':
return 'icona-shuzipc30-1'
case 'mobile':
return 'icona-shoujipc30'
case 'address':
return 'iconbiaotipc'
case 'idcard':
return 'icona-shenfenzhengpc30'
case 'gender':
return 'el-icon-s-opportunity'
case 'date':
return 'icona-riqipc30'
default:
return ''
}
}
const imageBoxRef = ref()
const generateId = () => Date.now().toString(36) + Math.random().toString(36).substr(2, 5)
//
const addOption = (item) => {
const newColumn: any = {
id: generateId(),
name: item.label,
type: item.value, //
value: '' //
}
// options
if (item.value === 'radio') {
newColumn.options = [
{ id: generateId(), label: '选项1' },
{ id: generateId(), label: '选项2' }
]
}
// dateFormat
if (item.value === 'date') {
newColumn.dateFormat = 'YYYY年M月D日' //
}
// addressFormat
if (item.value === 'address') {
newColumn.addressFormat = 'province/city/district/address' //
}
diyStore.editComponent.columnList.push(newColumn)
}
const removeOption = (index: number) => {
diyStore.editComponent.columnList.splice(index, 1)
}
onMounted(() => {
// nextTick(() => {
// if (diyStore.editComponent.columnList.length < 2) return;
// const sortable = Sortable.create(imageBoxRef.value, {
// group: 'item-wrap',
// animation: 200,
// onEnd: event => {
// const temp = diyStore.editComponent.columnList[event.oldIndex!]
// diyStore.editComponent.columnList.splice(event.oldIndex!, 1)
// diyStore.editComponent.columnList.splice(event.newIndex!, 0, temp)
// sortable.sort(
// range(diyStore.editComponent.columnList.length).map(value => {
// return value.toString()
// })
// )
// }
// })
// })
console.log(diyStore.editComponent.columnList);
})
const activeColumn = ref<any>({}) //
const activeColumnTemp = ref<any>({}) //
const activeRadioIndex = ref(0) //
const radioDialogVisible = ref(false)
const radioBoxRef = ref()
const optionsValue = ref('')
const visible = ref(false)
const dateFormat: any = reactive({
format1: '',
format2: '',
format3: '',
format4: ''
});
const openRadioDialog = (item, index) => {
activeRadioIndex.value = index // 便
activeColumn.value = item
activeColumnTemp.value = JSON.parse(JSON.stringify(item)) //
if(item.type == 'radio'){
if (!activeColumnTemp.value.options) activeColumnTemp.value.options = []
radioDialogVisible.value = true
// nextTick(() => initRadioSortable()) //
}else if(item.type == 'date'){
//
const today = new Date();
let year = today.getFullYear();
let month = String(today.getMonth() + 1).padStart(2, '0');
let day = String(today.getDate()).padStart(2, '0');
const hours = String(today.getHours()).padStart(2, '0');
const minutes = String(today.getMinutes()).padStart(2, '0');
dateFormat.format1 = `${ year }${ month }${ day }`;
dateFormat.format2 = `${ year }-${ month }-${ day }`;
dateFormat.format3 = `${ year }/${ month }/${ day }`;
dateFormat.format4 = `${ year }-${ month }-${ day } ${ hours }:${ minutes }`;
radioDialogVisible.value = true
} else if(item.type == 'address'){
radioDialogVisible.value = true
}
}
//
// const initRadioSortable = () => {
// Sortable.create(radioBoxRef.value, {
// group: 'radio-option-wrap',
// animation: 200,
// draggable: '.drag-radio-item',
// onEnd: event => {
// const options = activeColumnTemp.value.options // temp
// const temp = options[event.oldIndex!]
// options.splice(event.oldIndex!, 1)
// options.splice(event.newIndex!, 0, temp)
// }
// })
// }
const handleDialogConfirm = () => {
console.log(activeColumnTemp.value);
diyStore.editComponent.columnList[activeRadioIndex.value] = JSON.parse(JSON.stringify(activeColumnTemp.value)) //
radioDialogVisible.value = false //
}
const addOptionItem = () => {
const newOption = { id: generateId(), label: '选项' + (activeColumnTemp.value.options.length + 1) }
activeColumnTemp.value.options.push(newOption)
}
const addOtherOption = () => {
const newOption = { id: generateId(), label: '其他' }
activeColumnTemp.value.options.push(newOption)
}
const removeOptionItem = (index: number) => {
activeColumnTemp.value.options.splice(index, 1)
}
//
const uniqueByKey = (arr: any, key: any) => {
const seen = new Set();
return arr.filter((item: any) => {
const serializedKey = JSON.stringify(item[key]);
return seen.has(serializedKey) ? false : seen.add(serializedKey);
});
}
//
const batchAddOptions = () => {
if (optionsValue.value.trim()) {
const newOptions = optionsValue.value.split(',').map((option: any) => {
return {
id: diyStore.generateRandom(),
label: option.trim()
};
}).filter((option: any) => option.label !== '');
//
const uniqueNewOptions = uniqueByKey(newOptions, 'label');
//
const filteredNewOptions = uniqueNewOptions.filter(newOption =>
!activeColumnTemp.value.options.some(existingOption => existingOption.label === newOption.label)
);
//
if (filteredNewOptions.length > 0) {
activeColumnTemp.value.options.push(...filteredNewOptions);
} else {
ElMessage({
message: t('errorTipsTwo'),
type: "warning",
});
}
optionsValue.value = '';
visible.value = false;
}
};
defineExpose({}) defineExpose({})
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
:deep(.input-style .el-input__wrapper) {
box-shadow: none !important;
}
.input-short{
width: 80px;
margin: 0 10px;
}
</style>

View File

@ -5,13 +5,25 @@
<!-- 表单组件 字段内容设置 --> <!-- 表单组件 字段内容设置 -->
<slot name="field"></slot> <slot name="field"></slot>
<el-form label-width="100px" class="px-[10px]"> <el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('上传方式')"> <el-form-item>
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('上传方式') }}</span>
<el-tooltip effect="light" :content="t('拍摄时长限制1分钟从相册上传不限制时长。')" placement="top">
<el-icon>
<QuestionFilled color="#999999" />
</el-icon>
</el-tooltip>
</div>
</template>
<el-radio-group v-model="diyStore.editComponent.uploadMode"> <el-radio-group v-model="diyStore.editComponent.uploadMode">
<el-radio label="shoot_and_album">{{ t('拍摄和相册') }}</el-radio> <el-radio label="shoot_and_album">{{ t('拍摄和相册') }}</el-radio>
<el-radio label="shoot_only">{{ t('只允许拍摄') }}</el-radio> <el-radio label="shoot_only">{{ t('只允许拍摄') }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 表单组件 其他设置 -->
<slot name="other"></slot>
</div> </div>

View File

@ -50,7 +50,7 @@
</el-table-column> </el-table-column>
<el-table-column prop="update_time" :label="t('updateTime')" min-width="120" /> <el-table-column prop="update_time" :label="t('updateTime')" min-width="120" />
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="100"> <el-table-column :label="t('operation')" fixed="right" align="right" min-width="130">
<template #default="{ row }"> <template #default="{ row }">
<div class="flex items-center justify-end"> <div class="flex items-center justify-end">
<el-button type="primary" v-if="row.status == 1 && row.type=='DIY_FORM'" link @click="spreadEvent(row)">{{ t('promotion') }}</el-button> <el-button type="primary" v-if="row.status == 1 && row.type=='DIY_FORM'" link @click="spreadEvent(row)">{{ t('promotion') }}</el-button>
@ -156,7 +156,7 @@
</el-dialog> </el-dialog>
<!-- 推广弹出框 --> <!-- 推广弹出框 -->
<form-spread-popup ref="formSpreadPopupRef" /> <spread-popup ref="spreadPopupRef" />
<!-- 表单提交成功页弹出框 --> <!-- 表单提交成功页弹出框 -->
<form-submit-popup ref="formSubmitPopupRef" @complete="loadDiyFormList" /> <form-submit-popup ref="formSubmitPopupRef" @complete="loadDiyFormList" />
@ -181,9 +181,9 @@ import { useRoute, useRouter } from 'vue-router'
import { setTablePageStorage,getTablePageStorage } from "@/utils/common"; import { setTablePageStorage,getTablePageStorage } from "@/utils/common";
import { img } from '@/utils/common' import { img } from '@/utils/common'
import recordsDetail from '@/app/views/diy_form/records.vue' import recordsDetail from '@/app/views/diy_form/records.vue'
import formSpreadPopup from '@/app/views/diy_form/components/form-spread-popup.vue'
import formSubmitPopup from '@/app/views/diy_form/components/form-submit-popup.vue' import formSubmitPopup from '@/app/views/diy_form/components/form-submit-popup.vue'
import formWritePopup from '@/app/views/diy_form/components/form-write-popup.vue' import formWritePopup from '@/app/views/diy_form/components/form-write-popup.vue'
import spreadPopup from '@/components/spread-popup/index.vue'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -506,12 +506,19 @@ const shareEvent = async (formEl: FormInstance | undefined) => {
} }
// 广 // 广
const formSpreadPopupRef: any = ref(null) const spreadPopupRef = ref(null)
const spreadEvent = (data: any) => { const spreadEvent = (data: any) => {
formSpreadPopupRef.value.show(data) const pagePath = "/app/pages/index/diy_form"
const columnName = "form_id"
const columnValue = data.form_id
const title = "表单推广"
const folder = "diy_form"
spreadPopupRef.value?.show(pagePath, columnName, columnValue, title,folder)
} }
// //
const formSubmitPopupRef: any = ref(null) const formSubmitPopupRef: any = ref(null)

View File

@ -106,7 +106,7 @@
</el-table-column> </el-table-column>
<!-- <el-table-column fixed prop="create_time" :label="t('填表时间')" min-width="120" /> --> <!-- <el-table-column fixed prop="create_time" :label="t('填表时间')" min-width="120" /> -->
<el-table-column fixed prop="create_time" :label="t('fillInFormTotal')" min-width="500"> <el-table-column fixed prop="create_time" :label="t('fillInFormTotal')" min-width="500">
<template #default="{ row }" @click=""> <template #default="{ row }">
{{ row.write_count }} {{ row.write_count }}
</template> </template>
</el-table-column> </el-table-column>
@ -155,9 +155,11 @@
<div class="flex mb-[10px]" v-for="(item, index) in formDetail" :key="index"> <div class="flex mb-[10px]" v-for="(item, index) in formDetail" :key="index">
<div class="flex justify-end w-[100px]">{{ item.label }}</div> <div class="flex justify-end w-[100px]">{{ item.label }}</div>
<div class="flex ml-[20px]"> <div class="flex ml-[20px]">
<div v-if="Array.isArray(item.text)" class="mr-[10px]" v-for="(textItem, i) in item.text" :key="i"> <template v-if="Array.isArray(item.text)">
<div class="mr-[10px]" v-for="(textItem, i) in item.text" :key="i">
{{ textItem }} {{ textItem }}
</div> </div>
</template>
<div v-else>{{ item.text }}</div> <div v-else>{{ item.text }}</div>
</div> </div>
</div> </div>
@ -180,11 +182,10 @@
import { reactive, ref, defineAsyncComponent } from 'vue' import { reactive, ref, defineAsyncComponent } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getDiyFormFieldsList, getDiyFormFieldStat, getFormRecords,getFormRecordsInfo,deleteFormRecords,getFormRecordsMember} from '@/app/api/diy_form' import { getDiyFormFieldsList, getDiyFormFieldStat, getFormRecords,getFormRecordsInfo,deleteFormRecords,getFormRecordsMember} from '@/app/api/diy_form'
import { useRouter, useRoute } from 'vue-router' import { useRouter } from 'vue-router'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import { ElMessageBox, FormInstance } from 'element-plus' import { ElMessageBox, FormInstance } from 'element-plus'
const route = useRoute()
const router = useRouter() const router = useRouter()
const showDialog = ref(false) const showDialog = ref(false)
const activeName = ref('detail_data') const activeName = ref('detail_data')
@ -257,7 +258,6 @@ const deleteEvent = (row: any) => {
form_id: row.form_id form_id: row.form_id
}).then(() => { }).then(() => {
initData(); initData();
}).catch(() => {
}) })
}) })
} }
@ -303,7 +303,7 @@ const formMemberList = reactive({
data: [], data: [],
searchParam: { searchParam: {
keyword: '', keyword: '',
form_id: 0, form_id: 0
} }
}) })
@ -320,7 +320,7 @@ const getFormRecordsMemberFn = (page: number = 1) => {
formMemberList.loading = false; formMemberList.loading = false;
}).catch((error) => { }).catch((error) => {
formMemberList.loading = false; formMemberList.loading = false;
}); })
} }
// //
@ -387,5 +387,4 @@ defineExpose({
margin-right: 10px; margin-right: 10px;
} }
} }
</style> </style>

View File

@ -126,7 +126,7 @@
<span class="w-[70px] flex-shrink-0 text-right">{{t('account') }}</span> <span class="w-[70px] flex-shrink-0 text-right">{{t('account') }}</span>
<span>{{ row.transfer_account }}</span> <span>{{ row.transfer_account }}</span>
</div> </div>
<div class="flex items-center" v-if="row.transfer_payment_code"> <div class="flex items-start" v-if="row.transfer_payment_code">
<span class="w-[70px] flex-shrink-0 text-right">{{ t('transferCode') }}</span> <span class="w-[70px] flex-shrink-0 text-right">{{ t('transferCode') }}</span>
<el-image :src="img(row.transfer_payment_code)" :preview-src-list="[img(row.transfer_payment_code)]" :hide-on-click-modal="true" class="w-[50px] h-[50px]"></el-image> <el-image :src="img(row.transfer_payment_code)" :preview-src-list="[img(row.transfer_payment_code)]" :hide-on-click-modal="true" class="w-[50px] h-[50px]"></el-image>
</div> </div>

View File

@ -52,13 +52,11 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, computed } from 'vue' import { reactive, ref, computed,defineEmits } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getPayRefundInfo, getRefundType, getRefundTransfer } from '@/app/api/pay' import { getPayRefundInfo, getRefundType, getRefundTransfer } from '@/app/api/pay'
import { FormInstance, ElMessage } from 'element-plus' import { FormInstance } from 'element-plus'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { img, filterNumber } from '@/utils/common'
import useAppStore from '@/stores/modules/app'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -71,7 +69,6 @@ let refundNo = '';
const refundList = ref([]) const refundList = ref([])
const formData: Record<string, any> = ref(null) const formData: Record<string, any> = ref(null)
const handleClose = (done: () => void) => { const handleClose = (done: () => void) => {
showDialog.value = false; showDialog.value = false;
} }
@ -109,6 +106,7 @@ const transferEvent = (data:any) => {
transferDialog.value = true transferDialog.value = true
transferFormData.refund_no = data.refund_no transferFormData.refund_no = data.refund_no
transferFormData.refund_money = data.money transferFormData.refund_money = data.money
transferFormData.voucher = ''
} }
const initialFormData = { const initialFormData = {
@ -127,6 +125,7 @@ const formRules = computed(() => {
] ]
} }
}) })
const emit = defineEmits(['loadPayRefundList'])
const confirm = async (formEl: FormInstance | undefined) => { const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return if (loading.value || !formEl) return
@ -140,6 +139,7 @@ const confirm = async (formEl: FormInstance | undefined) => {
transferDialog.value = false transferDialog.value = false
refundList.value = [] refundList.value = []
getRefundListInfo(refundNo) getRefundListInfo(refundNo)
emit('loadPayRefundList')
}).catch(() => { }).catch(() => {
transferDialog.value = false transferDialog.value = false
loading.value = false loading.value = false

View File

@ -72,8 +72,7 @@
</div> </div>
</el-card> </el-card>
<el-image-viewer :url-list="previewImageList" v-if="imageViewerShow" @close="imageViewerShow = false" :initial-index="0" <el-image-viewer :url-list="previewImageList" v-if="imageViewerShow" @close="imageViewerShow = false" :initial-index="0" :zoom-rate="1" />
:zoom-rate="1" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -56,14 +56,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue' import { ref } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { useRoute, useRouter } from 'vue-router' import { useRoute } from 'vue-router'
import { getPayDetail, payAuditPass, payAuditRefuse } from '@/app/api/sys' import { getPayDetail, payAuditPass, payAuditRefuse } from '@/app/api/sys'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
import { ArrowLeft } from '@element-plus/icons-vue' import { ArrowLeft } from '@element-plus/icons-vue'
const route = useRoute() const route = useRoute()
const router = useRouter()
const pageName = route.meta.title const pageName = route.meta.title
const id: number = parseInt((route.query.id || 0)) const id: number = parseInt((route.query.id || 0))
const loading = ref(true) const loading = ref(true)

View File

@ -10,7 +10,14 @@
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never"> <el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="payRefundTable.searchParam" ref="searchFormRef"> <el-form :inline="true" :model="payRefundTable.searchParam" ref="searchFormRef">
<el-form-item :label="t('refundNo')" prop="refund_no"> <el-form-item :label="t('refundNo')" prop="refund_no">
<el-input v-model.trim="payRefundTable.searchParam.refund_no" :placeholder="t('refundNoPlaceholder')" /> <el-input v-model.trim="payRefundTable.searchParam.refund_no"
:placeholder="t('refundNoPlaceholder')" />
</el-form-item>
<el-form-item :label="t('status')" prop="status">
<el-select v-model="payRefundTable.searchParam.status" clearable class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item" :value="key" v-for="(item, key) in refundStatusList" :key="key" />
</el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('createTime')" prop="create_time"> <el-form-item :label="t('createTime')" prop="create_time">
<el-date-picker v-model="payRefundTable.searchParam.create_time" type="datetimerange" <el-date-picker v-model="payRefundTable.searchParam.create_time" type="datetimerange"
@ -49,22 +56,25 @@
</div> </div>
</div> </div>
</el-card> </el-card>
<refund-detail ref="refundDetailDialog"></refund-detail> <refund-detail @loadPayRefundList="handleMessage" ref="refundDetailDialog"></refund-detail>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getPayRefundPages } from '@/app/api/pay' import { getPayRefundPages ,getRefundStatus} from '@/app/api/pay'
import { useRouter, useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import type { FormInstance } from 'element-plus' import type { FormInstance } from 'element-plus'
import refundDetail from '@/app/views/finance/components/refund-detail.vue' import refundDetail from '@/app/views/finance/components/refund-detail.vue'
const route = useRoute() const route = useRoute()
const router = useRouter()
const pageName = route.meta.title const pageName = route.meta.title
const refundStatusList = ref([])
const checkStatusList = async () => {
refundStatusList.value = await (await getRefundStatus()).data
}
checkStatusList()
const payRefundTable = reactive({ const payRefundTable = reactive({
page: 1, page: 1,
limit: 10, limit: 10,
@ -73,6 +83,7 @@ const payRefundTable = reactive({
data: [], data: [],
searchParam: { searchParam: {
refund_no: '', refund_no: '',
status: '',
create_time: [] create_time: []
} }
}) })
@ -99,6 +110,9 @@ const loadPayRefundList = (page: number = 1) => {
}) })
} }
loadPayRefundList() loadPayRefundList()
const handleMessage = () => {
loadPayRefundList()
}
const refundDetailDialog: Record<string, any> | null = ref(null) const refundDetailDialog: Record<string, any> | null = ref(null)
const infoEvent = (res:any) => { const infoEvent = (res:any) => {
let data = {no: res.refund_no}; let data = {no: res.refund_no};

View File

@ -36,7 +36,7 @@
<div class="px-[20px] pb-[10px] font-bold mt-[40px]">{{ t('weapp') }}</div> <div class="px-[20px] pb-[10px] font-bold mt-[40px]">{{ t('weapp') }}</div>
<el-form label-width="40px" class="px-[20px]"> <el-form label-width="40px" class="px-[20px]">
<el-form-item label=" " v-if="weappConfig.qr_code"> <el-form-item label=" " v-if="weappConfig.qr_code">
<el-image class="w-[100px] h-[100px]" :src="img(weappConfig.qr_code)" /> <el-image class="w-[150px] h-[150px]" :src="img(weappConfig.qr_code)" />
</el-form-item> </el-form-item>
<el-form-item label=" " v-else> <el-form-item label=" " v-else>
<span class="text-gray-400">{{ t('weappNotSet') }}</span> <span class="text-gray-400">{{ t('weappNotSet') }}</span>

View File

@ -2,9 +2,8 @@
<!--应用市场--> <!--应用市场-->
<div class="main-container"> <div class="main-container">
<el-card class="box-card !border-none" shadow="never"> <el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-page-title">{{ t('localAppText') }}</span> <span class="text-page-title">{{ t("localAppText") }}</span>
<el-input class="!w-[250px]" :placeholder="t('search')" v-model.trim="search_name" @keyup.enter="query"> <el-input class="!w-[250px]" :placeholder="t('search')" v-model.trim="search_name" @keyup.enter="query">
<template #suffix> <template #suffix>
@ -15,48 +14,53 @@
</el-input> </el-input>
</div> </div>
<div class="flex justify-between my-[20px]"> <div class="flex justify-between items-center my-[20px]">
<div class="flex"> <div class="flex">
<div :class="['flex items-center text-[14px] h-[32px] text-[#a6a9ad] border-[1px] border-solid my-[3px] border-[var(--el-color-info-light-8)] rounded-full px-[20px] mr-[24px] cursor-pointer hover:bg-[var(--el-color-info-light-8)]', { '!text-[#fff] !bg-[#000] !border-[#000]': activeName === 'installed' }]" @click="activeNameTabFn('installed')"> <div :class="['flex items-center text-[14px] h-[32px] border-[1px] border-solid my-[3px] border-[var(--el-color-info-light-8)] rounded-full px-[20px] mr-[24px] cursor-pointer hover:bg-[var(--el-color-info-light-8)]', { '!text-[#fff] !bg-[#000] !border-[#000]': activeName === 'installed' }]" @click="activeNameTabFn('installed')">{{ t("installLabel") }}</div>
{{ t('installLabel') }} <div :class="['flex items-center text-[14px] h-[32px] border-[1px] border-solid my-[3px] border-[var(--el-color-info-light-8)] rounded-full px-[20px] mr-[24px] cursor-pointer hover:bg-[var(--el-color-info-light-8)]', { '!text-[#fff] !bg-[#000] !border-[#000]': activeName === 'uninstalled' }]" @click="activeNameTabFn('uninstalled')">{{ t("uninstalledLabel") }}</div>
</div> <div :class="['flex items-center text-[14px] h-[32px] border-[1px] border-solid my-[3px] border-[var(--el-color-info-light-8)] rounded-full px-[20px] mr-[24px] cursor-pointer hover:bg-[var(--el-color-info-light-8)]', { '!text-[#fff] !bg-[#000] !border-[#000]': activeName === 'all' }]" @click="activeNameTabFn('all')">{{ t("buyLabel") }}</div>
<div :class="['flex items-center text-[14px] h-[32px] text-[#a6a9ad] border-[1px] border-solid my-[3px] border-[var(--el-color-info-light-8)] rounded-full px-[20px] mr-[24px] cursor-pointer hover:bg-[var(--el-color-info-light-8)]', { '!text-[#fff] !bg-[#000] !border-[#000]': activeName === 'uninstalled' }]" @click="activeNameTabFn('uninstalled')"> <div :class="['relative flex items-center text-[14px] h-[32px] border-[1px] border-solid my-[3px] border-[var(--el-color-info-light-8)] rounded-full px-[20px] mr-[24px] cursor-pointer hover:bg-[var(--el-color-info-light-8)]', { '!text-[#fff] !bg-[#000] !border-[#000]': activeName === 'recentlyUpdated' }]" @click="activeNameTabFn('recentlyUpdated')">
{{ t('uninstalledLabel') }} <span v-if="localList['recentlyUpdated'].length > 0" class="w-[9px] h-[9px] bg-[#FF0000]" style="position: absolute; border-radius: 50%; right: 5px; top: -5px"></span>
</div> <span>{{ t('recentlyUpdated') }}</span>
<div :class="['flex items-center text-[14px] h-[32px] text-[#a6a9ad] border-[1px] border-solid my-[3px] border-[var(--el-color-info-light-8)] rounded-full px-[20px] mr-[24px] cursor-pointer hover:bg-[var(--el-color-info-light-8)]', { '!text-[#fff] !bg-[#000] !border-[#000]': activeName === 'all' }]" @click="activeNameTabFn('all')">
{{ t('buyLabel') }}
</div> </div>
</div> </div>
<el-button type="primary" round @click="handleCloudBuild" :loading="cloudBuildRef?.loading">{{ t('cloudBuild') }}</el-button> <div>
<el-button type="primary" v-show="activeName === 'recentlyUpdated'" round @click="batchUpgrade" :loading="upgradeRef?.loading" :disabled="authLoading">{{ t("batchUpgrade") }}</el-button>
<el-button type="primary" round @click="handleCloudBuild" :loading="cloudBuildRef?.loading" :disabled="authLoading">{{ t("cloudBuild") }}</el-button>
</div>
</div> </div>
<div> <div>
<el-table v-if="localList[activeName].length&&!authLoading" :data="info[activeName]" size="large" class="pt-[5px]"> <el-table v-if="localList[activeName].length && !loading" :data="info[activeName]" size="large" class="pt-[5px]" @selection-change="handleSelectionChange">
<el-table-column :label="t('appName')" align="left" width="320"> <el-table-column type="selection" v-if="activeName === 'recentlyUpdated'" />
<el-table-column :label="t('appName')" align="left" width="450">
<template #default="{ row }"> <template #default="{ row }">
<div class="flex items-center cursor-pointer" @click = "handleTips"> <div class="flex items-center cursor-pointer">
<el-image class="w-[54px] h-[54px]" :src="row.icon" fit="contain"> <el-image class="w-[54px] h-[54px]" :src="row.icon" fit="contain">
<template #error> <template #error>
<div class="flex items-center w-full h-full"> <div class="flex items-center w-full h-full">
<img class="max-w-full max-h-full" src="@/app/assets/images/icon-addon.png" alt=""> <img class="max-w-full max-h-full" src="@/app/assets/images/icon-addon.png" alt="" />
</div> </div>
</template> </template>
</el-image> </el-image>
<div class="flex flex-col justify-center pl-[20px] font-500 text-[13px]"> <div class="flex-1 w-0 flex flex-col justify-center pl-[20px] font-500 text-[13px]">
<div class="w-[236px] truncate leading-[18px]">{{ row.title }}</div> <div class="w-[236px] truncate leading-[18px]">{{ row.title }}</div>
<div class="w-[236px] truncate leading-[18px] mt-[6px]" v-if="row.install_info && Object.keys(row.install_info)?.length">{{ row.install_info.version }}</div> <div class="w-[236px] truncate leading-[18px] mt-[6px]" v-if="row.install_info && Object.keys(row.install_info)?.length">{{ row.install_info.version }}</div>
<div class="w-[236px] truncate leading-[18px] mt-[6px]" v-else>{{ row.version }}</div> <div class="w-[236px] truncate leading-[18px] mt-[6px]" v-else>{{ row.version }}</div>
<div class="mt-[3px]" v-if="row.install_info && Object.keys(row.install_info)?.length && row.install_info.version != row.version"> <div class="mt-[3px] flex flex-nowrap">
<el-tag type="danger" size="small">{{ t('newVersion') }}{{ row.version }}</el-tag> <el-tag type="danger" size="small" v-if="activeName == 'recentlyUpdated' && row.install_info && Object.keys(row.install_info)?.length && row.install_info.version != row.version">{{ t("newVersion") }}{{ row.version }}</el-tag>
<el-tooltip v-if="versionJudge(row)" effect="dark" content="该插件与框架版本不兼容,可能存在未知问题" placement="top-start">
<el-tag type="info" size="small" class="ml-[3px]">该插件与框架版本不兼容可能存在未知问题</el-tag>
</el-tooltip>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="left" min-width="120"> <el-table-column align="left" min-width="150">
<template #header> <template #header>
<div class="flex items-center"> <div class="flex items-center">
<span class="font-500 text-[13px] mr-[5px]">{{ t('appIdentification') }}</span> <span class="font-500 text-[13px] mr-[5px]">{{ t("appIdentification") }}</span>
<el-tooltip class="box-item" effect="light" :content="t('tipText')" placement="bottom"> <el-tooltip class="box-item" effect="light" :content="t('tipText')" placement="bottom">
<el-icon class="cursor-pointer text-[16px] text-[#a9a9a9]"> <el-icon class="cursor-pointer text-[16px] text-[#a9a9a9]">
<QuestionFilled /> <QuestionFilled />
@ -73,65 +77,67 @@
<span class="font-500 text-[13px] multi-hidden">{{ row.desc }}</span> <span class="font-500 text-[13px] multi-hidden">{{ row.desc }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('type')" align="left" min-width="100"> <el-table-column :label="t('type')" align="left" min-width="80">
<template #default="{ row }"> <template #default="{ row }">
<span class="font-500 text-[13px]">{{ row.type === 'app' ? t('app') : t('addon') }}</span> <span class="font-500 text-[13px]">{{ row.type === "app" ? t("app") : t("addon") }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="" :label="t('author')" align="left" min-width="100"> <el-table-column prop="" :label="t('author')" align="left" min-width="80">
<template #default="{ row }"> <template #default="{ row }">
<span class="font-500 text-[13px]">{{ row.author }}</span> <span class="font-500 text-[13px]">{{ row.author }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('operation')" fixed="right" align="right" width="200"> <el-table-column :label="t('operation')" fixed="right" align="right" width="250">
<template #default="{ row }"> <template #default="{ row }">
<el-button class="!text-[13px]" v-if="row.install_info && Object.keys(row.install_info)?.length && row.install_info.version != row.version" type="primary" link @click="upgradeAddonFn(row.key)">{{ t('upgrade') }}</el-button> <el-button class="!text-[13px]" v-if="activeName == 'recentlyUpdated' && row.install_info && Object.keys(row.install_info)?.length && row.install_info.version != row.version" type="primary" link @click="upgradeAddonFn(row.key)">{{ t("upgrade") }}</el-button>
<el-button class="!text-[13px]" v-if="row.install_info && Object.keys(row.install_info)?.length" type="primary" link @click="uninstallAddonFn(row.key)">{{ t('unload') }}</el-button> <el-button class="!text-[13px]" v-if="row.install_info && Object.keys(row.install_info)?.length" type="primary" link @click="uninstallAddonFn(row.key)">{{ t("unload") }}</el-button>
<template v-if="row.is_download && (!row.install_info || !Object.keys(row.install_info).length)"> <template v-if="row.is_download && (!row.install_info || !Object.keys(row.install_info).length)">
<el-button class="!text-[13px]" type="primary" link @click="installAddonFn(row.key)">{{ t('install') }}</el-button> <el-button class="!text-[13px]" type="primary" link @click="installAddonFn(row.key)">{{ t("install") }}</el-button>
<el-button class="!text-[13px]" type="primary" link @click="deleteAddonFn(row.key)">{{ t('delete') }}</el-button> <el-button class="!text-[13px]" type="primary" link @click="deleteAddonFn(row.key)">{{ t("delete") }}</el-button>
</template> </template>
<el-button class="!text-[13px]" v-if="!row.is_download" :loading="downloading == row.key" :disabled="downloading != ''" type="primary" link @click.stop="downEvent(row)"> <el-button class="!text-[13px]" v-if="!row.is_download" :loading="downloading == row.key" :disabled="downloading != ''" type="primary" link @click.stop="downEvent(row)">
<span>{{ t('down') }}</span> <span>{{ t("down") }}</span>
</el-button> </el-button>
<el-button class="!text-[13px]" type="primary" link @click="getAddonDetialFn(row)">{{ t('detail') }}</el-button> <el-button class="!text-[13px]" type="primary" link @click="getAddonDetailFn(row)">{{ t("detail") }}</el-button>
<el-button class="!text-[13px]" type="primary" link @click="updateInformationFn(row)">更新信息</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="data-loading" v-if="authLoading || !localList[activeName].length"> <div class="data-loading" v-if="loading || !localList[activeName].length">
<el-table :data="[]" size="large" class="pt-[5px]"> <el-table :data="[]" size="large" class="pt-[5px]">
<el-table-column :label="t('appName')" align="left" width="320"></el-table-column> <el-table-column :label="t('appName')" align="left" width="320" />
<el-table-column align="left" min-width="120"></el-table-column> <el-table-column align="left" min-width="120" />
<el-table-column prop="" :label="t('introduction')" align="left" min-width="200"></el-table-column> <el-table-column :label="t('introduction')" align="left" min-width="200" />
<el-table-column :label="t('type')" align="left" min-width="100"></el-table-column> <el-table-column :label="t('type')" align="left" min-width="100" />
<el-table-column prop="" :label="t('author')" align="left" min-width="100"></el-table-column> <el-table-column :label="t('author')" align="left" min-width="100" />
<el-table-column :label="t('operation')" fixed="right" align="right" width="150"></el-table-column> <el-table-column :label="t('operation')" fixed="right" align="right" width="150" />
<template #empty><span></span></template> <template #empty>
<span></span>
</template>
</el-table> </el-table>
<div class="h-[100px]" v-loading="authLoading" v-if="authLoading"> <div class="h-[100px]" v-loading="loading" v-if="loading"></div>
</div>
</div> </div>
<el-empty class="mx-auto overview-empty" v-if="!localList.installed.length && !loading && activeName == 'installed' && !authLoading"> <el-empty class="mx-auto overview-empty" v-if="!localList.installed.length && !loading && activeName == 'installed' && !authLoading">
<template #image> <template #image>
<div class="w-[230px] mx-auto"> <div class="w-[230px] mx-auto">
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt=""> <img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="" />
</div> </div>
</template> </template>
<template #description> <template #description>
<p class="flex items-center">{{ t('installed-empty') }}</p> <p class="flex items-center">{{ t("installed-empty") }}</p>
</template> </template>
</el-empty> </el-empty>
<el-empty class="mx-auto overview-empty" v-if="!localList.uninstalled.length && !loading && activeName == 'uninstalled' && !authLoading"> <el-empty class="mx-auto overview-empty" v-if="!localList.uninstalled.length && !loading && activeName == 'uninstalled' && !authLoading">
<template #image> <template #image>
<div class="w-[230px] mx-auto"> <div class="w-[230px] mx-auto">
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt=""> <img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="" />
</div> </div>
</template> </template>
<template #description> <template #description>
<p class="flex items-center"> <p class="flex items-center">
<span>{{ t('descriptionLeft') }}</span> <span>{{ t("descriptionLeft") }}</span>
<el-link type="primary" @click="goRouter" class="mx-[5px]">{{ t('link') }}</el-link> <el-link type="primary" @click="goRouter" class="mx-[5px]">{{ t("link") }}</el-link>
<span>{{ t('descriptionRight') }}</span> <span>{{ t("descriptionRight") }}</span>
</p> </p>
</template> </template>
</el-empty> </el-empty>
@ -141,8 +147,7 @@
<el-button class="w-[154px] !h-[48px] mt-[8px]" type="primary" @click="authCodeApproveFn">授权码认证</el-button> <el-button class="w-[154px] !h-[48px] mt-[8px]" type="primary" @click="authCodeApproveFn">授权码认证</el-button>
<el-popover ref="getAuthCodeDialog" placement="bottom" :width="478" trigger="click" class="mt-[8px]"> <el-popover ref="getAuthCodeDialog" placement="bottom" :width="478" trigger="click" class="mt-[8px]">
<div class="px-[18px] py-[8px]"> <div class="px-[18px] py-[8px]">
<p class="leading-[32px] text-[14px]"> <p class="leading-[32px] text-[14px]">您在官方应用市场购买任意一款应用即可获得授权码输入正确授权码认证通过后即可支持在线升级和其它相关服务</p>
您在官方应用市场购买任意一款应用即可获得授权码输入正确授权码认证通过后即可支持在线升级和其它相关服务</p>
<div class="flex justify-end mt-[36px]"> <div class="flex justify-end mt-[36px]">
<el-button class="w-[182px] !h-[48px]" plain @click="market">去应用市场逛逛</el-button> <el-button class="w-[182px] !h-[48px]" plain @click="market">去应用市场逛逛</el-button>
<el-button class="w-[100px] !h-[48px]" plain @click="getAuthCodeDialog.hide()">关闭</el-button> <el-button class="w-[100px] !h-[48px]" plain @click="getAuthCodeDialog.hide()">关闭</el-button>
@ -157,17 +162,27 @@
<el-empty class="mx-auto overview-empty" v-if="!localList.all.length && !loading && authinfo && activeName == 'all' && !authLoading"> <el-empty class="mx-auto overview-empty" v-if="!localList.all.length && !loading && authinfo && activeName == 'all' && !authLoading">
<template #image> <template #image>
<div class="w-[230px] mx-auto"> <div class="w-[230px] mx-auto">
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt=""> <img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="" />
</div> </div>
</template> </template>
<template #description> <template #description>
<p class="flex items-center"> <p class="flex items-center">
<span>{{ t('buyDescriptionLeft') }}</span> <span>{{ t("buyDescriptionLeft") }}</span>
<el-link type="primary" @click="goRouter" class="mx-[5px]">{{ t('link') }}</el-link> <el-link type="primary" @click="goRouter" class="mx-[5px]">{{ t("link") }}</el-link>
<span>{{ t('descriptionRight') }}</span> <span>{{ t("descriptionRight") }}</span>
</p> </p>
</template> </template>
</el-empty> </el-empty>
<el-empty class="mx-auto overview-empty" v-if="!localList.recentlyUpdated.length && !loading && authinfo && activeName == 'recentlyUpdated' && !authLoading">
<template #image>
<div class="w-[230px] mx-auto">
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="" />
</div>
</template>
<template #description>
<p class="flex items-center">{{ t("recentlyUpdatedEmpty") }}</p>
</template>
</el-empty>
</div> </div>
<el-dialog v-model="authCodeApproveDialog" title="授权码认证" width="400px"> <el-dialog v-model="authCodeApproveDialog" title="授权码认证" width="400px">
@ -183,13 +198,14 @@
</el-form-item> </el-form-item>
</div> </div>
<div class="text-sm mt-[10px] text-info">{{ t('authInfoTips') }}</div> <div class="text-sm mt-[10px] text-info">{{ t("authInfoTips") }}</div>
<div class="mt-[20px]"> <div class="mt-[20px]">
<el-button type="primary" class="w-full" size="large" :loading="saveLoading" @click="save(formRef)">{{ t('confirm') }}</el-button> <el-button type="primary" class="w-full" size="large" :loading="saveLoading" @click="save(formRef)">{{ t("confirm") }}
</el-button>
</div> </div>
<div class="mt-[10px] text-right"> <div class="mt-[10px] text-right">
<el-button type="primary" link @click="market">{{ t('notHaveAuth') }}</el-button> <el-button type="primary" link @click="market">{{ t("notHaveAuth") }}</el-button>
</div> </div>
</el-card> </el-card>
</el-form> </el-form>
@ -212,7 +228,7 @@
</el-form> </el-form>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button type="primary" @click="appStoreShowDialog = false">{{ t('confirm') }}</el-button> <el-button type="primary" @click="appStoreShowDialog = false">{{ t("confirm") }}</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
@ -228,17 +244,17 @@
<el-scrollbar max-height="50vh"> <el-scrollbar max-height="50vh">
<div class="min-h-[150px]"> <div class="min-h-[150px]">
<div class="my-3" v-if="installCheckResult.dir"> <div class="my-3" v-if="installCheckResult.dir">
<p class="pt-[20px] pl-[20px] ">{{ t('dirPermission') }}</p> <p class="pt-[20px] pl-[20px]">{{ t("dirPermission") }}</p>
<div class="px-[20px] pt-[10px] text-[14px]"> <div class="px-[20px] pt-[10px] text-[14px]">
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]"> <el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
<el-col :span="12"> <el-col :span="12">
<span>{{ t('path') }}</span> <span>{{ t("path") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('demand') }}</span> <span>{{ t("demand") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('status') }}</span> <span>{{ t("status") }}</span>
</el-col> </el-col>
</el-row> </el-row>
<el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in installCheckResult.dir.is_readable" :key="index"> <el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in installCheckResult.dir.is_readable" :key="index">
@ -246,10 +262,14 @@
<span>{{ item.dir }}</span> <span>{{ item.dir }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('readable') }}</span> <span>{{ t("readable") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span> <span v-if="item.status">
<el-icon color="green">
<Select />
</el-icon>
</span>
<span v-else> <span v-else>
<el-icon color="red"> <el-icon color="red">
<CloseBold /> <CloseBold />
@ -262,10 +282,14 @@
<span>{{ item.dir }}</span> <span>{{ item.dir }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('write') }}</span> <span>{{ t("write") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span> <span v-if="item.status">
<el-icon color="green">
<Select />
</el-icon>
</span>
<span v-else> <span v-else>
<el-icon color="red"> <el-icon color="red">
<CloseBold /> <CloseBold />
@ -279,10 +303,10 @@
</el-scrollbar> </el-scrollbar>
<div class="flex justify-end"> <div class="flex justify-end">
<el-tooltip effect="dark" :content="t('installTips')" placement="top"> <el-tooltip effect="dark" :content="t('installTips')" placement="top">
<el-button type="default" :disabled="!installCheckResult.is_pass || cloudInstalling" :loading="localInstalling" @click="handleInstall">{{ t('localInstall') }}</el-button> <el-button :disabled="!installCheckResult.is_pass || cloudInstalling" :loading="localInstalling" @click="handleInstall">{{ t("localInstall") }}</el-button>
</el-tooltip> </el-tooltip>
<el-tooltip effect="dark" :content="t('cloudInstallTips')" placement="top"> <el-tooltip effect="dark" :content="t('cloudInstallTips')" placement="top">
<el-button type="primary" :disabled="!installCheckResult.is_pass || localInstalling" :loading="cloudInstalling" @click="handleCloudInstall">{{ t('cloudInstall') }}</el-button> <el-button type="primary" :disabled="!installCheckResult.is_pass || localInstalling" :loading="cloudInstalling" @click="handleCloudInstall">{{ t("cloudInstall") }}</el-button>
</el-tooltip> </el-tooltip>
</div> </div>
</div> </div>
@ -302,17 +326,17 @@
<el-scrollbar max-height="50vh"> <el-scrollbar max-height="50vh">
<div class="min-h-[150px]"> <div class="min-h-[150px]">
<div class="bg-[#fff] my-3" v-if="uninstallCheckResult.dir"> <div class="bg-[#fff] my-3" v-if="uninstallCheckResult.dir">
<p class="pt-[20px] pl-[20px] ">{{ t('dirPermission') }}</p> <p class="pt-[20px] pl-[20px]">{{ t("dirPermission") }}</p>
<div class="px-[20px] pt-[10px] text-[14px]"> <div class="px-[20px] pt-[10px] text-[14px]">
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]"> <el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
<el-col :span="12"> <el-col :span="12">
<span>{{ t('path') }}</span> <span>{{ t("path") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('demand') }}</span> <span>{{ t("demand") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('status') }}</span> <span>{{ t("status") }}</span>
</el-col> </el-col>
</el-row> </el-row>
<el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in uninstallCheckResult.dir.is_readable" :key="index"> <el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in uninstallCheckResult.dir.is_readable" :key="index">
@ -320,10 +344,14 @@
<span>{{ item.dir }}</span> <span>{{ item.dir }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('readable') }}</span> <span>{{ t("readable") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span> <span v-if="item.status">
<el-icon color="green">
<Select />
</el-icon>
</span>
<span v-else> <span v-else>
<el-icon color="red"> <el-icon color="red">
<CloseBold /> <CloseBold />
@ -336,10 +364,14 @@
<span>{{ item.dir }}</span> <span>{{ item.dir }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span>{{ t('write') }}</span> <span>{{ t("write") }}</span>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span> <span v-if="item.status">
<el-icon color="green">
<Select />
</el-icon>
</span>
<span v-else> <span v-else>
<el-icon color="red"> <el-icon color="red">
<CloseBold /> <CloseBold />
@ -363,20 +395,31 @@
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
<!-- 更新信息 -->
</el-card> </el-card>
</div> </div>
<upgrade-log :upgradeKey="upgradeKey" ref="upgradeLogRef" />
<upgrade ref="upgradeRef" @complete="localListFn"/> <upgrade ref="upgradeRef" @complete="localListFn" @cloudbuild="handleCloudBuild" />
<cloud-build ref="cloudBuildRef" /> <cloud-build ref="cloudBuildRef" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, watch, h } from 'vue' import { ref, reactive, watch, h } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getAddonLocal, uninstallAddon, installAddon, preInstallCheck, cloudInstallAddon, getAddonInstalltask, getAddonCloudInstallLog, preUninstallCheck, cancelInstall } from '@/app/api/addon' import {
getAddonLocal,
uninstallAddon,
installAddon,
preInstallCheck,
cloudInstallAddon,
getAddonInstalltask,
getAddonCloudInstallLog,
preUninstallCheck,
cancelInstall
} from '@/app/api/addon'
import { deleteAddonDevelop } from '@/app/api/tools' import { deleteAddonDevelop } from '@/app/api/tools'
import { getInstallConfig } from '@/app/api/sys'
import { downloadVersion, getAuthInfo, setAuthInfo } from '@/app/api/module' import { downloadVersion, getAuthInfo, setAuthInfo } from '@/app/api/module'
import { getVersions } from '@/app/api/auth'
import { ElMessage, ElMessageBox, ElNotification, FormInstance, FormRules } from 'element-plus' import { ElMessage, ElMessageBox, ElNotification, FormInstance, FormRules } from 'element-plus'
import 'vue-web-terminal/lib/theme/dark.css' import 'vue-web-terminal/lib/theme/dark.css'
import { Terminal, TerminalFlash } from 'vue-web-terminal' import { Terminal, TerminalFlash } from 'vue-web-terminal'
@ -386,6 +429,7 @@ import { useRouter, useRoute } from 'vue-router'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import Upgrade from '@/app/components/upgrade/index.vue' import Upgrade from '@/app/components/upgrade/index.vue'
import CloudBuild from '@/app/components/cloud-build/index.vue' import CloudBuild from '@/app/components/cloud-build/index.vue'
import UpgradeLog from '@/app/components/upgrade-log/index.vue'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
@ -398,11 +442,11 @@ const installAfterTips = ref<string[]>([])
const userStore = useUserStore() const userStore = useUserStore()
const unloadHintDialog = ref(false) const unloadHintDialog = ref(false)
const terminalRef = ref(null) const terminalRef = ref(null)
const installPhpConfig = ref(null) const frameworkVersion = ref('')
const upgradeLogRef = ref<any>(null)
getInstallConfig().then(({ data }) => { getVersions().then((res) => {
installPhpConfig.value = data frameworkVersion.value = res.data.version.version
}).catch() })
const currDownData = ref() const currDownData = ref()
const downEventHintFn = () => { const downEventHintFn = () => {
@ -437,11 +481,10 @@ const downEvent = (param: Record<string, any>, isDown = false) => {
} }
const authCode = ref('') const authCode = ref('')
getAuthInfo().then(res => { getAuthInfo().then((res) => {
if (res.data.data && res.data.data.auth_code) { if (res.data.data && res.data.data.auth_code) {
authCode.value = res.data.data.auth_code authCode.value = res.data.data.auth_code
} }
}).catch(() => {
}) })
/** /**
@ -452,39 +495,47 @@ const search_name = ref('')
const info = ref({ const info = ref({
installed: [], installed: [],
uninstalled: [], uninstalled: [],
all: [] all: [],
recentlyUpdated: []
}) })
const query = () => { const query = () => {
if (search_name.value == '' || search_name.value == null) { if (search_name.value == '' || search_name.value == null) {
info.value.installed = localList.value.installed info.value.installed = localList.value.installed
info.value.uninstalled = localList.value.uninstalled info.value.uninstalled = localList.value.uninstalled
info.value.all = localList.value.all info.value.all = localList.value.all
info.value.recentlyUpdated = localList.value.recentlyUpdated
return false return false
} }
info.value.installed = localList.value.installed.filter((el: any) => el.title.indexOf(search_name.value) != -1) info.value.installed = localList.value.installed.filter((el: any) => el.title.indexOf(search_name.value) != -1)
info.value.uninstalled = localList.value.uninstalled.filter((el: any) => el.title.indexOf(search_name.value) != -1) info.value.uninstalled = localList.value.uninstalled.filter((el: any) => el.title.indexOf(search_name.value) != -1)
info.value.all = localList.value.all.filter((el: any) => el.title.indexOf(search_name.value) != -1) info.value.all = localList.value.all.filter((el: any) => el.title.indexOf(search_name.value) != -1)
info.value.recentlyUpdated = localList.value.recentlyUpdated.filter((el: any) => el.title.indexOf(search_name.value) != -1)
} }
const localList = ref({ const localList = ref({
installed: [], installed: [],
uninstalled: [], uninstalled: [],
all: [], all: [],
recentlyUpdated: [],
error: '' error: ''
}) })
const localListFn = () => { const localListFn = () => {
loading.value = true loading.value = true
getAddonLocal({}).then(res => { getAddonLocal({}).then((res) => {
const data = res.data.list const data = res.data.list
localList.value.error = res.data.error localList.value.error = res.data.error
localList.value.installed = [] localList.value.installed = []
localList.value.uninstalled = [] localList.value.uninstalled = []
localList.value.all = [] localList.value.all = []
localList.value.recentlyUpdated = []
for (const i in data) { for (const i in data) {
if (data[i].is_local == false) localList.value.all.push(data[i]) if (data[i].is_local == false) localList.value.all.push(data[i])
if (data[i].install_info && Object.keys(data[i].install_info)?.length) { if (data[i].install_info && Object.keys(data[i].install_info)?.length) {
localList.value.installed.push(data[i]) localList.value.installed.push(data[i])
if (data[i].install_info.version != data[i].version) {
localList.value.recentlyUpdated.push(data[i])
}
} else { } else {
if (data[i].is_download == true) localList.value.uninstalled.push(data[i]) if (data[i].is_download == true) localList.value.uninstalled.push(data[i])
} }
@ -506,11 +557,6 @@ const localListFn = () => {
localListFn() localListFn()
//
const handleTips = () => {
ElMessage('请在站点中运行程序!')
}
// //
const appLink: any = ref({}) const appLink: any = ref({})
@ -553,7 +599,7 @@ function makeIterator(array: string[]) {
let nextIndex = 0 let nextIndex = 0
return { return {
next () { next () {
if ((nextIndex + 1) == array.length) { if (nextIndex + 1 == array.length) {
nextIndex = 0 nextIndex = 0
} }
return { value: array[nextIndex++] } return { value: array[nextIndex++] }
@ -568,13 +614,13 @@ function makeIterator(array: string[]) {
const installAddonFn = (key: string) => { const installAddonFn = (key: string) => {
currAddon.value = key currAddon.value = key
preInstallCheck(key).then(res => { preInstallCheck(key).then((res) => {
installStep.value = 1 installStep.value = 1
installShowDialog.value = true installShowDialog.value = true
installAfterTips.value = [] installAfterTips.value = []
installCheckResult.value = res.data installCheckResult.value = res.data
userStore.clearRouters() userStore.clearRouters()
}).catch(() => { }) })
} }
/** /**
@ -582,7 +628,7 @@ const installAddonFn = (key: string) => {
*/ */
let notificationEl = null let notificationEl = null
const getInstallTask = (first: boolean = true) => { const getInstallTask = (first: boolean = true) => {
getAddonInstalltask().then(res => { getAddonInstalltask().then((res) => {
if (res.data) { if (res.data) {
if (first) { if (first) {
installLog = [] installLog = []
@ -591,10 +637,10 @@ const getInstallTask = (first: boolean = true) => {
notificationEl = ElNotification.success({ notificationEl = ElNotification.success({
title: t('warning'), title: t('warning'),
dangerouslyUseHTMLString: true, dangerouslyUseHTMLString: true,
message: h('div', {}, [ message: h('div', {}, [t('installingTips'), h('span', {
t('installingTips'), class: 'text-primary cursor-pointer',
h('span', { class: 'text-primary cursor-pointer', onClick: checkInstallTask }, [t('installPercent')]) onClick: checkInstallTask
]), }, [t('installPercent')])]),
duration: 0, duration: 0,
showClose: false showClose: false
}) })
@ -638,7 +684,7 @@ const handleInstall = () => {
if (!installCheckResult.value.is_pass || localInstalling.value) return if (!installCheckResult.value.is_pass || localInstalling.value) return
localInstalling.value = true localInstalling.value = true
installAddon({ addon: currAddon.value }).then(res => { installAddon({ addon: currAddon.value }).then((res) => {
installStep.value = 3 installStep.value = 3
localListFn() localListFn()
localInstalling.value = false localInstalling.value = false
@ -662,27 +708,25 @@ const handleCloudInstall = () => {
if (!installCheckResult.value.is_pass || cloudInstalling.value) return if (!installCheckResult.value.is_pass || cloudInstalling.value) return
cloudInstalling.value = true cloudInstalling.value = true
cloudInstallAddon({ addon: currAddon.value }).then(res => { cloudInstallAddon({ addon: currAddon.value })
.then((res) => {
installStep.value = 2 installStep.value = 2
terminalRef.value.execute('clear') terminalRef.value.execute('clear')
terminalRef.value.execute('开始安装插件') terminalRef.value.execute('开始安装插件')
getInstallTask() getInstallTask()
cloudInstalling.value = false cloudInstalling.value = false
}).catch((res) => { })
.catch((res) => {
cloudInstalling.value = false cloudInstalling.value = false
}) })
} }
const authElMessageBox = () => { const authElMessageBox = () => {
ElMessageBox.confirm( ElMessageBox.confirm(t('authTips'), t('warning'), {
t('authTips'),
t('warning'),
{
distinguishCancelAndClose: true, distinguishCancelAndClose: true,
confirmButtonText: t('toBind'), confirmButtonText: t('toBind'),
cancelButtonText: t('toNiucloud') cancelButtonText: t('toNiucloud')
} }).then(() => {
).then(() => {
authCodeApproveFn() authCodeApproveFn()
}).catch((action: string) => { }).catch((action: string) => {
if (action === 'cancel') { if (action === 'cancel') {
@ -693,13 +737,12 @@ const authElMessageBox = () => {
let installLog: string[] = [] let installLog: string[] = []
const getCloudInstallLog = () => { const getCloudInstallLog = () => {
getAddonCloudInstallLog(currAddon.value) getAddonCloudInstallLog(currAddon.value).then((res) => {
.then(res => {
const data = res.data.data ?? [] const data = res.data.data ?? []
if (data[0] && data[0].length && installShowDialog.value == true) { if (data[0] && data[0].length && installShowDialog.value == true) {
data[0].forEach(item => { data[0].forEach((item) => {
if (!installLog.includes(item.action)) { if (!installLog.includes(item.action)) {
terminalRef.value.pushMessage({ content: `正在执行:${item.action}` }) terminalRef.value.pushMessage({ content: `${ item.action }` })
installLog.push(item.action) installLog.push(item.action)
if (item.code == 0) { if (item.code == 0) {
@ -708,8 +751,7 @@ const getCloudInstallLog = () => {
} }
}) })
} }
}) }).catch(() => {
.catch(() => {
notificationEl?.close() notificationEl?.close()
}) })
} }
@ -729,17 +771,13 @@ const uninstallCheckResult = ref({})
* @param key * @param key
*/ */
const uninstallAddonFn = (key: string) => { const uninstallAddonFn = (key: string) => {
ElMessageBox.confirm( ElMessageBox.confirm(t('uninstallTips'), t('warning'), {
t('uninstallTips'),
t('warning'),
{
confirmButtonText: t('confirm'), confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'), cancelButtonText: t('cancel'),
type: 'warning' type: 'warning'
} }).then(() => {
).then(() => {
handleUninstallAddon(key) handleUninstallAddon(key)
}).catch(() => { }) })
} }
/** /**
@ -762,13 +800,11 @@ const handleCloudBuild = () => {
cloudBuildRef.value?.open() cloudBuildRef.value?.open()
return return
} }
ElMessageBox.confirm(t('cloudBuildTips'), t('warning'), ElMessageBox.confirm(t('cloudBuildTips'), t('warning'), {
{
confirmButtonText: t('confirm'), confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'), cancelButtonText: t('cancel'),
type: 'warning' type: 'warning'
} }).then(() => {
).then(() => {
cloudBuildRef.value?.open() cloudBuildRef.value?.open()
}) })
} }
@ -776,7 +812,7 @@ const handleCloudBuild = () => {
const handleUninstallAddon = (key: string) => { const handleUninstallAddon = (key: string) => {
preUninstallCheck(key).then(({ data }) => { preUninstallCheck(key).then(({ data }) => {
if (data.is_pass) { if (data.is_pass) {
uninstallAddon({ addon: key }).then(res => { uninstallAddon({ addon: key }).then((res) => {
localListFn() localListFn()
userStore.clearRouters() userStore.clearRouters()
loading.value = false loading.value = false
@ -791,7 +827,7 @@ const handleUninstallAddon = (key: string) => {
} }
const market = () => { const market = () => {
window.open(installPhpConfig.value.website_url) window.open('https://www.niucloud.com/app')
} }
/** /**
@ -800,34 +836,40 @@ const market = () => {
*/ */
const installShowDialogClose = (done: () => {}) => { const installShowDialogClose = (done: () => {}) => {
if (installStep.value == 2) { if (installStep.value == 2) {
ElMessageBox.confirm( ElMessageBox.confirm(t('installShowDialogCloseTips'), t('warning'), {
t('installShowDialogCloseTips'),
t('warning'),
{
confirmButtonText: t('confirm'), confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'), cancelButtonText: t('cancel'),
type: 'warning' type: 'warning'
} }).then(() => {
).then(() => {
cancelInstall(currAddon.value) cancelInstall(currAddon.value)
done() done()
}).catch(() => { }) })
} else if (installStep.value == 3) { } else if (installStep.value == 3) {
activeNameTabFn('installed') activeNameTabFn('installed')
location.reload() location.reload()
} else done() } else {
done()
}
flashInterval && clearInterval(flashInterval) flashInterval && clearInterval(flashInterval)
} }
// //
const appStoreShowDialog = ref(false) const appStoreShowDialog = ref(false)
const appStoreInfo = ref<AnyObject>({}) const appStoreInfo = ref({})
const getAddonDetialFn = (data: AnyObject) => { const getAddonDetailFn = (data: any) => {
appStoreShowDialog.value = true appStoreShowDialog.value = true
appStoreInfo.value = data appStoreInfo.value = data
} }
//
const upgradeKey = ref<string>('')
const updateInformationFn = (data: any) => {
// updateInformationDialog.value = true
upgradeKey.value = data.key
upgradeLogRef.value?.open()
}
// //
const authCodeApproveDialog = ref(false) const authCodeApproveDialog = ref(false)
const authinfo = ref('') const authinfo = ref('')
@ -836,18 +878,17 @@ const saveLoading = ref(false)
const authLoading = ref(true) const authLoading = ref(true)
const checkAppMange = () => { const checkAppMange = () => {
authLoading.value = true authLoading.value = true
getAuthInfo() getAuthInfo().then((res) => {
.then((res) => {
authLoading.value = false authLoading.value = false
if (res.data.data && res.data.data.length != 0) { if (res.data.data && res.data.data.length != 0) {
authinfo.value = res.data.data authinfo.value = res.data.data
} }
}) }).catch(() => {
.catch(() => {
authLoading.value = false authLoading.value = false
authCodeApproveDialog.value = false authCodeApproveDialog.value = false
}) })
} }
checkAppMange() checkAppMange()
const authCodeApproveFn = () => { const authCodeApproveFn = () => {
authCodeApproveDialog.value = true authCodeApproveDialog.value = true
@ -861,12 +902,8 @@ const formRef = ref<FormInstance>()
// //
const formRules = reactive<FormRules>({ const formRules = reactive<FormRules>({
auth_code: [ auth_code: [{ required: true, message: t('authCodePlaceholder'), trigger: 'blur' }],
{ required: true, message: t('authCodePlaceholder'), trigger: 'blur' } auth_secret: [{ required: true, message: t('authSecretPlaceholder'), trigger: 'blur' }]
],
auth_secret: [
{ required: true, message: t('authSecretPlaceholder'), trigger: 'blur' }
]
}) })
const save = async (formEl: FormInstance | undefined) => { const save = async (formEl: FormInstance | undefined) => {
@ -876,14 +913,12 @@ const save = async (formEl: FormInstance | undefined) => {
if (valid) { if (valid) {
saveLoading.value = true saveLoading.value = true
setAuthInfo(formData) setAuthInfo(formData).then(() => {
.then(() => {
saveLoading.value = false saveLoading.value = false
setTimeout(() => { setTimeout(() => {
location.reload() location.reload()
}, 1000) }, 1000)
}) }).catch(() => {
.catch(() => {
saveLoading.value = false saveLoading.value = false
}) })
} }
@ -895,24 +930,40 @@ const goRouter = () => {
} }
const deleteAddonFn = (key: string) => { const deleteAddonFn = (key: string) => {
ElMessageBox.confirm( ElMessageBox.confirm(t('deleteAddonTips'), t('warning'), {
t('deleteAddonTips'),
t('warning'),
{
confirmButtonText: t('confirm'), confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'), cancelButtonText: t('cancel'),
type: 'warning' type: 'warning'
} }).then(() => {
).then(() => {
deleteAddonDevelop(key).then(() => { deleteAddonDevelop(key).then(() => {
localListFn() localListFn()
}) })
}).catch(() => { }) })
}
const versionJudge = (row: any) => {
if (!row.support_version) return true
const supportVersionApp = row.support_version.split('.')
const frameworkVersionArr = frameworkVersion.value.split('.')
if (parseFloat(`${ supportVersionApp[0] }.${ supportVersionApp[1] }`) < parseFloat(`${ frameworkVersionArr[0] }.${ frameworkVersionArr[1] }`)) return true
return false
}
let batchUpgradeApp = []
const handleSelectionChange = (e: any) => {
batchUpgradeApp = e.map(item => item.key)
}
const batchUpgrade = () => {
if (!batchUpgradeApp.length) {
ElMessage({ message: '请先勾选要升级的插件', type: 'error', duration: 5000 })
return
}
upgradeAddonFn(batchUpgradeApp.toString())
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* 多行超出隐藏 */
.multi-hidden { .multi-hidden {
word-break: break-all; word-break: break-all;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -922,50 +973,6 @@ const deleteAddonFn = (key: string) => {
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
.demo-tabs>.el-tabs__content {
padding: 32px;
color: #6b778c;
font-size: 32px;
font-weight: 600;
}
.plug-item {
.plug-item-operate {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
font-size: var(--el-font-size-extra-small);
}
}
:deep(.t-container) {
box-shadow: none !important;
.t-window {
padding: 10px 20px !important;
}
}
.switch-btn.active {
border-color: var(--el-color-primary);
color: #fff;
background-color: var(--el-color-primary);
}
.plug-large {
.plug-item-operate {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
font-size: var(--el-font-size-extra-small);
}
}
.app-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
// -- // --
.table-head-bg { .table-head-bg {
background: #f5f7f9; background: #f5f7f9;
@ -980,13 +987,10 @@ html.dark .table-head-bg {
line-height: 18px; line-height: 18px;
} }
.app-store {
height: calc(100vh - 120px);
box-sizing: border-box;
}
:deep(.terminal .t-log-box span) { :deep(.terminal .t-log-box span) {
white-space: pre-wrap; white-space: pre-wrap;
} }
:deep(.data-loading) { :deep(.data-loading) {
.el-table__body-wrapper { .el-table__body-wrapper {
display: none !important; display: none !important;

View File

@ -4,7 +4,7 @@
<el-input class="input-width" v-model.trim="formData.continue_sign" @keyup="filterNumber($event)" :maxlength="3" clearable /> <el-input class="input-width" v-model.trim="formData.continue_sign" @keyup="filterNumber($event)" :maxlength="3" clearable />
<span class="ml-[10px]">{{ t('day') }}</span> <span class="ml-[10px]">{{ t('day') }}</span>
</el-form-item> </el-form-item>
<el-form-item :label="t('continueSign')" > <el-form-item :label="t('continueSignAward')">
<div class="flex-1"> <div class="flex-1">
<div v-for="(item,index) in gifts" :key="index" class="mb-[15px]"> <div v-for="(item,index) in gifts" :key="index" class="mb-[15px]">
<component :is="item.component" v-model="formData[item.key]" ref="giftRefs" v-if="item.component" /> <component :is="item.component" v-model="formData[item.key]" ref="giftRefs" v-if="item.component" />
@ -38,6 +38,10 @@ const props = defineProps({
default: () => { default: () => {
return {} return {}
} }
},
sign_period: {
type: Number,
default: 0
} }
}) })
const emits = defineEmits(['update:modelValue']) const emits = defineEmits(['update:modelValue'])
@ -94,6 +98,8 @@ const formRules = reactive<FormRules>({
callback(t('continueSignFormatError')) callback(t('continueSignFormatError'))
} else if (value < 2 || value > 365) { } else if (value < 2 || value > 365) {
callback(t('continueSignBerweenDays')) callback(t('continueSignBerweenDays'))
} else if (Number(value) > Number(props.sign_period)) {
callback(t('continueSignMustLessThanSignPeriod')) //
} else{ } else{
callback(); callback();
} }
@ -126,13 +132,16 @@ const formRules = reactive<FormRules>({
const verify = async () => { const verify = async () => {
let verify = true let verify = true
await formRef.value?.validate((valid) => {
verify = valid
})
if (!verify) return verify
for (let i = 0; i < giftRefs.value.length; i++) { for (let i = 0; i < giftRefs.value.length; i++) {
const item = giftRefs.value[i] const item = giftRefs.value[i]
!await item.verify() && (verify = false) !await item.verify() && (verify = false)
} }
await formRef.value?.validate((valid) => {
verify = valid
})
return verify return verify
} }

View File

@ -150,7 +150,6 @@ const getVerifyDetailFn = async () => {
} }
const setFormData = async (row: any = null) => { const setFormData = async (row: any = null) => {
console.log("setFormData",row);
code = row.code; code = row.code;
getVerifyDetailFn(); getVerifyDetailFn();
} }

View File

@ -90,7 +90,7 @@
<!-- 连签奖励 --> <!-- 连签奖励 -->
<el-dialog v-model="continueSignDialog" :title="t('continueSignTitle')" width="1200px" :destroy-on-close="true" v-if="formData.is_use"> <el-dialog v-model="continueSignDialog" :title="t('continueSignTitle')" width="1200px" :destroy-on-close="true" v-if="formData.is_use">
<sign-continue ref="continueRef" v-model="continue_award" /> <sign-continue ref="continueRef" v-model="continue_award" :sign_period="formData.sign_period" />
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="continueSignDialog = false">{{ t('cancel') }}</el-button> <el-button @click="continueSignDialog = false">{{ t('cancel') }}</el-button>
@ -141,7 +141,21 @@ const regExp: any = {
// //
const formRules = reactive<FormRules>({ const formRules = reactive<FormRules>({
day_award: [ day_award: [
{ required: true, message: t('daySignAwardPlaceholder'), trigger: 'change' } {
required: true,
trigger: 'change',
validator: (rule: any, value: any, callback: any) => {
let isVerify = false
daySignAwardText.value.forEach(item => {
item.is_use && (isVerify = true)
})
if (!isVerify) {
callback(t('daySignAwardPlaceholder'))
} else {
callback()
}
}
}
], ],
sign_period:[{ sign_period:[{
required: true, required: true,

View File

@ -17,8 +17,8 @@
<el-table-column :label="t('memberInfo')" min-width="120"> <el-table-column :label="t('memberInfo')" min-width="120">
<template #default="{ row }"> <template #default="{ row }">
<div class="flex items-center cursor-pointer " @click="toMember(row.member.member_id)" v-if="row.member"> <div class="flex items-center cursor-pointer " @click="toMember(row.member.member_id)" v-if="row.member">
<img class="w-[50px] h-[50px] mr-[10px]" v-if="row.member.headimg" :src="img(row.member.headimg)" alt=""> <img class="w-[50px] h-[50px] mr-[10px]" v-if="row.member.headimg" :src="img(row.member.headimg)" />
<img class="w-[50px] h-[50px] mr-[10px] rounded-full" v-else src="@/app/assets/images/member_head.png" alt=""> <img class="w-[50px] h-[50px] mr-[10px] rounded-full" v-else src="@/app/assets/images/member_head.png" />
<div class="flex flex-col"> <div class="flex flex-col">
<span>{{ row.member.nickname || '' }}</span> <span>{{ row.member.nickname || '' }}</span>
<span>{{ row.member.mobile || '' }}</span> <span>{{ row.member.mobile || '' }}</span>
@ -30,17 +30,16 @@
<el-table-column :label="t('verifyType')" min-width="120"> <el-table-column :label="t('verifyType')" min-width="120">
<template #default="{ row }"> <template #default="{ row }">
<div class="flex flex-col"> <div class="flex flex-col">
<div v-for="(item, key) in row.verify_type_array" class="my-[3px]" :key="key"> <div v-for="(item, key) in row.verify_type_array" class="my-[3px]" :key="key">{{ item.verify_type_name }}</div>
{{ item.verify_type_name }}
</div>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('createTime')" prop="create_time" min-width="120"/> <el-table-column :label="t('createTime')" prop="create_time" min-width="120"/>
<el-table-column :label="t('operation')" fixed="right" align="right" width="100"> <el-table-column :label="t('operation')" fixed="right" align="right" width="120">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button> <el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -57,12 +56,12 @@
<el-dialog v-model="showDialog" :title="t('addVerifier')" width="500px" :destroy-on-close="true"> <el-dialog v-model="showDialog" :title="t('addVerifier')" width="500px" :destroy-on-close="true">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form" v-loading="addLoading"> <el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form" v-loading="addLoading">
<el-form-item :label="t('member')" prop="member_id"> <el-form-item :label="t('member')" prop="member_id">
<el-select v-model="formData.member_id" filterable remote reserve-keyword clearable :placeholder="t('searchPlaceholder')" :remote-method="searchMember" :loading="searchLoading" class="input-width"> <el-select v-model="formData.member_id" filterable remote reserve-keyword clearable @focus="handleSelectFocus" :disabled="isEditMode" :placeholder="t('searchPlaceholder')" :remote-method="searchMember" :loading="searchLoading" class="input-width">
<el-option v-for="item in memberList" :key="item.member_id" :label="item.nickname" :value="item.member_id"/> <el-option v-for="item in memberList" :key="item.member_id" :label="item.nickname" :value="item.member_id"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('verifyType')" prop="verify_type"> <el-form-item :label="t('verifyType')" prop="verify_type">
<el-select v-model="formData.verify_type" multiple collapse-tags clearable :placeholder="t('verifyTypePlaceholder')" class="input-width"> <el-select v-model="formData.verify_type" multiple clearable :placeholder="t('verifyTypePlaceholder')" class="input-width">
<el-option v-for="(item, index) in verifyTypeList" :key="index" :label="item.name" :value="index"/> <el-option v-for="(item, index) in verifyTypeList" :key="index" :label="item.name" :value="index"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -79,10 +78,17 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref } from 'vue' import { reactive, ref, nextTick } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { getVerifierList, deleteVerifier, addVerifier, getVerifyTypeList } from '@/app/api/verify' import {
getVerifierList,
deleteVerifier,
addVerifier,
getVerifyTypeList,
getVerifyInfo,
editVerifier
} from '@/app/api/verify'
import { getMemberList } from '@/app/api/member' import { getMemberList } from '@/app/api/member'
import { ElMessageBox, FormInstance } from 'element-plus' import { ElMessageBox, FormInstance } from 'element-plus'
import { img } from '@/utils/common' import { img } from '@/utils/common'
@ -95,12 +101,15 @@ const showDialog = ref(false)
const addLoading = ref(false) const addLoading = ref(false)
const formData: Record<string, any> = reactive({ const formData: Record<string, any> = reactive({
member_id: '', member_id: '',
verify_type: '', verify_type: ''
}) })
const formRules = reactive({ const formRules = reactive({
member_id: [ member_id: [
{ required: true, message: t('memberIdPlaceholder'), trigger: 'blur' } { required: true, message: t('memberIdPlaceholder'), trigger: 'blur' }
],
verify_type: [
{ required: true, message: t('verifyTypePlaceholder'), trigger: 'blur' }
] ]
}) })
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
@ -137,10 +146,44 @@ loadVerifierList()
/** /**
* 添加核销员表 * 添加核销员表
*/ */
const isEditMode = ref(false);
const addEvent = () => { const addEvent = () => {
isEditMode.value = false
formData.member_id = ''
formData.id = ''
formData.verify_type = ''
showDialog.value = true showDialog.value = true
} }
const editEvent = async (row: any) => {
isEditMode.value = true
formData.member_id = ''
formData.verify_type = ''
memberList.value = []
try {
const res = await getVerifyInfo(row.id)
memberList.value = [{
member_id: res.data.member.member_id,
nickname: res.data.member.nickname
}]
nextTick(() => {
formData.member_id = res.data.member.member_id
formData.verify_type = res.data.verify_type
formData.id = row.id
showDialog.value = true
})
} catch (error) {
}
}
const handleSelectFocus = () => {
if (isEditMode.value && formData.member_id && memberList.value.length === 0) {
searchMember('')
}
}
/** /**
* 删除核销员表 * 删除核销员表
*/ */
@ -168,8 +211,8 @@ const addVerifiers = async (formEl: FormInstance | undefined) => {
await formEl.validate(async (valid) => { await formEl.validate(async (valid) => {
if (valid) { if (valid) {
addLoading.value = true addLoading.value = true
const api = formData.id ? editVerifier : addVerifier
addVerifier(formData).then(res => { api(formData).then(res => {
addLoading.value = false addLoading.value = false
showDialog.value = false showDialog.value = false
formData.member_id = '' formData.member_id = ''
@ -193,7 +236,7 @@ const searchMember = (query: string) => {
getMemberList({ keyword: query }).then(res => { getMemberList({ keyword: query }).then(res => {
memberList.value = res.data.data memberList.value = res.data.data
searchLoading.value = false searchLoading.value = false
}).catch() })
} else { } else {
memberList.value = [] memberList.value = []
searchLoading.value = false searchLoading.value = false
@ -207,9 +250,9 @@ const verifyTypeList = ref<any>([])
const setVerifyTypeList = () => { const setVerifyTypeList = () => {
getVerifyTypeList().then(res => { getVerifyTypeList().then(res => {
verifyTypeList.value = res.data verifyTypeList.value = res.data
}).catch() })
} }
setVerifyTypeList(); setVerifyTypeList()
/** /**
* 会员详情 * 会员详情
@ -217,7 +260,6 @@ setVerifyTypeList();
const toMember = (member_id: number) => { const toMember = (member_id: number) => {
router.push(`/member/detail?id=${ member_id }`) router.push(`/member/detail?id=${ member_id }`)
} }
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -73,14 +73,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { useRoute, useRouter } from 'vue-router' import { useRoute } from 'vue-router'
import { FormInstance } from 'element-plus' import { FormInstance } from 'element-plus'
import { getVerifyRecord, getVerifyTypeList, getVerifierSelect } from '@/app/api/verify' import { getVerifyRecord, getVerifyTypeList, getVerifierSelect } from '@/app/api/verify'
import verifyDetail from '@/app/views/marketing/components/verify-detail.vue' import verifyDetail from '@/app/views/marketing/components/verify-detail.vue'
import { img } from '@/utils/common'
const route = useRoute() const route = useRoute()
const router = useRouter()
const pageName = route.meta.title const pageName = route.meta.title
const recordTable = reactive({ const recordTable = reactive({

View File

@ -135,7 +135,7 @@ import { t } from '@/lang'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import { getRegisterChannelType, getMemberList, getMemberLabelAll, editMemberStatus, deleteMember, getMemberLevelAll } from '@/app/api/member' import { getRegisterChannelType, getMemberList, getMemberLabelAll, editMemberStatus, deleteMember, getMemberLevelAll } from '@/app/api/member'
import { ElMessageBox, FormInstance } from 'element-plus' import { ElMessageBox, FormInstance } from 'element-plus'
import { useRouter, useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import AddMember from '@/app/views/member/components/add-member.vue' import AddMember from '@/app/views/member/components/add-member.vue'
import detailMember from '@/app/views/member/components/detail-member.vue' import detailMember from '@/app/views/member/components/detail-member.vue'
import EditMember from '@/app/views/member/components/edit-member.vue' import EditMember from '@/app/views/member/components/edit-member.vue'
@ -205,7 +205,6 @@ const loadMemberList = (page: number = 1) => {
} }
loadMemberList() loadMemberList()
const router = useRouter()
const addMemberDialog: Record<string, any> | null = ref(null) const addMemberDialog: Record<string, any> | null = ref(null)
const editMemberDialog: Record<string, any> | null = ref(null) const editMemberDialog: Record<string, any> | null = ref(null)
const detailMemberDialog: Record<string, any> | null = ref(null) const detailMemberDialog: Record<string, any> | null = ref(null)

View File

@ -33,9 +33,7 @@
<el-statistic :value="formData.point"> <el-statistic :value="formData.point">
<template #title> <template #title>
<div style="display: inline-flex; align-items: center"> <div style="display: inline-flex; align-items: center">
<span class="text-[14px]"> <span class="text-[14px]">{{ t('point') }}</span>
{{ t('point') }}
</span>
<el-tooltip effect="dark" :content="t('adjust')" placement="top"> <el-tooltip effect="dark" :content="t('adjust')" placement="top">
<el-icon @click="adjustPoint(formData)" class="ml-2 cursor-pointer" :size="12"> <el-icon @click="adjustPoint(formData)" class="ml-2 cursor-pointer" :size="12">
<EditPen color="#273CE2" /> <EditPen color="#273CE2" />
@ -64,9 +62,7 @@
<el-statistic :value="formData.balance"> <el-statistic :value="formData.balance">
<template #title> <template #title>
<div style="display: inline-flex; align-items: center"> <div style="display: inline-flex; align-items: center">
<span class="text-[14px]"> <span class="text-[14px]">{{ t('balance') }}</span>
{{ t('balance') }}
</span>
<el-tooltip effect="dark" :content="t('adjust')" placement="top"> <el-tooltip effect="dark" :content="t('adjust')" placement="top">
<el-icon @click="adjustBalance(formData)" class="ml-2 cursor-pointer" :size="12"> <el-icon @click="adjustBalance(formData)" class="ml-2 cursor-pointer" :size="12">
<EditPen color="#273CE2" /> <EditPen color="#273CE2" />
@ -83,9 +79,7 @@
<div class="statistic-footer"> <div class="statistic-footer">
<div class="footer-item text-[14px] text-secondary"> <div class="footer-item text-[14px] text-secondary">
<span>{{ t('accumulative') }}</span> <span>{{ t('accumulative') }}</span>
<span class="red ml-1"> <span class="red ml-1">{{ formData.balance_get }}</span>
{{ formData.balance_get }}
</span>
</div> </div>
</div> </div>
</div> </div>
@ -95,9 +89,7 @@
<el-statistic :value="formData.growth"> <el-statistic :value="formData.growth">
<template #title> <template #title>
<div style="display: inline-flex; align-items: center"> <div style="display: inline-flex; align-items: center">
<span class="text-[14px]"> <span class="text-[14px]">{{ t('growth') }}</span>
{{ t('growth') }}
</span>
<!-- <el-tooltip effect="dark" :content="t('adjust')" placement="top">--> <!-- <el-tooltip effect="dark" :content="t('adjust')" placement="top">-->
<!-- <el-icon @click="adjustGrowth(formData)" class="ml-2 cursor-pointer" :size="12">--> <!-- <el-icon @click="adjustGrowth(formData)" class="ml-2 cursor-pointer" :size="12">-->
<!-- <EditPen color="#273CE2" />--> <!-- <EditPen color="#273CE2" />-->
@ -118,9 +110,7 @@
<el-statistic :value="formData.money" title="New transactions today"> <el-statistic :value="formData.money" title="New transactions today">
<template #title> <template #title>
<div style="display: inline-flex; align-items: center"> <div style="display: inline-flex; align-items: center">
<span class="text-[14px]"> <span class="text-[14px]">{{ t("money") }}</span>
{{ t("money") }}
</span>
<el-tooltip effect="dark" :content="t('detail')" placement="top"> <el-tooltip effect="dark" :content="t('detail')" placement="top">
<el-icon @click="infoBalance(formData)" class="ml-2 cursor-pointer" :size="12"> <el-icon @click="infoBalance(formData)" class="ml-2 cursor-pointer" :size="12">
<View /> <View />
@ -132,9 +122,7 @@
<div class="statistic-footer"> <div class="statistic-footer">
<div class="footer-item text-[14px] text-secondary"> <div class="footer-item text-[14px] text-secondary">
<span>{{ t('accumulative') }}</span> <span>{{ t('accumulative') }}</span>
<span class="green ml-1"> <span class="green ml-1">{{ formData.money_get }}</span>
{{ formData.money_get }}
</span>
</div> </div>
</div> </div>
</div> </div>
@ -144,9 +132,7 @@
<el-statistic :value="formData.commission" title="New transactions today"> <el-statistic :value="formData.commission" title="New transactions today">
<template #title> <template #title>
<div style="display: inline-flex; align-items: center "> <div style="display: inline-flex; align-items: center ">
<span class="text-[14px]"> <span class="text-[14px]">{{ t("commission") }}</span>
{{ t("commission") }}
</span>
<el-tooltip effect="dark" :content="t('detail')" placement="top"> <el-tooltip effect="dark" :content="t('detail')" placement="top">
<el-icon @click="infoCommission(formData)" class="ml-2 cursor-pointer" :size="12"> <el-icon @click="infoCommission(formData)" class="ml-2 cursor-pointer" :size="12">
<View /> <View />
@ -158,9 +144,7 @@
<div class="statistic-footer"> <div class="statistic-footer">
<div class="footer-item text-[14px] text-secondary"> <div class="footer-item text-[14px] text-secondary">
<span>{{ t('accumulative') }}</span> <span>{{ t('accumulative') }}</span>
<span class="green ml-1"> <span class="green ml-1">{{ formData.commission_get }}</span>
{{ formData.commission_get }}
</span>
</div> </div>
</div> </div>
</div> </div>
@ -172,22 +156,16 @@
<el-card class="box-card !border-none" shadow="never"> <el-card class="box-card !border-none" shadow="never">
<div class="flex items-center mt-[15px]"> <div class="flex items-center mt-[15px]">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('urserName') }}</span> <span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('urserName') }}</span>
<span class="text-[14px] text-[#666666]"> <span class="text-[14px] text-[#666666]">{{ formData.username || t('notAvailable') }}</span>
{{ formData.username || t('notAvailable') }}
</span>
</div> </div>
<div class="flex items-center mt-[15px]"> <div class="flex items-center mt-[15px]">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('nickname') }}</span> <span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('nickname') }}</span>
<span class="text-[14px] text-[#666666]"> <span class="text-[14px] text-[#666666]">{{ formData.nickname || t('notAvailable') }}</span>
{{ formData.nickname || t('notAvailable') }}
</span>
</div> </div>
<div class="flex items-center mt-[15px]"> <div class="flex items-center mt-[15px]">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('mobile') }}</span> <span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('mobile') }}</span>
<span class="text-[14px] text-[#666666]"> <span class="text-[14px] text-[#666666]">{{ formData.mobile || t('notAvailable') }}</span>
{{ formData.mobile || t('notAvailable') }}
</span>
</div> </div>
<div class="flex items-center mt-[15px]"> <div class="flex items-center mt-[15px]">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('memberLevel') }}</span> <span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('memberLevel') }}</span>
@ -276,12 +254,10 @@ import { img } from '@/utils/common'
import PointEdit from '@/app/views/member/components/member-point-edit.vue' import PointEdit from '@/app/views/member/components/member-point-edit.vue'
import BalanceEdit from '@/app/views/member/components/member-balance-edit.vue' import BalanceEdit from '@/app/views/member/components/member-balance-edit.vue'
import EditMember from '@/app/views/member/components/edit-member.vue' import EditMember from '@/app/views/member/components/edit-member.vue'
import useAppStore from '@/stores/modules/app'
const route = useRoute() const route = useRoute()
const pageName = route.meta.title const pageName = route.meta.title
const appStore = useAppStore()
const loading = ref(true) const loading = ref(true)
// //

View File

@ -11,8 +11,12 @@
<el-form label-width="80px" class="px-[10px]"> <el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('imgShape')"> <el-form-item :label="t('imgShape')">
<div class="flex items-center"> <div class="flex items-center">
<div class="bg-[#DFDFDF] cursor-pointer w-[50px] h-[50px] border-solid border-[1px] border-transparent rounded-[50%]" :class="{'border-[var(--el-color-primary)]': posterStore.editComponent.shape == 'circle'}" @click="imgShapeChangeFn('circle')"></div> <div class="bg-[#DFDFDF] cursor-pointer w-[50px] h-[50px] border-solid border-[1px] border-transparent rounded-[50%]"
<div class="bg-[#DFDFDF] cursor-pointer w-[50px] h-[50px] ml-[25px] border-solid border-[1px] border-transparent" :class="{'border-[var(--el-color-primary)]': posterStore.editComponent.shape == 'normal'}" @click="imgShapeChangeFn('normal')"></div> :class="{'border-[var(--el-color-primary)]': posterStore.editComponent.shape == 'circle'}"
@click="imgShapeChangeFn('circle')"></div>
<div class="bg-[#DFDFDF] cursor-pointer w-[50px] h-[50px] ml-[25px] border-solid border-[1px] border-transparent"
:class="{'border-[var(--el-color-primary)]': posterStore.editComponent.shape == 'normal'}"
@click="imgShapeChangeFn('normal')"></div>
</div> </div>
</el-form-item> </el-form-item>
</el-form> </el-form>

View File

@ -33,7 +33,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { t } from '@/lang' import { t } from '@/lang'
import { watch, ref } from 'vue' import { ref } from 'vue'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import usePosterStore from '@/stores/modules/poster' import usePosterStore from '@/stores/modules/poster'

View File

@ -82,8 +82,7 @@
:style="previewIframeStyle(item)" :style="previewIframeStyle(item)"
:class="{ 'selected' : posterStore.currentIndex == index }" :class="{ 'selected' : posterStore.currentIndex == index }"
@mousedown="posterStore.mouseDown($event,item.id,index)" @mousedown="posterStore.mouseDown($event,item.id,index)"
@click.stop="posterStore.changeCurrentIndex(index,item)" @click.stop="posterStore.changeCurrentIndex(index,item)">
>
<component :is="modules['preview-' + item.path]" :value="item"/> <component :is="modules['preview-' + item.path]" :value="item"/>
<span class="box1" @mousedown.stop="posterStore.resizeMouseDown($event,item, index)"></span> <span class="box1" @mousedown.stop="posterStore.resizeMouseDown($event,item, index)"></span>
<span class="box2" @mousedown.stop="posterStore.resizeMouseDown($event,item, index)"></span> <span class="box2" @mousedown.stop="posterStore.resizeMouseDown($event,item, index)"></span>
@ -299,7 +298,6 @@ const previewIframeStyle = (data: any)=>{
default: default:
style.left = data.x + 'px' style.left = data.x + 'px'
} }
// console.log(data.x,data.y)
return style return style
} }

View File

@ -4,7 +4,8 @@
<el-page-header :content="pageName" :icon="ArrowLeft" @back="back" /> <el-page-header :content="pageName" :icon="ArrowLeft" @back="back" />
</el-card> </el-card>
<el-form class="page-form" :model="formData" :rules="formRules" label-width="150px" ref="formRef" v-loading="loading"> <el-form class="page-form" :model="formData" :rules="formRules" label-width="150px" ref="formRef"
v-loading="loading">
<el-card class="box-card !border-none" shadow="never"> <el-card class="box-card !border-none" shadow="never">
<h3 class="panel-title !text-sm">{{ t('printerSet') }}</h3> <h3 class="panel-title !text-sm">{{ t('printerSet') }}</h3>
@ -83,8 +84,10 @@
</div> </div>
<template v-for="childItem in item.condition"> <template v-for="childItem in item.condition">
<div class="w-[300px] px-[12px] flex-1" v-if="childItem.type == 'checkbox'"> <div class="w-[300px] px-[12px] flex-1" v-if="childItem.type == 'checkbox'">
<el-checkbox-group v-model="formData.value[item.key]['trigger_' + triggerKey][childItem.key]"> <el-checkbox-group
<el-checkbox v-for="(checkboxItem, index) in childItem.list" :label="checkboxItem.value" :key="index">{{ checkboxItem.name }}</el-checkbox> v-model="formData.value[item.key]['trigger_' + triggerKey][childItem.key]">
<el-checkbox v-for="(checkboxItem, index) in childItem.list" :label="checkboxItem.value" :key="index">{{ checkboxItem.name }}
</el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
</template> </template>
@ -118,7 +121,14 @@
import { FormInstance, ElMessage } from 'element-plus' import { FormInstance, ElMessage } from 'element-plus'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { deepClone } from '@/utils/common'; import { deepClone } from '@/utils/common';
import { addPrinter, editPrinter,getPrinterInfo,getPrinterType,getPrinterBrand,getPrinterTemplateList } from '@/app/api/printer' import {
addPrinter,
editPrinter,
getPrinterInfo,
getPrinterType,
getPrinterBrand,
getPrinterTemplateList
} from '@/app/api/printer'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()

View File

@ -4,9 +4,7 @@
<div class="flex justify-between items-center mb-[5px]"> <div class="flex justify-between items-center mb-[5px]">
<span class="text-lg">{{pageName}}</span> <span class="text-lg">{{pageName}}</span>
<el-button type="primary" @click="addEvent"> <el-button type="primary" @click="addEvent">{{ t('addPrinter') }}</el-button>
{{ t('addPrinter') }}
</el-button>
</div> </div>
<el-tabs class="demo-tabs" model-value="/printer/list" @tab-change="handleClick"> <el-tabs class="demo-tabs" model-value="/printer/list" @tab-change="handleClick">
@ -73,6 +71,7 @@ import { t } from '@/lang'
import { getPrinterPageList, modifyPrinterStatus, deletePrinter,refreshPrinterToken,testPrint } from '@/app/api/printer' import { getPrinterPageList, modifyPrinterStatus, deletePrinter,refreshPrinterToken,testPrint } from '@/app/api/printer'
import { ElMessageBox,FormInstance } from 'element-plus' import { ElMessageBox,FormInstance } from 'element-plus'
import { useRoute,useRouter } from 'vue-router' import { useRoute,useRouter } from 'vue-router'
import { setTablePageStorage,getTablePageStorage } from "@/utils/common";
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -111,12 +110,13 @@ const loadPrinterList = (page: number = 1) => {
printerTable.loading = false printerTable.loading = false
printerTable.data = res.data.data printerTable.data = res.data.data
printerTable.total = res.data.total printerTable.total = res.data.total
setTablePageStorage(printerTable.page, printerTable.limit, printerTable.searchParam);
}).catch(() => { }).catch(() => {
printerTable.loading = false printerTable.loading = false
}) })
} }
loadPrinterList() loadPrinterList(getTablePageStorage(printerTable.searchParam).page)
const isRepeat = ref(false) const isRepeat = ref(false)

View File

@ -33,10 +33,15 @@
<h4 class="panel-title !text-sm">{{ item.title }}</h4> <h4 class="panel-title !text-sm">{{ item.title }}</h4>
<div v-for="(childItem,index) in item.list" :key="childItem.key" class="ml-[30px]" :style="{ 'margin-bottom' : item.list.length == (index + 1) ? '0' : '20px' }"> <div v-for="(childItem,index) in item.list" :key="childItem.key" class="ml-[30px]" :style="{ 'margin-bottom' : item.list.length == (index + 1) ? '0' : '20px' }">
<div class="flex"> <div class="flex">
<el-checkbox v-model="formData.value[item.key][childItem.key].status" v-if="childItem.label" :label="childItem.label" :value="childItem.status" :true-value="1" :false-value="0" class="w-[180px] mr-[10px]" :disabled="childItem.disabled" /> <el-checkbox v-model="formData.value[item.key][childItem.key].status"
v-if="childItem.label" :label="childItem.label"
:value="childItem.status" :true-value="1" :false-value="0"
class="w-[180px] mr-[10px]" :disabled="childItem.disabled" />
<template v-if="childItem.type == 'input'"> <template v-if="childItem.type == 'input'">
<el-input v-model.trim="formData.value[item.key][childItem.key].value" clearable :placeholder="'请输入' + (childItem.placeholder ? childItem.placeholder : childItem.label)" class="input-width mr-[30px]" maxlength="32" /> <el-input v-model.trim="formData.value[item.key][childItem.key].value" clearable
:placeholder="'请输入' + (childItem.placeholder ? childItem.placeholder : childItem.label)"
class="input-width mr-[30px]" maxlength="32" />
</template> </template>
<template v-if="childItem.type == 'checkbox'"> <template v-if="childItem.type == 'checkbox'">

View File

@ -68,6 +68,7 @@ import { t } from '@/lang'
import { getPrinterTemplatePageList, deletePrinterTemplate,getPrinterType } from '@/app/api/printer' import { getPrinterTemplatePageList, deletePrinterTemplate,getPrinterType } from '@/app/api/printer'
import { ElMessageBox,FormInstance } from 'element-plus' import { ElMessageBox,FormInstance } from 'element-plus'
import { useRoute,useRouter } from 'vue-router' import { useRoute,useRouter } from 'vue-router'
import { setTablePageStorage,getTablePageStorage } from "@/utils/common";
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -113,11 +114,12 @@ const loadPrinterTemplateList = (page: number = 1) => {
printerTemplateTable.loading = false printerTemplateTable.loading = false
printerTemplateTable.data = res.data.data printerTemplateTable.data = res.data.data
printerTemplateTable.total = res.data.total printerTemplateTable.total = res.data.total
setTablePageStorage(printerTemplateTable.page, printerTemplateTable.limit, printerTemplateTable.searchParam);
}).catch(() => { }).catch(() => {
printerTemplateTable.loading = false printerTemplateTable.loading = false
}) })
} }
loadPrinterTemplateList() loadPrinterTemplateList(getTablePageStorage(printerTemplateTable.searchParam).page)
/** /**
* 添加小票打印模板 * 添加小票打印模板

View File

@ -154,7 +154,6 @@ const cancel = () => {
} }
const setFormData = async (data: any = null) => { const setFormData = async (data: any = null) => {
console.log(data)
initData.value = cloneDeep(data) initData.value = cloneDeep(data)
loading.value = true loading.value = true
Object.assign(formData, initialFormData) Object.assign(formData, initialFormData)

View File

@ -81,11 +81,7 @@ setFormData()
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
// //
const formRules = reactive<FormRules>({ const formRules = reactive<FormRules>({})
site_name: [
{ required: true, message: t('siteNamePlaceholder'), trigger: 'blur' }
]
})
/** /**
* 保存 * 保存

View File

@ -83,7 +83,7 @@ import { t } from '@/lang'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import { getExportStatusList, getExportKeyList, getExportList, deleteExport } from '@/app/api/sys' import { getExportStatusList, getExportKeyList, getExportList, deleteExport } from '@/app/api/sys'
import { ElMessageBox, FormInstance } from 'element-plus' import { ElMessageBox, FormInstance } from 'element-plus'
import { useRouter, useRoute } from 'vue-router' import { useRoute } from 'vue-router'
const route = useRoute() const route = useRoute()
const pageName = route.meta.title const pageName = route.meta.title
@ -141,8 +141,6 @@ const loadExportList = (page: number = 1) => {
} }
loadExportList() loadExportList()
const router = useRouter()
/** /**
* 下载导出报表 * 下载导出报表
*/ */
@ -168,7 +166,6 @@ const deleteEvent = (id: number) => {
).then(() => { ).then(() => {
deleteExport(id).then(() => { deleteExport(id).then(() => {
loadExportList() loadExportList()
}).catch(() => {
}) })
}) })
} }

View File

@ -65,12 +65,8 @@ import { reactive, ref, computed } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getLoginConfig, setLoginConfig } from '@/app/api/member' import { getLoginConfig, setLoginConfig } from '@/app/api/member'
import { FormInstance } from 'element-plus' import { FormInstance } from 'element-plus'
import { useRoute } from 'vue-router'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
const route = useRoute()
const pageName = route.meta.title
const loading = ref(true) const loading = ref(true)
const ruleFormRef = ref<FormInstance>() const ruleFormRef = ref<FormInstance>()
const formData:any = reactive({ const formData:any = reactive({

View File

@ -85,10 +85,6 @@ import { getNoticeList } from '@/app/api/notice'
import Sms from '@/app/views/setting/components/notice-sms.vue' import Sms from '@/app/views/setting/components/notice-sms.vue'
import Wechat from '@/app/views/setting/components/notice-wechat.vue' import Wechat from '@/app/views/setting/components/notice-wechat.vue'
import Weapp from '@/app/views/setting/components/notice-weapp.vue' import Weapp from '@/app/views/setting/components/notice-weapp.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title
const smsDialog : Record<string, any> | null = ref(null) const smsDialog : Record<string, any> | null = ref(null)
const wechatDialog : Record<string, any> | null = ref(null) const wechatDialog : Record<string, any> | null = ref(null)
@ -153,9 +149,17 @@ loadNoticeList()
const setNotice = (data : any, type : string) => { const setNotice = (data : any, type : string) => {
data.type = type data.type = type
eval('data.status=data.is_' + type) data.status = data['is_' + type]
eval(type + 'Dialog.value.setFormData(data)') if (type === 'sms') {
eval(type + 'Dialog.value.showDialog = true;') smsDialog.value.setFormData(data)
smsDialog.value.showDialog = true
} else if (type === 'wechat') {
wechatDialog.value.setFormData(data)
wechatDialog.value.showDialog = true
} else if (type === 'weapp') {
weappDialog.value.setFormData(data)
weappDialog.value.showDialog = true
}
} }
</script> </script>

View File

@ -134,7 +134,6 @@ const setConfigInfo = (data:any) => {
element.config = data.config element.config = data.config
} }
}) })
console.log(payConfigData.value)
} }
// //
@ -157,10 +156,6 @@ const enablePaymentMode = async (data: any) => {
} }
} }
interface SortableEvt extends SortableEvent {
originalEvent?: DragEvent
}
// //
const fieldBoxRefs = ref<any>([]) const fieldBoxRefs = ref<any>([])
watch(isEdit, (newValue, oldValue) => { watch(isEdit, (newValue, oldValue) => {

View File

@ -142,7 +142,6 @@ const save = async (formEl: FormInstance | undefined) => {
setWebsite(formData).then(() => { setWebsite(formData).then(() => {
loading.value = false loading.value = false
appType.value == 'admin' ? useSystemStore().getWebsiteInfo() : useUserStore().getSiteInfo()
}).catch(() => { }).catch(() => {
loading.value = false loading.value = false
}) })

View File

@ -40,6 +40,17 @@
</div> </div>
<div class="form-tip">{{ t('mchPublicCertPathTips') }}</div> <div class="form-tip">{{ t('mchPublicCertPathTips') }}</div>
</el-form-item> </el-form-item>
<el-form-item :label="t('wechatpayPublicCert')" prop="wechatpay_config.wechat_public_cert_path">
<div class="input-width">
<upload-file v-model="formData.wechatpay_config.wechat_public_cert_path" api="sys/document/wechat" />
</div>
</el-form-item>
<el-form-item :label="t('wechatpayPublicCertId')" prop="wechatpay_config.wechat_public_cert_id">
<div class="input-width">
<el-input v-model.trim="formData.wechatpay_config.wechat_public_cert_id" placeholder="" class="input-width" show-word-limit clearable />
</div>
</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">
@ -98,7 +109,9 @@ const initialFormData = {
mch_id: '', mch_id: '',
mch_secret_key: '', mch_secret_key: '',
mch_secret_cert: '', mch_secret_cert: '',
mch_public_cert_path: '' mch_public_cert_path: '',
wechat_public_cert_path: '',
wechat_public_cert_id: ''
}, },
alipay_config: { alipay_config: {
app_secret_cert: '', app_secret_cert: '',

View File

@ -205,7 +205,6 @@ const getAddonDevelopCheckFn = (key: any) => {
// autofocus: false, // autofocus: false,
confirmButtonText: t('confirm'), confirmButtonText: t('confirm'),
callback: (action: any) => { callback: (action: any) => {
console.log(action)
} }
}) })
}) })

View File

@ -0,0 +1,796 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-page-title">{{ pageName }}</span>
<el-button type="primary" @click="manualBackupEvent">
{{ t('manualBackup') }}
</el-button>
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="tableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('content')" prop="content">
<el-input v-model.trim="tableData.searchParam.content" :placeholder="t('contentPlaceholder')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mb-[10px] flex items-center">
<el-button @click="batchDelete" size="small">{{ t('batchDelete') }}</el-button>
</div>
<el-table :data="tableData.data" size="large" v-loading="tableData.loading" ref="tableRef" @selection-change="handleSelectionChange">
<template #empty>
<span>{{ !tableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" :label="t('id')" width="120" />
<el-table-column prop="content" :label="t('content')" width="120" />
<el-table-column prop="version" :label="t('currentVersion')" width="120" />
<el-table-column prop="backup_dir" :label="t('backupDir')" width="220" />
<el-table-column prop="complete_time" :label="t('completeTime')" width="220" />
<el-table-column prop="remark" :label="t('remark')">
<template #default="{ row }">
<span v-if="row.remark" class="multi-hidden">{{ row.remark }}</span>
<span v-else>{{ t('remarkEmpty') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('operation')" align="right" fixed="right" width="200">
<template #default="{ row }">
<el-button type="primary" link @click="remarkEvent(row)">{{ t('remark') }}</el-button>
<el-button type="primary" link @click="restoreEvent(row)">{{ t('restore') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="tableData.page"
v-model:page-size="tableData.limit" layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total" @size-change="loadList()"
@current-change="loadList" />
</div>
</el-card>
<el-dialog v-model="showDialog" :title="iSBackupRecovery == 1 ? t('manualBackupTitle') : t('restoreTitle')" width="850px" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="true" :before-close="dialogClose">
<el-steps :active="numberOfSteps" align-center class="number-of-steps" finish-status="success" process-status="process">
<template v-if="iSBackupRecovery == 1">
<!-- 手动备份 -->
<el-step :title="t('testDirectoryPermissions')" />
<el-step :title="t('startBackUp')" />
<el-step :title="t('backUpEnd')" />
</template>
<template v-else>
<!-- 恢复 -->
<el-step :title="t('testDirectoryPermissions')" />
<el-step :title="t('startUpgrade')" />
<el-step :title="t('upgradeEnd')" />
</template>
</el-steps>
<div class="h-[400px]" style="overflow: auto">
<!-- 检测目录权限 -->
<div class="flex flex-col" v-show="active == 'check'">
<el-scrollbar>
<div class="bg-[#fff] my-3">
<div class="px-[20px] pt-[10px] text-[14px] el-table">
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
<el-col :span="18">
<span>{{ t("upgrade.path") }}</span>
</el-col>
<el-col :span="3">
<span>{{ t("upgrade.demand") }}</span>
</el-col>
<el-col :span="3">
<span>{{ t("status") }}</span>
</el-col>
</el-row>
<div style="height: calc(300px); overflow: auto" v-if="upgradeCheck && upgradeCheck.dir">
<el-row class="pb-[10px] items pl-[15px]" v-for="item in upgradeCheck.dir.is_readable">
<el-col :span="18">
<span>{{ item.dir }}</span>
</el-col>
<el-col :span="3">
<span :class="{ 'mx-[10px]' : (upgradeCheck.dir.is_readable.length + upgradeCheck.dir.is_write.length) > 9 }">{{ t("upgrade.readable") }}</span>
</el-col>
<el-col :span="3">
<span v-if="item.status" :class="{ 'mx-[20px]' : (upgradeCheck.dir.is_readable.length + upgradeCheck.dir.is_write.length) > 9 }">
<el-icon color="green">
<Select />
</el-icon>
</span>
<span v-else :class="{ 'mx-[20px]' : (upgradeCheck.dir.is_readable.length + upgradeCheck.dir.is_write.length) > 9 }">
<el-icon color="red">
<CloseBold />
</el-icon>
</span>
</el-col>
</el-row>
<el-row class="pb-[10px] items pl-[15px]" v-for="item in upgradeCheck.dir.is_write">
<el-col :span="18">
<span>{{ item.dir }}</span>
</el-col>
<el-col :span="3">
<span :class="{ 'mx-[10px]' : (upgradeCheck.dir.is_readable.length + upgradeCheck.dir.is_write.length) > 9 }">{{ t("upgrade.write") }}</span>
</el-col>
<el-col :span="3">
<span v-if="item.status" :class="{ 'mx-[20px]' : (upgradeCheck.dir.is_readable.length + upgradeCheck.dir.is_write.length) > 9 }">
<el-icon color="green">
<Select />
</el-icon>
</span>
<span v-else :class="{ 'mx-[20px]' : (upgradeCheck.dir.is_readable.length + upgradeCheck.dir.is_write.length) > 9 }">
<el-icon color="red">
<CloseBold />
</el-icon>
</span>
</el-col>
</el-row>
</div>
<div v-else>
<div v-loading="true" style="height: calc(300px); overflow: auto"></div>
</div>
</div>
</div>
</el-scrollbar>
</div>
<!-- 执行任务 -->
<div class="h-[370px] mt-[30px]" v-if="active == 'execute'">
<terminal ref="terminalRef" context="" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd"/>
</div>
<!-- 完成 -->
<div class="mt-[50px]" v-if="active == 'complete'">
<el-result icon="success" :title="iSBackupRecovery == 1 ? t('backupCompleteTips') : t('restoreCompleteTips')"></el-result>
</div>
</div>
<template #footer>
<div class="dialog-footer" v-if="active == 'check'">
<!-- 手动备份 -->
<el-button v-if="iSBackupRecovery == 1" type="primary" :loading="uploading" :disabled="isPass" @click="manualBackupFn()">{{ t("nextStep") }}</el-button>
<!-- 恢复 -->
<el-button v-else type="primary" :loading="uploading" :disabled="isPass" @click="restoreUpgradeBackupFn(currentId)">{{ t("nextStep") }}</el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="showRemarkDialog" :title="t('remark')" width="460px" :destroy-on-close="true">
<el-form :model="formData" ref="formRef" class="page-form" v-loading="remarkLoading">
<el-form-item class="mb-0">
<el-input v-model.trim="formData.remark" :rows="5" type="textarea" maxlength="200" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showRemarkDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="remarkLoading" @click="modifyRemarkFn()">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, nextTick, watch, h } from 'vue'
import { t } from '@/lang'
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
import { useRoute } from 'vue-router'
import { checkDirExist, checkPermission, getBackupRecords, restoreUpgradeBackup, deleteRecords, modifyBackupRemark, manualBackup, performBackupTasks, performRecoveryTasks } from '@/app/api/upgrade'
import { Terminal, TerminalFlash } from 'vue-web-terminal'
import 'vue-web-terminal/lib/theme/dark.css'
import { AnyObject } from '@/types/global'
const route = useRoute()
const pageName = route.meta.title
const searchFormRef = ref<FormInstance>()
const multipleSelection: any = ref([]) //
const tableRef = ref()
const repeat = ref(false)
const cloudBuildTask = ref<null | AnyObject>(null)
const tableData: any = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
content: ''
}
})
const showDialog: any = ref<boolean>(false)
const active = ref('check')
const interrupt: any = ref(false) //
const upgradeCheck = ref<null | AnyObject>(null)
const terminalRef: any = ref(null)
const cloudBuildLog: any = []
let notificationEl: any = null
const isPass: any = ref(false)
const uploading: any = ref(false)
const numberOfSteps = ref(0)
const currentId: any = ref(0)
let backupContents = []
let restoreContents = []
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadList()
}
//
const handleSelectionChange = (val: []) => {
multipleSelection.value = val
}
/**
* 获取列表
*/
const loadList = (page: number = 1) => {
tableData.loading = true
tableData.page = page
getBackupRecords({
page: tableData.page,
limit: tableData.limit,
...tableData.searchParam
}).then(res => {
tableData.loading = false
tableData.data = res.data.data
tableData.total = res.data.total
}).catch(() => {
tableData.loading = false
})
}
loadList()
const iSBackupRecovery = ref(0) // 1 2
//
const manualBackupEvent = () => {
ElMessageBox.confirm(t('manualBackupTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
// if (repeat.value) return
// repeat.value = true
backupContents = []
iSBackupRecovery.value = 1
showDialog.value = true
uploading.value = true
active.value = 'check'
interrupt.value = false
checkPermissionFn()
})
}
//
const manualBackupFn = (task: any = '') => {
//
if (interrupt.value) return
if (task == '') {
numberOfSteps.value = 1
active.value = 'execute'
}
manualBackup({ task }).then((res: any) => {
const data = res.data
if (task == '') {
terminalRef.value.execute('clear')
terminalRef.value.execute('开始执行')
}
if (data.content && !backupContents.includes(data.content)) {
backupContents.push(data.content)
terminalRef.value.pushMessage({ content: `${ data.content }` })
}
if (data.task == 'end') {
numberOfSteps.value = 2
setTimeout(() => {
numberOfSteps.value = 3
active.value = 'complete'
loadList()
repeat.value = false
}, 1500)
} else if (data.task == 'fail') {
//
setTimeout(() => {
loadList()
repeat.value = false
}, 2000)
} else {
// 2
setTimeout(() => {
manualBackupFn(data.task)
}, 2000)
}
}).catch(() => {
repeat.value = false
tableData.loading = false
})
}
/**
* 查询备份任务
*/
const getCloudBuildTaskFn = () => {
performBackupTasks({}).then(({ data }) => {
if (!data) return
cloudBuildTask.value = data
if (!showDialog.value && data.data && data.data.length > 0) {
showElNotification()
}
}).catch()
}
//
// getCloudBuildTaskFn()
/**
* 备份中任务提示
*/
const showElNotification = () => {
notificationEl = ElNotification.success({
title: t('warning'),
dangerouslyUseHTMLString: true,
message: h('div', {}, [
t('cloudbuild.executingTips'),
h('span', { class: 'text-primary cursor-pointer', onClick: elNotificationClick }, [t('cloudbuild.clickView')])
]),
duration: 0,
showClose: false
})
}
//
const elNotificationClick = () => {
iSBackupRecovery.value = 1
showDialog.value = true
nextTick(() => {
notificationEl && notificationEl.close()
terminalRef.value.execute('clear')
terminalRef.value.execute('开始执行')
getCloudBuildLogFn()
})
}
// list
const getCloudBuildLogFn = () => {
performBackupTasks({}).then(({ data }) => {
if (!data) return
cloudBuildTask.value = data
cloudBuildTask.value.data.forEach(item => {
if (!cloudBuildLog.includes(item.content)) {
terminalRef.value.pushMessage({ content: `${item.content}` })
cloudBuildLog.push(item.content)
}
})
const lastTask = data.data[data.data.length - 1].task
if (lastTask === 'end' || data.data.length == 0) {
setTimeout(() => {
active.value = 'complete'
loadList()
repeat.value = false
}, 1500)
} else if (lastTask === 'fail') {
//
setTimeout(() => {
loadList()
repeat.value = false
}, 2000)
} else {
setTimeout(() => {
getCloudBuildLogFn()
}, 2000)
}
})
}
//
const restoreEvent = (data: any) => {
ElMessageBox.confirm(t('restoreTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
// if (repeat.value) return
// repeat.value = true
restoreContents = []
iSBackupRecovery.value = 2
currentId.value = data.id
active.value = 'check'
interrupt.value = false
checkDirExistFn(data.id)
})
}
//
const checkDirExistFn = (id: any) => {
checkDirExist({ id }).then(({ data }) => {
if (data) {
showDialog.value = true
uploading.value = true
checkPermissionFn()
}
})
}
//
const checkPermissionFn = () => {
checkPermission({}).then(({ data }) => {
upgradeCheck.value = data
isPass.value = !data.is_pass
uploading.value = false
})
}
//
const restoreUpgradeBackupFn = (id: any, task: any = '') => {
//
if (interrupt.value) return
if (task == '') {
numberOfSteps.value = 1
active.value = 'execute'
}
restoreUpgradeBackup({
id,
task
}).then((res: any) => {
const data = res.data
if (task == '') {
uploading.value = false
terminalRef.value.execute('clear')
terminalRef.value.execute('开始执行')
}
if (data.content && !restoreContents.includes(data.content)) {
restoreContents.push(data.content)
terminalRef.value.pushMessage({ content: `${ data.content }` })
}
if (data.task == 'end') {
numberOfSteps.value = 2
setTimeout(() => {
numberOfSteps.value = 3
active.value = 'complete'
loadList()
repeat.value = false
}, 1500)
} else if (data.task == 'fail') {
//
setTimeout(() => {
loadList()
repeat.value = false
}, 2000)
} else {
// 2
setTimeout(() => {
restoreContents = []
restoreUpgradeBackupFn(id, data.task)
}, 2000)
}
}).catch(() => {
repeat.value = false
tableData.loading = false
})
}
/**
* 查询恢复任务
*/
const resumeUpgradeTasks = () => {
performRecoveryTasks({}).then(({ data }) => {
if (!data) return
cloudBuildTask.value = data
if (!showDialog.value && data.data && data.data.length > 0) {
recoveryTaskPrompt()
}
}).catch()
}
//
// resumeUpgradeTasks()
/**
* 恢复中任务提示
*/
const recoveryTaskPrompt = () => {
notificationEl = ElNotification.success({
title: t('warning'),
dangerouslyUseHTMLString: true,
message: h('div', {}, [
t('cloudbuild.executingTips'),
h('span', { class: 'text-primary cursor-pointer', onClick: recoveryTaskPromptClick }, [t('cloudbuild.clickView')])
]),
duration: 0,
showClose: false
})
}
//
const recoveryTaskPromptClick = () => {
iSBackupRecovery.value = 2
showDialog.value = true
nextTick(() => {
notificationEl && notificationEl.close()
terminalRef.value.execute('clear')
terminalRef.value.execute('开始执行')
restoreTaskList()
})
}
//
const restoreTaskList = () => {
performRecoveryTasks({}).then(({ data }) => {
if (!data) return
cloudBuildTask.value = data
cloudBuildTask.value.data.forEach(item => {
if (!cloudBuildLog.includes(item.content)) {
terminalRef.value.pushMessage({ content: `${item.content}` })
cloudBuildLog.push(item.content)
}
})
const lastTask = data.data[data.data.length - 1].task
if (lastTask === 'end' || data.data.length == 0) {
setTimeout(() => {
active.value = 'complete'
loadList()
repeat.value = false
}, 1500)
} else if (lastTask === 'fail') {
//
setTimeout(() => {
loadList()
repeat.value = false
}, 2000)
} else {
setTimeout(() => {
restoreTaskList()
}, 2000)
}
})
}
const dialogClose = (done: () => {}) => {
if (active.value == 'execute') {
ElMessageBox.confirm(
t('showDialogCloseTips'),
t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
terminalRef.value.execute('clear')
interrupt.value = true //
done()
}).catch(() => {
})
} else {
if (active.value == 'complete') {
//
setTimeout(() => {
location.reload()
}, 500)
}
done()
}
}
/**
* 升级进度动画
*/
let flashInterval: null | number = null
const terminalFlash = new TerminalFlash()
const onExecCmd = (key, command, success, failed, name) => {
if (command == '开始执行') {
success(terminalFlash)
const frames = makeIterator(['/', '——', '\\', '|'])
flashInterval = setInterval(() => {
terminalFlash.flush('> ' + frames.next().value)
}, 150)
}
}
const makeIterator = (array: string[]) => {
let nextIndex = 0
return {
next () {
if ((nextIndex + 1) == array.length) {
nextIndex = 0
}
return { value: array[nextIndex++] }
}
}
}
watch(() => showDialog.value, () => {
if (!showDialog.value) {
active.value = 'execute'
flashInterval && clearInterval(flashInterval)
}
})
const showRemarkDialog: any = ref<boolean>(false)
const remarkLoading = ref(false)
const formData: any = reactive({
id: 0,
remark: ''
})
//
const remarkEvent = (row: any) => {
formData.id = row.id
formData.remark = row.remark
showRemarkDialog.value = true
}
const modifyRemarkFn = () => {
remarkLoading.value = true
modifyBackupRemark({
id: formData.id,
remark: formData.remark
}).then(() => {
showRemarkDialog.value = false
remarkLoading.value = false
loadList()
}).catch(() => {
remarkLoading.value = false
})
}
//
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('deleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
if (repeat.value) return
repeat.value = true
tableData.loading = true
deleteRecords({
ids: id
}).then(() => {
loadList()
repeat.value = false
}).catch(() => {
repeat.value = false
tableData.loading = false
})
})
}
//
const batchDelete = () => {
if (multipleSelection.value.length == 0) {
ElMessage({
type: 'warning',
message: `${t('batchEmptySelectedTips')}`
})
return
}
ElMessageBox.confirm(t('deleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
tableData.loading = true
if (repeat.value) return
repeat.value = true
const ids: any = []
multipleSelection.value.forEach((item: any) => {
ids.push(item.id)
})
deleteRecords({
ids
}).then(() => {
loadList()
repeat.value = false
}).catch(() => {
repeat.value = false
tableData.loading = false
})
})
}
</script>
<style lang="scss" scoped>
:deep(.terminal .t-log-box span) {
white-space: pre-wrap;
}
.table-head-bg {
background-color: var(--el-table-header-bg-color);
}
::v-deep .number-of-steps {
.el-step__line {
margin: 0 25px;
background: #dddddd;
}
.el-step__head {
margin-top: 10px;
}
.is-success {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
.el-step__icon {
background: var(--el-color-primary);
box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
i {
color: #fff;
}
}
.el-step__line {
margin: 0 25px;
background: var(--el-color-primary);
}
}
.is-process {
color: var(--el-color-primary);
font-weight: inherit;
// font-size: 18px;
.el-step__icon {
padding: 10px;
border: 1px solid var(--el-color-primary);
box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
}
}
.is-wait {
color: #333;
}
}
/* 多行超出隐藏 */
.multi-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
</style>

View File

@ -0,0 +1,218 @@
<template>
<div v-loading="loading" class="main-container w-full">
<div class="p-5 bg-[#fff] overflow-hidden">
<div class="bg-[#fff] w-[100%] rounded-[8px] overflow-hidden">
<div class=" relative pb-[13px]" style="border-bottom: 2px solid #f0f2f6">
<div class="w-[66px] bg-primary h-[2px] absolute bottom-[0px]"></div>
<span class=" text-primary text-[18px] ml-[4px]">云编译</span>
</div>
<div class="flex mt-[20px] ml-[20px]">
<el-button class="w-[98px] !h-[36px]" type="primary" @click="handleCloudBuild" :loading="cloudBuildRef?.loading">云编译</el-button>
<div class="btn-time w-[181px] h-[36px] rounded-[4px] text-[#606266] text-[14px] ml-[10px]">
<span>云编译执行时间大约</span>
<span class="text-[16px] text-[#D43030] mx-[3px]">3</span>
<span>分钟</span>
</div>
</div>
<div class="mt-[21px] flex mb-[21px] items-center">
<span class="flex ml-[20px] text-[16px] items-center">
<i class="w-[3px] h-[12px] bg-primary mr-[6px] block"></i>
温馨提示
</span>
<span class="text-[14px] text-[#606266] ml-[7px]"> 以下情况可以进行云编译</span>
</div>
<div class="text-[14px] text-[#606266] ml-[13px] mb-[18px]">云编译不需要本地安装node环境即可进行针对使用者方便快捷</div>
<div class="ml-[40px] text-[14px] text-[#606266] mb-[18px]">1系统或插件每次安装或升级完成后需要云编译</div>
<div class="ml-[40px] text-[14px] text-[#606266] mb-[18px]">2开发者编写完前端代码之后可以使用云编译进行源码编译</div>
<div class="ml-[40px] text-[14px] text-[#606266] mb-[18px]">3由于云编译不是针对某个插件进行编译而是系统整体编译因此如果同时需要安装多个插件时往往需要安装到最后一个插件才整体进行云编译</div>
<div class="mt-[21px] flex mb-[21px] text-[16px] items-center">
<span class="flex ml-[20px] items-center">
<i class="w-[3px] h-[12px] bg-primary mr-[6px] block"></i>
云编译流程
</span>
</div>
<div class="ml-[40px]">
<el-timeline>
<el-timeline-item color="#4268EF">
<template #dot>
<div class="w-[15px] h-[15px] bg-primary rounded-[50%] text-[9px] text-[#fff] flex items-center justify-center">1</div>
</template>
<div class="text-[16px] text-[#303133]">编译admin代码</div>
<div class="py-[12px] px-[10px] bg-[#F7F8FA] mt-[10px] text-[#606266] text-[14px] w-[1085px]">
<span>云编译会将admin端的vue代码编译为对应的html文件同时将生成的代码下载到系统 niucloud 下的</span>
<span class="text-[#FF9D31] mx-[3px]">public/admin</span>
<span>目录中后台的访问路径将变为</span>
<span class="text-primary ml-[3px]">https:///admin</span>
</div>
</el-timeline-item>
<el-timeline-item color="#4268EF">
<template #dot>
<div class="w-[15px] h-[15px] bg-primary rounded-[50%] text-[9px] text-[#fff] flex items-center justify-center">2</div>
</template>
<div class="text-[16px] text-[#303133]">编译uniapp代码</div>
<div class="py-[12px] px-[10px] bg-[#F7F8FA] mt-[10px] text-[#606266] text-[14px] w-[1085px]">
<span>云编泽会将uniapp端的vue代码编译为对应的html文件同时将生成的代码下载到系统 niucloud下的</span>
<span class="text-[#FF9D31] mx-[3px]">public/wap</span>
<span>目录中这样手机端网页的访问路径将变为</span>
<span class="text-primary ml-[3px]"> https:///wap</span>
</div>
</el-timeline-item>
<el-timeline-item color="#4268EF">
<template #dot>
<div class="w-[15px] h-[15px] bg-primary rounded-[50%] text-[9px] text-[#fff] flex items-center justify-center">3</div>
</template>
<div class="text-[16px] text-[#303133]">编译web代码</div>
<div class="py-[12px] px-[10px] bg-[#F7F8FA] mt-[10px] text-[#606266] text-[14px] w-[1085px]">
<span>云编泽会将web端的vue代码编译为对应的html文件同时将生成的代码下载到系统 niucloud下的</span>
<span class="text-[#FF9D31] mx-[3px]">public/web</span>
<span>目录中这样电脑端网页的访问路径将变为</span>
<span class="text-primary ml-[3px]"> https:///web</span>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</div>
<div class="p-5 bg-[#fff] mt-[26px]">
<div class="bg-[#fff] w-[100%] rounded-[8px] overflow-hidden">
<div class="relative pb-[13px]" style="border-bottom: 2px solid #f0f2f6">
<div class="w-[85px] bg-primary h-[2px] absolute bottom-[0px]"></div>
<span class="text-primary text-[18px] ml-[4px]">本地编译</span>
</div>
<div class="mt-[21px] flex mb-[21px] text-[16px] items-center">
<span class="flex ml-[20px] items-center">
<i class="w-[3px] h-[12px] bg-primary mr-[6px] block"></i>
温馨提示
</span>
</div>
<div class="ml-[40px] text-[14px] text-[#606266] mb-[18px]">
<span>1如果本地安装了Node环境可以进行本地编译要求</span>
<span class="text-[#D43030] ml-[3px]">Node版本>18</span>
</div>
<div class="ml-[40px] text-[14px] text-[#606266] mb-[18px]">2默认本地编译流程与云编译相同执行本地编译命令后会将编译后的代码移动到系统niucloud下的public下的对应端口目录下</div>
<div class="ml-[40px] text-[14px] text-[#606266] mb-[18px]">3由于云编译配置的访问路径时固定的针对客户有独立部署adminwapweb等个性化端口名称配置需求需要进行本地编译</div>
<div class="mt-[34px] flex mb-[21px] text-[16px] items-center">
<span class="flex ml-[20px] items-center">
<i class="w-[3px] h-[12px] bg-primary mr-[6px] block"></i>
本地编译命令参考
</span>
</div>
<div>
<div class="ml-[40px] text-[14px] text-[#606266]">
<span class=" text-[#303133]">安装依赖</span>
进入admin端与uniapp端以及web端目录都可执行
</div>
<div class="ml-[40px] w-[900px] h-[42px] bg-[#282C34] rounded-[4px] mt-[10px] flex items-center">
<span class="text-[16px] text-[#FF9D31] ml-[10px]">npm install</span>
<span class="w-[58px] h-[20px] bg-[rgba(204,204,204,0.3)] ml-[auto] text-[#fff] text-[10px] flex cursor-pointer rounded-[4px] mr-[17px] items-center justify-center" @click="copyEvent('npm install')">复制命令</span>
</div>
</div>
<div class="mt-[21px]">
<div class="ml-[40px] text-[14px] text-[#606266]">
<span class="text-[#303133]">后台admin端口打包</span>
<span>进入admin目录下执行执行后编译代码默认移动到系统的niucloud下的</span>
<span class="text-[#FF9D31] mx-[3px]">public/admin</span>
<span>目录下</span>
</div>
<div class="ml-[40px] w-[900px] h-[42px] bg-[#282C34] rounded-[4px] mt-[10px] flex items-center">
<span class="text-[16px] text-[#FF9D31] ml-[10px]">npm run build</span>
<span class="w-[58px] h-[20px] bg-[rgba(204,204,204,0.3)] ml-[auto] text-[#fff] text-[10px] flex cursor-pointer rounded-[4px] mr-[17px] items-center justify-center" @click="copyEvent('npm run build')">复制命令</span>
</div>
</div>
<div class="mt-[21px]">
<div class="ml-[40px] text-[14px] text-[#606266]">
<span class="text-[#303133]">使用uniapp打包H5</span>
<span>进入uniapp目录下执行执行后编译代码默认移动到系统niucloud下的</span>
<span class="text-[#FF9D31] mx-[3px]">public/wap</span>
<span>目录下</span>
</div>
<div class="ml-[40px] w-[900px] h-[42px] bg-[#282C34] rounded-[4px] mt-[10px] flex items-center">
<span class="text-[16px] text-[#FF9D31] ml-[10px]">npm run build:h5</span>
<span class="w-[58px] h-[20px] bg-[rgba(204,204,204,0.3)] ml-[auto] text-[#fff] text-[10px] flex cursor-pointer rounded-[4px] mr-[17px] items-center justify-center" @click="copyEvent('npm run build:h5')">复制命令</span>
</div>
</div>
<div class="mt-[21px]">
<div class="ml-[40px] text-[14px] text-[#606266]">
<span class=" text-[#303133]">使用uniapp打包微信小程序</span>
<span>进入uniapp目录下执行执行后编译代码默认移动到系统niucloud下的</span>
<span class="text-[#FF9D31] mx-[3px]">uni-app/dist/build/mp-weixin</span>
<span>目录</span>
</div>
<div class="ml-[40px] w-[900px] h-[42px] bg-[#282C34] rounded-[4px] mt-[10px] flex items-center">
<span class="text-[16px] text-[#FF9D31] ml-[10px]">npm run build:mp-weixin</span>
<span class="w-[58px] h-[20px] bg-[rgba(204,204,204,0.3)] ml-[auto] text-[#fff] text-[10px] flex cursor-pointer rounded-[4px] mr-[17px] items-center justify-center" @click="copyEvent('npm run build:mp-weixin')">复制命令</span>
</div>
</div>
<div class="mt-[21px]">
<div class="ml-[40px] text-[14px] text-[#606266]">
<span class="text-[#303133]">web端打包</span>
<span>进入web目录下执行执行后编译代码默认移动到系统niucloud下的</span>
<span class="text-[#FF9D31] mx-[3px]">public/web</span>
<span>目录下</span>
</div>
<div class="ml-[40px] w-[900px] h-[42px] bg-[#282C34] rounded-[4px] mt-[10px] flex items-center">
<span class="text-[16px] text-[#FF9D31] ml-[10px]">npm run generate</span>
<span class="w-[58px] h-[20px] bg-[rgba(204,204,204,0.3)] ml-[auto] text-[#fff] text-[10px] flex cursor-pointer rounded-[4px] mr-[17px] items-center justify-center" @click="copyEvent('npm run build')">复制命令</span>
</div>
</div>
</div>
</div>
<upgrade ref="upgradeRef" @cloudbuild="handleCloudBuild"/>
<cloud-build ref="cloudBuildRef"/>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue"
import { ElMessage } from "element-plus"
import { useClipboard } from "@vueuse/core"
import { t } from "@/lang"
import Upgrade from "@/app/components/upgrade/index.vue"
import CloudBuild from "@/app/components/cloud-build/index.vue"
const loading = ref<Boolean>(false)
//
const cloudBuildRef = ref<any>(null)
const handleCloudBuild = () => {
ElMessageBox.confirm(t("cloudBuildTips"), t("warning"), {
confirmButtonText: t("confirm"),
cancelButtonText: t("cancel"),
type: "warning"
}).then(() => {
cloudBuildRef.value?.open()
})
}
//
const { copy, isSupported, copied } = useClipboard()
const copyEvent = (text: string) => {
if (!isSupported.value) {
ElMessage({
message: t("notSupportCopy"),
type: "warning"
})
return
}
copy(text)
}
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t("copySuccess"),
type: "success"
})
}
})
</script>
<style lang="scss" scoped>
.btn-time {
line-height: 36px;
text-align: center;
}
</style>

View File

@ -203,8 +203,7 @@ const confirm = async (formEl: FormInstance | undefined) => {
const setFormData = async(row: any = null) => { const setFormData = async(row: any = null) => {
formData.value = cloneDeep(Object.assign(initialFormData, row)) formData.value = cloneDeep(Object.assign(initialFormData, row))
getDictAllFn() getDictAllFn()
if(formData.value.model != '') if (formData.value.model != '') {
{
getGeneratorAllModelFn({ addon: formData.value.addon }) getGeneratorAllModelFn({ addon: formData.value.addon })
getGeneratorModelTableColumnFn({ model: formData.value.model }) getGeneratorModelTableColumnFn({ model: formData.value.model })
} }

View File

@ -174,7 +174,7 @@
import { reactive, ref, onMounted } from 'vue' import { reactive, ref, onMounted } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getGenerateTableList, deleteGenerateTable, generateCreate, generatePreview, generatorCheckFile, getAddonDevelop } from '@/app/api/tools' import { getGenerateTableList, deleteGenerateTable, generateCreate, generatePreview, generatorCheckFile, getAddonDevelop } from '@/app/api/tools'
import { img } from '@/utils/common' import { img,setTablePageStorage,getTablePageStorage } from '@/utils/common'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import AddTable from '@/app/views/tools/code/components/add-table.vue' import AddTable from '@/app/views/tools/code/components/add-table.vue'
import type { FormInstance } from 'element-plus' import type { FormInstance } from 'element-plus'
@ -211,7 +211,7 @@ onMounted(() => {
activeName.value = window.codeActiveName + '' activeName.value = window.codeActiveName + ''
window.codeActiveName = null window.codeActiveName = null
} }
loadGenerateTableList() loadGenerateTableList(getTablePageStorage(codeTableData.searchParam).page)
}) })
/** /**
* 获取代码生成列表 * 获取代码生成列表
@ -228,6 +228,7 @@ const loadGenerateTableList = (page: number = 1) => {
codeTableData.loading = false codeTableData.loading = false
codeTableData.data = res.data.data codeTableData.data = res.data.data
codeTableData.total = res.data.total codeTableData.total = res.data.total
setTablePageStorage(codeTableData.page, codeTableData.limit, codeTableData.searchParam);
}).catch(() => { }).catch(() => {
codeTableData.loading = false codeTableData.loading = false
}) })

View File

@ -75,10 +75,6 @@
import { ref } from 'vue' import { ref } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getSystem } from '@/app/api/tools' import { getSystem } from '@/app/api/tools'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title
const systemService = ref({}) const systemService = ref({})
const loading = ref(true); const loading = ref(true);

View File

@ -1,7 +1,11 @@
<template> <template>
<div class="main-container h-[500px] w-full p-5 bg-white" v-loading="loading"> <div class="main-container" >
<div class="flex flex-wrap px-2 plug-list pb-10"> <el-card class="box-card !border-none" shadow="never" v-loading="loading">
<div class="flex items-center bg-[#F7F8FA] p-3 w-[295px] relative plug-item mr-4 mb-4 cursor-pointer"> <div class="flex justify-between items-center">
<span class="text-page-title">{{ pageName }}</span>
</div>
<div class="flex flex-wrap px-2 plug-list pb-10 mt-[20px] ">
<div class="flex items-center p-3 w-[295px] relative plug-item mr-4 mb-4 bg-[var(--el-color-info-light-9)] cursor-pointer">
<div class="flex flex-col ml-2"> <div class="flex flex-col ml-2">
<span class="text-sm truncate w-[190px]">{{t('dataCache')}}</span> <span class="text-sm truncate w-[190px]">{{t('dataCache')}}</span>
<span class="text-xs text-gray-400 mt-1 truncate w-[190px]" :title="t('dataCacheDesc')">{{t('dataCacheDesc')}}</span> <span class="text-xs text-gray-400 mt-1 truncate w-[190px]" :title="t('dataCacheDesc')">{{t('dataCacheDesc')}}</span>
@ -9,6 +13,7 @@
<span class="plug-item-operate" @click="schemaCache()">{{t('refresh')}}</span> <span class="plug-item-operate" @click="schemaCache()">{{t('refresh')}}</span>
</div> </div>
</div> </div>
</el-card>
</div> </div>
</template> </template>
@ -17,8 +22,10 @@ import { ref } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { clearCache } from '@/app/api/sys' import { clearCache } from '@/app/api/sys'
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
import { useRouter, useRoute } from 'vue-router'
const loading = ref<Boolean>(false) const loading = ref<Boolean>(false)
const route = useRoute()
const pageName = route.meta.title
// //
const schemaCache = () => { const schemaCache = () => {

View File

@ -0,0 +1,161 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-page-title">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="tableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('upgradeName')" prop="name">
<el-input v-model.trim="tableData.searchParam.name" :placeholder="t('upgradeNamePlaceholder')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mb-[10px] flex items-center">
<el-button @click="batchDelete" size="small">{{ t('batchDelete') }}</el-button>
</div>
<el-table :data="tableData.data" size="large" v-loading="tableData.loading" ref="tableRef" @selection-change="handleSelectionChange">
<template #empty>
<span>{{ !tableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" :label="t('id')" width="140" />
<el-table-column prop="name" :label="t('upgradeName')" >
<template #default="{ row }">
<div v-if="!row.content || typeof row.content == 'string'">
{{ row.name }}{{ row.prev_version }}升级到{{ row.current_version }}
</div>
<div v-else>
<div v-for="item in row.content.content">{{ item.app.app_name }}{{ item.version }}升级到{{ item.upgrade_version }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="create_time" :label="t('completeTime')" width="220px" />
<el-table-column prop="status_name" :label="t('status')" width="120px" />
<el-table-column :label="t('operation')" align="right" width="160px">
<template #default="{ row }">
<el-button type="primary" link v-if="row.status == 'fail'" @click="handleFailReason(row)">{{ t('failReason') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="tableData.page"
v-model:page-size="tableData.limit" layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total" @size-change="loadList()"
@current-change="loadList" />
</div>
</el-card>
</div>
<el-dialog v-model="failReasonDialogVisible" :title="t('failReason')" width="60%">
<el-scrollbar class="h-[60vh] w-full whitespace-pre-wrap p-[20px]">
<div v-html="failReason"></div>
</el-scrollbar>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { t } from '@/lang'
import {ElMessage, ElMessageBox, FormInstance} from 'element-plus'
import { useRoute } from 'vue-router'
import { getUpgradeRecords, delUpgradeRecords } from '@/app/api/upgrade'
import 'vue-web-terminal/lib/theme/dark.css'
const route = useRoute()
const pageName = route.meta.title
const searchFormRef = ref<FormInstance>()
const tableRef = ref()
const tableData: any = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
name: ''
}
})
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadList()
}
/**
* 获取列表
*/
const loadList = (page: number = 1) => {
tableData.loading = true
tableData.page = page
getUpgradeRecords({
page: tableData.page,
limit: tableData.limit,
...tableData.searchParam
}).then(res => {
tableData.loading = false
tableData.data = res.data.data
tableData.total = res.data.total
}).catch(() => {
tableData.loading = false
})
}
loadList()
const failReason = ref('')
const failReasonDialogVisible = ref(false)
const handleFailReason = (data: any) => {
failReason.value = data.fail_reason
failReasonDialogVisible.value = true
}
let ids = []
const handleSelectionChange = (e: any) => {
ids = e.map(item => item.id)
}
const batchDelete = () => {
if (!ids.length) {
ElMessage({ message: '请先勾选要删除的记录', type: 'error', duration: 5000 })
return
}
deleteEvent(ids)
}
const deleteEvent = (ids: any) => {
ElMessageBox.confirm(t('deleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
delUpgradeRecords({
ids: ids
}).then(() => {
loadList()
}).catch(() => {
})
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -7,7 +7,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, nextTick, onMounted, ref } from 'vue' import { computed, ref } from 'vue'
import { getToken, img } from '@/utils/common' import { getToken, img } from '@/utils/common'
import { VueUeditorWrap } from 'vue-ueditor-wrap' import { VueUeditorWrap } from 'vue-ueditor-wrap'
import storage from '@/utils/storage' import storage from '@/utils/storage'
@ -84,7 +84,11 @@ const handleEditorReady = (editor) => {
// // DOM // // DOM
// updateStatsDisplay(charCount.value) // updateStatsDisplay(charCount.value)
// }) // })
console.log('扩展原型链', editor) // console.log('', editor)
editor.addListener('blur', () => {
// console.log('')
emit('handleBlur', editor.getContent()) //
})
// //
const originalCount = editor.getContentLength; // const originalCount = editor.getContentLength; //

View File

@ -0,0 +1,87 @@
<template>
<div id="vditor" />
<upload-attachment type="image" ref="imageRef" limit="" @confirm="imageSelect" />
<upload-attachment type="video" ref="videoRef" @confirm="videoSelect" />
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import Vditor from 'vditor'
import 'vditor/dist/index.css'
import { img } from '@/utils/common'
const props = defineProps({
modelValue: {
type: String,
default: ''
},
height: {
type: Number,
default: 600
},
mode: {
type: String,
default: 'wysiwyg'
}
})
const emits = defineEmits(['update:modelValue', 'handleBlur'])
const content = computed({
get() {
return props.modelValue
},
set(value) {
emits('update:modelValue', value)
}
})
const imageRef: Record<string, any> | null = ref(null)
const videoRef: Record<string, any> | null = ref(null)
const vditor = ref<Vditor | null>(null)
onMounted(() => {
vditor.value = new Vditor('vditor', {
height: props.height,
after: () => {
vditor.value!.setValue(content.value)
},
mode: props.mode,
toolbar: [
'emoji', 'headings', 'bold', 'italic', 'strike', '|', 'line', 'quote', 'list', 'ordered-list', 'check', 'outdent', 'indent',
{
name: 'image',
tip: '插入图片',
icon: '<svg t="1743135560768" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="32640" width="200" height="200"><path d="M734.608696 155.826087a200.347826 200.347826 0 0 1 200.347826 200.347826v311.652174c0 32.122435-7.568696 62.464-20.992 89.35513L410.000696 461.401043a100.173913 100.173913 0 0 0-111.549218 6.811827L89.043478 628.357565V356.173913a200.347826 200.347826 0 0 1 200.347826-200.347826h445.217392zM376.208696 518.989913L874.362435 811.408696A199.68 199.68 0 0 1 734.608696 868.173913H289.391304c-96.478609 0-177.040696-68.185043-196.073739-159.009391l245.693218-187.881739a33.391304 33.391304 0 0 1 37.175652-2.29287zM289.391304 89.043478C141.868522 89.043478 22.26087 208.65113 22.26087 356.173913v311.652174c0 147.522783 119.607652 267.130435 267.130434 267.130435h445.217392c147.522783 0 267.130435-119.607652 267.130434-267.130435V356.173913c0-147.522783-119.607652-267.130435-267.130434-267.130435H289.391304z m445.217392 356.173913a89.043478 89.043478 0 1 0 0-178.086956 89.043478 89.043478 0 0 0 0 178.086956z" fill="#333333" p-id="32641"></path></svg>',
click: () => {
imageRef.value.showDialog = true
}
},
{
name: 'video',
tip: '插入视频',
icon: '<svg t="1743136124938" class="icon" viewBox="0 0 1256 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="34055" width="200" height="200"><path d="M201.681455 74.472727a111.709091 111.709091 0 0 0-111.709091 111.709091v651.636364a111.709091 111.709091 0 0 0 111.709091 111.709091h837.818181a111.709091 111.709091 0 0 0 111.709091-111.709091V186.181818a111.709091 111.709091 0 0 0-111.709091-111.709091h-837.818181z m0-74.472727h837.818181a186.181818 186.181818 0 0 1 186.181819 186.181818v651.636364a186.181818 186.181818 0 0 1-186.181819 186.181818h-837.818181a186.181818 186.181818 0 0 1-186.181819-186.181818V186.181818a186.181818 186.181818 0 0 1 186.181819-186.181818zM798.72 533.085091a9.309091 9.309091 0 0 0-1.861818-13.032727l-248.226909-186.181819a9.309091 9.309091 0 0 0-14.894546 7.447273v372.363637a9.309091 9.309091 0 0 0 14.894546 7.447272l248.226909-186.181818a9.309091 9.309091 0 0 0 1.861818-1.861818z m-205.405091 247.621818a83.781818 83.781818 0 0 1-134.050909-67.025454v-372.363637a83.781818 83.781818 0 0 1 134.050909-67.025454l248.226909 186.181818a83.781818 83.781818 0 0 1 0 134.050909l-248.226909 186.181818z" fill="#000000" p-id="34056"></path></svg>',
click: () => {
videoRef.value.showDialog = true
}
},
'code', 'inline-code', 'insert-after', 'insert-before ', 'undo', 'redo', 'link', 'table', 'record', 'edit-mode', 'both', 'preview', 'fullscreen', 'outline', 'code-theme', 'content-theme', 'export', 'br'
],
blur: () => {
content.value = vditor.value!.getValue()
}
})
})
const imageSelect = (data: Record<string, any>) => {
data.forEach((item: any) => {
vditor.value.insertValue(`![描述](${img(item.url)})`)
})
}
const videoSelect = (data: Record<string, any>) => {
vditor.value.insertValue(`<video src="${img(data.url)}">`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -12,7 +12,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed, ref, watch} from 'vue' import { computed, ref } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'

View File

@ -0,0 +1,158 @@
<template>
<el-dialog v-model="showDialog" :title="titleName" width="500px" :destroy-on-close="true">
<el-tabs v-model="channel" class="mb-[10px]">
<el-tab-pane label="H5" name="h5"></el-tab-pane>
<el-tab-pane label="微信小程序" name="weapp"></el-tab-pane>
</el-tabs>
<!-- H5推广 -->
<div class="promote-flex flex" v-if="channel === 'h5'">
<div class="promote-img flex justify-center items-center bg-[#f8f8f8] w-[150px] h-[150px]">
<el-image :src="wapImage"/>
</div>
<div class="px-[20px] flex-1">
<div class="mb-[10px]">{{ t('promoteUrl') }}</div>
<el-input class="mb-[10px]" readonly :value="wapPreview">
<template #append>
<el-button @click="copyEvent(wapPreview)">{{ t('copy') }}</el-button>
</template>
</el-input>
<a class="text-primary" :href="wapImage" download>{{ t('downLoadQRCode') }}</a>
</div>
</div>
<!-- 小程序推广 -->
<div class="promote-flex flex" v-if="channel === 'weapp'">
<div class="promote-img flex justify-center items-center bg-[#f8f8f8] w-[150px] h-[150px]">
<el-image :src="img(weappData.path)" v-if="weappData.path" class="w-[150px] h-[150px]">
<template #error>
<div class="w-[150px] h-[150px] text-[14px] text-center leading-[150px]">{{ t('configureFailed') }}</div>
</template>
</el-image>
<div v-else class="w-[150px] h-[150px] text-[14px] text-center leading-[150px]">{{ t('configureFailed') }}</div>
</div>
<div class="px-[20px] flex-1" v-if="weappData.path">
<a class="text-primary" :href="img(weappData.path)" target="_blank" download>{{ t('downLoadQRCode') }}</a>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { t } from '@/lang'
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import QRCode from 'qrcode'
import storage from '@/utils/storage'
import { useClipboard } from '@vueuse/core'
import { getUrl, getQrcode } from '@/app/api/sys'
import { img } from '@/utils/common'
const showDialog = ref(false)
const channel = ref('h5')
const wapUrl = ref('')
const wapDomain = ref('')
const wapImage = ref('')
const wapPreview = ref('')
const pageName = ref('')
const weappData = reactive({
path: ''
})
getUrl().then((res: any) => {
wapUrl.value = res.data.wap_url
//
if (import.meta.env.MODE == 'production') return
wapDomain.value = res.data.wap_domain
// envwap
if (wapDomain.value) {
wapUrl.value = wapDomain.value + '/wap'
}
const wapDomainStorage = storage.get('wap_domain')
if (wapDomainStorage) {
wapUrl.value = wapDomainStorage
}
})
// **H5**
const generateH5QRCode = () => {
wapPreview.value = `${ wapUrl.value }${ pageName.value }`
QRCode.toDataURL(wapPreview.value, { errorCorrectionLevel: 'L', margin: 0, width: 120 }).then(url => {
wapImage.value = url
})
}
// ****
const fetchWeAppQRCode = () => {
// page '/'
if (pagePath.value.startsWith('/')) {
pagePath.value = pagePath.value.slice(1);
}
getQrcode({
page: pagePath.value, //
folder: folder.value, //
params: [
{
column_name: columnName.value,
column_value: columnValue.value
}
]
}).then((res: any) => {
if (res.data) {
weappData.path = res.data.weapp_path
}
})
}
//
const pagePath = ref("")
const columnName = ref("")
const columnValue = ref("")
const titleName = ref("")
const folder: any = ref("")
// ****
const show = (page: string, column: string, value: string, title: string, dir: string) => {
pagePath.value = page
columnName.value = column
columnValue.value = value
titleName.value = title
folder.value = dir
pageName.value = `${ pagePath.value }?${ columnName.value }=${ columnValue.value }`
generateH5QRCode()
fetchWeAppQRCode()
showDialog.value = true
}
// ****
const { copy, isSupported, copied } = useClipboard()
const copyEvent = (text: string) => {
if (!isSupported.value) {
ElMessage({
message: t('notSupportCopy'),
type: 'warning'
})
}
copy(text)
}
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t('copySuccess'),
type: 'success'
})
}
})
// ****
defineExpose({
show
})
</script>

View File

@ -28,7 +28,8 @@
<icon name="iconfont icon24gf-playCircle" color="#fff" size="25px" @click="previewVideo(index)" /> <icon name="iconfont icon24gf-playCircle" color="#fff" size="25px" @click="previewVideo(index)" />
</div> </div>
</div> </div>
<icon name="element CircleCloseFilled" color="#bbb" size="18px" @click="removeVideo(index)" class="absolute z-[2] top-[-9px] right-[-9px]"/> <icon name="element CircleCloseFilled" color="#bbb" size="18px" @click="removeVideo(index)"
class="absolute z-[2] top-[-9px] right-[-9px]" />
</div> </div>
<div class="rounded cursor-pointer relative bg-page video-wrap mr-[10px]" :style="style" v-if="videos.data.length < limit"> <div class="rounded cursor-pointer relative bg-page video-wrap mr-[10px]" :style="style" v-if="videos.data.length < limit">
<upload-attachment :limit="limit" type="video" @confirm="confirmSelect"> <upload-attachment :limit="limit" type="video" @confirm="confirmSelect">

View File

@ -162,12 +162,17 @@
"readable": "可读", "readable": "可读",
"write": "可写", "write": "可写",
"upgradeSuccess": "升级成功", "upgradeSuccess": "升级成功",
"localBuild": "手动编译", "localBuild": "本地编译",
"cloudBuild": "云编译", "cloudBuild": "重试",
"rollback": "回滚",
"showDialogCloseTips": "升级任务尚未完成,关闭将取消升级,是否要继续关闭?", "showDialogCloseTips": "升级任务尚未完成,关闭将取消升级,是否要继续关闭?",
"upgradeCompleteTips": "升级完成后还需要编译admin wap web端可选择云编译或者是手动编译", "upgradeCompleteTips": "升级完成后还必须要重新编译admin wap web端以免影响到程序正常运行。",
"upgradeTips": "应用和插件升级时,系统会自动备份当前程序及数据库。升级功能不会造成您当前程序的损坏或者数据的丢失,请放心使用,但是升级过程可能会因为兼容性等各种原因出现意外的升级错误,当出现错误时,请参考链接<a href='https://www.kancloud.cn/niushop/niushop_v6/3228611' target='_blank' class='text-primary'> https://www.kancloud.cn/niushop/niushop_v6/3228611 </a>手动回退上一版本!", "upgradeTips": "应用和插件升级时,系统会自动备份当前程序及数据库。升级功能不会造成您当前程序的损坏或者数据的丢失,请放心使用,但是升级过程可能会因为兼容性等各种原因出现意外的升级错误,当出现错误时,请参考链接<a href='https://www.kancloud.cn/niushop/niushop_v6/3228611' target='_blank' class='text-primary'> https://www.kancloud.cn/niushop/niushop_v6/3228611 </a>手动回退上一版本!",
"knownToKnow": "我已知晓,不需要再次提示" "knownToKnow": "我已知晓,不需要再次提示",
"cloudBuildErrorTips": "一键云编译队列任务过多,请等待几分钟后重试!",
"isNeedBackup": "是否需要备份",
"isNeedBackupTips": "检测到已存在备份,此次升级是否需要备份,不需要备份在升级出现异常时将会恢复最近的一次备份。",
"isNeedBackupBtn": "查看备份记录"
}, },
"cloudbuild": { "cloudbuild": {
"title": "云编译", "title": "云编译",
@ -186,6 +191,30 @@
"formSelectContentTypeName": "表单类型", "formSelectContentTypeName": "表单类型",
"formSelectContentTypeNamePlaceholder": "请选择表单类型", "formSelectContentTypeNamePlaceholder": "请选择表单类型",
"formSelectContentTypeAll": "全部", "formSelectContentTypeAll": "全部",
"formSelectContentTips": "请选择表单" "formSelectContentTips": "请选择表单",
"appName": "应用名/版本信息",
"appIdentification": "应用标识",
"introduction": "简介",
"type": "类型",
"localAppText": "插件管理",
"upgrade2": "升级",
"installLabel": "已安装",
"uninstalledLabel": "未安装",
"buyLabel": "已购买",
"cloudBuild": "云编译",
"newVersion": "最新版本",
"tipText": "标识指开发应用或插件的文件夹名称",
"gxx": "更新信息",
"return": "返回",
"nextStep": "下一步",
"prev": "上一步",
"viewUpgradeContent": "查看升级内容",
"testDirectoryPermissions": "检测目录权限",
"backupFiles": "备份文件",
"startUpgrade": "开始升级",
"upgradeEnd": "升级完成",
"cloudBuildTips": "是否要进行云编译该操作可能会影响到正在访问的客户是否要继续操作?",
"promoteUrl": "推广链接",
"downLoadQRCode": "下载二维码",
"configureFailed": "配置失败"
} }

View File

@ -17,15 +17,10 @@
<template v-else> <template v-else>
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''"> <el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
<template #title> <template #title>
<el-tooltip placement="right" effect="light">
<template #content>
该功能仅限{{ addons[meta.addon].title }}使用
</template>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1"> <div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" /> <icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
</div> </div>
<span class="ml-[10px]">{{ meta.title }}</span> <span class="ml-[10px]">{{ meta.title }}</span>
</el-tooltip>
</template> </template>
</el-menu-item> </el-menu-item>
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else> <el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else>
@ -45,14 +40,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { img } from '@/utils/common'
import menuItem from './menu-item.vue' import menuItem from './menu-item.vue'
import useSystemStore from '@/stores/modules/system' import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const userStore = useUserStore()
const routers = useUserStore().routers const routers = useUserStore().routers
const props = defineProps({ const props = defineProps({
routes: { routes: {

View File

@ -25,9 +25,9 @@
<!-- 预览 只有站点时展示--> <!-- 预览 只有站点时展示-->
<i class="iconfont iconicon_huojian1 cursor-pointer px-[8px]" :title="t('visitWap')" @click="toPreview"></i> <i class="iconfont iconicon_huojian1 cursor-pointer px-[8px]" :title="t('visitWap')" @click="toPreview"></i>
<!-- 切换语言 --> <!-- 切换语言 -->
<div class="navbar-item flex items-center h-full cursor-pointer"> <!-- <div class="navbar-item flex items-center h-full cursor-pointer">-->
<switch-lang /> <!-- <switch-lang />-->
</div> <!-- </div>-->
<!-- 切换全屏 --> <!-- 切换全屏 -->
<!-- <div class="navbar-item flex items-center h-full cursor-pointer" @click="toggleFullscreen"> <!-- <div class="navbar-item flex items-center h-full cursor-pointer" @click="toggleFullscreen">
<icon name="iconfont icontuichuquanping" v-if="isFullscreen" /> <icon name="iconfont icontuichuquanping" v-if="isFullscreen" />

View File

@ -38,7 +38,7 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import useSystemStore from '@/stores/modules/system' import useSystemStore from '@/stores/modules/system'
import { useDark, useToggle } from '@vueuse/core' import { useDark, useToggle } from '@vueuse/core'
import { setThemeColor, img } from '@/utils/common' import { setThemeColor } from '@/utils/common'
import { t } from '@/lang' import { t } from '@/lang'
import Storage from '@/utils/storage' import Storage from '@/utils/storage'

View File

@ -134,7 +134,6 @@ const submitForm = (formEl: FormInstance | undefined) => {
} }
}); });
} }
// --- end
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-aside :class="['h-screen layout-aside w-auto', { 'bright': !dark }]"> <el-aside :class="['h-screen layout-aside w-auto h-screen', { 'bright': !dark }]">
<side class="hidden-xs-only" /> <side class="hidden-xs-only" />
</el-aside> </el-aside>
@ -13,14 +13,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import { watch, computed } from 'vue' import { watch, computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import side from './side.vue'
import useSystemStore from '@/stores/modules/system' import useSystemStore from '@/stores/modules/system'
import side from './side.vue'
const route = useRoute()
const systemStore = useSystemStore() const systemStore = useSystemStore()
const dark = computed(() => { const dark = computed(() => {
return systemStore.dark return systemStore.dark
}) })
const route = useRoute()
watch(route, () => { watch(route, () => {
systemStore.$patch(state => { systemStore.$patch(state => {
state.menuDrawer = false state.menuDrawer = false
@ -34,10 +35,8 @@ watch(route, () => {
border-right: 1px solid var(--el-border-color-lighter); border-right: 1px solid var(--el-border-color-lighter);
&.bright { &.bright {
// background-color: #F5F7F9;
li { li {
// background-color: #F5F7F9;
&.is-active:not(.is-opened) { &.is-active:not(.is-opened) {
position: relative; position: relative;

View File

@ -9,12 +9,7 @@
<template v-else> <template v-else>
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''"> <el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
<template #title> <template #title>
<el-tooltip placement="right" effect="light">
<template #content>
该功能仅限{{ addons[meta.addon].title }}使用
</template>
<span :class="[{'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}, {'ml-[10px]': routes.meta.class == 2, 'ml-[15px]': routes.meta.class == 3}]">{{ meta.title }}</span> <span :class="[{'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}, {'ml-[10px]': routes.meta.class == 2, 'ml-[15px]': routes.meta.class == 3}]">{{ meta.title }}</span>
</el-tooltip>
</template> </template>
</el-menu-item> </el-menu-item>
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else> <el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else>
@ -31,9 +26,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { computed } from 'vue' import { computed } from 'vue'
import { img } from '@/utils/common'
import menuItem from './menu-item.vue' import menuItem from './menu-item.vue'
import useUserStore from '@/stores/modules/user'
import useSystemStore from "@/stores/modules/system"; import useSystemStore from "@/stores/modules/system";
const router = useRouter() const router = useRouter()
@ -60,6 +53,7 @@ const addons = computed(() => {
.el-icon { .el-icon {
width: auto; width: auto;
} }
li { li {
font-size: 15px; font-size: 15px;
} }

View File

@ -13,7 +13,7 @@
</div> </div>
</el-header> </el-header>
<el-scrollbar class="h-[calc( 100vh - 64px )]"> <el-scrollbar class="h-[calc( 100vh - 64px )]">
<el-menu :default-active="oneMenuActive" :router="true" class="aside-menu" unique-opened="true" :collapse="systemStore.menuIsCollapse"> <el-menu :default-active="oneMenuActive" :router="true" class="aside-menu" :unique-opened="true" :collapse="systemStore.menuIsCollapse">
<template v-for="(item, index) in oneMenuData" :key="index"> <template v-for="(item, index) in oneMenuData" :key="index">
<el-menu-item :index="item.original_name" @click="router.push({ name: item.name })" v-if="item.meta.show"> <el-menu-item :index="item.original_name" @click="router.push({ name: item.name })" v-if="item.meta.show">
<div v-if="item.meta.icon" class="w-[16px] h-[16px] relative flex justify-center"> <div v-if="item.meta.icon" class="w-[16px] h-[16px] relative flex justify-center">
@ -33,8 +33,11 @@
</el-scrollbar> </el-scrollbar>
</div> </div>
<el-scrollbar v-if="twoMenuData.length" class="two-menu w-[140px]"> <el-scrollbar v-if="twoMenuData.length" class="two-menu w-[140px]">
<div class="w-[140px] h-[64px] flex items-center justify-center text-[16px] border-0 border-b-[1px] border-solid border-[#eee]">{{ route.matched[1].meta.title }}</div> <div class="w-[140px] h-[64px] flex items-center justify-center text-[16px] border-b-[1px] border-solid border-[var(--el-border-color-lighter)]">
<el-menu :default-active="route.name" :router="true" class="aside-menu" :collapse="systemStore.menuIsCollapse"> {{ route.matched[1].meta.title }}
</div>
<el-menu class="aside-menu" :default-active="route.name" :default-openeds="menuOption" :router="true" :collapse="systemStore.menuIsCollapse">
<menu-item v-for="(route, index) in twoMenuData" :routes="route" :key="index" /> <menu-item v-for="(route, index) in twoMenuData" :routes="route" :key="index" />
</el-menu> </el-menu>
<div class="h-[48px]"></div> <div class="h-[48px]"></div>
@ -51,6 +54,7 @@ import useUserStore from '@/stores/modules/user'
import menuItem from './menu-item.vue' import menuItem from './menu-item.vue'
import { img, isUrl } from '@/utils/common' import { img, isUrl } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers' import { findFirstValidRoute } from '@/router/routers'
import { cloneDeep } from 'lodash-es'
const systemStore = useSystemStore() const systemStore = useSystemStore()
const userStore = useUserStore() const userStore = useUserStore()
@ -66,10 +70,12 @@ const addonRouters: Record<string, any> = {}
routers.forEach(item => { routers.forEach(item => {
item.original_name = item.name item.original_name = item.name
if (item.meta.addon == '') { if (item.meta.addon == '') {
if (item.meta.attr == '') {
if (item.children && item.children.length) { if (item.children && item.children.length) {
item.name = findFirstValidRoute(item.children) item.name = findFirstValidRoute(item.children)
} }
oneMenuData.value.push(item) oneMenuData.value.push(item)
}
} else if (item.meta.addon != '' && systemStore?.apps.length == 1 && systemStore?.apps[0].key == item.meta.addon) { } else if (item.meta.addon != '' && systemStore?.apps.length == 1 && systemStore?.apps[0].key == item.meta.addon) {
if (item.children) { if (item.children) {
item.children.forEach((citem: Record<string, any>) => { item.children.forEach((citem: Record<string, any>) => {
@ -91,18 +97,10 @@ routers.forEach(item => {
if (systemStore?.apps.length > 1) { if (systemStore?.apps.length > 1) {
const routers:Record<string, any>[] = [] const routers:Record<string, any>[] = []
systemStore?.apps.forEach((item: Record<string, any>) => { systemStore?.apps.forEach((item: Record<string, any>) => {
routers.push({ if (addonRouters[item.key]) {
path: addonRouters[item.key] ? addonRouters[item.key].path : '', addonRouters[item.key].name = addonIndexRoute[item.key]
meta: { routers.push(addonRouters[item.key])
icon: addonRouters[item.key]?.meta.icon || 'element-Setting', }
addon: item.key,
title: item.title,
app: item.app,
show: true
},
original_name: item.key,
name: addonIndexRoute[item.key]
})
}) })
oneMenuData.value.unshift(...routers) oneMenuData.value.unshift(...routers)
} }
@ -110,6 +108,10 @@ if (systemStore?.apps.length > 1) {
const oneMenuActive = ref(route.matched[1].name) const oneMenuActive = ref(route.matched[1].name)
watch(route, () => { watch(route, () => {
if (route.meta.attr != '') {
oneMenuActive.value = route.matched[2].name
twoMenuData.value = route.matched[1].children ?? []
} else {
// //
if (systemStore?.apps.length > 1) { if (systemStore?.apps.length > 1) {
twoMenuData.value = route.matched[1].children twoMenuData.value = route.matched[1].children
@ -130,6 +132,19 @@ watch(route, () => {
} }
} }
} }
}
}, { immediate: true })
//
const menuOption = ref([])
watch(twoMenuData.value, () => {
menuOption.value = [];
if(twoMenuData.value && Object.values(twoMenuData.value).length){
let data = cloneDeep(twoMenuData.value);
for(let key in data){
menuOption.value.push(data[key].name);
}
}
}, { immediate: true }) }, { immediate: true })
</script> </script>

View File

@ -27,9 +27,9 @@
<!-- 预览 只有站点时展示--> <!-- 预览 只有站点时展示-->
<!-- <i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'admin'"></i>--> <!-- <i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'admin'"></i>-->
<!-- 切换语言 --> <!-- 切换语言 -->
<div class="navbar-item flex items-center h-full cursor-pointer"> <!-- <div class="navbar-item flex items-center h-full cursor-pointer">-->
<switch-lang /> <!-- <switch-lang />-->
</div> <!-- </div>-->
<!-- 切换全屏 --> <!-- 切换全屏 -->
<!-- <div class="navbar-item flex items-center h-full cursor-pointer" @click="toggleFullscreen"> <!-- <div class="navbar-item flex items-center h-full cursor-pointer" @click="toggleFullscreen">
<icon name="iconfont-icontuichuquanping" v-if="isFullscreen" /> <icon name="iconfont-icontuichuquanping" v-if="isFullscreen" />

Some files were not shown because too many files have changed in this diff Show More