up
@ -91,3 +91,11 @@ export function getInstalledAddonList() {
|
||||
return request.get('addon/list/install')
|
||||
}
|
||||
|
||||
export function getAddonInit() {
|
||||
return request.get('addon/init')
|
||||
}
|
||||
|
||||
export function getAppIndex() {
|
||||
return request.get('app/index')
|
||||
}
|
||||
|
||||
|
||||
@ -78,3 +78,212 @@ export function editSms(params: Record<string, any>) {
|
||||
export function getSmsLog(params: Record<string, any>) {
|
||||
return request.get(`notice/sms/log`, { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录子账号
|
||||
* @param params
|
||||
*/
|
||||
export function getAccountIsLogin() {
|
||||
return request.get(`notice/niusms/config`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录子账号
|
||||
* @param params
|
||||
*/
|
||||
export function loginAccount(params: Record<string, any>) {
|
||||
return request.post(`notice/niusms/account/login`,params,{ showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册子账号
|
||||
* @param params
|
||||
*/
|
||||
export function registerAccount(params: Record<string, any>) {
|
||||
return request.post(`notice/niusms/account/register`,params,{ showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录子账号信息
|
||||
* @param params
|
||||
*/
|
||||
export function getAccountInfo(username: string) {
|
||||
return request.get(`notice/niusms/account/info/${username}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模版列表
|
||||
* @param params
|
||||
*/
|
||||
export function getTemplateList(params: Record<string, any>) {
|
||||
return request.get(`notice/niusms/template/list/${params.sms_type}/${params.username}`,{})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签名列表
|
||||
* @param params
|
||||
*/
|
||||
export function getSignList(username: string, params: Record<string, any>) {
|
||||
return request.get(`notice/niusms/sign/list/${username}`,{params})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加签名
|
||||
* @param params
|
||||
*/
|
||||
export function addSign(username: string, params: Record<string, any>) {
|
||||
return request.post(`notice/niusms/sign/report/${username}`, params, { showSuccessMessage: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除签名
|
||||
* @param params
|
||||
*/
|
||||
export function deleteSign(username: string, params: Record<string, any>) {
|
||||
return request.post(`notice/niusms/sign/delete/${username}`, params, { showSuccessMessage: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新子账号信息
|
||||
* @param params
|
||||
*/
|
||||
export function editAccount(username: string,params: Record<string, any>) {
|
||||
return request.post(`notice/niusms/account/edit/${username}`, params, { showSuccessMessage: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取短信发送记录
|
||||
* @param params
|
||||
*/
|
||||
export function getSmsSendList(username: string, params: Record<string, any>) {
|
||||
return request.get(`notice/niusms/account/send_list/${username}`,{params})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取充值列表
|
||||
* @param params
|
||||
*/
|
||||
export function getSmsOrdersList(username: string, params: Record<string, any>) {
|
||||
return request.get(`notice/niusms/order/list/${username}`,{params})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取套餐列表
|
||||
* @param params
|
||||
*/
|
||||
export function getSmsPackagesList() {
|
||||
return request.get(`notice/niusms/packages`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图像验证码
|
||||
* @param params
|
||||
*/
|
||||
export function getSmsCaptcha() {
|
||||
return request.get(`notice/niusms/captcha`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* @param params
|
||||
*/
|
||||
export function getSmsSend(params: Record<string, any>) {
|
||||
return request.post(`notice/niusms/send`,params,{ showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加签名配置项
|
||||
* @param params
|
||||
*/
|
||||
export function getSmsSignConfig() {
|
||||
return request.get(`notice/niusms/sign/report/config`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 模版报备配置项
|
||||
* @param params
|
||||
*/
|
||||
export function getTemplateReportConfig() {
|
||||
return request.get(`notice/niusms/template/report/config`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 模版报备
|
||||
* @param params
|
||||
*/
|
||||
export function reportTemplate(sms_type: string, username: string, params: Record<string, any>) {
|
||||
return request.post(`notice/niusms/template/report/${sms_type}/${username}`,params,{ showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 模版详情
|
||||
* @param params
|
||||
*/
|
||||
export function getreportTemplateInfo(sms_type: string, username: string,params: Record<string, any>) {
|
||||
return request.get(`notice/niusms/template/info/${sms_type}/${username}`,{params})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 充值下单
|
||||
* @param params
|
||||
*/
|
||||
export function smsOrderCreate(username: string, params: Record<string, any>) {
|
||||
return request.post(`notice/niusms/order/create/${username}`, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付信息
|
||||
* @param params
|
||||
*/
|
||||
export function getOrderPayInfo(username: string, params: Record<string, any>) {
|
||||
return request.get(`notice/niusms/order/pay/${username}`, {params})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单详情
|
||||
* @param params
|
||||
*/
|
||||
export function getOrderInfo(username: string, params: Record<string, any>) {
|
||||
return request.get(`notice/niusms/order/info/${username}`, {params})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付状态
|
||||
* @param params
|
||||
*/
|
||||
export function getOrderPayStatus(username: string, params: Record<string, any>) {
|
||||
return request.get(`notice/niusms/order/status/${username}`, {params})
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算金额
|
||||
* @param params
|
||||
*/
|
||||
export function calculateOrderPay(username: string, params: Record<string, any>) {
|
||||
return request.post(`notice/niusms/order/calculate/${username}`, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用牛云短信
|
||||
* @param params
|
||||
*/
|
||||
export function enableNiusms(params: Record<string, any>) {
|
||||
return request.put(`notice/niusms/enable`,params,{ showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步模版状态
|
||||
* @param params
|
||||
*/
|
||||
export function templateSync(sms_type: string, username: string) {
|
||||
return request.get(`notice/niusms/template/sync/${sms_type}/${username}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
* @param params
|
||||
*/
|
||||
export function resetPassword(username: string,params: Record<string, any>) {
|
||||
return request.post(`notice/niusms/account/reset/password/${username}`,params,{ showSuccessMessage: true})
|
||||
}
|
||||
@ -212,6 +212,15 @@ export function getLogInfo(id: number) {
|
||||
return request.get(`site/log/${ id }`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空操作日志列表
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export function logDestroy() {
|
||||
return request.delete(`site/log/destroy`)
|
||||
}
|
||||
|
||||
/***************************************************** 账单列表 **************************************************/
|
||||
|
||||
/**
|
||||
|
||||
@ -20,8 +20,8 @@ export function getUpgradeTask() {
|
||||
* 升级
|
||||
* @param addon
|
||||
*/
|
||||
export function upgradeAddon(addon: string = '') {
|
||||
return request.post(addon ? `upgrade/${ addon }` : 'upgrade')
|
||||
export function upgradeAddon(addon: string = '', params: Record<string, any> = {}) {
|
||||
return request.post(addon ? `upgrade/${ addon }` : 'upgrade', params)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,6 +62,14 @@ 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
|
||||
@ -128,4 +136,4 @@ export function performRecoveryTasks(params: Record<string, any>) {
|
||||
*/
|
||||
export function performBackupTasks(params: Record<string, any>) {
|
||||
return request.get("backup/task", params)
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,3 +75,23 @@ export function getSiteGroupCommitRecord(params: Record<string, any>) {
|
||||
return request.get('wxoplatform/sitegroup/commit', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回代码审核
|
||||
* @param params
|
||||
*/
|
||||
export function undoAudit(params: Record<string, any>) {
|
||||
return request.put('wxoplatform/undo/weappaudit', params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
|
||||
export function syncSiteWeapp(params: Record<string, any>) {
|
||||
return request.post('wxoplatform/async/siteweapp', params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取授权记录
|
||||
* @param params
|
||||
*/
|
||||
export function getAuthRecord(params: Record<string, any>) {
|
||||
return request.get('wxoplatform/authorization/record', { params })
|
||||
}
|
||||
|
||||
BIN
admin/src/app/assets/images/error_icon.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
admin/src/app/assets/images/index/app_store1.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
admin/src/app/assets/images/index/banner_1.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
admin/src/app/assets/images/index/banner_2.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
admin/src/app/assets/images/index/cloud.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
admin/src/app/assets/images/index/low-play.mp4
Normal file
BIN
admin/src/app/assets/images/index/message_empty.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
admin/src/app/assets/images/index/site_add1.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
admin/src/app/assets/images/index/site_list1.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
admin/src/app/assets/images/index/site_tc1.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
admin/src/app/assets/images/index/site_user1.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
admin/src/app/assets/images/success_icon.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
admin/src/app/assets/images/tools/authorize.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
admin/src/app/assets/images/tools/upgrade.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@ -1,68 +1,104 @@
|
||||
<template>
|
||||
<el-dialog v-model="showDialog" :title="t('cloudbuild.title')" width="850px" :close-on-click-modal="false" :close-on-press-escape="false" :before-close="dialogClose">
|
||||
|
||||
<div v-if="active == 'build'" class="h-[60vh]" v-loading="loading">
|
||||
<div class="h-[60vh] flex flex-col" v-if="cloudBuildCheck && !cloudBuildTask">
|
||||
<el-scrollbar>
|
||||
<div v-show="active == 'build'" class="h-[50vh]" v-loading="loading">
|
||||
<div class="h-[50vh] flex flex-col" v-if="cloudBuildCheck && !cloudBuildTask">
|
||||
<!-- <el-scrollbar> -->
|
||||
<div class="bg-[#fff] my-3" v-if="cloudBuildCheck.dir">
|
||||
<p class="pt-[20px] pl-[20px] ">{{ t('cloudbuild.dirPermission') }}</p>
|
||||
<div class="">
|
||||
<p class="pl-[20px] ">{{ t('cloudbuild.dirPermission') }}</p>
|
||||
<div class="mt-[10px] mx-[20px] text-[14px] cursor-pointer text-primary flex items-center justify-between bg-[#EFF6FF] rounded-[4px] p-[10px]" @click="cloudBuildCheckDirFn">
|
||||
<div class="flex items-center">
|
||||
<el-icon :size="17"><QuestionFilled /></el-icon>
|
||||
<span class="ml-[5px] leading-[20px]">编译权限错误,查看解决方案</span></div>
|
||||
<div class="border-[1px] border-primary rounded-[3px] w-[72px] h-[26px] leading-[25px] text-center">立即查看</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="12">
|
||||
<el-col :span="18">
|
||||
<span>{{ t('cloudbuild.path') }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t('cloudbuild.demand') }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t('status') }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="pb-[10px] items pl-[15px]" v-for="item in cloudBuildCheck.dir.is_readable">
|
||||
<el-col :span="12">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span>{{ t('cloudbuild.readable') }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span>
|
||||
<span v-else>
|
||||
<el-scrollbar style="height: calc(300px); overflow: auto">
|
||||
<el-row class="pb-[10px] items pl-[15px]" v-for="item in cloudBuildCheck.dir.is_readable">
|
||||
<el-col :span="18">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<span>{{ t('cloudbuild.readable') }}</span>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span>
|
||||
<span v-else>
|
||||
<el-icon color="red">
|
||||
<CloseBold />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="pb-[10px] items pl-[15px]" v-for="item in cloudBuildCheck.dir.is_write">
|
||||
<el-col :span="18">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<span>{{ t('cloudbuild.write') }}</span>
|
||||
</el-col>
|
||||
<el-col :span="3" >
|
||||
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span>
|
||||
<span v-else>
|
||||
<el-icon color="red">
|
||||
<CloseBold />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="pb-[10px] items pl-[15px]" v-for="item in cloudBuildCheck.dir.is_write">
|
||||
<el-col :span="12">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span>{{ t('cloudbuild.write') }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span v-if="item.status"><el-icon color="green"><Select /></el-icon></span>
|
||||
<span v-else>
|
||||
<el-icon color="red">
|
||||
<CloseBold />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<!-- </el-scrollbar> -->
|
||||
</div>
|
||||
<div class="h-[60vh]" v-show="cloudBuildTask">
|
||||
<div class="h-[45vh]" v-show="cloudBuildTask && !errorLog">
|
||||
<terminal ref="terminalRef" context="" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd"/>
|
||||
</div>
|
||||
<div class="flex justify-end mt-[20px]" v-show="cloudBuildTask && !errorLog">
|
||||
<el-button @click="dialogCancel()" class="!w-[90px]">取消</el-button>
|
||||
<el-button type="primary" :loading="timeloading" class="!w-[140px]">已用时 {{ formattedDuration }}</el-button>
|
||||
</div>
|
||||
<div class="h-[50vh] flex flex-col" v-show="errorLog">
|
||||
<div class="flex-1 h-0">
|
||||
<el-result icon="error" :title="t('编译失败')" :sub-title="errorInfo">
|
||||
<template #icon>
|
||||
<img src="@/app/assets/images/error_icon.png" alt="">
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-button @click="errorLog=false" class="!w-[90px]">错误信息</el-button>
|
||||
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active == 'complete'">
|
||||
<div class="h-[60vh] flex flex-col">
|
||||
<div class="flex-1 h-0">
|
||||
<el-result icon="success" :title="t('cloudbuild.cloudbuildSuccess')"></el-result>
|
||||
<div v-show="active == 'complete'">
|
||||
<div class="h-[50vh] flex flex-col">
|
||||
<div class="flex-1 h-0 flex justify-center items-center flex-col">
|
||||
<el-result icon="success" :title="t('cloudbuild.cloudbuildSuccess')" :sub-title="`编译耗时${formattedDuration},成功编译完成。`">
|
||||
<template #icon>
|
||||
<img src="@/app/assets/images/success_icon.png" alt="">
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-button @click="handleReturn" class="!w-[90px]">返回</el-button>
|
||||
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -70,7 +106,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, h, watch } from 'vue'
|
||||
import { ref, h, watch ,computed} from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getCloudBuildLog, getCloudBuildTask, cloudBuild, clearCloudBuildTask, preBuildCheck } from '@/app/api/cloud'
|
||||
import { Terminal, TerminalFlash } from 'vue-web-terminal'
|
||||
@ -86,6 +122,18 @@ const loading = ref(false)
|
||||
const terminalRef = ref(null)
|
||||
|
||||
let cloudBuildLog = []
|
||||
|
||||
// 计时器相关
|
||||
const buildStartTime = ref<number | null>(null)
|
||||
const buildDuration = ref<number>(0)
|
||||
let buildTimer: number | null = null
|
||||
const formattedDuration = computed(() => {
|
||||
const seconds = buildDuration.value
|
||||
const mins = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
return mins > 0 ? `${mins}分${secs}秒` : `${secs}秒`
|
||||
})
|
||||
|
||||
/**
|
||||
* 查询升级任务
|
||||
*/
|
||||
@ -96,22 +144,30 @@ const getCloudBuildTaskFn = () => {
|
||||
cloudBuildTask.value = data
|
||||
|
||||
if (!showDialog.value) {
|
||||
showElNotification()
|
||||
// showElNotification()
|
||||
localStorage.setItem('cloud_build_task', 'true')
|
||||
}
|
||||
}).catch()
|
||||
}
|
||||
getCloudBuildTaskFn()
|
||||
|
||||
const errorLog = ref(false)
|
||||
const errorInfo = ref('')
|
||||
const timeloading = ref(false)
|
||||
const getCloudBuildLogFn = () => {
|
||||
timeloading.value = true
|
||||
getCloudBuildLog().then(res => {
|
||||
if (!res.data) {
|
||||
if (showDialog.value && cloudBuildLog.length) {
|
||||
active.value = 'complete'
|
||||
timeloading.value = false
|
||||
terminalRef.value.execute('clear')
|
||||
clearCloudBuildTask()
|
||||
buildTimer && clearInterval(buildTimer) // 清除计时器
|
||||
localStorage.removeItem('cloud_build_start_time')
|
||||
localStorage.removeItem('cloud_build_task')
|
||||
}
|
||||
notificationEl && notificationEl.close()
|
||||
cloudBuildTask.value = null
|
||||
// cloudBuildTask.value = null
|
||||
return
|
||||
}
|
||||
|
||||
@ -120,6 +176,22 @@ const getCloudBuildLogFn = () => {
|
||||
|
||||
if (data[0] && data[0].length && showDialog.value) {
|
||||
if (cloudBuildLog.length == 0) {
|
||||
const storedTime = localStorage.getItem('cloud_build_start_time')
|
||||
if (storedTime) {
|
||||
buildStartTime.value = Number(storedTime)
|
||||
} else {
|
||||
const now = Date.now()
|
||||
buildStartTime.value = now
|
||||
localStorage.setItem('cloud_build_start_time', String(now))
|
||||
}
|
||||
|
||||
buildDuration.value = Math.floor((Date.now() - buildStartTime.value) / 1000)
|
||||
buildTimer && clearInterval(buildTimer)
|
||||
buildTimer = setInterval(() => {
|
||||
if (buildStartTime.value) {
|
||||
buildDuration.value = Math.floor((Date.now() - buildStartTime.value) / 1000)
|
||||
}
|
||||
}, 1000)
|
||||
terminalRef.value.execute('clear')
|
||||
terminalRef.value.execute('开始编译')
|
||||
}
|
||||
@ -128,10 +200,25 @@ const getCloudBuildLogFn = () => {
|
||||
if (!cloudBuildLog.includes(item.action)) {
|
||||
terminalRef.value.pushMessage({ content: `${item.action}` })
|
||||
cloudBuildLog.push(item.action)
|
||||
|
||||
|
||||
if (item.code == 0) {
|
||||
error = item.msg
|
||||
terminalRef.value.pushMessage({ content: item.msg, class: 'error' })
|
||||
timeloading.value = false
|
||||
errorLog.value = true
|
||||
errorInfo.value = item.msg
|
||||
// 停止计时器
|
||||
if (buildTimer) {
|
||||
clearInterval(buildTimer)
|
||||
buildTimer = null
|
||||
}
|
||||
|
||||
// 保证 duration 也被最后更新一次
|
||||
if (buildStartTime.value) {
|
||||
buildDuration.value = Math.floor((Date.now() - buildStartTime.value) / 1000)
|
||||
}
|
||||
localStorage.removeItem('cloud_build_start_time')
|
||||
localStorage.removeItem('cloud_build_task')
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -145,22 +232,30 @@ const getCloudBuildLogFn = () => {
|
||||
}).catch()
|
||||
}
|
||||
|
||||
const closeType = ref('normal')
|
||||
const handleReturn = () => {
|
||||
active.value = 'build'
|
||||
errorLog.value = false
|
||||
closeType.value = 'success'
|
||||
}
|
||||
|
||||
|
||||
let notificationEl : any = null
|
||||
/**
|
||||
* 升级中任务提示
|
||||
*/
|
||||
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 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 = () => {
|
||||
showDialog.value = true
|
||||
@ -171,7 +266,7 @@ const elNotificationClick = () => {
|
||||
const open = async () => {
|
||||
loading.value = true
|
||||
active.value = 'build'
|
||||
|
||||
closeType.value = 'normal'
|
||||
if (cloudBuildTask.value) {
|
||||
showDialog.value = true
|
||||
loading.value = false
|
||||
@ -228,7 +323,7 @@ const makeIterator = (array: string[]) => {
|
||||
}
|
||||
|
||||
const dialogClose = (done: () => {}) => {
|
||||
if (active.value == 'build' && cloudBuildTask.value) {
|
||||
if (active.value == 'build' && cloudBuildTask.value && closeType.value == 'normal') {
|
||||
ElMessageBox.confirm(
|
||||
t('cloudbuild.showDialogCloseTips'),
|
||||
t('warning'),
|
||||
@ -239,19 +334,57 @@ const dialogClose = (done: () => {}) => {
|
||||
}
|
||||
).then(() => {
|
||||
terminalRef.value.execute('clear')
|
||||
localStorage.removeItem('cloud_build_start_time')
|
||||
localStorage.removeItem('cloud_build_task')
|
||||
done()
|
||||
buildTimer && clearInterval(buildTimer)
|
||||
buildTimer = null
|
||||
buildStartTime.value = null
|
||||
buildDuration.value = 0
|
||||
}).catch(() => { })
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
}
|
||||
|
||||
const dialogCancel = () => {
|
||||
if (active.value == 'build' && cloudBuildTask.value && closeType.value == 'normal') {
|
||||
ElMessageBox.confirm(
|
||||
t('cloudbuild.showDialogCloseTips'),
|
||||
t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
terminalRef.value.execute('clear')
|
||||
localStorage.removeItem('cloud_build_start_time')
|
||||
localStorage.removeItem('cloud_build_task')
|
||||
buildTimer && clearInterval(buildTimer)
|
||||
buildTimer = null
|
||||
buildStartTime.value = null
|
||||
buildDuration.value = 0
|
||||
showDialog.value = false
|
||||
}).catch(() => { })
|
||||
} else {
|
||||
showDialog.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const cloudBuildCheckDirFn = () => {
|
||||
window.open('https://doc.niucloud.com/v6.html?keywords=/chang-jian-wen-ti-chu-li/er-shi-wu-3001-sheng-7ea7-yun-bian-yi-mu-lu-du-xie-quan-xian-zhuang-tai-bu-tong-guo-ru-he-chu-li')
|
||||
}
|
||||
|
||||
watch(() => showDialog.value, () => {
|
||||
if (!showDialog.value) {
|
||||
cloudBuildTask.value = null
|
||||
active.value = 'build'
|
||||
cloudBuildLog = []
|
||||
flashInterval && clearInterval(flashInterval)
|
||||
buildTimer && clearInterval(buildTimer)
|
||||
buildStartTime.value = null
|
||||
buildDuration.value = 0
|
||||
clearCloudBuildTask()
|
||||
}
|
||||
})
|
||||
@ -259,7 +392,8 @@ watch(() => showDialog.value, () => {
|
||||
defineExpose({
|
||||
open,
|
||||
cloudBuildTask,
|
||||
loading
|
||||
loading,
|
||||
elNotificationClick
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -270,4 +404,30 @@ defineExpose({
|
||||
:deep(.terminal .t-log-box span) {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
:deep(.el-result__icon) {
|
||||
color: unset !important; // 清除默认颜色
|
||||
}
|
||||
:deep(.el-dialog__title){
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
:deep(.el-result__title p){
|
||||
font-size: 25px;
|
||||
color: #1D1F3A;
|
||||
font-weight: 500;
|
||||
}
|
||||
:deep(.el-result__subtitle p){
|
||||
font-size: 15px;
|
||||
color: #4F516D;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 5;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
:deep(.el-result){
|
||||
margin-top: -100px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" :title="t('gxx')" width="850">
|
||||
<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>
|
||||
@ -60,6 +60,12 @@ const getAppVersionListFn = () => {
|
||||
})
|
||||
|
||||
frameworkVersionList.value = data
|
||||
if(frameworkVersionList.value.length == 0){
|
||||
ElMessage.warning('暂无版本更新信息')
|
||||
return
|
||||
}else{
|
||||
dialogVisible.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
const getFrameworkVersionListFn = () => {
|
||||
@ -73,6 +79,7 @@ const getFrameworkVersionListFn = () => {
|
||||
}
|
||||
})
|
||||
frameworkVersionList.value = data
|
||||
dialogVisible.value = true
|
||||
})
|
||||
}
|
||||
|
||||
@ -83,13 +90,13 @@ const loading = ref(true)
|
||||
const dialogVisible = ref(false)
|
||||
const open = async () => {
|
||||
nextTick(() => {
|
||||
activeName.value = 0 // 重置
|
||||
|
||||
if (props.upgradeKey) {
|
||||
getAppVersionListFn()
|
||||
} else {
|
||||
getFrameworkVersionListFn()
|
||||
}
|
||||
dialogVisible.value = true
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@ -4,32 +4,40 @@
|
||||
<template v-if="upgradeContent">
|
||||
<!-- 检测服务是否到期 -->
|
||||
<template v-if="step == 1">
|
||||
<div class="text-lg">
|
||||
<template v-if="upgradeContent.upgrade_version">
|
||||
<span>本次升级将从</span>
|
||||
<span class="font-bold px-[2px]">{{ upgradeContent.version }}</span>
|
||||
<span>升级到</span>
|
||||
<span class="font-bold px-[2px]">{{ upgradeContent.upgrade_version }}</span>
|
||||
<span>版本</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>当前版本</span>
|
||||
<span class="font-bold px-[2px]">{{ upgradeContent.version }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="mt-[10px]">
|
||||
<el-alert type="info" show-icon :closable="false">
|
||||
<template #title>
|
||||
<span>当前最新版本为{{ upgradeContent.last_version }},您的服务{{ upgradeContent.expire_time ? `已于${upgradeContent.expire_time}到期` : '长期有效' }}。</span>
|
||||
<span>如需升级到最新版可在<a class="text-primary" href="https://www.niucloud.com" target="_blank">niucloud-admin官网</a>购买相关服务后再进行升级</span>
|
||||
<template v-for="(item, index) in upgradeContent.content">
|
||||
<div class="text-lg">
|
||||
<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>
|
||||
</el-alert>
|
||||
</div>
|
||||
<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 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 :closable="false">
|
||||
<template #title>
|
||||
<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>
|
||||
</el-alert>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<div v-if="step == 2">
|
||||
<el-steps :active="numberOfSteps" align-center class="number-of-steps" finish-status="success" process-status="process">
|
||||
<el-steps :active="numberOfSteps" align-center class="number-of-steps" process-status="process" v-if="!errorDialog && active != 'complete'">
|
||||
<el-step :title="t('testDirectoryPermissions')" />
|
||||
<el-step :title="t('backupFiles')" />
|
||||
<el-step :title="t('upgrade.option')" />
|
||||
<el-step :title="t('startUpgrade')" />
|
||||
<el-step :title="t('upgradeEnd')" />
|
||||
</el-steps>
|
||||
@ -64,26 +72,26 @@
|
||||
<div class="bg-[#fff] my-3" v-if="upgradeCheck.dir">
|
||||
<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="12">
|
||||
<el-col :span="18">
|
||||
<span>{{ t("upgrade.path") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t("upgrade.demand") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t("status") }}</span>
|
||||
</el-col>
|
||||
</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-col :span="12">
|
||||
<el-col :span="18">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t("upgrade.readable") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3" >
|
||||
<span v-if="item.status">
|
||||
<el-icon color="green">
|
||||
<Select />
|
||||
@ -97,13 +105,13 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="pb-[10px] items pl-[15px]" v-for="item in upgradeCheck.dir.is_write">
|
||||
<el-col :span="12">
|
||||
<el-col :span="18">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t("upgrade.write") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span v-if="item.status">
|
||||
<el-icon color="green">
|
||||
<Select />
|
||||
@ -121,7 +129,7 @@
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="h-[370px] mt-[30px]" v-show="upgradeTask">
|
||||
<div class="h-[370px] mt-[30px]" v-show="showTerminal && upgradeTask && !errorDialog">
|
||||
<terminal ref="terminalRef" :context="upgradeTask ? upgradeTask.upgrade.app_key : ''" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd" />
|
||||
</div>
|
||||
</div>
|
||||
@ -129,35 +137,50 @@
|
||||
<div class="flex flex-col" v-show="active == 'backup'">
|
||||
<el-scrollbar>
|
||||
<div class="bg-[#fff] my-3">
|
||||
<div class="p-[20px] mt-[50px] mx-[10px] border-[1px] border-[#E6E6E6] rounded-[10px]">
|
||||
<div class="flex justify-between items-center mt-[-9px]">
|
||||
<el-checkbox v-model="upgradeOption.is_need_cloudbuild" :label="t('upgrade.isNeedCloudbuild')" :true-value="true" :false-value="false" size="large" ></el-checkbox>
|
||||
</div>
|
||||
<div class="text-[14px] text-[#374151] mb-[10px]">{{ t('upgrade.cloudbuildTips') }}</div>
|
||||
</div>
|
||||
<div class="p-[20px] mt-[20px] mx-[10px] border-[1px] border-[#E6E6E6] rounded-[10px]" v-if="upgradeContent.last_backup">
|
||||
<div class="flex justify-between items-center mt-[-9px]">
|
||||
<el-checkbox v-model="upgradeOption.is_need_backup" :label="t('upgrade.isNeedBackup')" :true-value="true" :false-value="false" size="large" ></el-checkbox>
|
||||
<el-button link type="primary" class="!text-[#9699B6]" @click="toBackupRecord">{{ t('upgrade.isNeedBackupBtn') }}</el-button>
|
||||
</div>
|
||||
|
||||
<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="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 class="text-[14px] text-[#374151] mb-[10px]">{{ t('upgrade.isNeedBackupTips') }}</div>
|
||||
<div class="text-[14px] text-[#9699B6]">{{ t('上次备份时间:') }}{{ upgradeContent.last_backup.complete_time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="mt-[50px]" v-show="errorDialog">
|
||||
<el-result icon="error" :title="t('升级失败')" :sub-title="errorMsg">
|
||||
<template #icon>
|
||||
<img src="@/app/assets/images/error_icon.png" alt="" />
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-button @click="handleBack()" class="!w-[90px]">错误信息</el-button>
|
||||
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
</div>
|
||||
<div class="mt-[50px]" v-show="active == 'complete'">
|
||||
<el-result icon="success" :title="t('upgrade.upgradeSuccess')"></el-result>
|
||||
<el-alert :title="t('upgrade.upgradeCompleteTips')" type="error" :closable="false" v-show="upgradeTask && upgradeTask.executed && !upgradeTask.executed.includes('cloudBuild')"/>
|
||||
<el-result icon="success" :title="t('upgrade.upgradeSuccess')">
|
||||
<template #icon>
|
||||
<img src="@/app/assets/images/success_icon.png" alt="">
|
||||
</template>
|
||||
<template #extra>
|
||||
<div class="text-[16px] text-[#4F516D] mt-[-5px]" v-show="upgradeTask && upgradeTask.executed && !upgradeTask.executed.includes('cloudBuild')">{{ t('upgrade.upgradeCompleteTips') }}</div>
|
||||
<div class="text-[16px] text-[#9699B6] mt-[10px]">本次升级用时{{ formatUpgradeDuration }}</div>
|
||||
<div class="mt-[20px]">
|
||||
<el-button @click="handleBack()" class="!w-[90px]">返回</el-button>
|
||||
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-result>
|
||||
<!-- <el-alert :title="t('upgrade.upgradeCompleteTips')" type="error" :closable="false" v-show="upgradeTask && upgradeTask.executed && !upgradeTask.executed.includes('cloudBuild')"/> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -165,13 +188,13 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<!-- 查看升级内容 -->
|
||||
<el-button v-if="step == 1 && upgradeContent.version_list.length" @click="step = 2" type="primary">{{ t("upgrade.upgradeButton") }}</el-button>
|
||||
<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">
|
||||
<template v-if="step == 2 && active != 'complete'">
|
||||
<!-- <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="uploading" @click="() => { upgradeAddonFn() }">{{ t("nextStep") }}</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>
|
||||
@ -183,7 +206,7 @@
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<el-button @click="upgradeTipsConfirm(true)" type="primary">{{ t("upgrade.knownToKnow") }}</el-button>
|
||||
<el-button @click="handleUpgrade()" 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>
|
||||
</div>
|
||||
</template>
|
||||
@ -202,7 +225,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, h, watch } from "vue"
|
||||
import { ref, h, watch ,computed} from "vue"
|
||||
import { t } from "@/lang"
|
||||
import { getVersions } from "@/app/api/auth"
|
||||
import { getFrameworkNewVersion } from "@/app/api/module"
|
||||
@ -219,14 +242,17 @@ import "vue-web-terminal/lib/theme/dark.css"
|
||||
import { AnyObject } from "@/types/global"
|
||||
import { ElNotification, ElMessage, ElMessageBox } from "element-plus"
|
||||
import Storage from "@/utils/storage"
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const showDialog = ref<boolean>(false)
|
||||
const upgradeContent = ref<null | AnyObject>(null)
|
||||
const isAllowUpgrade = ref(true) // 是否允许升级
|
||||
const upgradeTask = ref<null | AnyObject>(null)
|
||||
const active = ref('upgrade')
|
||||
const step = ref(1)
|
||||
const upgradeCheck = ref<null | AnyObject>(null)
|
||||
const uploading = ref(false)
|
||||
const loading = ref(false)
|
||||
const terminalRef: any = ref(null)
|
||||
const emits = defineEmits(["complete", "cloudbuild"])
|
||||
const upgradeTipsShowDialog = ref<boolean>(false)
|
||||
@ -235,6 +261,10 @@ let errorLog: any = []
|
||||
const cloudBuildErrorTipsShowDialog = ref<boolean>(false)
|
||||
const retrySecond = ref(30)
|
||||
let retrySecondInterval: any = null
|
||||
const upgradeOption = ref({
|
||||
is_need_backup: true,
|
||||
is_need_cloudbuild: true
|
||||
})
|
||||
|
||||
// 升级步骤排除,backupCode 备份代码,backupSql 备份数据库
|
||||
const excludeSteps: any = ref([
|
||||
@ -250,11 +280,38 @@ const excludeSteps: any = ref([
|
||||
/**
|
||||
* 查询升级任务
|
||||
*/
|
||||
const showTerminal = ref(false)
|
||||
const upgradeStartTime = ref<number | null>(null)
|
||||
const upgradeDuration = ref(0) // 单位:秒
|
||||
let upgradeTimer: ReturnType<typeof setInterval> | null = null
|
||||
const errorDialog = ref(false)
|
||||
const errorMsg = ref('')
|
||||
const getUpgradeTaskFn = () => {
|
||||
getUpgradeTask().then(({ data }) => {
|
||||
if (!data) return
|
||||
|
||||
if (!upgradeContent.value) upgradeContent.value = data.upgrade_content
|
||||
if (!upgradeContent.value) {
|
||||
upgradeContent.value = data.upgrade_content
|
||||
|
||||
if ( upgradeContent.value || !data.upgrade_content || !Array.isArray(data.upgrade_content.content)) {
|
||||
return
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -262,8 +319,15 @@ const getUpgradeTaskFn = () => {
|
||||
return
|
||||
}
|
||||
if (!upgradeTask.value) {
|
||||
showTerminal.value = true
|
||||
terminalRef.value.execute("clear")
|
||||
terminalRef.value.execute("开始升级")
|
||||
upgradeStartTime.value = Date.now()
|
||||
upgradeDuration.value = 0
|
||||
if (upgradeTimer) clearInterval(upgradeTimer)
|
||||
upgradeTimer = setInterval(() => {
|
||||
upgradeDuration.value++
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
upgradeTask.value = data
|
||||
@ -280,8 +344,15 @@ const getUpgradeTaskFn = () => {
|
||||
if (!errorLog.includes(item)) {
|
||||
terminalRef.value.pushMessage({ content: item, class: "error" })
|
||||
errorLog.push(item)
|
||||
errorMsg.value = item
|
||||
}
|
||||
})
|
||||
errorDialog.value = true
|
||||
showTerminal.value = false
|
||||
if (upgradeTimer) {
|
||||
clearInterval(upgradeTimer)
|
||||
upgradeTimer = null
|
||||
}
|
||||
}
|
||||
// 恢复完毕
|
||||
if (data.step == "restoreComplete") {
|
||||
@ -291,9 +362,14 @@ const getUpgradeTaskFn = () => {
|
||||
// 升级完成
|
||||
if (data.step == "upgradeComplete") {
|
||||
active.value = "complete"
|
||||
showTerminal.value = false
|
||||
numberOfSteps.value = 4
|
||||
notificationEl && notificationEl.close()
|
||||
emits("complete")
|
||||
if (upgradeTimer) {
|
||||
clearInterval(upgradeTimer)
|
||||
upgradeTimer = null
|
||||
}
|
||||
clearUpgradeTask()
|
||||
return
|
||||
}
|
||||
@ -304,6 +380,26 @@ const getUpgradeTaskFn = () => {
|
||||
}
|
||||
|
||||
getUpgradeTaskFn()
|
||||
const isBack = ref(false)
|
||||
const handleBack = () => {
|
||||
active.value = "upgrade"
|
||||
isBack.value = true
|
||||
showTerminal.value = true
|
||||
errorDialog.value = false // 隐藏错误弹窗
|
||||
}
|
||||
|
||||
|
||||
const formatUpgradeDuration = computed(() => {
|
||||
const s = upgradeDuration.value
|
||||
const h = Math.floor(s / 3600)
|
||||
const m = Math.floor((s % 3600) / 60)
|
||||
const sec = s % 60
|
||||
return [
|
||||
h > 0 ? `${h}小时` : '',
|
||||
m > 0 ? `${m}分钟` : '',
|
||||
`${sec}秒`
|
||||
].filter(Boolean).join('')
|
||||
})
|
||||
|
||||
const executeUpgradeFn = () => {
|
||||
executeUpgrade().then(() => {
|
||||
@ -344,6 +440,8 @@ const showElNotification = () => {
|
||||
const elNotificationClick = () => {
|
||||
showDialog.value = true
|
||||
getUpgradeTaskFn()
|
||||
step.value = 2
|
||||
numberOfSteps.value = 3
|
||||
active.value = "upgrade"
|
||||
notificationEl && notificationEl.close()
|
||||
}
|
||||
@ -362,11 +460,14 @@ getFrameworkNewVersion().then(({ data }) => {
|
||||
*/
|
||||
const is_pass = ref(false)
|
||||
const repeat = ref(false)
|
||||
const readyLoading = ref(false)
|
||||
|
||||
const handleUpgrade = async() => {
|
||||
if (repeat.value) return
|
||||
repeat.value = true
|
||||
const appKey = upgradeContent.value?.app.app_key != "niucloud-admin" ? upgradeContent.value?.app.app_key : ""
|
||||
readyLoading.value = true
|
||||
|
||||
const appKey = upgradeContent.value?.upgrade_apps.join(',') != 'niucloud-admin' ? upgradeContent.value?.upgrade_apps.join(',') : ''
|
||||
|
||||
await preUpgradeCheck(appKey).then(async ({ data }) => {
|
||||
upgradeCheck.value = data
|
||||
@ -376,29 +477,35 @@ const handleUpgrade = async() => {
|
||||
upgradeTipsShowDialog.value = false
|
||||
showDialog.value = true
|
||||
repeat.value = false
|
||||
readyLoading.value = false
|
||||
}).catch(() => {
|
||||
repeat.value = true
|
||||
repeat.value = false
|
||||
readyLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const upgradeAddonFn = () => {
|
||||
if (!is_pass.value) return
|
||||
if (uploading.value) return
|
||||
uploading.value = true
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
|
||||
const appKey = upgradeContent.value?.app.app_key != "niucloud-admin" ? upgradeContent.value?.app.app_key : ""
|
||||
const appKey = upgradeContent.value?.upgrade_apps.join(',') != 'niucloud-admin' ? upgradeContent.value?.upgrade_apps.join(',') : ''
|
||||
|
||||
upgradeAddon(appKey).then(() => {
|
||||
upgradeAddon(appKey, upgradeOption.value).then(() => {
|
||||
getUpgradeTaskFn()
|
||||
}).catch(() => {
|
||||
uploading.value = false
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const open = (addonKey: string = "", callback = null) => {
|
||||
errorDialog.value = false // 隐藏错误弹窗
|
||||
if (upgradeTask.value) {
|
||||
ElMessage({ message: "已有正在执行中的升级任务", type: "error" })
|
||||
showDialog.value = true
|
||||
step.value = 2
|
||||
numberOfSteps.value = 3
|
||||
active.value = "upgrade"
|
||||
if (callback) callback()
|
||||
} else {
|
||||
if (addonKey && frameworkVersion.value != newFrameworkVersion.value) {
|
||||
@ -406,8 +513,25 @@ const open = (addonKey: string = "", callback = null) => {
|
||||
if (callback) callback()
|
||||
return
|
||||
}
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
getUpgradeContent(addonKey).then(({ data }) => {
|
||||
loading.value = false
|
||||
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")) {
|
||||
handleUpgrade()
|
||||
} else {
|
||||
@ -415,6 +539,7 @@ const open = (addonKey: string = "", callback = null) => {
|
||||
}
|
||||
if (callback) callback()
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
@ -448,7 +573,7 @@ const makeIterator = (array: string[]) => {
|
||||
}
|
||||
|
||||
const dialogClose = (done: () => {}) => {
|
||||
if (active.value == "upgrade" && upgradeTask.value && ['upgradeComplete', 'restoreComplete'].includes(upgradeTask.value.step) === false) {
|
||||
if (active.value == "upgrade" && upgradeTask.value && ['upgradeComplete', 'restoreComplete'].includes(upgradeTask.value.step) === false && !isBack.value) {
|
||||
ElMessageBox.confirm(t("upgrade.showDialogCloseTips"), t("warning"), {
|
||||
confirmButtonText: t("confirm"),
|
||||
cancelButtonText: t("cancel"),
|
||||
@ -466,19 +591,29 @@ watch(
|
||||
() => {
|
||||
if (!showDialog.value) {
|
||||
clearUpgradeTaskFn()
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const clearUpgradeTaskFn = () => {
|
||||
active.value = "upgrade"
|
||||
uploading.value = false
|
||||
loading.value = false
|
||||
upgradeTask.value = null
|
||||
isBack.value = false
|
||||
errorDialog.value = false
|
||||
errorMsg.value = ''
|
||||
showTerminal.value = false
|
||||
upgradeLog = []
|
||||
errorLog = []
|
||||
numberOfSteps.value = 0
|
||||
flashInterval && clearInterval(flashInterval)
|
||||
retrySecondInterval && clearInterval(retrySecondInterval)
|
||||
upgradeOption.value = {
|
||||
is_need_backup: true,
|
||||
is_need_cloudbuild: true
|
||||
}
|
||||
step.value = 1
|
||||
clearUpgradeTask().then(() => {
|
||||
})
|
||||
}
|
||||
@ -520,15 +655,30 @@ const upgradeTipsConfirm = (isLock: boolean = false) => {
|
||||
}
|
||||
const activeName = ref(0)
|
||||
const numberOfSteps = ref(0)
|
||||
|
||||
const toBackupRecord = () => {
|
||||
const routeUrl = router.resolve({
|
||||
path: '/tools/backup_records'
|
||||
})
|
||||
window.open(routeUrl.href, '_blank')
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
open,
|
||||
loading
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-button){
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
.table-head-bg {
|
||||
background-color: var(--el-table-header-bg-color);
|
||||
}
|
||||
:deep(.el-checkbox__label){
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
:deep(.terminal .t-log-box span) {
|
||||
white-space: pre-wrap;
|
||||
@ -550,8 +700,27 @@ defineExpose({
|
||||
|
||||
.el-step__icon {
|
||||
background: var(--el-color-primary);
|
||||
color: #fff;
|
||||
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||||
|
||||
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-finish {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
|
||||
.el-step__icon {
|
||||
background: var(--el-color-primary)!important;
|
||||
color: #fff !important;
|
||||
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||||
|
||||
i {
|
||||
color: #fff;
|
||||
@ -572,7 +741,9 @@ defineExpose({
|
||||
.el-step__icon {
|
||||
padding: 10px;
|
||||
border: 1px solid var(--el-color-primary);
|
||||
box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||||
background: var(--el-color-primary)!important;
|
||||
color: #fff !important;
|
||||
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
|
||||
@ -580,6 +751,26 @@ defineExpose({
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
:deep(.el-dialog__title){
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
:deep(.el-result__title p){
|
||||
font-size: 25px;
|
||||
color: #1D1F3A;
|
||||
font-weight: 500;
|
||||
}
|
||||
:deep(.el-result__subtitle p){
|
||||
font-size: 15px;
|
||||
color: #4F516D;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<el-dialog v-model="dialogVisible" :title="t('accountSettings')" width="500">
|
||||
<el-form :model="saveInfo" label-width="90px" ref="formRef" class="page-form">
|
||||
<el-form-item :label="t('headImg')">
|
||||
<upload-image v-model="saveInfo.head_img" :limit="1" :type="'avatar'" imageFit="cover" />
|
||||
<upload-image v-model="saveInfo.head_img" :limit="1" imageFit="cover" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('userName')">
|
||||
<span>{{saveInfo.username}}</span>
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
{
|
||||
"search": "搜索应用名称",
|
||||
"appName": "应用名/版本信息",
|
||||
"introduction": "简介",
|
||||
"type": "类型",
|
||||
"app": "应用",
|
||||
"addon": "插件",
|
||||
"noPlug": "暂无应用",
|
||||
"install": "安装",
|
||||
"unload": "卸载",
|
||||
"installLabel": "已安装",
|
||||
"uninstalledLabel": "未安装",
|
||||
"version": "版本",
|
||||
"title": "名称",
|
||||
"desc": "简介",
|
||||
"plugDetail": "插件信息",
|
||||
"author": "作者",
|
||||
"detail": "详情",
|
||||
"addonInstall": "插件安装",
|
||||
"dirPermission": "目录读写权限",
|
||||
"path": "路径",
|
||||
"demand": "要求",
|
||||
"readable": "可读",
|
||||
"write": "可写",
|
||||
"packageManageTool": "包管理工具",
|
||||
"name": "名称",
|
||||
"addonInstallSuccess": "插件安装成功",
|
||||
"envCheck": "环境检查",
|
||||
"installProgress": "安装进度",
|
||||
"installComplete": "安装完成",
|
||||
"localAppText": "插件管理",
|
||||
"marketAppText": "官方市场",
|
||||
"installShowDialogCloseTips": "安装任务尚未完成,关闭将取消安装任务,是否要继续关闭?",
|
||||
"marketDevelopMessage": "官方市场正在开发中!",
|
||||
"jobError": "任务队列未启动 请在服务端源码部署目录打开终端执行 php think queue:listen",
|
||||
"conflictFiles": "冲突文件",
|
||||
"process": "启动进程",
|
||||
"open": "开启",
|
||||
"down": "下载",
|
||||
"addonVersion": "插件版本",
|
||||
"versionCode": "版本号",
|
||||
"createTime": "发布时间",
|
||||
"buyLabel": "已购买",
|
||||
"installTips": "安装后需手动更新插件引用的依赖和编译各个端口的前端源码",
|
||||
"localInstall": "本地安装",
|
||||
"cloudInstall": "一键云安装",
|
||||
"cloudInstallTips": "云安装可实现一键安装,安装后无需手动更新依赖和编译前端源码",
|
||||
"installingTips": "有插件正在安装中请等待安装完成之后再进行其他操作,点击查看",
|
||||
"installPercent": "安装进度",
|
||||
"downloading": "下载中",
|
||||
"authTips": "云安装需先绑定授权码,如果已有授权请先进行绑定,没有授权可到niucloud官网购买云服务之后再进行操作",
|
||||
"toBind": "绑定授权",
|
||||
"toNiucloud": "去niucloud官网",
|
||||
"descriptionLeft": "暂无任何应用,马上去",
|
||||
"link": "官方应用市场",
|
||||
"descriptionRight": "逛逛",
|
||||
"installed-empty": "暂未安装任何应用,请先安装",
|
||||
"siteAddressTips": "授权域名不匹配",
|
||||
"authCodePlaceholder": "请输入授权码",
|
||||
"authSecretPlaceholder": "请输入授权秘钥",
|
||||
"updateCode": "重新绑定",
|
||||
"notHaveAuth": "还没有授权?去购买",
|
||||
"authInfoTips": "授权码和授权秘钥可在Niucloud官网我的授权 授权详情中查看",
|
||||
"addonUninstall": "插件卸载",
|
||||
"appIdentification": "应用标识",
|
||||
"tipText": "标识指开发应用或插件的文件夹名称"
|
||||
}
|
||||
@ -29,5 +29,14 @@
|
||||
"managerPlaceholder": "请选择用户",
|
||||
"managerTips": "选择或者新增用户作为管理员",
|
||||
"newAddManager": "新增用户",
|
||||
"userDeleteTips": "是否要删除该管理员?"
|
||||
"userDeleteTips": "是否要删除该管理员?",
|
||||
"addRole": "新增角色",
|
||||
"updateRole": "编辑角色",
|
||||
"roleName": "角色名称",
|
||||
"roleDeleteTips": "确定要删除该角色吗?",
|
||||
"roleNamePlaceholder": "请输入角色名称",
|
||||
"rulesPlaceholder": "请选择权限",
|
||||
"checkStrictly": "父子级不关联",
|
||||
"permission": "权限",
|
||||
"foldText":"展开/折叠"
|
||||
}
|
||||
|
||||
@ -43,5 +43,7 @@
|
||||
"knownToKnow": "我已知晓,不需要再次提示",
|
||||
"siteAuthTips": "上传代码需先绑定授权码,请联系平台管理员进行绑定",
|
||||
"againUpload": "重新上传",
|
||||
"uploadWeapp": "上传小程序"
|
||||
"uploadWeapp": "上传小程序",
|
||||
"undoAudit" : "撤回审核",
|
||||
"undoAuditTips" : "撤回代码审核,单个账号每天审核撤回次数最多不超过 5 次(每天的额度从0点开始生效),一个月不超过 10 次。是否要继续撤回?"
|
||||
}
|
||||
|
||||
@ -44,10 +44,10 @@
|
||||
"createTime": "发布时间",
|
||||
"buyLabel": "已购买",
|
||||
"recentlyUpdated": "最近更新",
|
||||
"installTips": "安装后需手动更新插件引用的依赖和编译各个端口的前端源码",
|
||||
"installTips": "本地安装过程仅对应用和插件的程序代码和数据库进行安装处理,并不会对前端代码进行编译,本地安装之后,必须对各前端端口进行编译,才能正常使用。",
|
||||
"localInstall": "本地安装",
|
||||
"cloudInstall": "一键云安装",
|
||||
"cloudInstallTips": "云安装可实现一键安装,安装后无需手动更新依赖和编译前端源码",
|
||||
"cloudInstallTips": "云安装可实现一键安装,云安装不仅会把应用和插件的程序代码安装处理,同时,会在云端编译各前端代码。云安装完成之后即可正常使用程序! ",
|
||||
"installingTips": "有插件正在安装中请等待安装完成之后再进行其他操作,点击查看",
|
||||
"installPercent": "安装进度",
|
||||
"downloading": "下载中",
|
||||
@ -70,9 +70,10 @@
|
||||
"appIdentification": "应用标识",
|
||||
"tipText": "标识指开发应用或插件的文件夹名称",
|
||||
"uninstallTips": "是否要卸载该插件?",
|
||||
"upgrade": "一键升级",
|
||||
"upgrade": "升级",
|
||||
"newVersion": "最新版本",
|
||||
"cloudBuild": "云编译",
|
||||
"cloudBuildTips": "是否要进行云编译该操作可能会影响到正在访问的客户是否要继续操作?",
|
||||
"deleteAddonTips": "删除插件会把插件目录连同文件全部删除,确定要删除吗?"
|
||||
"deleteAddonTips": "删除插件会把插件目录连同文件全部删除,确定要删除吗?",
|
||||
"batchUpgrade": "批量升级"
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
"searchValueEmptyTips": "请输入搜索内容",
|
||||
"verify": "核销",
|
||||
"buyInfo": "预订信息",
|
||||
"orderRefunding": "该订单正在维权中不能进行核销",
|
||||
"orderRefunding": "该订单正在售后中不能进行核销",
|
||||
"verifyTips": "是否要核销该订单?",
|
||||
"toOrder": "查看订单",
|
||||
"verifyType": "核销类型",
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
"searchValueEmptyTips": "请输入搜索内容",
|
||||
"verify": "核销",
|
||||
"buyInfo": "预订信息",
|
||||
"orderRefunding": "该订单正在维权中不能进行核销",
|
||||
"orderRefunding": "该订单正在售后中不能进行核销",
|
||||
"verifyTips": "是否要核销该订单?",
|
||||
"toOrder": "查看订单",
|
||||
"verifyType": "核销类型",
|
||||
|
||||
52
admin/src/app/lang/zh-cn/setting.oplatform.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"oplatformSetting": "开放平台设置",
|
||||
"appidPlaceholder": "请输入微信第三方平台AppId",
|
||||
"appSecretPlaceholder": "请输入微信第三方平台AppSecret",
|
||||
"tokenPlaceholder": "请输入消息校验Token",
|
||||
"aesKeyPlaceholder": "请输入消息加解密Key",
|
||||
"oplatformComm": "开放平台通信",
|
||||
"empowerStartDomain": "授权发起页域名",
|
||||
"empowerReceiveUrl": "授权事件接收URL",
|
||||
"messageValidationToken": "消息校验Token",
|
||||
"messageDecryptKey": "消息加解密Key",
|
||||
"messageReceiveUrl": "消息与事件接收URL",
|
||||
"wechatDomain": "公众号开发域名",
|
||||
"weappDomain": "小程序服务器域名",
|
||||
"weappBusinessDomain": "小程序业务域名",
|
||||
"oplatformBuilder": "开发者设置",
|
||||
"builderEmail": "开发者邮箱",
|
||||
"builderMobile": "开发者手机号",
|
||||
"builderQQ": "开发者QQ",
|
||||
"builderWx": "开发者微信",
|
||||
"messageDecryptKeyTips": "在代替公众号或小程序收发消息过程中使用。必须是长度为43位的字符串,只能是字母和数字。",
|
||||
"regenerate": "重新生成",
|
||||
"messagesReceiving": "消息与事件接收",
|
||||
"domainSetting": "域名配置",
|
||||
"developerWeappUpload": "开发小程序配置",
|
||||
"developerAppid": "开发小程序appid",
|
||||
"uploadKey": "代码上传密钥",
|
||||
"uploadKeyTips": "",
|
||||
"developAppid": "开发小程序APPID",
|
||||
"developAppidPlaceholder": "请输入开发小程序APPID",
|
||||
"uploadIpTips": "如果小程序代码上传开启了ip白名单设置,在ip白名单中添加ip:",
|
||||
"groupName": "站点套餐",
|
||||
"lastTime": "上次同步时间",
|
||||
"weappVersionUpdate": "同步模板库",
|
||||
"weappVersionUpdateRecord": "模板库同步记录",
|
||||
"createTime": "提交时间",
|
||||
"userVersion": "版本号",
|
||||
"failReason": "失败原因",
|
||||
"updateTips": " 1、同步小程序时系统通过已绑定的开发小程序同步至微信第三方平台的普通模板库中。\n 2、同步完成后,系统将自动为站点套餐下已授权的小程序提交代码。\n 3、一键同步功能支持按所有站点套餐进行批量同步,同时也可针对单个站点套餐单独操作。\n 4、使用此功能前,请确保已启动消息队列服务。",
|
||||
"seeUpdateRecord": "查看同步记录",
|
||||
"commitRecord": "同步记录",
|
||||
"oneClickSync": "一键同步",
|
||||
"syncTemplateError": "未能同步到模板库",
|
||||
"templateID": "模板ID",
|
||||
"siteWeappSync": "站点小程序同步",
|
||||
"syncSiteWeappTips": "是否要给该套餐下已授权小程序的站点提交代码?",
|
||||
"publicInfo": "公众平台信息",
|
||||
"publicType": "公众平台类型",
|
||||
"siteName": "站点名称",
|
||||
"authTime": "授权时间",
|
||||
"qrcode": "二维码"
|
||||
}
|
||||
@ -29,6 +29,8 @@
|
||||
"descPlaceholder": "网站简介",
|
||||
"phonePlaceholder": "客服电话",
|
||||
"app" : "站点应用",
|
||||
"manager": "站点管理员",
|
||||
"managerPlaceholder": "请选择站点管理员",
|
||||
"addon" : "站点插件",
|
||||
"siteDomain": "站点域名",
|
||||
"siteDomainPlaceholder": "请输入站点域名",
|
||||
|
||||
77
admin/src/app/lang/zh-cn/site.manage.json
Normal file
@ -0,0 +1,77 @@
|
||||
{
|
||||
"siteName":"站点名称",
|
||||
"groupId":"套餐",
|
||||
"app":"应用",
|
||||
"logo":"站点logo",
|
||||
"status":"状态",
|
||||
"businessHours":"营业时间",
|
||||
"createTime":"创建时间",
|
||||
"expireTime":"到期时间",
|
||||
"siteNamePlaceholder":"请输入站点名称/编号",
|
||||
"createTimePlaceholder":"请输入创建时间",
|
||||
"addSite":"添加站点",
|
||||
"editSite":"编辑站点",
|
||||
"updateSite":"编辑站点",
|
||||
"statusExpire":"已到期",
|
||||
"phone":"客服电话",
|
||||
"groupIdPlaceholder":"请选择套餐",
|
||||
"uIdPlaceholder":"请选择站点管理员",
|
||||
"appIdPlaceholder":"请选择应用",
|
||||
"keywordsPlaceholder":"请输入关键字",
|
||||
"keywords":"关键字",
|
||||
"username": "账号",
|
||||
"realName": "真实姓名",
|
||||
"passwordPlaceholder": "请输入管理员密码",
|
||||
"usernamePlaceholder": "请输入管理员账号",
|
||||
"realNamePlaceholder": "请输入真实姓名",
|
||||
"confirmPasswordPlaceholder": "请再次确认密码",
|
||||
"userRealNamePlaceholder": "请输入名称",
|
||||
"expireTimePlaceholder":"请选择到期时间",
|
||||
"confirmPasswordError": "两次输入的密码不一致",
|
||||
"operationTip": "温馨提示:站点登录页面",
|
||||
"siteId": "站点ID",
|
||||
"siteInfo": "站点信息",
|
||||
"siteInlet": "站点入口",
|
||||
"enterSite": "访问站点",
|
||||
"siteList": "站点列表",
|
||||
"openClose": "开启/停止",
|
||||
"closeTxt":"停止",
|
||||
"openTxt":"开启",
|
||||
"siteUrlDevelopMessage": "站点域名功能正在开发中",
|
||||
"url": "域名",
|
||||
"startDate":"开始时间",
|
||||
"endDate":"结束时间",
|
||||
"manager": "站点管理员",
|
||||
"managerPlaceholder": "请选择站点管理员",
|
||||
"newAddManager": "新增管理员",
|
||||
"edit": "编辑",
|
||||
"siteDeleteTips": "确定要删除该站点吗?该操作将删除该站点和站点相关数据,该操作无法退回,确定要继续删除吗?",
|
||||
"siteDomain": "站点域名",
|
||||
"siteDomainPlaceholder": "请输入站点域名",
|
||||
"siteDomainTips": "站点域名的配置是针对站点的wap和web端",
|
||||
"siteDomainTipsTwo": "需要将域名配置到您的服务器,同时域名需要解析您的服务器才可生效",
|
||||
"siteDomainTipsThree": "站点域名不需要加http或者https,末尾不需要加/",
|
||||
"toSite": "访问站点",
|
||||
"noPermission": "您没有该站点的管理权限",
|
||||
"closeSiteTips": "是否要停止该站点?",
|
||||
"addSiteGroup": "添加套餐",
|
||||
"updateTime": "修改时间",
|
||||
"groupName": "套餐名称",
|
||||
"groupDesc": "套餐说明",
|
||||
"groupRoles": "套餐权限",
|
||||
"groupDeleteTips": "确定要删除该套餐吗?",
|
||||
"groupNamePlaceholder": "请输入套餐名称",
|
||||
"groupDescPlaceholder": "请输入套餐说明",
|
||||
"groupRolesPlaceholder": "请选择套餐权限",
|
||||
"groupPlaceholder": "请选择权限",
|
||||
"permission": "权限",
|
||||
"updateGroup":"编辑套餐",
|
||||
"addGroup":"添加套餐",
|
||||
"checkStrictly": "父子级不关联",
|
||||
"remark": "套餐说明",
|
||||
"reset": "重置",
|
||||
"search": "搜索",
|
||||
"foldText":"展开/折叠",
|
||||
"appName": "套餐内含应用",
|
||||
"addonName": "套餐内含插件"
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
"key":"插件标识",
|
||||
"keyPlaceholder":"请输入插件标识",
|
||||
"keyPlaceholderErr":"插件标识格式不正确,只能以字母开头且只能输入字母、数字、下划线",
|
||||
"keyPlaceholder1":"插件标识指开发插件的文件夹名称,申请之后不能修改(只能包括字母、数字和下划线,且只能以字母开头,格式如:f1111、f11_22)",
|
||||
"keyPlaceholder1":"插件标识指开发插件的文件夹名称,申请之后不能修改(仅允许使用字母、数字与下划线组合,且必须以字母开头,同时名称中至少包含一个下划线,格式如:a11_34、f11_22)",
|
||||
"keyPlaceholder2":"插件标识设置后建议进行插件标识检测,如果当前插件标识已经在niucloud官方市场注册,则只能在本地使用,无法在官方市场发布销售",
|
||||
"desc":"插件描述",
|
||||
"descPlaceholder":"请输入插件描述",
|
||||
|
||||
30
admin/src/app/lang/zh-cn/tools.addon.list.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"developmentProcess":"开发流程",
|
||||
"pluginList":"插件列表",
|
||||
"addAddon":"新建插件",
|
||||
"title":"插件名称",
|
||||
"titlePlaceholder":"请输入插件名称",
|
||||
"author":"作者",
|
||||
"authorPlaceholder":"请输入作者",
|
||||
"key":"插件标识",
|
||||
"type":"插件类型",
|
||||
"version":"版本号",
|
||||
"status":"状态",
|
||||
"codeDeleteTips":"删除插件后对应文件会一并删除,是否确认",
|
||||
"step1":"新建一个插件",
|
||||
"describe1":"点击新建插件,生成插件后系统会生成对应插件的基础代码",
|
||||
"btn1":"新建插件",
|
||||
"step2":"安装插件",
|
||||
"describe2":"插件创建之后处于未安装状态,点击进入插件列表选择对应新建插件点击安装",
|
||||
"btn2":"插件列表",
|
||||
"step3":"开发插件",
|
||||
"describe3":"插件安装成功之后就可以进行开发,具体查看开发教程",
|
||||
"btn3":"查看教程",
|
||||
"step4":"打包插件",
|
||||
"describe4":"插件开发的前端代码是直接在对应开发环境运行的,并没有放入插件对应目录,点击打包后会将对应插件的代码整合到插件目录方便后期安装与打包发行版本",
|
||||
"btn4":"插件列表",
|
||||
"step5":"上传到云市场",
|
||||
"describe5":"插件打包成功之后可以上传到官方云市场进行销售,可以打包后选择下载代码zip格式,然后在官网开发商引用选择上传版本",
|
||||
"btn5":"官方市场",
|
||||
"addonDownloadText":"插件打包成功,是否下载"
|
||||
}
|
||||
49
admin/src/app/lang/zh-cn/tools.code.list.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"tableName":"表名称",
|
||||
"tableContent":"描述",
|
||||
"addon":"插件",
|
||||
"moduleName":"模块名",
|
||||
"className":"类名",
|
||||
"editType":"编辑方式",
|
||||
"createTime":"创建时间",
|
||||
"updateTime":"更新时间",
|
||||
"popup":"弹出",
|
||||
"page":"新页面",
|
||||
"tableNamePlaceholder":"请输入表名",
|
||||
"tableContentPlaceholder":"请输入描述",
|
||||
"addonPlaceholder":"请选择插件",
|
||||
"moduleNamePlaceholder":"请输入模块名",
|
||||
"classNamePlaceholder":"请输入类名",
|
||||
"editTypePlaceholder":"请选择编辑方式",
|
||||
"addCode":"导入数据表",
|
||||
"updateCode":"编辑代码生成",
|
||||
"codeDeleteTips":"确定要删除吗?",
|
||||
"tableComment":"表描述",
|
||||
"tableCreateTime":"创建时间",
|
||||
"tableUpdateTime":"修改时间",
|
||||
"addBtn":"添加",
|
||||
"searchPlaceholder":"请输入表名或表描述搜索",
|
||||
"selectTableTips":"确认导入该数据表吗?",
|
||||
"download":"下载代码",
|
||||
"codeGeneration":"代码生成",
|
||||
"codeList":"生成列表",
|
||||
"step1":"选择数据表生成代码",
|
||||
"describe1":"点击添加,选择对应数据表,添加之后会跳转到对应生成代码的配置管理页面",
|
||||
"btn1":"添加",
|
||||
"step2":"基础设置",
|
||||
"describe2":"代码生成查看基础设置,包括基础的表名、描述、所属插件设置",
|
||||
"btn2":"插件列表",
|
||||
"step3":"字段设置",
|
||||
"describe3":"代码中字段的管理,包括字段是否进行增加、编辑、列表、查询展示等",
|
||||
"btn3":"查看教程",
|
||||
"step4":"页面设置",
|
||||
"describe4":"正在开发中,会将字段进行展示管理配置,同时提供预览,真正实现自定义表单配置",
|
||||
"btn4":"插件列表",
|
||||
"step5":"生成配置",
|
||||
"describe5":"针对代码中展示页面配置,包括单独页面编辑表单还是弹框,生成的代码穿插到系统中还是进行下载等",
|
||||
"btn5":"官方市场",
|
||||
"saveAndSync":"同步代码",
|
||||
"saveAndSyncText":"同步的代码与项目产生冲突,是否确认覆盖?",
|
||||
"saveAndSyncText1":"同步的代码会加入到项目代码中,是否确认继续",
|
||||
"addonName": "所属插件"
|
||||
}
|
||||
@ -4,5 +4,9 @@
|
||||
"prevVersion": "前一版本",
|
||||
"currentVersion": "版本",
|
||||
"upgradeNamePlaceholder": "请输入内容",
|
||||
"completeTime": "升级时间"
|
||||
"completeTime": "升级时间",
|
||||
"status": "状态",
|
||||
"failReason": "失败原因",
|
||||
"batchDelete": "批量删除",
|
||||
"deleteTips": "确定要删除吗?"
|
||||
}
|
||||
|
||||
@ -29,5 +29,7 @@
|
||||
"uploadIpTips": "",
|
||||
"developAppid": "开发小程序APPID",
|
||||
"developAppidPlaceholder": "请输入开发小程序APPID",
|
||||
"uploadIpTips": "如果小程序代码上传开启了ip白名单设置,在ip白名单中添加ip:"
|
||||
"uploadIpTips": "如果小程序代码上传开启了ip白名单设置,在ip白名单中添加ip:",
|
||||
"siteWeappSync": "站点小程序同步",
|
||||
"syncSiteWeappTips": "是否要给该套餐下已授权小程序的站点提交代码?"
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
{
|
||||
"groupName": "站点套餐",
|
||||
"lastTime": "上次同步时间",
|
||||
"weappVersionUpdate": "小程序同步",
|
||||
"weappVersionUpdateRecord": "小程序同步记录",
|
||||
"weappVersionUpdate": "同步模板库",
|
||||
"weappVersionUpdateRecord": "模板库同步记录",
|
||||
"createTime": "提交时间",
|
||||
"userVersion": "版本号",
|
||||
"failReason": "失败原因",
|
||||
@ -11,5 +11,7 @@
|
||||
"commitRecord": "同步记录",
|
||||
"oneClickSync": "一键同步",
|
||||
"syncTemplateError": "未能同步到模板库",
|
||||
"templateID": "模板ID"
|
||||
"templateID": "模板ID",
|
||||
"siteWeappSync": "站点小程序同步",
|
||||
"syncSiteWeappTips": "是否要给该套餐下已授权小程序的站点提交代码?"
|
||||
}
|
||||
|
||||
@ -1,32 +1,38 @@
|
||||
<template>
|
||||
<!--授权信息-->
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never" v-if="!loading">
|
||||
<div>
|
||||
<div class="text-[#333] text-[18px]">授权信息</div>
|
||||
<div class="ml-[50px] mt-[40px]">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<span class="mr-[6px] text-[14px] text-[#666666] w-[70px] text-right">授权公司:</span>
|
||||
<span class="text-[14px] text-[#333]">{{ authinfo.company_name || "--" }}</span>
|
||||
<el-card class="box-card !border-none min-h-[300px]" shadow="never" v-loading="loading">
|
||||
<div v-if="!loading">
|
||||
<div class="title text-[16px] font-bold text-[#1D1F3A] mb-[30px]">授权信息</div>
|
||||
<div class="">
|
||||
<div class="flex items-center">
|
||||
<div class="w-[92px] h-[92px] rounded-[10px] flex justify-center items-center mr-[20px]">
|
||||
<img src="@/app/assets/images/tools/authorize.png" class="w-[92px] h-[92px]" />
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center mt-[20px]">
|
||||
<span class="mr-[6px] text-[14px] text-[#666666] w-[70px] text-right">授权域名:</span>
|
||||
<span class="text-[14px] text-[#333]">{{ authinfo.site_address || "--" }}</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center mt-[20px]">
|
||||
<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 />
|
||||
</el-icon>
|
||||
<el-icon v-else @click="isCheck = !isCheck" class="text-[12px] cursor-pointer text-[#4383F9]"> <Hide /> </el-icon>
|
||||
</span>
|
||||
<div class="flex flex-col justify-between font-500">
|
||||
<div class="flex flex-wrap items-center mb-[12px]">
|
||||
<span class="mr-[6px] text-[14px] text-[#666666] w-[70px] text-left">授权公司:</span>
|
||||
<span class="text-[14px] text-[#333]">{{ authinfo.company_name || "--" }}</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center mb-[12px]">
|
||||
<span class="mr-[6px] text-[14px] text-[#666666] w-[70px] text-left">授权域名:</span>
|
||||
<span class="text-[14px] text-[#333]">{{ authinfo.site_address || "--" }}</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center">
|
||||
<span class="mr-[6px] text-[14px] text-[#666666] w-[70px] text-left">授权码:</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-[14px] cursor-pointer text-[#9699B6]">
|
||||
<View />
|
||||
</el-icon>
|
||||
<el-icon v-else @click="isCheck = !isCheck" class="text-[14px] cursor-pointer text-[#9699B6]"> <Hide /> </el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[60px] mb-[50px]">
|
||||
<el-button class="w-[150px] !h-[46px] mt-[8px]" type="primary" @click="authCodeApproveFn">授权码认证</el-button>
|
||||
|
||||
<div class="mt-[17px] ml-[110px]">
|
||||
<el-button class="!w-[140px] !h-[32px] mt-[8px] !rounded-[4px]" type="primary" @click="authCodeApproveFn">授权码认证</el-button>
|
||||
<el-popover ref="getAuthCodeDialog" placement="bottom-start" :width="478" trigger="click" class="mt-[8px]">
|
||||
<div class="px-[18px] py-[8px]">
|
||||
<p class="leading-[32px] text-[14px]">您在官方应用市场购买任意一款应用,即可获得授权码。输入正确授权码认证通过后,即可支持在线升级和其它相关服务</p>
|
||||
@ -36,7 +42,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<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>
|
||||
<el-button class="!w-[140px] !h-[32px] mt-[8px] !rounded-[4px] !text-[var(--el-color-primary)] hover:!text-[var(--el-color-primary)] !bg-transparent" plain type="primary">如何获取授权码?</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
@ -164,4 +170,8 @@ const getVersionsInfo = () => {
|
||||
getVersionsInfo()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-button){
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,22 +1,58 @@
|
||||
<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>
|
||||
<el-card class="box-card !border-none min-h-[500px]" shadow="never" v-loading="loadingVersion">
|
||||
<div v-if="!loadingVersion">
|
||||
<div class="mb-[30px]" v-if="newVersion">
|
||||
<div class="title text-[16px] font-bold text-[#1D1F3A] mb-[20px]">版本信息</div>
|
||||
<div class="text-[14px] text-[#1D1F3A] mb-[20px]"><span>系统当前版本:</span><span class="font-bold ">v{{ version }}</span> </div>
|
||||
<div class="flex">
|
||||
<div class="w-[92px] h-[92px] rounded-[10px] flex justify-center items-center mr-[20px]">
|
||||
<img src="@/app/assets/images/tools/upgrade.png" class="w-[92px] h-[92px]" />
|
||||
</div>
|
||||
<div class="flex flex-col justify-between items-start">
|
||||
<div class="text-[14px] text-[#1D1F3A]">系统最新版本为</div>
|
||||
<div class="text-[14px] text-[#1D1F3A] font-bold">v{{ newVersion.version_no }}({{ versionCode }})</div>
|
||||
<div class="text-[#9699B6] text-[16px]" v-if="!shouldShowUpgradeButton">
|
||||
<span>已是最新</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-button class="w-[102px] !h-[32px]" type="primary" :loading="loading" @click="handleUpgrade" v-if="!(!newVersion || (newVersion && newVersion.version_no == version))">一键升级</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</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 class="panel-title bg-[#F4F5F7] border-[#E6E6E6] border-solid border-b-[1px] h-[40px] flex items-center p-[10px]">
|
||||
<span class="text-[14px] font-500 text-[#1D1F3A]">升级记录</span>
|
||||
</div>
|
||||
<div >
|
||||
<div class="time-dialog" style="overflow: auto">
|
||||
<el-scrollbar>
|
||||
<el-timeline style="width: 100%">
|
||||
<el-timeline-item v-for="(item, index) in frameworkVersionList" :key="index" placement="left" :hollow="true">
|
||||
<el-collapse v-model="activeName" accordion>
|
||||
<el-collapse-item :name="index">
|
||||
<template #title>
|
||||
<div class="flex justify-between items-start flex-col">
|
||||
<span class="text-[#1D1F3A] text-[14px] leading-[20px]">版本: v{{ item.version_no }}</span>
|
||||
<span class="text-[#9699B6] text-[13px] mt-2">{{ item.release_time }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #icon="{ isActive }">
|
||||
<div class="ml-auto text-[#374151] flex items-center">
|
||||
<span class="text-[#374151] text-[14px]">{{ isActive ? '收起' : '更新内容' }}</span>
|
||||
<span class="iconfont iconjiantouxia ml-[4px] !text-[10px] transition-transform duration-300" v-if="!isActive"></span>
|
||||
<span class="iconfont iconjiantoushang ml-[4px] !text-[10px] transition-transform duration-300" v-if="isActive"></span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="px-[20px] py-[20px] bg-overlay timeline-log-wrap whitespace-pre-wrap rounded-[4px] bg-[#F9F9FB] text-[#4F516D]" 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>
|
||||
</div>
|
||||
@ -66,7 +102,7 @@ const upgradeRef = ref<any>(null)
|
||||
const upgradeLogRef = ref<any>(null)
|
||||
const authCodeApproveDialog = ref(false)
|
||||
const frameworkVersionList = ref([])
|
||||
|
||||
const activeName = ref(0)
|
||||
const checkVersion = ref(false)
|
||||
|
||||
const formData = reactive<Record<string, string>>({
|
||||
@ -100,10 +136,12 @@ const save = async(formEl: FormInstance | undefined) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const loadingVersion = ref(false)
|
||||
const getFrameworkVersionListFn = () => {
|
||||
loadingVersion.value = true
|
||||
getFrameworkVersionList().then(({ data }) => {
|
||||
frameworkVersionList.value = data
|
||||
loadingVersion.value = false
|
||||
if (checkVersion.value) {
|
||||
if (!newVersion.value || (newVersion.value && newVersion.value.version_no == version.value)) {
|
||||
ElMessage({
|
||||
@ -114,10 +152,24 @@ const getFrameworkVersionListFn = () => {
|
||||
} else {
|
||||
checkVersion.value = true
|
||||
}
|
||||
}).catch(() => {
|
||||
loadingVersion.value = false
|
||||
})
|
||||
}
|
||||
getFrameworkVersionListFn()
|
||||
|
||||
const shouldShowUpgradeButton = computed(() => {
|
||||
if (!newVersion.value || newVersion.value.version_no === version.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 将版本号转为字符串再处理
|
||||
const currentVersionStr = String(version.value);
|
||||
const latestVersionStr = String(newVersion.value.version_no);
|
||||
// 移除点号并转为数字比较
|
||||
const currentVersionNum = parseInt(currentVersionStr.replace(/\./g, ''), 10);
|
||||
const latestVersionNum = parseInt(latestVersionStr.replace(/\./g, ''), 10);
|
||||
return latestVersionNum > currentVersionNum;
|
||||
});
|
||||
const newVersion: any = computed(() => {
|
||||
return frameworkVersionList.value.length ? frameworkVersionList.value[0] : null
|
||||
})
|
||||
@ -136,7 +188,11 @@ const getVersionsInfo = () => {
|
||||
})
|
||||
}
|
||||
getVersionsInfo()
|
||||
|
||||
const timeSplit = (str: string) => {
|
||||
const [date, time] = str.split(" ")
|
||||
const [hours, minutes] = time.split(":")
|
||||
return [date, `${ hours }:${ minutes }`]
|
||||
}
|
||||
interface AuthInfo {
|
||||
company_name: string
|
||||
site_address: string
|
||||
@ -191,5 +247,32 @@ const openUpgrade = () => {
|
||||
upgradeLogRef.value?.open()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-timeline-item__node--normal){
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
}
|
||||
:deep(.el-timeline-item__tail){
|
||||
left: 6px !important;
|
||||
}
|
||||
:deep(.el-timeline-item__node.is-hollow){
|
||||
background: #9699B6 !important;
|
||||
border-width: 3px !important;
|
||||
}
|
||||
:deep(.time-dialog .el-timeline-item__wrapper) {
|
||||
top: -2px !important;
|
||||
}
|
||||
:deep(.el-collapse-item__header){
|
||||
background: #F9F9FB !important;
|
||||
border: 1px solid #F1F1F8 !important;
|
||||
padding: 0 20px !important;
|
||||
line-height: normal !important;
|
||||
height: 70px !important;
|
||||
}
|
||||
:deep(.el-collapse-item__content){
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
:deep(.el-button){
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
</style>
|
||||
@ -7,7 +7,7 @@
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mt-[20px]">
|
||||
<div class="flex justify-between items-start mt-[20px]">
|
||||
<el-form :inline="true" :model="sysUserLogTableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('ip')" prop="ip">
|
||||
<el-input v-model.trim="sysUserLogTableData.searchParam.ip" :placeholder="t('ipPlaceholder')" />
|
||||
@ -25,6 +25,11 @@
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="flex justify-end items-center w-[20%]">
|
||||
<div>
|
||||
<el-button type="primary" class="w-[100px]" @click="clearEvent()">{{ t('清空日志') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -61,7 +66,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getLogList } from '@/app/api/site'
|
||||
import { getLogList ,logDestroy} from '@/app/api/site'
|
||||
import UserLogDetail from '@/app/views/auth/components/user-log-detail.vue'
|
||||
import { FormInstance } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
@ -117,6 +122,20 @@ const detailEvent = (data: any) => {
|
||||
userLogDetailDialog.value.setFormData(data)
|
||||
userLogDetailDialog.value.showDialog = true
|
||||
}
|
||||
|
||||
const clearEvent = () => {
|
||||
ElMessageBox.confirm(t('确定要全部清空操作日志吗?'), t('提示'), {
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
logDestroy().then(() => {
|
||||
loadSysUserLogList()
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
<div v-else-if="row.menu_type == 2">{{ t('menuTypeButton') }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="api_url" :label="t('authId')" min-width="150" align="center" />
|
||||
<el-table-column prop="api_url" :label="t('authId')" min-width="150" align="left" />
|
||||
<el-table-column :label="t('status')" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag class="ml-2" type="success" v-if="row.status == 1">{{ t('statusNormal') }}</el-tag>
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<!--角色管理-->
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<el-card class="box-card !border-none setting-card" shadow="never">
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<!-- <div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="flex justify-between items-center mt-[20px]">
|
||||
<el-form :inline="true" :model="roleTableData.searchParam" ref="searchFormRef">
|
||||
@ -138,4 +138,8 @@ const deleteEvent = (id: number) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.setting-card .el-card__body){
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -186,4 +186,6 @@ const deleteEvent = (key: string) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@ -6,84 +6,89 @@
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
</div>
|
||||
<el-tabs v-model="activeName" class="mt-[20px]">
|
||||
<el-tab-pane :label="t('管理员')" name="userList">
|
||||
<div class="flex justify-between items-center mt-[20px]">
|
||||
<el-form :inline="true" :model="userTableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('accountNumber')" prop="search">
|
||||
<el-input v-model.trim="userTableData.searchParam.search" class="input-width" :placeholder="t('accountNumberPlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadUserList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" class="w-[100px] self-start" @click="addEvent">{{ t('addUser') }}</el-button>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mt-[20px]">
|
||||
<el-form :inline="true" :model="userTableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('accountNumber')" prop="search">
|
||||
<el-input v-model.trim="userTableData.searchParam.search" class="input-width" :placeholder="t('accountNumberPlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadUserList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" class="w-[100px] self-start" @click="addEvent">{{ t('addUser') }}</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-table :data="userTableData.data" size="large" v-loading="userTableData.loading">
|
||||
<template #empty>
|
||||
<span>{{ !userTableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<el-table :data="userTableData.data" size="large" v-loading="userTableData.loading">
|
||||
<template #empty>
|
||||
<span>{{ !userTableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
|
||||
<el-table-column :label="t('headImg')" width="100" align="left">
|
||||
<template #default="{ row }">
|
||||
<div class="w-[35px] h-[35px] flex items-center justify-center">
|
||||
<img v-if="row.head_img" :src="img(row.head_img)" class="w-[35px] rounded-full" />
|
||||
<img v-else src="@/app/assets/images/member_head.png" class="w-[35px] rounded-full" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" :label="t('accountNumber')" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="real_name" :label="t('userRealName')" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.real_name ? row.real_name :'--' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('userRoleName')" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.is_admin">{{ t('administrator') }}</span>
|
||||
<span v-else-if="row.role_array.length">{{ row.role_array.join(' | ') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('status')" min-width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag class="ml-2" type="success" v-if="row.status == 1">{{ t('statusUnlock') }}</el-tag>
|
||||
<el-tag class="ml-2" type="error" v-if="row.status == 0">{{ t('statusLock') }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="last_time" :label="t('lastLoginTime')" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.last_time || '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('lastLoginIP')" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.last_ip || '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('operation')" align="right" fixed="right" width="160">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.is_admin != 1">
|
||||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
|
||||
<el-button type="primary" link @click="lockEvent(row.uid)" v-if="row.status">{{ t('lock') }}</el-button>
|
||||
<el-button type="primary" link @click="unlockEvent(row.uid)" v-else>{{ t('unlock') }}</el-button>
|
||||
<el-button type="primary" link @click="deleteEvent(row.uid)">{{ t('delete') }}</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-button link disabled>{{ t('adminDisabled') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="userTableData.page" v-model:page-size="userTableData.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="userTableData.total"
|
||||
@size-change="loadUserList()" @current-change="loadUserList" />
|
||||
</div>
|
||||
</div>
|
||||
<el-table-column :label="t('headImg')" width="100" align="left">
|
||||
<template #default="{ row }">
|
||||
<div class="w-[35px] h-[35px] flex items-center justify-center">
|
||||
<img v-if="row.head_img" :src="img(row.head_img)" class="w-[35px] rounded-full" />
|
||||
<img v-else src="@/app/assets/images/member_head.png" class="w-[35px] rounded-full" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" :label="t('accountNumber')" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="real_name" :label="t('userRealName')" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.real_name ? row.real_name :'--' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('userRoleName')" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.is_admin">{{ t('administrator') }}</span>
|
||||
<span v-else-if="row.role_array.length">{{ row.role_array.join(' | ') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('status')" min-width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag class="ml-2" type="success" v-if="row.status == 1">{{ t('statusUnlock') }}</el-tag>
|
||||
<el-tag class="ml-2" type="error" v-if="row.status == 0">{{ t('statusLock') }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="last_time" :label="t('lastLoginTime')" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.last_time || '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('lastLoginIP')" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.last_ip || '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('operation')" align="right" fixed="right" width="160">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.is_admin != 1">
|
||||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
|
||||
<el-button type="primary" link @click="lockEvent(row.uid)" v-if="row.status">{{ t('lock') }}</el-button>
|
||||
<el-button type="primary" link @click="unlockEvent(row.uid)" v-else>{{ t('unlock') }}</el-button>
|
||||
<el-button type="primary" link @click="deleteEvent(row.uid)">{{ t('delete') }}</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-button link disabled>{{ t('adminDisabled') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="userTableData.page" v-model:page-size="userTableData.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="userTableData.total"
|
||||
@size-change="loadUserList()" @current-change="loadUserList" />
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('管理员角色')" name="userRole">
|
||||
<userRole></userRole>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<edit-user ref="editUserDialog" @complete="loadUserList()" />
|
||||
</el-card>
|
||||
</div>
|
||||
@ -94,6 +99,7 @@ import { reactive, ref } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getUserList, lockUser, unlockUser, deleteUser } from '@/app/api/site'
|
||||
import EditUser from '@/app/views/auth/components/edit-user.vue'
|
||||
import userRole from '@/app/views/auth/role.vue'
|
||||
import { img } from '@/utils/common'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
@ -112,7 +118,7 @@ const userTableData = reactive({
|
||||
user_type: ''
|
||||
}
|
||||
})
|
||||
|
||||
const activeName = ref('userList')
|
||||
const searchFormRef = ref<FormInstance>()
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
@ -216,4 +222,6 @@ const deleteEvent = (uid: number) => {
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@ -40,10 +40,9 @@
|
||||
<el-button type="primary" link>{{ t('preview') }}</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-button type="primary" link v-if="row.status == -1 || row.status == -2" @click="handleFailReason(row)">
|
||||
{{ t('failReason') }}</el-button>
|
||||
<el-button type="primary" link v-if="row.status == -2" @click="againUpload(row)" :loading="uploading">
|
||||
{{ t('againUpload') }}</el-button>
|
||||
<el-button type="primary" link v-if="row.status == -1 || row.status == -2" @click="handleFailReason(row)">{{ t('failReason') }}</el-button>
|
||||
<el-button type="primary" link v-if="row.status == -2" @click="againUpload(row)" :loading="uploading">{{ t('againUpload') }}</el-button>
|
||||
<el-button type="primary" link v-if="row.status == 2" @click="undoAuditFn(row)">{{ t('undoAudit') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -105,7 +104,7 @@ import { getAppType } from '@/utils/common'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { AnyObject } from '@/types/global'
|
||||
import Storage from '@/utils/storage'
|
||||
import {siteWeappCommit} from "@/app/api/wxoplatform";
|
||||
import { siteWeappCommit, undoAudit } from "@/app/api/wxoplatform";
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@ -285,6 +284,25 @@ const configElMessageBox = () => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回代码审核
|
||||
* @param data
|
||||
*/
|
||||
const undoAuditFn = (data: any) => {
|
||||
ElMessageBox.confirm(
|
||||
t('undoAuditTips'),
|
||||
t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel')
|
||||
}
|
||||
).then(() => {
|
||||
undoAudit({ id: data.id }).then(() => {
|
||||
getWeappVersionListFn()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const failReason = ref('')
|
||||
const failReasonDialogVisible = ref(false)
|
||||
const handleFailReason = (data: any) => {
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="bg-[#F7F9FA] min-h-screen">
|
||||
<!-- <div class="flex justify-between items-center">
|
||||
<layoutHeader></layoutHeader>
|
||||
</div> -->
|
||||
<div class="flex justify-between items-center py-[24px] pl-[62px] pr-[64px] home-head">
|
||||
<div class="flex items-center" v-if="webConfig">
|
||||
<img class="w-[32x] h-[32px] rounded-full" v-if="webConfig.icon" :src="img(webConfig.icon)" alt="">
|
||||
@ -7,69 +10,166 @@
|
||||
<span class="ml-[10px] text-[16px] font-bold">{{webConfig.site_name}}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div v-if="userStore.userInfo.is_super_admin" class="border-primary border-[1px] h-[30px] px-[15px] flex items-center rounded-[6px] mr-[10px] cursor-pointer" @click="toHome">
|
||||
<span class="iconfont iconguanliduan !text-primary mr-1"></span>
|
||||
<span class="text-[14px] text-primary">控制台</span>
|
||||
</div>
|
||||
<span class="mr-[12px] text-[14px]">{{userStore.userInfo.username}}</span>
|
||||
<span @click="logoutFn()" class="text-[14px] cursor-pointer text-[var(--el-color-primary)]">退出</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-[1400px] m-auto mt-[62px]">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="w-full m-auto " :style="{height:'calc(100vh - 80px)'}">
|
||||
<!-- <div class="flex justify-between items-center">
|
||||
<span class="text-[24px] font-bold">站点列表</span>
|
||||
<el-button type="primary" class="w-[90px] !h-[34px]" :disabled="siteGroupLoading" @click="handleChick">创建站点</el-button>
|
||||
</div>
|
||||
<div class="flex justify-between items-center mt-[18px]">
|
||||
<div class="w-[800px] text-[14px] whitespace-nowrap">
|
||||
</div> -->
|
||||
<div class="flex justify-between h-full">
|
||||
<div class="text-[14px] whitespace-nowrap w-[220px] bg-white h-full border-r-[1px] border-[var(--el-border-color-light)]">
|
||||
<el-scrollbar :always="true">
|
||||
<span :class="['px-[10px] cursor-pointer h-[35px] leading-[35px] inline-block', {'class-select text-[var(--el-color-primary)]': params.app == ''}]" @click="cutAppFn('')">所有应用</span>
|
||||
<span :class="['px-[10px] cursor-pointer h-[35px] leading-[35px] inline-block', {'class-select text-[var(--el-color-primary)]': params.app == item.key}]" @click="cutAppFn(item.key)" v-for="(item,index) in addonList" :key="index">{{item.title}}</span>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<el-input v-model.trim="params.keywords" class="!w-[300px] !h-[34px]" placeholder="请输入要搜索的站点名称/编号" @keyup.enter.native="getHomeSiteFn()">
|
||||
<template #suffix>
|
||||
<el-icon @click.stop="getHomeSiteFn()" class="cursor-pointer">
|
||||
<Search />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<div class="min-h-[580px]">
|
||||
<div class="flex flex-wrap mt-[30px]" v-loading="loading">
|
||||
<div v-for="(item, index) in tableData" :key="index" @click="selectSite(item)" :class="['home-item w-[327px] box-border mb-[30px] cursor-pointer',{'mr-[30px]': index ==0 || (index+1)%4 != 0}]">
|
||||
<div class="flex items-center px-[24px] pt-[22px] pb-[10px] home-item-head relative">
|
||||
<div class="absolute h-[4px] w-full z-1 left-0 top-0" :style="{'background-color': item.theme_color}" v-if="item.theme_color"></div>
|
||||
<img v-if="item.icon" class="w-[46px] h-[46px] mr-[15px] img-shadow rounded-[6px] overflow-hidden" :src="img(item.icon)" />
|
||||
<img v-else class="w-[46px] h-[46px] mr-[15px] rounded-[6px] img-shadow overflow-hidden" src="@/app/assets/images/site_default.png" />
|
||||
<div class="flex flex-col flex-1 justify-center">
|
||||
<div class="flex items-center flex-wrap">
|
||||
<span class="text-[16px] text-[#000] max-w-[160px] font-bold truncate mr-[10px]">{{item.site_name}}</span>
|
||||
<div class="flex items-center justify-center min-w-[42px] h-[18px] bg-[#FF5500] rounded-tl-md rounded-br-md items-tab" v-if="item.app_name">
|
||||
<span class="text-[12px] text-[#000]">{{item.app_name}}</span>
|
||||
</div>
|
||||
<div class="px-[20px] mt-[20px]">
|
||||
<div class="mt-3 text-[#9699B6] mb-1">应用</div>
|
||||
<div class="">
|
||||
<div :class="['px-[25px] cursor-pointer h-[40px] leading-[40px]', {'bg-[#EAEBF0] rounded-[4px]': params.app == ''}]" @click="cutAppFn('')">
|
||||
<span class="iconfont iconsuoyouyingyongc !text-[17px] mr-[10px] "></span>
|
||||
<span>所有应用</span>
|
||||
</div>
|
||||
<span class="text-[12px] mt-[3px] text-[#666]">{{item.create_time ? item.create_time.split(" ")[0] : '--'}} 到 {{item.expire_time ? item.expire_time.split(" ")[0] : '--'}}</span>
|
||||
<div :class="['px-[25px] cursor-pointer h-[40px] leading-[40px] flex items-center', {'bg-[#EAEBF0] rounded-[4px]': params.app == item.key}]" @click="cutAppFn(item.key)" v-for="(item, index) in (showMore ? addonList : addonList.slice(0, 4))" :key="index">
|
||||
<img v-if="item.icon" :src="item.icon" class="w-[17px] h-[17px] mr-[10px] rounded-full" />
|
||||
<span class="iconfont iconsuoyouyingyongc !text-[17px] mr-[10px] " v-else></span>
|
||||
<span>{{item.title}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="addonList.length > 4" class="text-[#9699B6] text-[14px] cursor-pointer flex items-center mt-1" @click="showMore = !showMore">
|
||||
<span class="iconfont iconjiantouxia ml-[4px] !text-[14px] mr-[10px] transition-transform duration-300" :class="{ 'rotate-180': showMore }"></span>
|
||||
<span>{{ showMore ? '收起' : '更多' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-[24px] py-[20px] text-[#666]">
|
||||
<p class="text-[14px]">站点编号:{{item.site_id}}</p>
|
||||
<p class="text-[14px] mt-[2px]">站点套餐:{{item.group_name || '--'}}</p>
|
||||
|
||||
<div class="px-[20px] mt-[20px]">
|
||||
<div class="text-[#9699B6] mb-1">平台分类</div>
|
||||
<div class="">
|
||||
<div :class="['px-[25px] cursor-pointer h-[40px] leading-[40px] flex items-center', {'bg-[#EAEBF0] rounded-[4px]': typeName == 'h5'}]" @click="cutAppTypeFn('h5')">
|
||||
<span class="iconfont iconH5c !text-[17px] mr-[10px] "></span>
|
||||
<span>H5</span>
|
||||
</div>
|
||||
<div :class="['px-[25px] cursor-pointer h-[40px] leading-[40px] flex items-center', {'bg-[#EAEBF0] rounded-[4px]': typeName == 'wx'}]" @click="cutAppTypeFn('wx')">
|
||||
<span class="iconfont iconxiaochengxutongbuc !text-[17px] mr-[10px] "></span>
|
||||
<span>微信小程序</span>
|
||||
</div>
|
||||
<div :class="['px-[25px] cursor-pointer h-[40px] leading-[40px] flex items-center', {'bg-[#EAEBF0] rounded-[4px]': typeName == 'pc'}]" @click="cutAppTypeFn('pc')">
|
||||
<span class="iconfont iconPCc !text-[17px] mr-[10px] "></span>
|
||||
<span>PC</span>
|
||||
</div>
|
||||
<div :class="['px-[25px] cursor-pointer h-[40px] leading-[40px] flex items-center', {'bg-[#EAEBF0] rounded-[4px]': typeName == 'app'}]" @click="cutAppTypeFn('app')">
|
||||
<span class="iconfont iconAPPc !text-[17px] mr-[10px] "></span>
|
||||
<span>APP</span>
|
||||
</div>
|
||||
<div :class="['px-[25px] cursor-pointer h-[40px] leading-[40px] flex items-center', {'bg-[#EAEBF0] rounded-[4px]': typeName == 'zfb'}]" @click="cutAppTypeFn('zfb')">
|
||||
<span class="iconfont iconzhifubaoxiaochengxuc !text-[17px] mr-[10px] "></span>
|
||||
<span>支付宝小程序</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="flex-1 h-full p-[20px] flex flex-col">
|
||||
<div class="flex-1 min-h-0">
|
||||
<el-scrollbar>
|
||||
<div class="flex justify-between items-center input-box">
|
||||
<el-input v-model.trim="params.keywords" class="!w-[350px] !h-[40px]" placeholder="请输入要搜索的站点名称/编号" @keyup.enter.native="getHomeSiteFn()">
|
||||
<template #suffix>
|
||||
<el-icon @click.stop="getHomeSiteFn()" class="cursor-pointer">
|
||||
<Search />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="flex items-center text-[14px] cursor-pointer pr-[10px] h-[40px]">
|
||||
<div class="text-[#9699B6]">排序:</div>
|
||||
<div>
|
||||
<!-- <span>创建时间</span>
|
||||
<span class="ml-[10px]">站点名称</span>
|
||||
<span class="ml-[10px]">站点类型</span> -->
|
||||
<el-select v-model="params.sort" placeholder="请选择" class="!w-[200px] !h-[40px] !border-none" @change="getHomeSiteFn()">
|
||||
<el-option v-for="item in sortList" :key="item.value" :label="item.label" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<el-button type="primary" class="w-[90px] !h-[34px] ml-[20px]" :disabled="siteGroupLoading" @click="handleChick">创建站点</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-[-20px]">
|
||||
<div class="flex flex-wrap mt-[30px]" v-loading="loading">
|
||||
<div v-for="(item, index) in tableData" :key="index" @click="selectSite(item)" :class="['home-item w-[313px] ml-[20px] box-border mb-[20px] border-[1px] border-[#E8E9EB] rounded-[6px] cursor-pointer']">
|
||||
<div class="flex items-center px-[24px] pt-[22px] pb-[10px] home-item-head relative">
|
||||
<!-- <div class="absolute h-[4px] w-full z-1 left-0 top-0" :style="{'background-color': item.theme_color}" v-if="item.theme_color"></div> -->
|
||||
<img v-if="item.icon" class="w-[46px] h-[46px] mr-[15px] img-shadow rounded-[6px] overflow-hidden" :src="img(item.icon)" />
|
||||
<img v-else class="w-[46px] h-[46px] mr-[15px] rounded-[6px] img-shadow overflow-hidden" src="@/app/assets/images/site_default.png" />
|
||||
<div class="flex flex-col flex-1 justify-center">
|
||||
<div class="flex items-center flex-wrap">
|
||||
<span class="text-[16px] text-[#000] max-w-[160px] truncate mr-[10px]">{{item.site_name}}</span>
|
||||
<div class="flex items-center justify-center min-w-[42px] h-[18px] bg-[#FF5500] rounded-tl-md rounded-br-md items-tab" v-if="item.app_name">
|
||||
<span class="text-[12px] text-[#000]">{{item.app_name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <span class="text-[12px] mt-[3px] text-[#666]">{{item.create_time ? item.create_time.split(" ")[0] : '--'}} 到 {{item.expire_time ? item.expire_time.split(" ")[0] : '--'}}</span> -->
|
||||
<span class="text-[12px] mt-[3px] text-[#666]">NO:{{item.site_id}}</span>
|
||||
</div>
|
||||
<!-- <div class="absolute right-[12px] top-[10px] isshow">
|
||||
<span class="iconfont icona-gengduoV6xx-28"></span>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="px-[24px] py-[20px] text-[#666]">
|
||||
<!-- <p class="text-[14px]">站点编号:{{item.site_id}}</p> -->
|
||||
|
||||
<p class="text-[14px] mt-[2px] text-[var(--el-color-primary)]">
|
||||
<span class="iconfont icona-Frame427322133 !text-[14px]"></span>
|
||||
<span>{{item.group_name || '--'}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!tableData.length && !loading" class="m-auto mt-[100px]">
|
||||
<img src="@/app/assets/images/site_empty.png" class="w-[220px] h-[165px]"/>
|
||||
<p class="text-center text-gray-400 text-[14px] mt-[20px]">暂无站点</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[16px] flex justify-center pr-[10px]">
|
||||
<el-pagination v-model:current-page="site.params.page" v-model:page-size="site.params.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="site.total"
|
||||
@size-change="getHomeSiteFn()" @current-change="getHomeSiteFn" :hide-on-single-page="true"/>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="flex justify-between text-[12px] mt-[20px] text-[#666]" v-if="copyright">
|
||||
<div>
|
||||
<a :href="copyright.copyright_link" target="_blank">
|
||||
<span class="mr-3" v-if="copyright.company_name">{{ copyright.company_name }}</span>
|
||||
</a>
|
||||
<a href="https://beian.miit.gov.cn/" v-if="copyright.icp" target="_blank">
|
||||
<span class="mr-3">{{ copyright.icp }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center cursor-pointer">
|
||||
<span class="mx-1" @click="getInfoFn">版权信息</span> | <span
|
||||
class="mx-1">开发者联盟与隐私的声明</span> | <span class="mx-1">隐私政策</span> | <span
|
||||
class="mx-1">联系我们</span> | <span class="mx-1">Cookies</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!tableData.length && !loading" class="m-auto mt-[100px]">
|
||||
<img src="@/app/assets/images/site_empty.png" class="w-[220px] h-[165px]"/>
|
||||
<p class="text-center text-gray-400 text-[14px] mt-[20px]">暂无站点</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="site.params.page" v-model:page-size="site.params.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="site.total"
|
||||
@size-change="getHomeSiteFn()" @current-change="getHomeSiteFn" :hide-on-single-page="true"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="版权信息" width="500">
|
||||
<span>{{ copyright.copyright_desc }}</span>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="dialogVisible = false">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="createSiteDialog" width="54vw" :destroy-on-close="true" style="border-radius: 25px;">
|
||||
<template #title>
|
||||
<div class="text-[#333333] text-[22px] ml-[15px] leading-[1] mt-[10px]">创建站点</div>
|
||||
@ -97,12 +197,12 @@
|
||||
<div class="w-[140px] h-[40px] px-[15px] truncate text-white text-[16px] text-center leading-[40px] creatBg relative -left-[1px] -top-[2px]">
|
||||
{{ item.site_group.group_name }}
|
||||
</div>
|
||||
<el-scrollbar class="flex pb-[20px] pt-[4px] box-border !h-[260px]">
|
||||
<div class="flex mx-[30px] mt-[14px] leading-[1] items-center" v-for="app in item.site_group.app_name">
|
||||
<el-scrollbar class="flex pb-[20px] pt-[4px] box-border !h-[260px] w-[100%] scrollbarBox">
|
||||
<div class="flex mx-[30px] mt-[14px] leading-[1] items-center w-full" v-for="app in item.site_group.app_name">
|
||||
<div class="nc-iconfont nc-icon-duiV6mm text-[#466CEA]"></div>
|
||||
<div class="text-[14px] text-[#666666] ml-[3px] truncate">{{ app }}</div>
|
||||
</div>
|
||||
<div class="flex mx-[30px] mt-[14px] leading-[1] text-center" v-for="addon in item.site_group.addon_name">
|
||||
<div class="flex mx-[30px] mt-[14px] leading-[1] text-center w-full" v-for="addon in item.site_group.addon_name">
|
||||
<div class="nc-iconfont nc-icon-duiV6mm text-[#466CEA]"></div>
|
||||
<div class="text-[14px] text-[#666666] ml-[3px] truncate">{{ addon }}</div>
|
||||
</div>
|
||||
@ -124,24 +224,34 @@
|
||||
import {reactive, ref, toRefs, computed, watch} from 'vue'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { getHomeSite, getSiteGroup, createSite } from '@/app/api/home'
|
||||
import { img } from '@/utils/common'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import useSystemStore from '@/stores/modules/system'
|
||||
import storage from '@/utils/storage'
|
||||
import { getWebCopyright } from '@/app/api/sys'
|
||||
import { getInstalledAddonList } from '@/app/api/addon'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { AnyObject } from '@/types/global'
|
||||
import Test from '@/utils/test'
|
||||
import { img,getToken } from '@/utils/common'
|
||||
|
||||
const userStore:AnyObject = useUserStore()
|
||||
|
||||
const showMore = ref(false)
|
||||
const copyright = ref(null)
|
||||
getWebCopyright().then(({ data }) => {
|
||||
copyright.value = data
|
||||
})
|
||||
const dialogVisible = ref(false)
|
||||
const getInfoFn = () => {
|
||||
if (copyright.value.copyright_desc) {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
}
|
||||
interface Site{
|
||||
params: {
|
||||
keywords: string,
|
||||
page: number,
|
||||
limit: number,
|
||||
app: string,
|
||||
sort: boolean
|
||||
sort: string
|
||||
},
|
||||
loading: boolean,
|
||||
tableData: {
|
||||
@ -156,13 +266,55 @@ interface Site{
|
||||
total: 0
|
||||
}
|
||||
|
||||
const sortList = ref([
|
||||
// {
|
||||
// label: '全部',
|
||||
// value: 'all'
|
||||
// },
|
||||
{
|
||||
label: '创建时间',
|
||||
value: 'create_time'
|
||||
},
|
||||
{
|
||||
label: '站点编号',
|
||||
value: 'site_id'
|
||||
},
|
||||
// {
|
||||
// label: '站点类型',
|
||||
// value: 'app_name'
|
||||
// }
|
||||
])
|
||||
|
||||
const appList = ref([
|
||||
{
|
||||
title: 'H5',
|
||||
key: 'H5'
|
||||
},
|
||||
{
|
||||
title: '微信小程序',
|
||||
key: 'weapp'
|
||||
},
|
||||
{
|
||||
title: 'PC',
|
||||
key: 'pc'
|
||||
},
|
||||
{
|
||||
title: 'APP',
|
||||
key: 'app'
|
||||
},
|
||||
{
|
||||
title: '支付宝小程序',
|
||||
key: 'aliapp'
|
||||
}
|
||||
])
|
||||
|
||||
const site:Site = reactive({
|
||||
params: {
|
||||
keywords: '',
|
||||
page: 1,
|
||||
limit: 12,
|
||||
limit: 20,
|
||||
app: '',
|
||||
sort: false
|
||||
sort: 'create_time'
|
||||
},
|
||||
loading: false,
|
||||
tableData: [],
|
||||
@ -188,16 +340,34 @@ const cutAppFn = (app:any) => {
|
||||
site.params.app = app
|
||||
getHomeSiteFn()
|
||||
}
|
||||
const typeName = ref('')
|
||||
const cutAppTypeFn = (app:any) => {
|
||||
// site.params.app = app
|
||||
typeName.value = app
|
||||
getHomeSiteFn()
|
||||
}
|
||||
|
||||
// 网络设置
|
||||
const webConfig = computed(() => useSystemStore().website)
|
||||
|
||||
const selectSite = (site: any) => {
|
||||
storage.set({ key: 'siteId', data: site.site_id })
|
||||
storage.set({ key: 'siteInfo', data: site })
|
||||
storage.set({ key: 'comparisonSiteIdStorage', data: site.site_id })
|
||||
location.href = `${location.origin}/site/`
|
||||
window.localStorage.setItem('site.siteId', site.site_id)
|
||||
window.localStorage.setItem('site.siteInfo', site)
|
||||
window.localStorage.setItem('site.comparisonSiteIdStorage', site.site_id)
|
||||
window.open(`${ location.origin }/site/`);
|
||||
}
|
||||
|
||||
const toHome = () => {
|
||||
if (!window.localStorage.getItem('admin.token')) {
|
||||
window.localStorage.setItem('admin.token', getToken())
|
||||
window.localStorage.setItem('admin.comparisonTokenStorage', getToken())
|
||||
}
|
||||
if (!window.localStorage.getItem('admin.userinfo')) {
|
||||
window.localStorage.setItem('admin.userinfo', JSON.stringify(useUserStore().userInfo))
|
||||
}
|
||||
location.replace(location.origin + '/admin')
|
||||
}
|
||||
|
||||
const logoutFn = () => {
|
||||
userStore.logout()
|
||||
}
|
||||
@ -313,12 +483,18 @@ watch(() => createSiteDialog.value, () => {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
.isshow{
|
||||
display: none;
|
||||
}
|
||||
.home-item:hover {
|
||||
box-shadow: 0px 0px 18px rgba(0,0,0, 0.07);
|
||||
// border-color: var(--el-color-primary);
|
||||
.title {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.isshow{
|
||||
display: block;
|
||||
}
|
||||
.home-item-head{
|
||||
// background-color: #A1A7B7;
|
||||
span{
|
||||
@ -340,6 +516,21 @@ watch(() => createSiteDialog.value, () => {
|
||||
.create-site-item{
|
||||
box-shadow: 0px 0px 9px 0px rgba(0,0,0,0.1);
|
||||
}
|
||||
:deep(.el-button){
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
:deep(.input-box .el-input__wrapper) {
|
||||
box-shadow: none !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
:deep(.input-box .el-select__wrapper) {
|
||||
height: 40px !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
:deep(.scrollbarBox .el-scrollbar__wrap ){
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.create-site-name .el-input__wrapper{
|
||||
|
||||
@ -1,115 +1,223 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<div class="box-border">
|
||||
<el-card class="box-card !border-none profile-data" shadow="never">
|
||||
<template #header>
|
||||
<div class="border-none w-full">
|
||||
<span class="text-[16px]">{{ t("dataSummarize") }}</span>
|
||||
<!-- <span class="text-[12px] text-[#666] leading-[16px] ml-[18px]">更新时间 : </span>
|
||||
<span class="text-[12px] text-[#666] leading-[16px]">{{ time }}</span> -->
|
||||
<div class="flex items-center justify-center">
|
||||
<el-card class="box-card !border-none profile-data w-[1280px] mt-[20px] loading-box" shadow="never" v-loading="loading" >
|
||||
<!-- <div> -->
|
||||
<div class="box-border">
|
||||
<div class="bg-[#fff] mb-[20px] rounded-[6px] relative banner-box" v-if="showBanner" @mouseenter="hovering = 'banner'" @mouseleave="hovering = ''">
|
||||
<span class="absolute right-0 top-[-5px] text-[#999] hover:text-red-500 cursor-pointer z-10" v-if="hovering === 'banner'">
|
||||
<el-icon class="icon" :size="20" color="#7b7b7b" @click="closeModule('banner')">
|
||||
<CircleCloseFilled />
|
||||
</el-icon>
|
||||
</span>
|
||||
<div class="flex h-[156px]">
|
||||
<div class="w-full h-full ">
|
||||
<el-carousel :interval="3000" height="156px" class="rounded-[6px]">
|
||||
<el-carousel-item >
|
||||
<div class="h-full index-carousel" @click="toApplication">
|
||||
<img :src="img('static/resource/images/banner_1.png')" alt="" class="w-full h-full cursor-pointer">
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
<el-carousel-item >
|
||||
<div class="h-full index-carousel" @click="toApplication">
|
||||
<img :src="img('static/resource/images/banner_2.png')" alt="" class="w-full h-full cursor-pointer">
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showQuickStart" class="mt-[50px] flex items-center justify-between relative" @mouseenter="hovering = 'quickStart'" @mouseleave="hovering = ''">
|
||||
<span class="absolute right-0 top-[-5px] text-[#999] hover:text-red-500 cursor-pointer z-10" v-if="hovering === 'quickStart'">
|
||||
<el-icon class="icon" :size="20" color="#7b7b7b" @click="closeModule('quickStart')">
|
||||
<CircleCloseFilled />
|
||||
</el-icon>
|
||||
</span>
|
||||
<div class="w-[67%]">
|
||||
<p class="text-[18px] text-[#1D1F3A] mb-[10px]">快速开始,构建你的项目及应用</p>
|
||||
<p class="text-[14px] text-[#4F516D]">基于Vue3+PHP8+MYSQL8的跨平台应用构建</p>
|
||||
<div class="mt-[20px] grid grid-cols-2 gap-[20px]">
|
||||
<!-- 卡片 1 -->
|
||||
<div class="bg-[#EDEEF4] p-[20px] rounded-[6px] cursor-pointer flex flex-col items-start quick-action-card relative" @click="toLink('/admin/tools/addon')">
|
||||
<div class="title flex items-center">
|
||||
<span class="iconfont iconkaifayigechajianc !text-[20px] mr-[10px]"></span>
|
||||
<span class="text-[14px] text-[#1D1F3A]">开发一个插件</span>
|
||||
</div>
|
||||
<div class="absolute bottom-3 left-5 right-4 desc">
|
||||
<p class="text-[12px] text-[#4F516D]">创建自定义功能扩展</p>
|
||||
</div>
|
||||
<span class="absolute right-4 top-[3px] -translate-y-1/2 text-[16px] iconfont iconFrame-1"></span>
|
||||
</div>
|
||||
<div class="bg-[#EDEEF4] p-[20px] rounded-[6px] cursor-pointer flex flex-col items-start quick-action-card relative" @click="toLink('/admin/tools/code')">
|
||||
<div class="title flex items-center">
|
||||
<span class="iconfont iconAIshengchengchajianc !text-[20px] mr-[10px]"></span>
|
||||
<span class="text-[14px] text-[#1D1F3A]">AI生成插件</span>
|
||||
</div>
|
||||
<div class="absolute bottom-3 left-5 right-4 desc">
|
||||
<p class="text-[12px] text-[#4F516D]">使用AI自动生成插件代码</p>
|
||||
</div>
|
||||
<span class="absolute right-4 top-[3px] -translate-y-1/2 text-[16px] iconfont iconFrame-1"></span>
|
||||
</div>
|
||||
<div class="bg-[#EDEEF4] p-[20px] rounded-[6px] cursor-pointer flex flex-col items-start quick-action-card relative" @click="toHref('/app_manage/app_store','uninstalled')">
|
||||
<div class="title flex items-center">
|
||||
<span class="iconfont iconanzhuangyigechajianc !text-[20px] mr-[10px]"></span>
|
||||
<span class="text-[14px] text-[#1D1F3A]">安装一个插件</span>
|
||||
</div>
|
||||
<div class="absolute bottom-3 left-5 right-4 desc">
|
||||
<p class="text-[12px] text-[#4F516D]">从市场安装现有插件</p>
|
||||
</div>
|
||||
<span class="absolute right-4 top-[3px] -translate-y-1/2 text-[16px] iconfont iconFrame-1"></span>
|
||||
</div>
|
||||
<div class="bg-[#EDEEF4] p-[20px] rounded-[6px] cursor-pointer flex flex-col items-start quick-action-card relative" @click="toLink('/admin/tools/cloud_compile')">
|
||||
<div class="title flex items-center">
|
||||
<span class="iconfont iconyunbianyic !text-[20px] mr-[10px]"></span>
|
||||
<span class="text-[14px] text-[#1D1F3A]">云编译</span>
|
||||
</div>
|
||||
<div class="absolute bottom-3 left-5 right-4 desc">
|
||||
<p class="text-[12px] text-[#4F516D]">在线编译你的应用</p>
|
||||
</div>
|
||||
<span class="absolute right-4 top-[3px] -translate-y-1/2 text-[16px] iconfont iconFrame-1"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-[33%] ml-[20px]">
|
||||
<p class="text-[18px] text-[#1D1F3A] mb-[10px]">AI编程</p>
|
||||
<div class="flex items-center text-[14px] text-[#4F516D]">
|
||||
<div :class="['cursor-pointer mr-[20px]', { '!text-primary': activeName1 === '1' }]" @click="activeNameTabFn1('1')">{{ t("插件开发") }}</div>
|
||||
<div :class="['cursor-pointer mr-[20px]', { '!text-primary': activeName1 === '2' }]" @click="activeNameTabFn1('2')">{{ t("插件设计") }}</div>
|
||||
<div :class="['cursor-pointer mr-[20px]', { '!text-primary': activeName1 === '3' }]" @click="activeNameTabFn1('3')">{{ t("APP编译") }}</div>
|
||||
<div :class="['cursor-pointer mr-[20px]', { '!text-primary': activeName1 === '4' }]" @click="activeNameTabFn1('4')">{{ t("小程序发布") }}</div>
|
||||
</div>
|
||||
<div class="bg-[#EDEEF4] h-[160px] rounded-[6px] mt-[20px] text-[#666] cursor-pointer">
|
||||
<video src="@/app/assets/images/index/low-play.mp4" preload="auto" :controls="true" muted autoplay loop class="w-full h-full"></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="20" class="mt-[15px] top">
|
||||
<div v-if="showNiuCloud && appList.length > 0" class="mt-[50px] relative" @mouseenter="hovering = 'niuCloud'" @mouseleave="hovering = ''">
|
||||
<span class="absolute right-0 top-[-5px] text-[#999] hover:text-red-500 cursor-pointer z-10" v-if="hovering === 'niuCloud'">
|
||||
<el-icon class="icon" :size="20" color="#7b7b7b" @click="closeModule('niuCloud')">
|
||||
<CircleCloseFilled />
|
||||
</el-icon>
|
||||
</span>
|
||||
<p class="text-[18px] text-[#1D1F3A]">NIUCLOUD生态精选应用推荐</p>
|
||||
<!-- <div class="flex justify-between mt-[20px]">
|
||||
<div class="flex">
|
||||
<div :class="['flex items-center text-[14px] h-[32px] rounded-full px-[20px] mr-[24px] text-[#fff] bg-[#AFB1C8] hover:bg-[#7B7E9A] cursor-pointer', { '!text-[#fff] !bg-[#7B7E9A]': activeName === 'installed' }]" @click="activeNameTabFn('installed')">{{ t("全部") }}</div>
|
||||
<div :class="['flex items-center text-[14px] h-[32px] rounded-full px-[20px] mr-[24px] text-[#fff] bg-[#AFB1C8] hover:bg-[#7B7E9A] cursor-pointer', { '!text-[#fff] !bg-[#7B7E9A]': activeName === 'uninstalled' }]" @click="activeNameTabFn('uninstalled')">{{ t("商城") }}</div>
|
||||
<div :class="['flex items-center text-[14px] h-[32px] rounded-full px-[20px] mr-[24px] text-[#fff] bg-[#AFB1C8] hover:bg-[#7B7E9A] cursor-pointer', { '!text-[#fff] !bg-[#7B7E9A]': activeName === 'all' }]" @click="activeNameTabFn('all')">{{ t("数字人") }}</div>
|
||||
<div :class="['flex items-center text-[14px] h-[32px] rounded-full px-[20px] mr-[24px] text-[#fff] bg-[#AFB1C8] hover:bg-[#7B7E9A] cursor-pointer', { '!text-[#fff] !bg-[#7B7E9A]': activeName === 'all1' }]" @click="activeNameTabFn('all1')">{{ t("拼团") }}</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="mt-[20px]">
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<div v-for="(item,index) in appList.slice(0,5)" :key="index" @click="toApplicationDetail(item)" class="bg-[#EDEEF4] rounded-[8px] overflow-hidden text-[#666] cursor-pointer hover:shadow-xl transition-shadow duration-300 border-[1px] border-[#EDEEF4]">
|
||||
<img :src="img(item.app_logo)" alt="" class="w-full rounded-t-[6px]" >
|
||||
<div class="bg-[#fff] p-[10px]">
|
||||
<div class="text-[16px] text-[#1D1F3A] mb-[10px]">
|
||||
<span class="using-hidden">{{ item.app_name }}</span>
|
||||
</div>
|
||||
<div class="text-[12px] text-[#4F516D]">
|
||||
<span class="using-hidden">{{ item.app_desc }}</span>
|
||||
</div>
|
||||
<div class="text-[12px] mt-[20px] flex justify-between">
|
||||
<div class="flex items-center">
|
||||
<img :src="img(item.developer.headimg)" alt="" class="w-[18px] h-[18px] rounded-full mr-[6px]" v-if="item.developer.headimg">
|
||||
<img src="@/app/assets/images/member_head.png" alt="" class="w-[18px] h-[18px] rounded-full mr-[6px]" v-else>
|
||||
<span class="text-[#4F516D] text-[12px] using-hidden">{{ item.developer.nickname }}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="mr-[10px]">
|
||||
<span class="iconfont iconshoucang11 !text-[12px] mr-[8px] !text-[#4F516D]"></span>
|
||||
<span class="text-[#4F516D] text-[12px]">{{ item.collect_num}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="iconfont iconxiaoliang !text-[12px] mr-[8px] !text-[#4F516D]"></span>
|
||||
<span class="text-[#4F516D] text-[12px]">{{ item.sale_num }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-[18px] mt-[50px] mb-[15px]">{{ t("dataSummarize") }}</div>
|
||||
<el-card class="box-card !border-none profile-data" shadow="never">
|
||||
<el-row :gutter="20" class="top">
|
||||
<el-col>
|
||||
<el-card shadow="never" @click="toHref('site/list','1')" class="box-card border cursor-pointer min-w-[180px] first-con">
|
||||
<img class="max-w-[24px] max-h-[24px] mb-[10px]" src="@/app/assets/images/index/site_normal.png" />
|
||||
<el-statistic :value="statInfo.today_data.norma_site_count">
|
||||
<template #title>
|
||||
<div class="text-[14px] mb-[15px] text-[#666]">{{ t("normalSiteSum") }}</div>
|
||||
</template>
|
||||
</el-statistic>
|
||||
<el-card shadow="never" @click="toHref('site/manage','1')" class="cursor-pointer min-w-[180px] first-con">
|
||||
<div class="text-[20px] font-bold">{{ statInfo.today_data.norma_site_count }}</div>
|
||||
<div class="text-[14px] mb-[9px] text-[#4F516D]">{{ t("normalSiteSum") }}</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<el-card shadow="never" @click="toHref('site/list','1')" class="cursor-pointer min-w-[180px] first-con">
|
||||
<img class="max-w-[24px] max-h-[24px] mb-[10px]" src="@/app/assets/images/index/site2.png" />
|
||||
<el-statistic :value="statInfo.today_data.week_expire_site_count">
|
||||
<template #title>
|
||||
<div class="text-[14px] mb-[15px] text-[#666]">{{ t("weekExpireSiteCount") }}</div>
|
||||
</template>
|
||||
</el-statistic>
|
||||
<el-card shadow="never" @click="toHref('site/manage','1')" class="cursor-pointer min-w-[180px] first-con">
|
||||
<div class="text-[20px] font-bold">{{ statInfo.today_data.week_expire_site_count }}</div>
|
||||
<div class="text-[14px] mb-[9px] text-[#4F516D]">{{ t("weekExpireSiteCount") }}</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<el-card shadow="never" @click="toHref('site/list','2')" class="cursor-pointer min-w-[180px] first-con">
|
||||
<img class="max-w-[24px] max-h-[24px] mb-[15px]" src="@/app/assets/images/index/site3.png" />
|
||||
<el-statistic :value="statInfo.today_data.expire_site_count">
|
||||
<template #title>
|
||||
<div class="text-[14px] mb-[9px] text-[#666]">{{ t("expireSiteSum") }}</div>
|
||||
</template>
|
||||
</el-statistic>
|
||||
<el-card shadow="never" @click="toHref('site/manage','2')" class="cursor-pointer min-w-[180px] first-con">
|
||||
<div class="text-[20px] font-bold">{{ statInfo.today_data.expire_site_count }}</div>
|
||||
<div class="text-[14px] mb-[9px] text-[#4F516D]">{{ t("expireSiteSum") }}</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<el-card shadow="never" @click="toHref('/app_manage/app_store','uninstalled')" class="cursor-pointer min-w-[180px] first-con">
|
||||
<img class="max-w-[24px] max-h-[24px] mb-[15px]" src="@/app/assets/images/index/not_install.png" />
|
||||
<el-statistic :value="statInfo.app.app_no_installed_count">
|
||||
<template #title>
|
||||
<div class="text-[14px] mb-[9px] text-[#666]">{{ t("noInstallAppSun") }}</div>
|
||||
</template>
|
||||
</el-statistic>
|
||||
<div class="text-[20px] font-bold">{{ statInfo.app.app_no_installed_count }}</div>
|
||||
<div class="text-[14px] mb-[9px] text-[#4F516D]">{{ t("noInstallAppSun") }}</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<el-card shadow="never" @click="toHref('/app_manage/app_store','installed')" class="cursor-pointer min-w-[180px] first-con">
|
||||
<img class="max-w-[24px] max-h-[24px] mb-[10px]" src="@/app/assets/images/index/install.png" />
|
||||
<el-statistic :value="statInfo.app.app_installed_count">
|
||||
<template #title>
|
||||
<div class="text-[14px] mb-[9px] text-[#666]">{{ t("installAppSun") }}</div>
|
||||
</template>
|
||||
</el-statistic>
|
||||
<div class="text-[20px] font-bold">{{ statInfo.app.app_installed_count }}</div>
|
||||
<div class="text-[14px] mb-[9px] text-[#4F516D]">{{ t("installAppSun") }}</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<div class="text-[16px] mt-[20px] mb-[15px]">{{ t("常用功能") }}</div>
|
||||
<el-card class="box-card border" shadow="never">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex-1 h-[125px] flex justify-center flex-col items-center cursor-pointer mr-[25px]" @click="toLink('site/list')">
|
||||
<img class="w-[64px] h-[64px] mb-[5px]" src="@/app/assets/images/index/site_list.png" />
|
||||
<span class="text-[14px]">{{ t("siteList") }}</span>
|
||||
</div>
|
||||
<div class="flex-1 h-[125px] flex justify-center flex-col items-center cursor-pointer mr-[25px]" @click="toLink('site/group')">
|
||||
<img class="w-[64px] h-[64px] mb-[5px]" src="@/app/assets/images/index/site_tc.png" />
|
||||
<span class="text-[14px]">{{ t("sitePackage") }}</span>
|
||||
</div>
|
||||
<div class="flex-1 h-[125px] flex justify-center flex-col items-center cursor-pointer mr-[25px]" @click="toLink('site/list')">
|
||||
<img class="w-[64px] h-[64px] mb-[5px]" src="@/app/assets/images/index/site_add.png" />
|
||||
<span class="text-[14px]">{{ t("newSite") }}</span>
|
||||
</div>
|
||||
<div class="flex-1 h-[125px] flex justify-center flex-col items-center cursor-pointer mr-[25px]" @click="toLink('/admin/site/user')">
|
||||
<img class="w-[64px] h-[64px] mb-[5px]" src="@/app/assets/images/index/site_user.png" />
|
||||
<span class="text-[14px]">{{ t("administrator") }}</span>
|
||||
</div>
|
||||
<div class="flex-1 h-[125px] flex justify-center flex-col items-center cursor-pointer" @click="toApplication">
|
||||
<img class="w-[64px] h-[64px] mb-[5px]" src="@/app/assets/images/index/app_store.png" />
|
||||
<span class="text-[14px]">{{ t("appMarketplace") }}</span>
|
||||
</div>
|
||||
<div class="text-[18px] mt-[50px] mb-[15px]">{{ t("常用功能") }}</div>
|
||||
<div class="flex justify-between">
|
||||
<div class="flex-1 h-[80px] flex items-center cursor-pointer mr-[25px] bg-[#EDEEF4] rounded-[6px] pl-[25px] hover:bg-[#dcdfe6] transition-all duration-300" @click="toTypeLink('site/manage','list')">
|
||||
<img class="w-[32px] h-[32px] " src="@/app/assets/images/index/site_list1.png" />
|
||||
<span class="text-[14px] ml-3">{{ t("siteList") }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<div class="flex-1 h-[80px] flex items-center cursor-pointer mr-[25px] bg-[#EDEEF4] rounded-[6px] pl-[25px] hover:bg-[#dcdfe6] transition-all duration-300" @click="toTypeLink('site/manage','group')">
|
||||
<img class="w-[32px] h-[32px] " src="@/app/assets/images/index/site_tc1.png" />
|
||||
<span class="text-[14px] ml-3">{{ t("sitePackage") }}</span>
|
||||
</div>
|
||||
<div class="flex-1 h-[80px] flex items-center cursor-pointer mr-[25px] bg-[#EDEEF4] rounded-[6px] pl-[25px] hover:bg-[#dcdfe6] transition-all duration-300" @click="toTypeLink('site/manage','list')" >
|
||||
<img class="w-[32px] h-[32px] " src="@/app/assets/images/index/site_add1.png" />
|
||||
<span class="text-[14px] ml-3">{{ t("newSite") }}</span>
|
||||
</div>
|
||||
<div class="flex-1 h-[80px] flex items-center cursor-pointer mr-[25px] bg-[#EDEEF4] rounded-[6px] pl-[25px] hover:bg-[#dcdfe6] transition-all duration-300" @click="toLink('/admin/site/user')">
|
||||
<img class="w-[32px] h-[32px] " src="@/app/assets/images/index/site_user1.png" />
|
||||
<span class="text-[14px] ml-3">{{ t("administrator") }}</span>
|
||||
</div>
|
||||
<div class="flex-1 h-[80px] flex items-center cursor-pointer bg-[#EDEEF4] rounded-[6px] pl-[25px] hover:bg-[#dcdfe6] transition-all duration-300" @click="toApplication">
|
||||
<img class="w-[32px] h-[32px] " src="@/app/assets/images/index/app_store1.png" />
|
||||
<span class="text-[14px] ml-3">{{ t("appMarketplace") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-[20px] flex site">
|
||||
<div class="mt-[50px] flex site">
|
||||
<div class="flex-1 ">
|
||||
<div class="text-[16px] mb-[15px]">{{ t("newSite") }}</div>
|
||||
<el-card class="box-card border profile-data mr-[30px]" shadow="never">
|
||||
<template #header></template>
|
||||
<div ref="newSiteStat" class="echarts-con" :style="{ width: '100%', height: '320px' }"></div>
|
||||
<div class="text-[18px] mb-[15px]">{{ t("newSite") }}</div>
|
||||
<el-card class="box-card border mr-[30px] echarts-box" shadow="never">
|
||||
<div ref="newSiteStat" class="echarts-con" :style="{ width: '100%', height: '280px' }"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div class="flex-1 ">
|
||||
<div class="text-[16px] mb-[15px]">{{ t("addUser") }}</div>
|
||||
<el-card class="box-card border flex-1 profile-data" shadow="never">
|
||||
<template #header></template>
|
||||
<div ref="addUser" class="echarts-con" :style="{ width: '100%', height: '320px' }"></div>
|
||||
<div class="text-[18px] mb-[15px]">{{ t("addUser") }}</div>
|
||||
<el-card class="box-card border flex-1 echarts-box" shadow="never">
|
||||
<div ref="addUser" class="echarts-con" :style="{ width: '100%', height: '280px' }"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between text-[12px] mt-[20px] text-[#666]" v-if="copyright">
|
||||
<div>
|
||||
<a :href="copyright.copyright_link" target="_blank">
|
||||
<!-- <span class="mr-3" v-if="copyright.copyright_desc">{{ copyright.copyright_desc }}</span> -->
|
||||
<span class="mr-3" v-if="copyright.company_name">{{ copyright.company_name }}</span>
|
||||
</a>
|
||||
<a href="https://beian.miit.gov.cn/" v-if="copyright.icp" target="_blank">
|
||||
@ -123,7 +231,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- </div> -->
|
||||
</el-card>
|
||||
<el-dialog v-model="dialogVisible" title="版权信息" width="500">
|
||||
<span>{{ copyright.copyright_desc }}</span>
|
||||
@ -137,17 +245,28 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch ,onMounted} from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getStatInfo } from '@/app/api/stat'
|
||||
import { getAppIndex} from '@/app/api/addon'
|
||||
import * as echarts from 'echarts'
|
||||
import { getFrameworkNewVersion } from '@/app/api/module'
|
||||
import { getWebCopyright } from '@/app/api/sys'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { AnyObject } from '@/types/global'
|
||||
import useStyleStore from '@/stores/modules/style'
|
||||
import Storage from '@/utils/storage'
|
||||
import { img } from '@/utils/common'
|
||||
|
||||
const activeName = ref('installed')
|
||||
|
||||
const activeNameTabFn = (data: any) => {
|
||||
activeName.value = data
|
||||
}
|
||||
const activeName1 = ref('1')
|
||||
|
||||
const activeNameTabFn1 = (data: any) => {
|
||||
activeName1.value = data
|
||||
}
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(true)
|
||||
const newSiteStat = ref<any>(null)
|
||||
@ -178,6 +297,17 @@ const newVersion = ref<NewVersion>({
|
||||
last_version: ''
|
||||
})
|
||||
|
||||
// 插件
|
||||
const appList = ref<any>([])
|
||||
const getAppIndexFn = () => {
|
||||
loading.value = true
|
||||
getAppIndex().then((res) => {
|
||||
appList.value = res.data.data
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
getAppIndexFn()
|
||||
|
||||
const getInfoFn = () => {
|
||||
if (copyright.value.copyright_desc) {
|
||||
dialogVisible.value = true
|
||||
@ -201,10 +331,10 @@ const statInfo = ref<StatInfo>({
|
||||
|
||||
const getStatInfoFn = async() => {
|
||||
statInfo.value = await (await getStatInfo()).data
|
||||
loading.value = false
|
||||
setTimeout(() => {
|
||||
drawChart()
|
||||
}, 20)
|
||||
loading.value = false
|
||||
}
|
||||
getStatInfoFn()
|
||||
|
||||
@ -228,11 +358,11 @@ const drawChart = () => {
|
||||
data: [],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#2FCEB6',//点的颜色
|
||||
color: '#2FCEB6' //点的颜色
|
||||
}
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#2FCEB6',//线的颜色
|
||||
color: '#2FCEB6' //线的颜色
|
||||
}
|
||||
},
|
||||
|
||||
@ -260,11 +390,11 @@ const drawChart = () => {
|
||||
data: [],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#F7DC76',//点的颜色
|
||||
color: '#F7DC76' //点的颜色
|
||||
}
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#F7DC76',//线的颜色
|
||||
color: '#F7DC76' //线的颜色
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -297,16 +427,24 @@ watch(() => route.path, (newval, oldval) => {
|
||||
const toLink = (link: any) => {
|
||||
router.push(link)
|
||||
}
|
||||
const toHref = (url: any, id: any) => {
|
||||
const toHref = (url: any,id: any) => {
|
||||
router.push({
|
||||
path: url,
|
||||
query: { id }
|
||||
})
|
||||
}
|
||||
const toTypeLink = (url: any,type: any) => {
|
||||
router.push({
|
||||
path: url,
|
||||
query: { type }
|
||||
})
|
||||
}
|
||||
const toApplication = () => {
|
||||
window.open('https://www.niucloud.com/app')
|
||||
}
|
||||
|
||||
const toApplicationDetail = (item: any) => {
|
||||
window.open(`https://www.niucloud.com/app/detail?id=${item.app_id}`)
|
||||
}
|
||||
// 更新时间
|
||||
const time = ref('')
|
||||
const nowTime = () => {
|
||||
@ -329,6 +467,29 @@ const nowTime = () => {
|
||||
}
|
||||
nowTime()
|
||||
|
||||
const showQuickStart = ref(true)
|
||||
const showNiuCloud = ref(true)
|
||||
const showBanner = ref(true)
|
||||
const hovering = ref('')
|
||||
|
||||
onMounted(() => {
|
||||
showQuickStart.value = localStorage.getItem('showQuickStart') !== 'false'
|
||||
showNiuCloud.value = localStorage.getItem('showNiuCloud') !== 'false'
|
||||
showBanner.value = localStorage.getItem('showBanner') !== 'false'
|
||||
})
|
||||
|
||||
const closeModule = (module:any) => {
|
||||
if (module === 'quickStart') {
|
||||
showQuickStart.value = false
|
||||
localStorage.setItem('showQuickStart', 'false')
|
||||
} else if (module === 'niuCloud') {
|
||||
showNiuCloud.value = false
|
||||
localStorage.setItem('showNiuCloud', 'false')
|
||||
} else if (module === 'banner') {
|
||||
showBanner.value = false
|
||||
localStorage.setItem('showBanner', 'false')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -353,14 +514,87 @@ nowTime()
|
||||
// border: 1px solid #E9ECEF;
|
||||
// background: #fff;
|
||||
padding: 20px 30px 10px;
|
||||
height: 160px;
|
||||
// border-radius: 8px;
|
||||
// height: 80px;
|
||||
background: #EDEEF4 !important;
|
||||
border-radius: 6px !important;
|
||||
border: none !important;
|
||||
&:hover {
|
||||
background: #dcdfe6 !important; // 你可以换成你想要的颜色
|
||||
}
|
||||
}
|
||||
|
||||
.echarts-con {
|
||||
// border: 1px solid #E9ECEF;
|
||||
// background: #fff;
|
||||
padding-top: 20px;
|
||||
// border-radius: 8px;
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
.echarts-box{
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
|
||||
.quick-action-card {
|
||||
position: relative;
|
||||
overflow: visible; /* 允许伪元素超出 */
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* 伪元素作为视觉外壳 */
|
||||
.quick-action-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #EDEEF4;
|
||||
border-radius: 8px;
|
||||
z-index: -1;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
/* 悬浮时放大背景,但不动内容 */
|
||||
.quick-action-card:hover::before {
|
||||
transform: scaleY(1.3);
|
||||
transform-origin: center;
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.desc{
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.quick-action-card:hover > .desc{
|
||||
opacity: 1;
|
||||
}
|
||||
.quick-action-card > span:last-child {
|
||||
opacity: 0;
|
||||
transform: translate(-8px, 8px); /* 初始位置稍微向下右 */
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.quick-action-card:hover > span:last-child {
|
||||
opacity: 1;
|
||||
transform: translate(0, 0); /* 回到原位(实现右上浮动) */
|
||||
}
|
||||
|
||||
|
||||
.quick-action-card:hover > .desc,
|
||||
.quick-action-card:hover > span:last-child {
|
||||
opacity: 1;
|
||||
}
|
||||
.quick-action-card:hover > .title {
|
||||
transform: translateY(-15px);
|
||||
}
|
||||
|
||||
.title {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
:deep(.banner-box .el-carousel__indicator){
|
||||
padding: 2px !important;
|
||||
}
|
||||
:deep(.loading-box .el-loading-spinner){
|
||||
top: 33%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -4,38 +4,59 @@
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ t("localAppText") }}</span>
|
||||
</div>
|
||||
|
||||
<el-input class="!w-[250px]" :placeholder="t('search')" v-model.trim="search_name" @keyup.enter="query">
|
||||
<template #suffix>
|
||||
<el-icon class="el-input__icon cursor-pointer" size="14px" @click="query">
|
||||
<search />
|
||||
</el-icon>
|
||||
<el-tabs v-model="activeName" class="mt-[10px]" @tab-click="handleClick">
|
||||
<el-tab-pane :label="t('installLabel')" name="installed"></el-tab-pane>
|
||||
<el-tab-pane :label="t('uninstalledLabel')" name="uninstalled"></el-tab-pane>
|
||||
<el-tab-pane :label="t('buyLabel')" name="all"></el-tab-pane>
|
||||
<el-tab-pane :label="t('recentlyUpdated')" name="recentlyUpdated">
|
||||
<template #label>
|
||||
<span class="custom-tabs-label">
|
||||
<span>{{ t('recentlyUpdated') }}</span>
|
||||
<span v-if="localList['recentlyUpdated'].length > 0" class="w-[15px] h-[15px] bg-[#DA203E] absolute text-[#fff] text-[11px] flex items-center justify-center rounded-full top-[3px] right-[-12px]">{{ localList['recentlyUpdated'].length }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between my-[20px]">
|
||||
<div class="flex">
|
||||
<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>
|
||||
<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 :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="['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')">
|
||||
<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>
|
||||
<span>{{ t('recentlyUpdated') }}</span>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div class="flex justify-between my-[10px]">
|
||||
<div class="flex items-center search-form">
|
||||
<el-input class="!w-[192px] !h-[32px] rounded-[4px]" :placeholder="t('search')" v-model.trim="search_name" @keyup.enter="query">
|
||||
<template #suffix>
|
||||
<el-icon class="el-input__icon cursor-pointer" size="14px" @click="query">
|
||||
<search />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-select v-model="search_type" placeholder="请选择类型" class="!w-[192px] !h-[32px] rounded-[4px] ml-[20px] " >
|
||||
<el-option :label="t('全部')" value="" />
|
||||
<el-option v-for="(label, value) in typeList" :key="value" :label="label" :value="value"></el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="query" class="ml-[20px]">{{ t("搜索") }}</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" v-show="activeName === 'recentlyUpdated'" @click="batchUpgrade" :loading="upgradeRef?.loading" :disabled="authLoading">{{ t("batchUpgrade") }}</el-button>
|
||||
<!-- <el-button type="primary" @click="handleCloudBuild" :loading="cloudBuildRef?.loading" :disabled="authLoading">{{ t("cloudBuild") }}</el-button> -->
|
||||
</div>
|
||||
<el-button type="primary" round @click="handleCloudBuild" :loading="cloudBuildRef?.loading" :disabled="authLoading">{{ t("cloudBuild") }}</el-button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<el-table v-if="localList[activeName].length && !loading" :data="info[activeName]" size="large" class="pt-[5px]">
|
||||
<el-table-column :label="t('appName')" align="left" width="450">
|
||||
</div>
|
||||
<div class="relative">
|
||||
<el-table v-if="localList[activeName].length && !loading" :tree-props="{ children: 'children' }" :default-expand-all="true" :data="info[activeName]" row-key="key" size="large" @selection-change="handleSelectionChange">
|
||||
<el-table-column width="24">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center cursor-pointer">
|
||||
<div class="tree-child-cell" :class="{ 'is-tree-parent': row.children?.length, 'is-tree-child': typeof row.support_app === 'string' && row.support_app !== '' && visibleRowKeys.has(row.support_app)}">
|
||||
<span style="opacity: 0;">.</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="selection" v-if="activeName === 'recentlyUpdated'" />
|
||||
<el-table-column :label="t('appName')" align="left" width="500">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center cursor-pointer relative left-[-10px]">
|
||||
<el-image class="w-[54px] h-[54px]" :src="row.icon" fit="contain">
|
||||
<template #error>
|
||||
<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>
|
||||
</template>
|
||||
</el-image>
|
||||
@ -45,22 +66,23 @@
|
||||
<div class="w-[236px] truncate leading-[18px] mt-[6px]" v-else>{{ row.version }}</div>
|
||||
<div class="mt-[3px] flex flex-nowrap">
|
||||
<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 v-if="versionJudge(row)" effect="dark" :content="`该插件适配框架版本为${ row.support_version },与已安装框架版本不完全兼容`" placement="top-start">
|
||||
<el-tag type="warning" size="small" class="ml-[3px]">该插件适配框架版本为{{ row.support_version }},与已安装框架版本不完全兼容</el-tag>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="left" min-width="150">
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<span class="font-500 text-[13px] mr-[5px]">{{ t("appIdentification") }}</span>
|
||||
<el-tooltip class="box-item" effect="light" :content="t('tipText')" placement="bottom">
|
||||
<el-icon class="cursor-pointer text-[16px] text-[#a9a9a9]">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
<el-icon class="cursor-pointer text-[16px] text-[#a9a9a9]">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@ -68,21 +90,25 @@
|
||||
<span class="font-500 text-[13px]">{{ row.key }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="" :label="t('introduction')" align="left" min-width="200">
|
||||
|
||||
<el-table-column :label="t('introduction')" align="left" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<span class="font-500 text-[13px] multi-hidden">{{ row.desc }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('type')" align="left" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<span class="font-500 text-[13px]">{{ row.type === "app" ? t("app") : t("addon") }}</span>
|
||||
<span class="font-500 text-[13px] multi-hidden">{{ row.type === "app" ? t("app") : t("addon") }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="" :label="t('author')" align="left" min-width="80">
|
||||
|
||||
<el-table-column :label="t('author')" align="left" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<span class="font-500 text-[13px]">{{ row.author }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" width="250">
|
||||
<template #default="{ row }">
|
||||
<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>
|
||||
@ -231,72 +257,80 @@
|
||||
|
||||
<!-- 安装弹窗 -->
|
||||
<el-dialog v-model="installShowDialog" :title="t('addonInstall')" width="850px" :close-on-click-modal="false" :close-on-press-escape="false" :before-close="installShowDialogClose">
|
||||
<el-steps :space="200" :active="installStep" finish-status="success" align-center>
|
||||
<el-steps :space="200" :active="installStep" class="number-of-steps" process-status="process" align-center v-if="installStep != 2 && !errorDialog ">
|
||||
<el-step :title="t('envCheck')" class="flex-1" />
|
||||
<el-step :title="t('installProgress')" class="flex-1" />
|
||||
<el-step :title="t('installComplete')" class="flex-1" />
|
||||
</el-steps>
|
||||
<div v-show="installStep == 1" v-loading="!installCheckResult.dir">
|
||||
<el-scrollbar max-height="50vh">
|
||||
<div v-show="installStep == 0" v-loading="!installCheckResult.dir">
|
||||
<!-- <el-scrollbar max-height="50vh"> -->
|
||||
<div class="min-h-[150px]">
|
||||
<div class="my-3" v-if="installCheckResult.dir">
|
||||
<p class="pt-[20px] pl-[20px]">{{ t("dirPermission") }}</p>
|
||||
<div v-if="!installCheckResult.is_pass" class="mt-[10px] mx-[20px] text-[14px] cursor-pointer text-primary flex items-center justify-between bg-[#EFF6FF] rounded-[4px] p-[10px]" @click="cloudBuildCheckDirFn">
|
||||
<div class="flex items-center">
|
||||
<el-icon :size="17"><QuestionFilled /></el-icon>
|
||||
<span class="ml-[5px] leading-[20px]">编译权限错误,查看解决方案</span></div>
|
||||
<div class="border-[1px] border-primary rounded-[3px] w-[72px] h-[26px] leading-[25px] text-center">立即查看</div>
|
||||
</div>
|
||||
<div class="px-[20px] pt-[10px] text-[14px]">
|
||||
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
|
||||
<el-col :span="12">
|
||||
<el-col :span="18">
|
||||
<span>{{ t("path") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t("demand") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t("status") }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in installCheckResult.dir.is_readable" :key="index">
|
||||
<el-col :span="12">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span>{{ t("readable") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span v-if="item.status">
|
||||
<el-icon color="green">
|
||||
<Select />
|
||||
</el-icon>
|
||||
</span>
|
||||
<span v-else>
|
||||
<el-icon color="red">
|
||||
<CloseBold />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in installCheckResult.dir.is_write" :key="index">
|
||||
<el-col :span="12">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span>{{ t("write") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span v-if="item.status">
|
||||
<el-icon color="green">
|
||||
<Select />
|
||||
</el-icon>
|
||||
</span>
|
||||
<span v-else>
|
||||
<el-icon color="red">
|
||||
<CloseBold />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-scrollbar style="height: calc(300px); overflow: auto">
|
||||
<el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in installCheckResult.dir.is_readable" :key="index">
|
||||
<el-col :span="18">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<span>{{ t("readable") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="3" >
|
||||
<span v-if="item.status">
|
||||
<el-icon color="green">
|
||||
<Select />
|
||||
</el-icon>
|
||||
</span>
|
||||
<span v-else>
|
||||
<el-icon color="red">
|
||||
<CloseBold />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in installCheckResult.dir.is_write" :key="index">
|
||||
<el-col :span="18">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<span>{{ t("write") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<span v-if="item.status" class="text-right">
|
||||
<el-icon color="green">
|
||||
<Select />
|
||||
</el-icon>
|
||||
</span>
|
||||
<span v-else>
|
||||
<el-icon color="red">
|
||||
<CloseBold />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<!-- </el-scrollbar> -->
|
||||
<div class="flex justify-end">
|
||||
<el-tooltip effect="dark" :content="t('installTips')" placement="top">
|
||||
<el-button :disabled="!installCheckResult.is_pass || cloudInstalling" :loading="localInstalling" @click="handleInstall">{{ t("localInstall") }}</el-button>
|
||||
@ -306,15 +340,41 @@
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="installStep == 2" class="h-[50vh] mt-[20px]">
|
||||
<div v-show="installStep == 1 && !errorDialog" class="h-[50vh] mt-[20px]">
|
||||
<terminal ref="terminalRef" :context="currAddon" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd" />
|
||||
</div>
|
||||
<div v-show="installStep == 3" class="h-[50vh] mt-[20px] flex flex-col">
|
||||
<el-result icon="success" :title="t('addonInstallSuccess')"></el-result>
|
||||
<div v-show="installStep == 2" class="h-[50vh] mt-[20px] flex flex-col">
|
||||
<!-- <el-result icon="success" :title="t('addonInstallSuccess')"></el-result> -->
|
||||
<!-- 提示信息 -->
|
||||
<div v-for="(item, index) in installAfterTips" class="mb-[10px]" :key="index">
|
||||
<!-- <div v-for="(item, index) in installAfterTips" class="mb-[10px]" :key="index">
|
||||
<el-alert :title="item" type="error" :closable="false" />
|
||||
</div>
|
||||
</div> -->
|
||||
<el-result icon="success" :title="t('addonInstallSuccess')">
|
||||
<template #icon>
|
||||
<img src="@/app/assets/images/success_icon.png" alt="">
|
||||
</template>
|
||||
<template #extra>
|
||||
<div v-for="(item, index) in installAfterTips" class="mb-[10px]" :key="index">
|
||||
<div class="text-[16px] text-[#4F516D] mt-[5px]">{{ item }}</div>
|
||||
</div>
|
||||
<div class="text-[16px] text-[#9699B6] mt-[10px]" v-if="upgradeDuration>0">本次安装用时{{ formatUpgradeDuration }}</div>
|
||||
<div class="mt-[20px]">
|
||||
<el-button @click="handleBack()" v-if="installType=='cloud'" class="!w-[90px]">返回</el-button>
|
||||
<el-button @click="installShowDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-result>
|
||||
</div>
|
||||
<div class="mt-[50px]" v-show="errorDialog">
|
||||
<el-result icon="error" :title="t('安装失败')" :sub-title="errorMsg">
|
||||
<template #icon>
|
||||
<img src="@/app/assets/images/error_icon.png" alt="">
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-button @click="handleBack()" v-if="installType=='cloud'" class="!w-[90px]">错误信息</el-button>
|
||||
<el-button @click="installShowDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
@ -325,24 +385,24 @@
|
||||
<p class="pt-[20px] pl-[20px]">{{ t("dirPermission") }}</p>
|
||||
<div class="px-[20px] pt-[10px] text-[14px]">
|
||||
<el-row class="py-[10px] items table-head-bg pl-[15px] mb-[10px]">
|
||||
<el-col :span="12">
|
||||
<el-col :span="18">
|
||||
<span>{{ t("path") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t("demand") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t("status") }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in uninstallCheckResult.dir.is_readable" :key="index">
|
||||
<el-col :span="12">
|
||||
<el-col :span="18">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t("readable") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span v-if="item.status">
|
||||
<el-icon color="green">
|
||||
<Select />
|
||||
@ -356,13 +416,13 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in uninstallCheckResult.dir.is_write" :key="index">
|
||||
<el-col :span="12">
|
||||
<el-col :span="18">
|
||||
<span>{{ item.dir }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3">
|
||||
<span>{{ t("write") }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="3" >
|
||||
<span v-if="item.status">
|
||||
<el-icon color="green">
|
||||
<Select />
|
||||
@ -400,7 +460,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, h } from 'vue'
|
||||
import { ref, reactive, watch, h ,computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import {
|
||||
getAddonLocal,
|
||||
@ -411,7 +471,8 @@ import {
|
||||
getAddonInstalltask,
|
||||
getAddonCloudInstallLog,
|
||||
preUninstallCheck,
|
||||
cancelInstall
|
||||
cancelInstall,
|
||||
getAddonInit
|
||||
} from '@/app/api/addon'
|
||||
import { deleteAddonDevelop } from '@/app/api/tools'
|
||||
import { downloadVersion, getAuthInfo, setAuthInfo } from '@/app/api/module'
|
||||
@ -444,6 +505,17 @@ getVersions().then((res) => {
|
||||
frameworkVersion.value = res.data.version.version
|
||||
})
|
||||
|
||||
const treeProps = reactive({
|
||||
checkStrictly: false
|
||||
})
|
||||
|
||||
const typeList = ref({})
|
||||
const getAddonInitFn = () => {
|
||||
getAddonInit().then((res) => {
|
||||
typeList.value = res.data.type_list
|
||||
})
|
||||
}
|
||||
getAddonInitFn()
|
||||
const currDownData = ref()
|
||||
const downEventHintFn = () => {
|
||||
downEvent(currDownData.value, true)
|
||||
@ -487,6 +559,7 @@ getAuthInfo().then((res) => {
|
||||
* 本地下载的插件列表
|
||||
*/
|
||||
const search_name = ref('')
|
||||
const search_type = ref('')
|
||||
// 表格展示数据
|
||||
const info = ref({
|
||||
installed: [],
|
||||
@ -494,19 +567,80 @@ const info = ref({
|
||||
all: [],
|
||||
recentlyUpdated: []
|
||||
})
|
||||
const query = () => {
|
||||
if (search_name.value == '' || search_name.value == null) {
|
||||
info.value.installed = localList.value.installed
|
||||
info.value.uninstalled = localList.value.uninstalled
|
||||
info.value.all = localList.value.all
|
||||
info.value.recentlyUpdated = localList.value.recentlyUpdated
|
||||
return false
|
||||
}
|
||||
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.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 buildInfo = (list: any[]) => {
|
||||
const map = new Map()
|
||||
const result: any[] = []
|
||||
|
||||
// 所有插件都先放进 map,初始化 children
|
||||
list.forEach(item => {
|
||||
map.set(item.key, { ...item, children: [] })
|
||||
})
|
||||
|
||||
// 第二次遍历构建父子关系
|
||||
list.forEach(item => {
|
||||
if (item.support_app && map.has(item.support_app)) {
|
||||
const parent = map.get(item.support_app)
|
||||
parent.children.push(map.get(item.key)) // 直接取已经构建好的对象
|
||||
}
|
||||
})
|
||||
|
||||
// 最终收集那些没有作为子插件挂载出去的插件(即顶层插件)
|
||||
map.forEach((item: any) => {
|
||||
if (!item.support_app || !map.has(item.support_app)) {
|
||||
result.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
// const query = () => {
|
||||
// if (search_name.value == '' || search_name.value == null) {
|
||||
// info.value.installed = buildInfo(localList.value.installed)
|
||||
// info.value.uninstalled = buildInfo(localList.value.uninstalled)
|
||||
// info.value.all = buildInfo(localList.value.all)
|
||||
// info.value.recentlyUpdated = buildInfo(localList.value.recentlyUpdated)
|
||||
// return false
|
||||
// }
|
||||
|
||||
// const filteredInstalled = localList.value.installed.filter((el: any) => el.title.indexOf(search_name.value) != -1)
|
||||
// const filteredUninstalled = localList.value.uninstalled.filter((el: any) => el.title.indexOf(search_name.value) != -1)
|
||||
// const filteredAll = localList.value.all.filter((el: any) => el.title.indexOf(search_name.value) != -1)
|
||||
// const filteredRecentlyUpdated = localList.value.recentlyUpdated.filter((el: any) => el.title.indexOf(search_name.value) != -1)
|
||||
|
||||
// // 构建父子关系
|
||||
// info.value.installed = buildInfo(filteredInstalled)
|
||||
// info.value.uninstalled = buildInfo(filteredUninstalled)
|
||||
// info.value.all = buildInfo(filteredAll)
|
||||
// info.value.recentlyUpdated = buildInfo(filteredRecentlyUpdated)
|
||||
// }
|
||||
const query = () => {
|
||||
const name = search_name.value
|
||||
const type = search_type.value
|
||||
|
||||
// 如果没填搜索关键词也没选类型,重置所有列表
|
||||
if ((!name || name === '') && (type === '' || type == null)) {
|
||||
info.value.installed = buildInfo(localList.value.installed)
|
||||
info.value.uninstalled = buildInfo(localList.value.uninstalled)
|
||||
info.value.all = buildInfo(localList.value.all)
|
||||
info.value.recentlyUpdated = buildInfo(localList.value.recentlyUpdated)
|
||||
return
|
||||
}
|
||||
|
||||
// 公共筛选函数
|
||||
const filterList = (list: any[]) => {
|
||||
return list.filter((el: any) => {
|
||||
const matchName = !name || el.title.includes(name)
|
||||
const matchType = !type || el.type === type
|
||||
return matchName && matchType
|
||||
})
|
||||
}
|
||||
|
||||
info.value.installed = buildInfo(filterList(localList.value.installed))
|
||||
info.value.uninstalled = buildInfo(filterList(localList.value.uninstalled))
|
||||
info.value.all = buildInfo(filterList(localList.value.all))
|
||||
info.value.recentlyUpdated = buildInfo(filterList(localList.value.recentlyUpdated))
|
||||
}
|
||||
|
||||
const localList = ref({
|
||||
installed: [],
|
||||
uninstalled: [],
|
||||
@ -574,7 +708,7 @@ const currAddon = ref('')
|
||||
const installShowDialog = ref(false)
|
||||
|
||||
// 安装步骤
|
||||
const installStep = ref(1)
|
||||
const installStep = ref(0)
|
||||
|
||||
// 安装检测结果
|
||||
const installCheckResult = ref({})
|
||||
@ -611,7 +745,10 @@ const installAddonFn = (key: string) => {
|
||||
currAddon.value = key
|
||||
|
||||
preInstallCheck(key).then((res) => {
|
||||
installStep.value = 1
|
||||
installStep.value = 0
|
||||
isBack.value = false
|
||||
errorDialog.value = false
|
||||
installType.value = ''
|
||||
installShowDialog.value = true
|
||||
installAfterTips.value = []
|
||||
installCheckResult.value = res.data
|
||||
@ -622,10 +759,20 @@ const installAddonFn = (key: string) => {
|
||||
/**
|
||||
* 获取正在进行的安装任务
|
||||
*/
|
||||
const upgradeStartTime = ref<number | null>(null)
|
||||
const upgradeDuration = ref(0) // 单位:秒
|
||||
let upgradeTimer: ReturnType<typeof setInterval> | null = null
|
||||
let notificationEl = null
|
||||
|
||||
const getInstallTask = (first: boolean = true) => {
|
||||
getAddonInstalltask().then((res) => {
|
||||
if (res.data) {
|
||||
upgradeStartTime.value = Date.now()
|
||||
upgradeDuration.value = 0
|
||||
if (upgradeTimer) clearInterval(upgradeTimer)
|
||||
upgradeTimer = setInterval(() => {
|
||||
upgradeDuration.value++
|
||||
}, 1000)
|
||||
if (first) {
|
||||
installLog = []
|
||||
currAddon.value = res.data.addon
|
||||
@ -643,7 +790,14 @@ const getInstallTask = (first: boolean = true) => {
|
||||
}
|
||||
}
|
||||
if (res.data.error) {
|
||||
ElMessage({ message: '插件安装失败', type: 'error', duration: 5000 })
|
||||
terminalRef.value.pushMessage({ content: res.data.error, class: 'error' })
|
||||
errorMsg.value = res.data.error
|
||||
errorDialog.value = true
|
||||
if (upgradeTimer) {
|
||||
clearInterval(upgradeTimer)
|
||||
upgradeTimer = null
|
||||
}
|
||||
// ElMessage({ message: '插件安装失败', type: 'error', duration: 5000 })
|
||||
return
|
||||
}
|
||||
if (res.data.mode == 'cloud') {
|
||||
@ -652,9 +806,14 @@ const getInstallTask = (first: boolean = true) => {
|
||||
setTimeout(() => {
|
||||
getInstallTask(false)
|
||||
}, 2000)
|
||||
|
||||
} else {
|
||||
if (!first) {
|
||||
installStep.value = 3
|
||||
installStep.value = 2
|
||||
if (upgradeTimer) {
|
||||
clearInterval(upgradeTimer)
|
||||
upgradeTimer = null
|
||||
}
|
||||
localListFn()
|
||||
userStore.clearRouters()
|
||||
notificationEl.close()
|
||||
@ -667,21 +826,52 @@ const getInstallTask = (first: boolean = true) => {
|
||||
|
||||
getInstallTask()
|
||||
|
||||
const isBack = ref(false)
|
||||
const handleBack = () => {
|
||||
isBack.value = true
|
||||
installStep.value = 1
|
||||
errorDialog.value = false
|
||||
}
|
||||
|
||||
const formatUpgradeDuration = computed(() => {
|
||||
const s = upgradeDuration.value
|
||||
const h = Math.floor(s / 3600)
|
||||
const m = Math.floor((s % 3600) / 60)
|
||||
const sec = s % 60
|
||||
return [
|
||||
h > 0 ? `${h}小时` : '',
|
||||
m > 0 ? `${m}分钟` : '',
|
||||
`${sec}秒`
|
||||
].filter(Boolean).join('')
|
||||
})
|
||||
|
||||
const checkInstallTask = () => {
|
||||
installShowDialog.value = true
|
||||
installStep.value = 2
|
||||
installStep.value = 1
|
||||
}
|
||||
|
||||
const localInstalling = ref(false)
|
||||
/**
|
||||
* 安装插件
|
||||
*/
|
||||
const installType = ref('')
|
||||
const handleInstall = () => {
|
||||
if (!installCheckResult.value.is_pass || localInstalling.value) return
|
||||
installType.value = 'local'
|
||||
localInstalling.value = true
|
||||
upgradeStartTime.value = Date.now()
|
||||
upgradeDuration.value = 0
|
||||
if (upgradeTimer) clearInterval(upgradeTimer)
|
||||
upgradeTimer = setInterval(() => {
|
||||
upgradeDuration.value++
|
||||
}, 1000)
|
||||
|
||||
installAddon({ addon: currAddon.value }).then((res) => {
|
||||
installStep.value = 3
|
||||
installStep.value = 2
|
||||
if (upgradeTimer) {
|
||||
clearInterval(upgradeTimer)
|
||||
upgradeTimer = null
|
||||
}
|
||||
localListFn()
|
||||
localInstalling.value = false
|
||||
if (res.data.length) installAfterTips.value = res.data
|
||||
@ -703,10 +893,11 @@ const handleCloudInstall = () => {
|
||||
|
||||
if (!installCheckResult.value.is_pass || cloudInstalling.value) return
|
||||
cloudInstalling.value = true
|
||||
installType.value = 'cloud'
|
||||
|
||||
cloudInstallAddon({ addon: currAddon.value })
|
||||
.then((res) => {
|
||||
installStep.value = 2
|
||||
installStep.value = 1
|
||||
terminalRef.value.execute('clear')
|
||||
terminalRef.value.execute('开始安装插件')
|
||||
getInstallTask()
|
||||
@ -730,7 +921,8 @@ const authElMessageBox = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const errorDialog = ref(false)
|
||||
const errorMsg = ref('')
|
||||
let installLog: string[] = []
|
||||
const getCloudInstallLog = () => {
|
||||
getAddonCloudInstallLog(currAddon.value).then((res) => {
|
||||
@ -831,16 +1023,23 @@ const market = () => {
|
||||
* @param done
|
||||
*/
|
||||
const installShowDialogClose = (done: () => {}) => {
|
||||
if (installStep.value == 2) {
|
||||
if (installStep.value == 1 && !isBack.value && !errorDialog.value) {
|
||||
ElMessageBox.confirm(t('installShowDialogCloseTips'), t('warning'), {
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
cancelInstall(currAddon.value)
|
||||
if (upgradeTimer) {
|
||||
clearInterval(upgradeTimer)
|
||||
upgradeTimer = null
|
||||
}
|
||||
isBack.value = false
|
||||
installType.value = ''
|
||||
errorDialog.value = false
|
||||
done()
|
||||
})
|
||||
} else if (installStep.value == 3) {
|
||||
} else if (installStep.value == 2) {
|
||||
activeNameTabFn('installed')
|
||||
location.reload()
|
||||
} else {
|
||||
@ -862,7 +1061,6 @@ const getAddonDetailFn = (data: any) => {
|
||||
const upgradeKey = ref<string>('')
|
||||
const updateInformationFn = (data: any) => {
|
||||
// updateInformationDialog.value = true
|
||||
|
||||
upgradeKey.value = data.key
|
||||
upgradeLogRef.value?.open()
|
||||
}
|
||||
@ -925,6 +1123,10 @@ const goRouter = () => {
|
||||
window.open('https://www.niucloud.com/app')
|
||||
}
|
||||
|
||||
const cloudBuildCheckDirFn = () => {
|
||||
window.open('https://doc.niucloud.com/v6.html?keywords=/chang-jian-wen-ti-chu-li/er-shi-wu-3001-sheng-7ea7-yun-bian-yi-mu-lu-du-xie-quan-xian-zhuang-tai-bu-tong-guo-ru-he-chu-li')
|
||||
}
|
||||
|
||||
const deleteAddonFn = (key: string) => {
|
||||
ElMessageBox.confirm(t('deleteAddonTips'), t('warning'), {
|
||||
confirmButtonText: t('confirm'),
|
||||
@ -938,12 +1140,30 @@ const deleteAddonFn = (key: string) => {
|
||||
}
|
||||
|
||||
const versionJudge = (row: any) => {
|
||||
if (!row.support_version) return true
|
||||
if (!row.support_version) return false
|
||||
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())
|
||||
}
|
||||
|
||||
const visibleRowKeys = computed(() => {
|
||||
return new Set((info.value[activeName.value] || []).map(row => row.key));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -979,6 +1199,249 @@ html.dark .table-head-bg {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
:deep(.hide-expand .el-table__expand-icon>.el-icon){
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
:deep(.el-input__wrapper){
|
||||
box-shadow: none !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid #D1D5DB !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
:deep(.el-select__wrapper){
|
||||
box-shadow: none !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid #D1D5DB !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
:deep(.el-button){
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
/* 设置 el-select 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-select__placeholder.is-transparent) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 设置 el-select 选中后的颜色 */
|
||||
:deep(.search-form .el-select__placeholder) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-input 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-input__inner::placeholder) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-input 输入内容后的颜色 */
|
||||
:deep(.search-form .el-input__inner) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-date-picker 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-date-editor .el-range-input::placeholder) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 设置 el-date-picker 的输入内容颜色 */
|
||||
:deep(.search-form .el-date-editor .el-range-input) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
:deep(.el-table tr td:first-child) {
|
||||
border-bottom: none;
|
||||
// background-color: inherit !important;
|
||||
height: 100px;
|
||||
}
|
||||
:deep(.el-table__body tr:hover td:first-child) {
|
||||
// border-bottom: 1px solid var(--el-table-border-color);
|
||||
}
|
||||
:deep(.el-table__body tr) {
|
||||
position: relative;
|
||||
}
|
||||
:deep(.el-table__body td:first-child::before) {
|
||||
opacity: 0;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: var(--el-table-border-color);
|
||||
// transition: opacity 0.2s;
|
||||
z-index: 1;
|
||||
}
|
||||
:deep(.el-table__body td:first-child) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr:hover td:first-child::before) {
|
||||
opacity: 1;
|
||||
|
||||
}
|
||||
/* 创建伪元素当作 hover 边框线,默认隐藏 */
|
||||
:deep(.el-table__body td:first-child::after) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
background-color: var(--el-table-border-color);
|
||||
opacity: 0;
|
||||
// transition: opacity 0.2s ease;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 悬浮时显示这条伪边框线 */
|
||||
:deep(.el-table__body tr:hover td:first-child::after) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
:deep(.el-table__fixed-body-wrapper .el-table__row .el-table__cell) {
|
||||
overflow: visible;
|
||||
}
|
||||
:deep(.el-table .el-table__expand-icon){
|
||||
position: relative;
|
||||
top: 12.5px;
|
||||
left: -13px;
|
||||
z-index: 99;
|
||||
margin: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
:deep(.el-table__fixed-body-wrapper .el-table__cell:first-child) {
|
||||
background-color: inherit !important; /* 从行继承背景色 */
|
||||
}
|
||||
|
||||
:deep(.el-table tr td:nth-child(1)::before){
|
||||
overflow: hidden !important;
|
||||
}
|
||||
:deep(.tree-child-cell) {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
:deep(.el-table .cell){
|
||||
overflow: visible !important;
|
||||
}
|
||||
:deep(.tree-child-cell.is-tree-child::before) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -5px;
|
||||
top: -99px;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
height: 100px;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
:deep(.tree-child-cell.is-tree-child::after) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -5px;
|
||||
width: 8px;
|
||||
height: 1px;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
:deep(.hidden-selection-column .cell) {
|
||||
display: none;
|
||||
}
|
||||
:deep(.el-dialog__title){
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
:deep(.el-result__title p){
|
||||
font-size: 25px;
|
||||
color: #1D1F3A;
|
||||
font-weight: 500;
|
||||
}
|
||||
:deep(.el-result__subtitle p){
|
||||
font-size: 15px;
|
||||
color: #4F516D;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
::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);
|
||||
color: #fff;
|
||||
// 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-finish {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
|
||||
.el-step__icon {
|
||||
background: var(--el-color-primary)!important;
|
||||
color: #fff !important;
|
||||
// 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);
|
||||
background: var(--el-color-primary)!important;
|
||||
color: #fff !important;
|
||||
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
|
||||
.is-wait {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<el-input class="input-width" v-model.trim="formData.continue_sign" @keyup="filterNumber($event)" :maxlength="3" clearable />
|
||||
<span class="ml-[10px]">{{ t('day') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('continueSign')" >
|
||||
<el-form-item :label="t('continueSignAward')" >
|
||||
<div class="flex-1">
|
||||
<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" />
|
||||
@ -132,13 +132,16 @@ const formRules = reactive<FormRules>({
|
||||
|
||||
const verify = async () => {
|
||||
let verify = true
|
||||
await formRef.value?.validate((valid) => {
|
||||
verify = valid
|
||||
})
|
||||
|
||||
if (!verify) return verify
|
||||
|
||||
for (let i = 0; i < giftRefs.value.length; i++) {
|
||||
const item = giftRefs.value[i]
|
||||
!await item.verify() && (verify = false)
|
||||
}
|
||||
await formRef.value?.validate((valid) => {
|
||||
verify = valid
|
||||
})
|
||||
return verify
|
||||
}
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
|
||||
<el-form class="page-form" :model="formData" label-width="150px" ref="ruleFormRef" v-loading="loading">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-sm">{{ t('admin') }}</h3>
|
||||
|
||||
<h3 class="text-[16px] text-[#1D1F3A] font-bold mb-4">{{ pageName }}</h3>
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('admin') }}</h3>
|
||||
<el-form-item :label="t('isCaptcha')">
|
||||
<el-switch v-model="formData.is_captcha" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
@ -12,18 +12,18 @@
|
||||
<upload-image v-model="formData.bg" />
|
||||
<div class="form-tip">{{t('adminBgImgTip')}}</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card mt-[15px] !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-sm">{{ t('site') }}</h3>
|
||||
<div class="box-card mt-[20px] !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('site') }}</h3>
|
||||
|
||||
<el-form-item :label="t('isCaptcha')">
|
||||
<el-switch v-model="formData.is_site_captcha" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('bgImg')">
|
||||
<upload-image v-model="formData.site_bg" />
|
||||
<div class="form-tip">{{t('siteBgImgTip')}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('isCaptcha')">
|
||||
<el-switch v-model="formData.is_site_captcha" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('bgImg')">
|
||||
<upload-image v-model="formData.site_bg" />
|
||||
<div class="form-tip">{{t('siteBgImgTip')}}</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
|
||||
@ -41,7 +41,10 @@ import { t } from '@/lang'
|
||||
import { getConfigLogin, setConfigLogin } from '@/app/api/sys'
|
||||
import { FormInstance } from 'element-plus'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
const loading = ref(true)
|
||||
const ruleFormRef = ref<FormInstance>()
|
||||
const formData = reactive<Record<string, number | string>>({
|
||||
|
||||
118
admin/src/app/views/setting/components/sms-niu.vue
Normal file
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<el-dialog v-model="showDialog" :title="t('aliSms')" width="580px" :destroy-on-close="true">
|
||||
<el-form :model="formData" label-width="140px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
|
||||
<el-form-item :label="t('isUse')">
|
||||
<el-radio-group v-model="formData.is_use">
|
||||
<el-radio :label="1">{{ t('startUsing') }}</el-radio>
|
||||
<el-radio :label="0">{{ t('statusDeactivate') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('aliSign')" prop="sign">
|
||||
<el-input v-model.trim="formData.sign" :placeholder="t('aliSignPlaceholder')" class="input-width" show-word-limit clearable />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('aliAppKey')" prop="app_key">
|
||||
<el-input v-model.trim="formData.app_key" :placeholder="t('aliAppKeyPlaceholder')" class="input-width" clearable />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('aliSecretKey')" prop="secret_key">
|
||||
<el-input v-model.trim="formData.secret_key" :placeholder="t('aliSecretKeyPlaceholder')" class="input-width" clearable />
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{t('confirm')}}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { getSmsInfo, editSms } from '@/app/api/notice'
|
||||
|
||||
const showDialog = ref(false)
|
||||
const loading = ref(true)
|
||||
|
||||
/**
|
||||
* 表单数据
|
||||
*/
|
||||
const initialFormData = {
|
||||
sms_type: '',
|
||||
sign: '',
|
||||
app_key: '',
|
||||
secret_key: '',
|
||||
is_use: ''
|
||||
}
|
||||
const formData: Record<string, any> = reactive({ ...initialFormData })
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
sign: [
|
||||
{ required: true, message: t('aliSignPlaceholder'), trigger: 'blur' }
|
||||
],
|
||||
app_key: [
|
||||
{ required: true, message: t('aliAppKeyPlaceholder'), trigger: 'blur' }
|
||||
],
|
||||
secret_key: [
|
||||
{ required: true, message: t('aliSecretKeyPlaceholder'), trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['complete'])
|
||||
|
||||
/**
|
||||
* 确认
|
||||
* @param formEl
|
||||
*/
|
||||
const confirm = async (formEl: FormInstance | undefined) => {
|
||||
if (loading.value || !formEl) return
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
|
||||
const data = formData
|
||||
|
||||
editSms(data).then(res => {
|
||||
loading.value = false
|
||||
showDialog.value = false
|
||||
emit('complete')
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
// showDialog.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const setFormData = async (row: any = null) => {
|
||||
loading.value = true
|
||||
Object.assign(formData, initialFormData)
|
||||
if (row) {
|
||||
const data = await (await getSmsInfo(row.sms_type)).data
|
||||
Object.keys(formData).forEach((key: string) => {
|
||||
if (data[key] != undefined) formData[key] = data[key]
|
||||
if (data.params[key] != undefined) formData[key] = data.params[key].value
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
showDialog,
|
||||
setFormData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
374
admin/src/app/views/setting/components/sms_niu_login.vue
Normal file
@ -0,0 +1,374 @@
|
||||
<template>
|
||||
<el-card class="box-card !border-none" shadow="never" v-loading="loading">
|
||||
<div v-if="type=='login'" >
|
||||
<div class="bg-[var(--el-color-primary-light-9)] p-2 text-[14px] rounded-[6px]">
|
||||
<span class="">还未注册牛云短信?</span>
|
||||
<span @click="toRegister" class="cursor-pointer text-primary">去注册</span>
|
||||
</div>
|
||||
<el-form :model="formData" label-width="100px" ref="formRef" :rules="formRules" class="page-form mt-[20px] ml-[50px]">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input placeholder="请输入用户名" class="input-width" v-model="formData.username" clearable autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input placeholder="请输入密码" type="password" show-password class="input-width" autocomplete="new-password" v-model="formData.password" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="login">登录</el-button>
|
||||
<el-button @click="editPass()">忘记密码</el-button>
|
||||
<el-button @click="back()" v-if="props.isLogin">返回</el-button>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
<div v-if="type=='register'">
|
||||
<div class="bg-[var(--el-color-primary-light-9)] p-2 text-[14px] rounded-[6px]">
|
||||
<span class="">已有账号,</span>
|
||||
<span @click="type='login'" class="cursor-pointer text-primary">去登录</span>
|
||||
</div>
|
||||
<el-form :model="registerFormData" label-width="100px" ref="registerFormRef" :rules="registerFormRules" class="page-form mt-[20px] ml-[50px]">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input placeholder="请输入用户名" class="input-width" autocomplete="off" v-model="registerFormData.username" clearable />
|
||||
</el-form-item>
|
||||
<div class="mb-[10px] text-[12px] ml-[100px] text-[#999] leading-[20px]">子账户用户名,仅支持6~50位英文+数字组合</div>
|
||||
<el-form-item label="公司名称" prop="company">
|
||||
<el-input placeholder="请输入公司名称" class="input-width" v-model="registerFormData.company" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input placeholder="请输入手机号" class="input-width" maxlength="11" show-word-limit v-model="registerFormData.mobile" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="验证码" prop="captcha_code">
|
||||
<div class="flex items-center">
|
||||
<el-input placeholder="请输入验证码" class="input-width" maxlength="4" show-word-limit v-model="registerFormData.captcha_code" clearable />
|
||||
<img :src="registerFormData.captcha_img" alt="验证码" class="w-[100px] h-[32px] cursor-pointer ml-[10px]" @click="getSmsCaptchaFn" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="动态码" prop="code">
|
||||
<div class="flex items-center">
|
||||
<el-input placeholder="请输入动态码" class="input-width" maxlength="4" show-word-limit v-model="registerFormData.code" clearable />
|
||||
<el-button class="ml-[10px]" @click="getSmsSendFn" :disabled="countdown > 0" :loading="sending">
|
||||
{{ countdown > 0 ? `${countdown}秒后重新获取` : '获取动态码' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="初始密码" prop="password">
|
||||
<el-input placeholder="请输入初始密码" type="password" autocomplete="new-password" show-password class="input-width" v-model="registerFormData.password" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input placeholder="请输入备注" class="input-width" v-model="registerFormData.remark" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="register">注册</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div v-if="type=='password'">
|
||||
<div class="bg-[var(--el-color-primary-light-9)] p-2 text-[14px] rounded-[6px]">
|
||||
<span class="text-primary">忘记密码,快去修改</span>
|
||||
</div>
|
||||
<el-form :model="changeFormData" label-width="100px" ref="changeFormRef" :rules="changeFormRules" class="page-form mt-[20px] ml-[50px]">
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input placeholder="请输入手机号" class="input-width" maxlength="11" show-word-limit v-model="changeFormData.mobile" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="验证码" prop="captcha_code">
|
||||
<div class="flex items-center">
|
||||
<el-input placeholder="请输入验证码" class="input-width" maxlength="4" show-word-limit v-model="changeFormData.captcha_code" clearable />
|
||||
<img :src="changeFormData.captcha_img" alt="验证码" class="w-[100px] h-[32px] cursor-pointer ml-[10px]" @click="getSmsCaptchaFn" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="动态码" prop="code">
|
||||
<div class="flex items-center">
|
||||
<el-input placeholder="请输入动态码" class="input-width" maxlength="4" show-word-limit v-model="changeFormData.code" clearable />
|
||||
<el-button class="ml-[10px]" @click="getSmsSendFn" :disabled="countdown > 0" :loading="sending">
|
||||
{{ countdown > 0 ? `${countdown}秒后重新获取` : '获取动态码' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="reset()">重置密码</el-button>
|
||||
<el-button @click="type='login'">返回</el-button>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref ,computed } from 'vue'
|
||||
import { loginAccount,getSmsCaptcha,getSmsSend,resetPassword,registerAccount } from '@/app/api/notice'
|
||||
|
||||
const props = defineProps({
|
||||
info: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
isLogin: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const formRef = ref()
|
||||
const type = ref('login')
|
||||
const emit = defineEmits(['complete'])
|
||||
const formData = ref({
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const isBack = computed(() => {
|
||||
return !!props.info && Object.keys(props.info).length > 0;
|
||||
})
|
||||
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const login = async () => {
|
||||
await formRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
loginAccount(formData.value).then((res) => {
|
||||
emit('complete')
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
const back = () => {
|
||||
emit('complete')
|
||||
}
|
||||
|
||||
// 注册
|
||||
const registerFormData = ref({
|
||||
code: '',
|
||||
key: '',
|
||||
remark: '',
|
||||
username: '',
|
||||
password: '',
|
||||
company: '',
|
||||
mobile: '',
|
||||
captcha_key: '',
|
||||
captcha_code: '',
|
||||
captcha_img: ''
|
||||
})
|
||||
|
||||
const captchaType = ref('login')
|
||||
const toRegister = async () => {
|
||||
captchaType.value = 'register'
|
||||
loading.value = true
|
||||
const success = await getSmsCaptchaFn()
|
||||
if (success) {
|
||||
registerFormData.value.username = ''
|
||||
registerFormData.value.password = ''
|
||||
type.value = 'register'
|
||||
loading.value = false
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const registerFormRef = ref()
|
||||
|
||||
const registerFormRules = computed(() => {
|
||||
return {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^[A-Za-z0-9]{6,50}$/,
|
||||
message: '用户名格式不正确',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
],
|
||||
captcha_code: [
|
||||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: '请输入动态码', trigger: 'blur' },
|
||||
],
|
||||
company: [
|
||||
{ required: true, message: '请输入公司名称', trigger: 'blur' },
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const register = async () => {
|
||||
await registerFormRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
let params = {
|
||||
code: registerFormData.value.code,
|
||||
key: registerFormData.value.key,
|
||||
remark: registerFormData.value.remark,
|
||||
username: registerFormData.value.username,
|
||||
password: registerFormData.value.password,
|
||||
company: registerFormData.value.company,
|
||||
mobile: registerFormData.value.mobile,
|
||||
}
|
||||
registerAccount(params).then((res) => {
|
||||
type.value = 'login'
|
||||
}).catch((err) => {
|
||||
getSmsCaptchaFn()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重置密码
|
||||
const changeFormRef = ref()
|
||||
const changeFormData = ref({
|
||||
mobile: '',
|
||||
captcha_key: '',
|
||||
captcha_code: '',
|
||||
captcha_img: '',
|
||||
code: '',
|
||||
key: ''
|
||||
})
|
||||
|
||||
const getSmsCaptchaFn = async () => {
|
||||
try {
|
||||
const res = await getSmsCaptcha()
|
||||
if (captchaType.value === 'register') {
|
||||
registerFormData.value.captcha_key = res.data.captcha_key
|
||||
registerFormData.value.captcha_img = res.data.img
|
||||
} else if (captchaType.value === 'password') {
|
||||
changeFormData.value.captcha_key = res.data.captcha_key
|
||||
changeFormData.value.captcha_img = res.data.img
|
||||
}
|
||||
return true // 表示成功
|
||||
} catch (error) {
|
||||
console.error('获取验证码失败', error)
|
||||
return false // 表示失败
|
||||
}
|
||||
}
|
||||
|
||||
const sending = ref(false); // 发送中状态
|
||||
const countdown = ref(0); // 倒计时秒数
|
||||
|
||||
const getSmsSendFn = () => {
|
||||
if (countdown.value > 0 || sending.value) return; // 正在倒计时或发送中,直接返回
|
||||
if (type.value === 'register') {
|
||||
registerFormRef.value.validateField(['mobile', 'captcha_code'], (valid) => {
|
||||
if (!valid) return;
|
||||
sending.value = true; // 标记为发送中
|
||||
const params = {
|
||||
mobile: registerFormData.value.mobile,
|
||||
captcha_key: registerFormData.value.captcha_key,
|
||||
captcha_code: registerFormData.value.captcha_code
|
||||
}
|
||||
getSmsSend(params).then((res) => {
|
||||
startCountdown(60); // 启动60秒倒计时
|
||||
registerFormData.value.key = res.data.key;
|
||||
}).catch((err) => {
|
||||
getSmsCaptchaFn()
|
||||
sending.value = false;
|
||||
}).finally(() => {
|
||||
sending.value = false; // 无论成功失败都重置发送状态
|
||||
});
|
||||
});
|
||||
|
||||
} else if (type.value === 'password') {
|
||||
changeFormRef.value.validateField(['mobile', 'captcha_code'], (valid) => {
|
||||
if (!valid) return;
|
||||
sending.value = true; // 标记为发送中
|
||||
|
||||
const params = {
|
||||
mobile: changeFormData.value.mobile,
|
||||
captcha_key: changeFormData.value.captcha_key,
|
||||
captcha_code: changeFormData.value.captcha_code
|
||||
}
|
||||
getSmsSend(params).then((res) => {
|
||||
startCountdown(60); // 启动60秒倒计时
|
||||
changeFormData.value.key = res.data.key;
|
||||
}).catch((err) => {
|
||||
getSmsCaptchaFn()
|
||||
sending.value = false;
|
||||
}).finally(() => {
|
||||
sending.value = false; // 无论成功失败都重置发送状态
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 启动倒计时
|
||||
const startCountdown = (seconds) => {
|
||||
countdown.value = seconds;
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(timer);
|
||||
sending.value = false; // 发送状态重置
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const changeFormRules = computed(() => {
|
||||
return {
|
||||
mobile: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '请输入正确的手机号',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
captcha_code: [
|
||||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: '请输入动态码', trigger: 'blur' },
|
||||
]
|
||||
};
|
||||
});
|
||||
const editPass = async () => {
|
||||
loading.value = true
|
||||
captchaType.value = 'password'
|
||||
const success = await getSmsCaptchaFn()
|
||||
if (success) {
|
||||
loading.value = false
|
||||
type.value = 'password'
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
const reset = async () => {
|
||||
await changeFormRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
let params = {
|
||||
key: changeFormData.value.key,
|
||||
code: changeFormData.value.code,
|
||||
mobile: changeFormData.value.mobile
|
||||
}
|
||||
resetPassword(props.info.username,{...params}).then((res) => {
|
||||
let newPassword = res.data.password
|
||||
ElMessageBox.confirm(`新密码为:${newPassword}`, '请保存好新密码', {
|
||||
confirmButtonText: '确定',
|
||||
showCancelButton: false,
|
||||
}).then(() => {
|
||||
type.value='login'
|
||||
emit('complete')
|
||||
}).catch(() => {
|
||||
type.value='login'
|
||||
emit('complete')
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
128
admin/src/app/views/setting/components/sms_recharge.vue
Normal file
@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<el-card class="box-card !border-none p-[10px]" shadow="never">
|
||||
<div class="panel-title">选择套餐</div>
|
||||
<div class="flex flex-wrap mb-[30px]">
|
||||
<div v-for="(item,index) in smsPackages" :key="index" :span="4">
|
||||
<div class="package-card mr-[10px]" :class="{ active: selectedPackage?.id === item.id }" @click="selectPackage(item)">
|
||||
<div class="text-[14px] mb-1">{{ item.package_name }}</div>
|
||||
<div class="text-[24px] text-primary ">{{ item.sms_num }}条</div>
|
||||
<div class="flex mt-2 text-[14px] justify-center items-center ">
|
||||
<div>¥{{ item.price }}</div>
|
||||
<div class="line-through ml-2">¥{{ item.original_price }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-title">选择支付方式</div>
|
||||
<el-radio-group v-model="payMethod" class="mb-4">
|
||||
<el-radio label="alipay">支付宝</el-radio>
|
||||
<!-- 可扩展其他方式 -->
|
||||
</el-radio-group>
|
||||
|
||||
<div class="mb-4 text-[14px] ml-[10px] mt-[10px]">
|
||||
应付:<span class="text-[24px] font-semibold text-primary"><span class="text-[14px] font-400">¥</span>{{ totalAmount }}</span>
|
||||
</div>
|
||||
|
||||
<div class="ml-[50px]">
|
||||
<el-button type="primary" :disabled="!selectedPackage || loading" :loading="loading" @click="submitPayment">支付</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { getSmsPackagesList, smsOrderCreate, getOrderPayInfo, getOrderPayStatus, calculateOrderPay } from '@/app/api/notice'
|
||||
|
||||
const props = defineProps({
|
||||
username: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['back', 'complete'])
|
||||
|
||||
const smsPackages = ref<any[]>([])
|
||||
const selectedPackage = ref<any | null>(null)
|
||||
const payMethod = ref('alipay')
|
||||
|
||||
const totalAmount = ref(0)
|
||||
const getSmsPackagesListFn = () => {
|
||||
getSmsPackagesList().then(res => {
|
||||
smsPackages.value = res.data.data
|
||||
if (smsPackages.value.length > 0 && props.username) {
|
||||
selectPackage(smsPackages.value[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const selectPackage = (pkg: any) => {
|
||||
selectedPackage.value = pkg
|
||||
calculateAmount()
|
||||
}
|
||||
|
||||
// 计算金额
|
||||
const calculateAmount = () => {
|
||||
if (!selectedPackage.value) return
|
||||
calculateOrderPay(props.username, {
|
||||
package_id: selectedPackage.value.id
|
||||
}
|
||||
).then(res => {
|
||||
totalAmount.value = res.data.pay_money
|
||||
})
|
||||
}
|
||||
const loading = ref(false)
|
||||
const submitPayment = async () => {
|
||||
if (!selectedPackage.value || loading.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await smsOrderCreate(props.username, {
|
||||
package_id: selectedPackage.value.id
|
||||
})
|
||||
|
||||
if (res.data.order_status === 'payment') {
|
||||
emit('complete')
|
||||
} else {
|
||||
const orderNo = res.data.out_trade_no
|
||||
const payInfo = await getOrderPayInfo(props.username, { out_trade_no: orderNo })
|
||||
|
||||
window.open(payInfo.data.pay_info.url, '_blank')
|
||||
await ElMessageBox.confirm('请确认支付是否完成', '支付提示', {
|
||||
confirmButtonText: '已完成支付',
|
||||
cancelButtonText: '返回',
|
||||
type: 'warning',
|
||||
})
|
||||
emit('complete')
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error('支付失败,请重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
emit('back')
|
||||
}
|
||||
|
||||
getSmsPackagesListFn()
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.package-card {
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
border: 1px solid #e4e7ed;
|
||||
transition: all 0.2s;
|
||||
padding: 10px;
|
||||
width: 170px;
|
||||
border-radius: 5px;
|
||||
|
||||
&.active {
|
||||
border-color: var(--el-color-primary);
|
||||
box-shadow: 0 0 6px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
131
admin/src/app/views/setting/components/sms_recharge_record.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table :data="tableData.data" size="large" v-loading="tableData.loading" ref="goodBankListTableRef">
|
||||
<template #empty>
|
||||
<span>{{ !tableData.loading ? t("emptyData") : "" }}</span>
|
||||
</template>
|
||||
<el-table-column prop="order_no" :label="t('订单编号')" min-width="130" />
|
||||
<el-table-column prop="package_name" :label="t('短信套餐')" min-width="130" />
|
||||
<el-table-column prop="sms_num" :label="t('短信条数')" min-width="130" />
|
||||
<el-table-column prop="order_money" :label="t('订单总价')" min-width="130" />
|
||||
<el-table-column prop="pay_money" :label="t('实付金额')" min-width="130" />
|
||||
<el-table-column prop="order_status_name" :label="t('订单状态')" min-width="130" />
|
||||
<el-table-column prop="sms" :label="t('创建时间')" min-width="130" >
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.create_time }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="detailEvent(row)">{{ t("详情") }}</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="loadRankList()" @current-change="loadRankList" />
|
||||
</div>
|
||||
<el-dialog v-model="visibleDetail" :title="t('模版详情')" width="600px" destroy-on-close >
|
||||
<el-form label-width="100px" ref="formRef" class="page-form" v-loading="loading">
|
||||
<el-form-item :label="t('订单编号')" prop="template_id">
|
||||
<div>{{ detail.order_no }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('用户名称')" prop="template_id">
|
||||
<div>{{ detail.username }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('套餐名称')" prop="template_id">
|
||||
<div>{{ detail.package_name }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('订单状态')" prop="title">
|
||||
<div >{{ detail.order_status_name }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('短信条数')" prop="title">
|
||||
<div >{{ detail.sms_num }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('订单金额')" prop="title">
|
||||
<div >{{ detail.order_money }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('付款金额')" prop="title">
|
||||
<div >{{ detail.pay_money }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('创建时间')" prop="title">
|
||||
<div >{{ detail.create_time }}</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visibleDetail = false">{{ t("cancel") }}</el-button>
|
||||
<el-button type="primary" @click="visibleDetail = false">{{ t("confirm") }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref , reactive,onMounted} from 'vue'
|
||||
import { getSmsOrdersList ,getOrderInfo} from '@/app/api/notice'
|
||||
import { t } from "@/lang";
|
||||
|
||||
const props = defineProps({
|
||||
username: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
// 表单内容
|
||||
const tableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
data: [],
|
||||
searchParam: {
|
||||
name: "",
|
||||
order: '',
|
||||
sort: ''
|
||||
},
|
||||
});
|
||||
|
||||
// 获取列表
|
||||
const loadRankList = () => {
|
||||
tableData.loading = true;
|
||||
let params = {
|
||||
page: tableData.page,
|
||||
limit: tableData.limit,
|
||||
...tableData.searchParam
|
||||
}
|
||||
getSmsOrdersList(props.username, params).then((res) => {
|
||||
tableData.loading = false;
|
||||
tableData.data = res.data.data;
|
||||
tableData.total = res.data.total;
|
||||
}).catch(() => {
|
||||
tableData.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 详情
|
||||
const detail = ref({})
|
||||
const visibleDetail = ref(false)
|
||||
const loading = ref(false)
|
||||
const detailEvent = (row:any) => {
|
||||
loading.value = true
|
||||
visibleDetail.value = true
|
||||
getOrderInfo(props.username,{out_trade_no:row.out_trade_no}).then(res=>{
|
||||
detail.value = res.data
|
||||
loading.value = false
|
||||
})
|
||||
visibleDetail.value = true
|
||||
}
|
||||
onMounted(() => {
|
||||
if (props.username) {
|
||||
loadRankList()
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
106
admin/src/app/views/setting/components/sms_send.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form :inline="true" :model="tableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('短信标题')" prop="content">
|
||||
<el-input v-model.trim="tableData.searchParam.content" :placeholder="t('请输入短信标题')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('接收人手机号')" prop="mobile">
|
||||
<el-input v-model.trim="tableData.searchParam.mobile" :placeholder="t('请输入接收人手机号')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('状态')" prop="smsStatus">
|
||||
<el-select v-model="tableData.searchParam.smsStatus" :placeholder="t('请选择状态')">
|
||||
<el-option :label="t('全部')" :value="''"></el-option>
|
||||
<el-option :label="t('发送成功')" :value="1"></el-option>
|
||||
<el-option :label="t('发送失败')" :value="2"></el-option>
|
||||
<el-option :label="t('待返回')" :value="3"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadRankList()">{{ t("search") }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t("reset") }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="tableData.data" size="large" v-loading="tableData.loading" ref="goodBankListTableRef">
|
||||
<template #empty>
|
||||
<span>{{ !tableData.loading ? t("emptyData") : "" }}</span>
|
||||
</template>
|
||||
<el-table-column prop="content" :label="t('标题')" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column prop="mobile" :label="t('接收人')" min-width="130" />
|
||||
<el-table-column prop="smsStatusName" :label="t('发送状态')" min-width="130" />
|
||||
|
||||
<el-table-column prop="reportTime" :label="t('创建时间')" min-width="130" >
|
||||
<template #default="{ row }">
|
||||
<div>{{ timeStampTurnTime(row.reportTime) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sendTime" :label="t('发送时间')" min-width="130" >
|
||||
<template #default="{ row }">
|
||||
<div>{{timeStampTurnTime(row.sendTime) }}</div>
|
||||
</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="loadRankList()" @current-change="loadRankList" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref ,reactive,onMounted} from 'vue'
|
||||
import { getSmsSendList } from '@/app/api/notice'
|
||||
import { timeStampTurnTime } from '@/utils/common'
|
||||
import { t } from "@/lang";
|
||||
|
||||
const props = defineProps({
|
||||
username: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
// 表单内容
|
||||
const tableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
data: [],
|
||||
searchParam: {
|
||||
mobile: '',
|
||||
content: '',
|
||||
smsStatus: ''
|
||||
}
|
||||
})
|
||||
|
||||
// 获取列表
|
||||
const loadRankList = () => {
|
||||
tableData.loading = true;
|
||||
let params = {
|
||||
page: tableData.page,
|
||||
limit: tableData.limit,
|
||||
...tableData.searchParam
|
||||
}
|
||||
getSmsSendList(props.username, params).then((res) => {
|
||||
tableData.loading = false;
|
||||
tableData.data = res.data.data;
|
||||
tableData.total = res.data.total;
|
||||
}).catch(() => {
|
||||
tableData.loading = false;
|
||||
});
|
||||
}
|
||||
onMounted(() => {
|
||||
if (props.username) {
|
||||
loadRankList();
|
||||
}
|
||||
})
|
||||
const searchFormRef = ref<FormInstance>();
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
loadRankList()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
395
admin/src/app/views/setting/components/sms_signature.vue
Normal file
@ -0,0 +1,395 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="visible" :title="t('选择签名')" width="1200px" destroy-on-close :close-on-click-modal="false">
|
||||
<el-alert type="warning" :closable="false" class="!mb-[10px]">
|
||||
<template #default>
|
||||
<p class="">签名数据的变更(新增 / 删除)需经过五分钟的生效周期,在此期间系统将完成数据同步与更新</p>
|
||||
</template>
|
||||
</el-alert>
|
||||
<div class="flex justify-between items-center mb-[16px]">
|
||||
<el-button type="primary" @click="addEvent">{{ t('添加短信签名') }}</el-button>
|
||||
</div>
|
||||
<div class="mb-[10px] flex items-center">
|
||||
<el-checkbox v-model="toggleCheckbox" size="large" class="px-[14px]" @change="toggleChange" :indeterminate="isIndeterminate" />
|
||||
<el-button @click="batchDeleteEvent" size="small">{{t("批量删除")}}</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData.data" size="large" v-loading="tableData.loading" ref="smsSignListTableRef" @selection-change="handleSelectionChange">
|
||||
<template #empty>
|
||||
<span>{{ !tableData.loading ? t("emptyData") : "" }}</span>
|
||||
</template>
|
||||
<el-table-column type="selection" :selectable="checkSelectable" width="55" />
|
||||
<el-table-column prop="sign" :label="t('签名名称')" min-width="200" />
|
||||
<el-table-column prop="is_default" :label="t('使用状态')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.is_default? t('使用中') : t('未使用') }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="auditResultName" :label="t('审核状态')" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<div :class="[row.auditResult == 2 ? 'text-green-600' : '']">{{ row.auditResultName }}</div>
|
||||
<div class="text-red-600" v-if="row.auditResult != 2">{{ row.auditMsg }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="realNameDx" :label="t('实名状态')" min-width="200">
|
||||
<template #header>
|
||||
<div style="display: inline-flex; align-items: center">
|
||||
<span class="mr-[5px]">{{ t('实名状态') }}</span>
|
||||
<el-tooltip class="box-item" effect="light" placement="top">
|
||||
<template #content>
|
||||
状态标识说明:<br />未实名状态显示为灰色;<br />实名通过状态显示为绿色;<br />实名失败状态显示为红色。<br />
|
||||
短信接收条件:仅当手机号在对应运营商处实名通过后,才可接收短信。
|
||||
</template>
|
||||
<el-icon color="#666">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<div class="flex gap-[5px]">
|
||||
<el-tag :type="row.realNameLt == 0 ? 'info' : row.realNameLt == 1 ? 'success' : 'danger'">{{ t("联通") }}</el-tag>
|
||||
<el-tag :type="row.realNameYd == 0 ? 'info' : row.realNameYd == 1 ? 'success' : 'danger'">{{ t("移动") }}</el-tag>
|
||||
<el-tag :type="row.realNameDx == 0 ? 'info' : row.realNameDx == 1 ? 'success' : 'danger'">{{ t("电信") }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" :label="t('createTime')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.createTime }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('操作')" fixed="right" align="right" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="selectTemplate(row)" v-if="!row.is_default && row.auditResult == 2">
|
||||
{{ t("使用") }}
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="deleteTemplate(row)" v-if="!row.is_default">
|
||||
{{ t("删除") }}
|
||||
</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="loadSignList()" @current-change="loadSignList" />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">{{ t("cancel") }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="visibleAdd" :title="t('添加签名')" width="800px" destroy-on-close :close-on-click-modal="false">
|
||||
<el-form label-width="150px" :model="formData" ref="formRef" :rules="formRules" class="page-form ml-[20px]">
|
||||
<el-form-item :label="t('短信签名')" prop="signature">
|
||||
<el-input v-model="formData.signature" placeholder="请输入短信签名" class="input-width" maxlength="20" show-word-limit clearable />
|
||||
</el-form-item>
|
||||
<div class="my-[5px] ml-[150px] text-[12px] text-[#999] leading-[20px]">字数要求在2-20个字符,不能使用空格和特殊符号“ - + = * & % # @ ~等;</div>
|
||||
<el-form-item :label="t('短信示例内容')" prop="contentExample">
|
||||
<el-input v-model="formData.contentExample" placeholder="请输入短信示例内容" clearable maxlength="50" show-word-limit class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('企业名称')" prop="companyName">
|
||||
<el-input v-model="formData.companyName" placeholder="请输入企业名称" clearable maxlength="20" show-word-limit class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('社会统一信用代码')" prop="creditCode">
|
||||
<el-input v-model="formData.creditCode" placeholder="请输入社会统一信用代码" clearable maxlength="20" show-word-limit class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('法人姓名')" prop="legalPerson">
|
||||
<el-input v-model="formData.legalPerson" placeholder="请输入法人姓名" clearable maxlength="20" show-word-limit class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('经办人姓名')" prop="principalName">
|
||||
<el-input v-model="formData.principalName" placeholder="请输入经办人姓名" clearable maxlength="20" show-word-limit class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('经办人身份证')" prop="principalIdCard">
|
||||
<el-input v-model="formData.principalIdCard" placeholder="请输入经办人身份证" clearable maxlength="18" show-word-limit class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('签名来源')">
|
||||
<el-radio-group v-model="formData.signSource" >
|
||||
<el-radio v-for="item in signCofig.signsourceList" :key="item.type" :label="item.type" >{{item.name}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('签名类型')">
|
||||
<el-radio-group v-model="formData.signType">
|
||||
<el-radio v-for="item in signCofig.signTypeList" :key="item.type" :label="item.type" >{{item.name}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('上传图片')" prop="imgUrl">
|
||||
<upload-image v-model="formData.imgUrl" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('是否默认')">
|
||||
<el-radio-group v-model="formData.defaultSign" >
|
||||
<el-radio :label="1">是</el-radio>
|
||||
<el-radio :label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visibleAdd = false">{{ t("cancel") }}</el-button>
|
||||
<el-button type="primary" @click="onSave()">{{ t("confirm") }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
import { getSignList,addSign,getSmsSignConfig,deleteSign } from '@/app/api/notice'
|
||||
import { t } from "@/lang";
|
||||
|
||||
const visible = ref(false)
|
||||
const visibleAdd = ref(false)
|
||||
const emit = defineEmits(['select'])
|
||||
const props = defineProps({
|
||||
username: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
const initialFormData = {
|
||||
defaultSign: 0,
|
||||
imgUrl: '',
|
||||
contentExample: '',
|
||||
signType: '',
|
||||
signSource: '',
|
||||
principalIdCard: '',
|
||||
principalName: '',
|
||||
legalPerson: '',
|
||||
creditCode: '',
|
||||
companyName: '',
|
||||
signature: ''
|
||||
}
|
||||
const formData = reactive({ ...initialFormData })
|
||||
|
||||
|
||||
const signCofig = reactive({
|
||||
signTypeList: [],
|
||||
signsourceList:[]
|
||||
})
|
||||
const getSmsSignConfigFn = ()=> {
|
||||
getSmsSignConfig().then(res => {
|
||||
signCofig.signTypeList = res.data.sign_type_list
|
||||
signCofig.signsourceList = res.data.sign_source_list
|
||||
formData.signSource = res.data.sign_source_list[0].type
|
||||
formData.signType = res.data.sign_type_list[0].type
|
||||
})
|
||||
}
|
||||
|
||||
getSmsSignConfigFn()
|
||||
|
||||
const formRef = ref()
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
signature: [
|
||||
{ required: true, message: '请输入短信签名', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const lengthValid = value.length >= 2 && value.length <= 20;
|
||||
const invalidChars = /[\s\-+=*&%#@~;]/;
|
||||
|
||||
if (!lengthValid) {
|
||||
callback(new Error('短信签名字数需在 2-20 个字符之间'));
|
||||
} else if (invalidChars.test(value)) {
|
||||
callback(new Error('短信签名不能包含空格或特殊字符 - + = * & % # @ ~ ;'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
|
||||
companyName: [
|
||||
{ required: true, message: '请输入企业名称', trigger: 'blur' },
|
||||
],
|
||||
contentExample: [
|
||||
{ required: true, message: '请输入短信示例内容', trigger: 'blur' },
|
||||
],
|
||||
creditCode: [
|
||||
{ required: true, message: '请输入社会统一信用代码', trigger: 'blur' },
|
||||
],
|
||||
legalPerson: [
|
||||
{ required: true, message: '请输入法人姓名', trigger: 'blur' },
|
||||
],
|
||||
principalName: [
|
||||
{ required: true, message: '请输入经办人姓名', trigger: 'blur' },
|
||||
],
|
||||
principalIdCard: [
|
||||
{ required: true, message: '请输入经办人身份证', trigger: 'blur' },
|
||||
{ validator: idCardVerify, trigger: 'blur' }
|
||||
],
|
||||
imgUrl: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const needImage = [3, 4, 5].includes(formData.signSource) || formData.signType === 1;
|
||||
if (needImage) {
|
||||
if (!value || value.length === 0) {
|
||||
callback(new Error('请上传图片'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
callback(); // 不需要校验
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
|
||||
};
|
||||
})
|
||||
|
||||
const idCardVerify = (rule: any, value: any, callback: any) => {
|
||||
if (value && !/^[1-9]\d{5}(19|20)\d{2}((0\d)|(1[0-2]))(([0-2]\d)|3[0-1])\d{3}([0-9Xx])$/.test(value)) {
|
||||
callback(new Error(t('请输入正确的身份证号码')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
await formRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
addSign(props.username,formData).then((res) => {
|
||||
setTimeout(() => {
|
||||
visibleAdd.value = false
|
||||
loadSignList()
|
||||
}, 500);
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 表单内容
|
||||
const tableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
data: [],
|
||||
searchParam: {},
|
||||
});
|
||||
|
||||
const open = () => {
|
||||
visible.value = true
|
||||
loadSignList()
|
||||
}
|
||||
// 获取列表
|
||||
const loadSignList = () => {
|
||||
tableData.loading = true;
|
||||
let params = {
|
||||
page: tableData.page,
|
||||
limit: tableData.limit,
|
||||
...tableData.searchParam,
|
||||
}
|
||||
getSignList(props.username, params).then((res) => {
|
||||
tableData.loading = false;
|
||||
tableData.data = res.data.data;
|
||||
tableData.total = res.data.total
|
||||
}).catch(() => {
|
||||
tableData.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
const addEvent = () => {
|
||||
Object.assign(formData, initialFormData)
|
||||
formData.signSource = signCofig.signsourceList[0].type
|
||||
formData.signType = signCofig.signTypeList[0].type
|
||||
visibleAdd.value = true
|
||||
}
|
||||
|
||||
const deleteTemplate = (row:any) => {
|
||||
ElMessageBox.confirm(t('确定删除该签名吗?'), t('提示'), {
|
||||
confirmButtonText: t('确定'),
|
||||
cancelButtonText: t('取消'),
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
deleteSign(props.username, { signatures: [row.sign] }).then((res) => {
|
||||
// loadSignList()
|
||||
tableData.loading = true;
|
||||
setTimeout(() => {
|
||||
loadSignList()
|
||||
},1000)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 批量复选框
|
||||
const toggleCheckbox = ref()
|
||||
|
||||
// 复选框中间状态
|
||||
const isIndeterminate = ref(false)
|
||||
|
||||
// 监听批量复选框事件
|
||||
const toggleChange = (value: any) => {
|
||||
isIndeterminate.value = false
|
||||
smsSignListTableRef.value.toggleAllSelection()
|
||||
}
|
||||
|
||||
const smsSignListTableRef = ref()
|
||||
|
||||
// 选中数据
|
||||
const multipleSelection: any = ref([])
|
||||
|
||||
// 监听表格单行选中
|
||||
const handleSelectionChange = (val: []) => {
|
||||
multipleSelection.value = val
|
||||
|
||||
toggleCheckbox.value = false
|
||||
if (multipleSelection.value.length > 0 && multipleSelection.value.length < tableData.data.length) {
|
||||
isIndeterminate.value = true
|
||||
} else {
|
||||
isIndeterminate.value = false
|
||||
}
|
||||
|
||||
if (multipleSelection.value.length == tableData.data.length && tableData.data.length && multipleSelection.value.length) {
|
||||
toggleCheckbox.value = true
|
||||
}
|
||||
}
|
||||
const checkSelectable = (row: any, index: number) => {
|
||||
return !row.is_default; // 只有不是“使用中”的行可选
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
const batchDeleteEvent = () => {
|
||||
if (multipleSelection.value.length == 0) {
|
||||
ElMessage({
|
||||
type: "warning",
|
||||
message: `${ t("请选择要删除的签名") }`,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(t("确定删除选中的签名吗?"), t("warning"), {
|
||||
confirmButtonText: t("confirm"),
|
||||
cancelButtonText: t("cancel"),
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
const signatures: any = []
|
||||
multipleSelection.value.forEach((item: any) => {
|
||||
signatures.push(item.sign)
|
||||
})
|
||||
|
||||
deleteSign(props.username,{
|
||||
signatures: signatures
|
||||
}).then(() => {
|
||||
tableData.loading = true;
|
||||
setTimeout(() => {
|
||||
loadSignList()
|
||||
},1000)
|
||||
// loadSignList()
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const selectTemplate = (row:any) => {
|
||||
visible.value = false
|
||||
emit('select', row)
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
330
admin/src/app/views/setting/components/sms_template.vue
Normal file
@ -0,0 +1,330 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex justify-between items-start my-[10px]">
|
||||
<el-form :inline="true" :model="tableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('模版ID')" prop="template_id">
|
||||
<el-input v-model.trim="tableData.searchParam.template_id" :placeholder="t('请输入模版ID')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('模版名称')" prop="name">
|
||||
<el-input v-model.trim="tableData.searchParam.name" :placeholder="t('请输入模版名称')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('状态')" prop="status">
|
||||
<el-select v-model="tableData.searchParam.status" :placeholder="t('请选择状态')">
|
||||
<el-option :label="t('全部')" :value="''"></el-option>
|
||||
<el-option v-for="(statusText, statusValue) in template_status_list" :key="statusValue" :label="statusText" :value="statusValue"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadSmsTemplateList()">{{ t("search") }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t("reset") }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" @click="syncEvent()">{{ t("同步模版状态") }}</el-button>
|
||||
</div>
|
||||
<el-table :data="pagedData" size="large" v-loading="tableData.loading" ref="goodBankListTableRef">
|
||||
<template #empty>
|
||||
<span>{{ !tableData.loading ? t("emptyData") : "" }}</span>
|
||||
</template>
|
||||
<el-table-column prop="template_id" :label="t('模版ID')" min-width="100" />
|
||||
<el-table-column prop="name" :label="t('模版名称')" min-width="130" />
|
||||
<el-table-column prop="template_type_name" :label="t('模版类型')" min-width="130" >
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.template_type_name ? row.template_type_name : '--'}}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sms" :label="t('模版内容')" min-width="200" >
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.sms?.content }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="audit_info" :label="t('审核状态')" min-width="130" >
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.audit_info?.audit_status_name }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="reportEvent(row)">{{ row.audit_info.audit_status!=1 && row.audit_info.audit_status!=2? t("报备") : t("修改") }}</el-button>
|
||||
<el-button type="primary" link @click="editEvent(row)">{{ t("详情") }}</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"/>
|
||||
</div>
|
||||
<el-dialog v-model="visibleDetail" :title="t('模版详情')" width="600px" destroy-on-close >
|
||||
<el-form label-width="100px" ref="formRef" class="page-form">
|
||||
<el-form-item :label="t('短信类型')" prop="template_id">
|
||||
<div>{{ detail.sms_type }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('模版名称')" prop="template_id">
|
||||
<div>{{ detail.name }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('模版类型')" prop="title">
|
||||
<div >{{ detail.title }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('短信内容')" prop="title" v-if="detail.sms">
|
||||
<div >{{ detail.sms?.content }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('审核状态')" prop="title">
|
||||
<div >{{ detail.audit_info.audit_status_name }}</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<!-- <el-button @click="visibleDetail = false">{{ t("cancel") }}</el-button> -->
|
||||
<el-button type="primary" @click="visibleDetail = false">{{ t("confirm") }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="visibleReport" :title="t('模版报备')" width="820px" destroy-on-close >
|
||||
<el-form label-width="100px" ref="formRef" class="page-form" v-loading="reportLoading">
|
||||
<el-form-item :label="t('模版名称')" prop="template_id">
|
||||
<div class="input-width">{{ detail.name }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('模版类型')" prop="title">
|
||||
<el-radio-group v-model="reportData.template_type">
|
||||
<el-radio v-for="[key, value] in Object.entries(template_type_list)" :key="key" :label="Number(key)">{{ value }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div class="ml-[100px] mb-[10px] mt-[-10px] text-[12px] text-[#999] leading-[20px]">
|
||||
<div>验证码:仅支持验证码类型变量</div>
|
||||
<div>行业通知:不支持验证码类型变量</div>
|
||||
<div>营销推广:不支持变量</div>
|
||||
</div>
|
||||
|
||||
<el-form-item :label="t('变量类型')" prop="params_json" v-if="detail.variable && Object.keys(detail.variable).length > 0">
|
||||
<div v-for="(label, key) in detail.variable" :key="key" class="mb-2 flex items-center">
|
||||
<div class="flex flex-1 items-center">
|
||||
<div class="w-32 mr-1 ">{{ label }}</div>
|
||||
<el-select v-model="reportData.params_json[key]" placeholder="请选择类型" class="flex-1" filterable clearable>
|
||||
<el-option v-for="item in template_params_type_list" :key="item.type" :label="item.name + '(' + item.desc + ')'" :value="item.type"/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visibleReport = false">{{ t("cancel") }}</el-button>
|
||||
<el-button type="primary" @click="reportTemplateFn()">{{ t("confirm") }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="visibleAsync" :title="t('同步模版状态')" width="800px" destroy-on-close >
|
||||
<el-alert type="warning" :closable="false" class="!mb-[10px]">
|
||||
<template #default>
|
||||
以下模版名称重复,请先调整模版名称后重新同步模版
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-form label-width="100px" ref="formRef" class="page-form">
|
||||
<div v-if="Object.keys(repeatList).length" class="h-[500px] overflow-y-auto">
|
||||
<el-table :data="repeatListArray" border style="width: 100%;">
|
||||
<el-table-column label="模版名称" prop="name" />
|
||||
<el-table-column label="插件名称">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-for="item in row.platforms" :key="item" class="mr-1 mb-1">{{ item }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visibleAsync = false">{{ t("cancel") }}</el-button>
|
||||
<el-button type="primary" @click="visibleAsync = false">{{ t("confirm") }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref ,computed,reactive,onMounted,watch} from 'vue'
|
||||
import { getTemplateList,getTemplateReportConfig,reportTemplate,templateSync ,getreportTemplateInfo} from '@/app/api/notice'
|
||||
import { t } from "@/lang";
|
||||
|
||||
const props = defineProps({
|
||||
username: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
signature:{
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
// 表单内容
|
||||
const tableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
data: [], // 当前页展示的数据(通过 computed 生成)
|
||||
allData: [], // 原始完整数据
|
||||
searchParam: {
|
||||
template_id: "",
|
||||
name: '',
|
||||
status: ''
|
||||
}
|
||||
});
|
||||
const filterData = () => {
|
||||
const { template_id, name, status } = tableData.searchParam;
|
||||
return tableData.allData.filter(item => {
|
||||
const matchId = !template_id || String(item.template_id || '').includes(template_id);
|
||||
const matchName = !name || String(item.name || '').includes(name);
|
||||
const matchStatus = !status || item.audit_info.audit_status == status;
|
||||
return matchId && matchName && matchStatus;
|
||||
});
|
||||
};
|
||||
|
||||
watch(() => [tableData.limit, tableData.page], () => {
|
||||
pagedDataChange()
|
||||
})
|
||||
|
||||
|
||||
// 获取列表
|
||||
const loadSmsTemplateList = () => {
|
||||
tableData.loading = true;
|
||||
getTemplateList({ sms_type: 'niuyun', username: props.username }).then((res) => {
|
||||
tableData.allData = res.data;
|
||||
tableData.page = 1; // 搜索后回到第一页
|
||||
pagedDataChange()
|
||||
}).catch(() => {
|
||||
tableData.loading = false;
|
||||
});
|
||||
}
|
||||
const searchFormRef = ref(null)
|
||||
const resetForm = (formRef) => {
|
||||
if (!formRef) return;
|
||||
tableData.searchParam = {
|
||||
template_id: "",
|
||||
name: '',
|
||||
status: ''
|
||||
}
|
||||
loadSmsTemplateList()
|
||||
};
|
||||
const pagedData = ref([])
|
||||
const pagedDataChange = () => {
|
||||
const filtered = filterData(); // 使用筛选后的数据
|
||||
tableData.total = filtered.length;
|
||||
const start = (tableData.page - 1) * tableData.limit;
|
||||
const end = start + tableData.limit;
|
||||
pagedData.value = filtered.slice(start, end);
|
||||
tableData.loading = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.username) {
|
||||
loadSmsTemplateList()
|
||||
}
|
||||
})
|
||||
|
||||
const visibleAsync = ref(false)
|
||||
const repeatList = ref({})
|
||||
const syncEvent = () => {
|
||||
templateSync('niuyun', props.username).then((res) => {
|
||||
repeatList.value = res.data.repeat_list;
|
||||
if (repeatList.value && Object.keys(repeatList.value).length > 0) {
|
||||
visibleAsync.value = true
|
||||
} else {
|
||||
loadSmsTemplateList()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const repeatListArray = computed(() => {
|
||||
return Object.entries(repeatList.value).map(([name, platforms]) => ({
|
||||
name,
|
||||
platforms
|
||||
}))
|
||||
})
|
||||
|
||||
// 详情
|
||||
const detail = ref(null)
|
||||
const visibleDetail = ref(false)
|
||||
const editEvent = (row:any) => {
|
||||
visibleDetail.value = true
|
||||
detail.value = row
|
||||
}
|
||||
|
||||
// 报备
|
||||
const template_params_type_list = ref({})
|
||||
const template_type_list = ref({})
|
||||
const template_status_list = ref({})
|
||||
const visibleReport = ref(false)
|
||||
const reportData = ref({
|
||||
template_type: 1,
|
||||
template_key: '',
|
||||
params_json:{}
|
||||
})
|
||||
const getTemplateReportConfigFn = () => {
|
||||
getTemplateReportConfig().then((res) => {
|
||||
template_params_type_list.value = res.data.template_params_type_list
|
||||
template_type_list.value = res.data.template_type_list
|
||||
template_status_list.value = res.data.template_status_list
|
||||
})
|
||||
}
|
||||
|
||||
getTemplateReportConfigFn()
|
||||
const reportLoading = ref(false)
|
||||
const reportEvent = (row:any) => {
|
||||
reportLoading.value = true
|
||||
let signature = props.signature
|
||||
if(!signature){
|
||||
ElMessage.error('请先配置签名')
|
||||
return
|
||||
}else{
|
||||
if(row.template_id){
|
||||
visibleReport.value = true;
|
||||
detail.value = row;
|
||||
getreportTemplateInfo('niuyun', props.username, { template_key: row.key }).then((res) => {
|
||||
const paramJson = res.data?.param_json ?? {};
|
||||
reportData.value.template_key = res.data.template_key;
|
||||
reportData.value.template_type = Number(res.data.template_type);
|
||||
reportData.value.params_json = {};
|
||||
if (detail.value.variable) {
|
||||
for (const key in detail.value.variable) {
|
||||
reportData.value.params_json[key] = paramJson[key] ?? '';
|
||||
}
|
||||
}
|
||||
reportLoading.value = false;
|
||||
});
|
||||
}else{
|
||||
visibleReport.value = true
|
||||
reportLoading.value = false
|
||||
detail.value = row
|
||||
reportData.value.template_type = 1
|
||||
reportData.value.template_key = detail.value.key
|
||||
reportData.value.params_json = {}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
const reportTemplateFn = () => {
|
||||
if (!detail.value.sms) {
|
||||
ElMessage.error('请先配置模版内容')
|
||||
return
|
||||
}
|
||||
// 校验每个变量是否已选择类型
|
||||
const missingParams = Object.entries(detail.value.variable).some(
|
||||
([key]) => !reportData.value.params_json[key]
|
||||
);
|
||||
if (missingParams) {
|
||||
ElMessage.error('请为每个变量选择类型');
|
||||
return;
|
||||
}
|
||||
if (detail.value.template_id) {
|
||||
reportData.value.template_id = Number(detail.value.template_id)
|
||||
}
|
||||
reportTemplate(detail.value.sms_type, props.username, reportData.value).then((res) => {
|
||||
visibleReport.value = false
|
||||
loadSmsTemplateList()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@ -3,7 +3,8 @@
|
||||
|
||||
<el-form class="page-form" :model="formData" label-width="150px" ref="formRef" :rules="formRules" v-loading="loading">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-sm">{{ t('copyrightEdit') }}</h3>
|
||||
<h3 class="text-[16px] text-[#1D1F3A] font-bold mb-4">{{ pageName }}</h3>
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('copyrightEdit') }}</h3>
|
||||
|
||||
<el-form-item :label="t('logo')">
|
||||
<upload-image v-model="formData.logo" />
|
||||
@ -17,23 +18,21 @@
|
||||
<el-form-item :label="t('copyrightDesc')" >
|
||||
<el-input v-model.trim="formData.copyright_desc" type="textarea" rows="4" clearable :placeholder="t('copyrightDescPlaceholder')" class="input-width" maxlength="150" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card mt-[15px] !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-sm">{{ t('putOnRecordEdit') }}</h3>
|
||||
|
||||
<el-form-item :label="t('icp')" prop="icp">
|
||||
<el-input v-model.trim="formData.icp" :placeholder="t('icpPlaceholder')" class="input-width" clearable maxlength="20"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('govRecord')" >
|
||||
<el-input v-model.trim="formData.gov_record" :placeholder="t('govRecordPlaceholder')" class="input-width" clearable maxlength="50"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('govUrl')" >
|
||||
<el-input v-model.trim="formData.gov_url" :placeholder="t('govUrlPlaceholder')" class="input-width" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('marketSupervisionUrl')" >
|
||||
<el-input v-model.trim="formData.market_supervision_url" rows="4" clearable :placeholder="t('marketSupervisionUrlPlaceholder')" class="input-width" />
|
||||
</el-form-item>
|
||||
<div class="mt-[20px]">
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('putOnRecordEdit') }}</h3>
|
||||
<el-form-item :label="t('icp')" prop="icp">
|
||||
<el-input v-model.trim="formData.icp" :placeholder="t('icpPlaceholder')" class="input-width" clearable maxlength="20"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('govRecord')" >
|
||||
<el-input v-model.trim="formData.gov_record" :placeholder="t('govRecordPlaceholder')" class="input-width" clearable maxlength="50"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('govUrl')" >
|
||||
<el-input v-model.trim="formData.gov_url" :placeholder="t('govUrlPlaceholder')" class="input-width" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('marketSupervisionUrl')" >
|
||||
<el-input v-model.trim="formData.market_supervision_url" rows="4" clearable :placeholder="t('marketSupervisionUrlPlaceholder')" class="input-width" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
|
||||
|
||||
@ -61,11 +61,11 @@
|
||||
<img :src="img(addonLayout.cover)" class="w-full h-full" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="panel-title !text-sm">{{ t('themeColor') }}</h3>
|
||||
<!-- <h3 class="panel-title !text-sm">{{ t('themeColor') }}</h3>
|
||||
<div>
|
||||
<el-color-picker v-model="themeColor[currAddon]" size="large" />
|
||||
<div class="form-tip text-[#999] mt-2">设置的色调会在前端站点列表体现[home/index],用于区分不同的应用</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<template #footer>
|
||||
@ -150,7 +150,6 @@ const confirm = () => {
|
||||
showDialog.value = false
|
||||
}
|
||||
|
||||
|
||||
// 跳转自定义布局
|
||||
const toDiyLayout = () => {
|
||||
let url = 'https://doc.niucloud.com/saas.html?keywords=/ru-he-kai-fa-zi-ding-yi-bu-ju-hou-tai-bu-ju';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
|
||||
<el-form class="page-form" :model="formData" :rules="formRules" label-width="150px" ref="ruleFormRef" v-loading="loading">
|
||||
<el-form class="page-form" :model="formData" :rules="formRules" label-width="150px" ref="ruleFormRef" v-loading="loading" @submit.prevent>
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-sm">{{ t('commonSetting') }}</h3>
|
||||
|
||||
|
||||
131
admin/src/app/views/setting/oplatform.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none setting-card" shadow="never">
|
||||
|
||||
<div class="flex justify-between items-center p-[20px]">
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
</div>
|
||||
<el-tabs v-model="activeName" class="">
|
||||
<el-tab-pane :label="t('小程序同步')" name="xcx">
|
||||
<weappVersion></weappVersion>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('开放平台配置')" name="setting">
|
||||
<setting></setting>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('授权记录')" name="auth_record">
|
||||
<el-card class="box-card !border-none p-[20px]" shadow="never">
|
||||
<el-table :data="authRecordTableData.data" size="large" v-loading="authRecordTableData.loading">
|
||||
<template #empty>
|
||||
<span>{{ !authRecordTableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
|
||||
<el-table-column prop="user_version" :label="t('publicInfo')" :show-overflow-tooltip="true" min-width="150px">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center">
|
||||
<div class="mr-[10px] rounded-full w-[60px] h-[60px] flex items-center justify-center">
|
||||
<el-image v-if="row.value.authorizer_info.head_img" class="w-[60px] h-[60px]" :src="img(row.value.authorizer_info.head_img)" fit="contain">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<img class="w-[60px] h-[60px]" src="@/app/assets/images/member_head.png" fit="contain"/>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<img class="max-w-[60px] max-h-[60px]" v-else src="@/app/assets/images/member_head.png" alt="">
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span>{{ row.value.authorizer_info.nick_name || '' }}</span>
|
||||
<span class="text-info text-sm">{{ row.value.authorizer_info.principal_name || '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="user_version" :label="t('publicType')">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.config_key == 'wechat_authorization_info'">微信公众号</div>
|
||||
<div v-if="row.config_key == 'weapp_authorization_info'">微信小程序</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="user_version" :label="t('qrcode')">
|
||||
<template #default="{ row }">
|
||||
<el-image class="w-[60px] h-[60px]" :src="img(row.value.authorizer_info.qrcode_url)" fit="contain">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<img class="w-[60px] h-[60px]" src="@/app/assets/images/category_default.png" fit="contain"/>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('APPID')" :show-overflow-tooltip="true">
|
||||
<template #default="{ row }">
|
||||
{{ row.value.authorization_info.authorizer_appid }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('siteName')">
|
||||
<template #default="{ row }">
|
||||
{{ row.site.site_name }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" :label="t('authTime')"></el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="authRecordTableData.page" v-model:page-size="authRecordTableData.limit" layout="total, sizes, prev, pager, next, jumper" :total="authRecordTableData.total" @size-change="loadCommitRecordList()" @current-change="loadCommitRecordList" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { useRoute } from 'vue-router'
|
||||
import weappVersion from '@/app/views/wxoplatform/weapp_version.vue'
|
||||
import setting from '@/app/views/wxoplatform/setting.vue'
|
||||
import { getAuthRecord } from '@/app/api/wxoplatform'
|
||||
import { img } from '@/utils/common'
|
||||
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
const activeName = ref('xcx')
|
||||
|
||||
const authRecordTableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {}
|
||||
})
|
||||
|
||||
const loadAuthRecordList = (page: number = 1) => {
|
||||
authRecordTableData.loading = true
|
||||
authRecordTableData.page = page
|
||||
|
||||
getAuthRecord({
|
||||
page: authRecordTableData.page,
|
||||
limit: authRecordTableData.limit
|
||||
}).then(res => {
|
||||
authRecordTableData.loading = false
|
||||
authRecordTableData.data = res.data.data
|
||||
authRecordTableData.total = res.data.total
|
||||
}).catch(() => {
|
||||
authRecordTableData.loading = false
|
||||
})
|
||||
}
|
||||
loadAuthRecordList()
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.setting-card .el-card__body){
|
||||
padding: 0 !important;
|
||||
}
|
||||
:deep(.el-tabs__header){
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -39,9 +39,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, reactive, ref } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getSmsList } from '@/app/api/notice'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getSmsList ,getAccountIsLogin} from '@/app/api/notice'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
const smsTypeRefs = ref([])
|
||||
@ -73,10 +74,19 @@ const setSmsTypeRefs = (el, index) => {
|
||||
}
|
||||
|
||||
loadSmsList()
|
||||
const isLogin = ref(false)
|
||||
const editEvent = (data: any, index: number) => {
|
||||
smsTypeRefs.value[index].setFormData(data)
|
||||
smsTypeRefs.value[index].showDialog = true
|
||||
if (data.sms_type == 'niuyun') {
|
||||
getAccountIsLogin().then((res: any) => {
|
||||
router.push('/setting/niusms/setting')
|
||||
})
|
||||
|
||||
} else {
|
||||
smsTypeRefs.value[index].setFormData(data)
|
||||
smsTypeRefs.value[index].showDialog = true
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
345
admin/src/app/views/setting/sms_niu.vue
Normal file
@ -0,0 +1,345 @@
|
||||
<template>
|
||||
<!--短信设置-->
|
||||
<div class="main-container" v-loading = "loading">
|
||||
<div v-if="!loading">
|
||||
<div v-if="isLogin && !isRecharge">
|
||||
<el-card class="box-card !border-none mb-[15px]" shadow="never">
|
||||
<el-page-header :content="pageName" :icon="ArrowLeft" @back="back()" />
|
||||
</el-card>
|
||||
<el-form label-width="100px" ref="formRef" class="page-form">
|
||||
<el-card class="box-card !border-none relative" shadow="never">
|
||||
<el-alert type="warning" :closable="false" class="!mb-[30px]">
|
||||
<template #default>
|
||||
<p class="mb-[5px]">牛云短信操作指引</p>
|
||||
<p class="mb-[5px]">* 开启准备:若要开启牛云短信功能,需登录对应账号,并配置可用的短信签名。</p>
|
||||
<p class="mb-[5px]">* 审核说明:短信签名设置与模板消息开启均需经过审核。审核时间为周一至周日的 9:30 - 22:00(法定节假日审核时间顺延)。工作日内,审核预计耗时 2 小时;非工作日,预计耗时 4 小时。</p>
|
||||
<p class="mb-[5px]">* 签名报备要求:签名可使用公司全称或简称(简称需为公司全称的一部分,不能增减或跳字,且签名需具备唯一性)。若使用非公司简称作为签名,需提供 app 在 ICP 备案截图或商标证明 ,且签名必须与资质名称完全一致。</p>
|
||||
<p class="mb-[5px]">* 模版报备要点:报备模板时,请确认模板中变量对应的类型。若模板某变量内容超出长度限制,系统将自动截取,以确保短信正常发出。</p>
|
||||
<p class="mb-[5px]">* 短信发送条件:短信成功发送需满足两个条件,一是签名审核通过且在运营商处实名认证成功;二是模板审核通过。</p>
|
||||
<p class="mb-[5px]">* 其他事项:短信数量不足时,请及时进行充值。如有任何疑问,可联系客服,客服电话:400 - 886 - 7993(服务时间为 9:00 - 18:00 )。</p>
|
||||
</template>
|
||||
</el-alert>
|
||||
<h3 class="panel-title">{{ t('短信信息') }}</h3>
|
||||
<el-row class="row-bg px-[30px] mb-[20px]">
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="t('用户名')">
|
||||
<div class="input-width">
|
||||
<span>{{ formData.username }}</span>
|
||||
<el-button type="primary" link @click="isLogin = false" class="ml-[10px]">{{ t('切换账户') }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="t('公司名称')">
|
||||
<div class="input-width">{{ formData.company }}</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="t('账户状态')">
|
||||
<div class="input-width">{{ formData.status_name }}</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="t('手机号')">
|
||||
<div class="input-width">
|
||||
<span>{{ formData.mobiles ? formData.mobiles : '暂无'}}</span>
|
||||
<el-button type="primary" link @click="changeMobile()" class="ml-[10px]">{{ t('更换手机号') }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="t('签名')">
|
||||
<div class="input-width">
|
||||
<span>{{ formData.signature ? formData.signature : '暂无'}}</span>
|
||||
<el-button type="primary" link @click="openDialog" class="ml-[10px]">{{ t('更换签名') }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<h3 class="panel-title">{{ t('短信权限') }}</h3>
|
||||
<el-row class="row-bg px-[30px] mb-[20px]">
|
||||
<el-col :span="24">
|
||||
<el-form-item :label="t('是否开启')">
|
||||
<el-switch v-model="is_enable" :active-value="1" :inactive-value="0" :before-change="beforeChangeIsEnable" />
|
||||
</el-form-item>
|
||||
<div class="mb-[10px] text-[12px] ml-[30px] text-[#999] leading-[20px]">是否开启牛云短信模版</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<h3 class="panel-title">{{ t('短信条数') }}</h3>
|
||||
<el-row class="row-bg px-[30px] mb-[20px]">
|
||||
<el-col :span="24">
|
||||
<el-form-item :label="t('短信')">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-primary text-[20px] mx-[5px]">{{formData.sms_count}}</span>条
|
||||
<el-button @click="isRecharge =true" class="ml-[30px]">{{ t('短信充值') }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<el-tab-pane label="短信模版" name="template">
|
||||
<smsTemplate :username="username" :signature="formData.signature"></smsTemplate>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="充值记录" name="recharge">
|
||||
<smsRechargeRecord :username="username"></smsRechargeRecord>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="发送记录" name="send">
|
||||
<smsSend :username="username"></smsSend>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</div>
|
||||
<smsNiuLogin v-show="!isLogin" :info="formData" :isLogin="isLoginStatus" @complete="getAccountIsLoginFn"></smsNiuLogin>
|
||||
<smsRecharge v-show="isRecharge" @back="isRecharge = false" @complete="backRecharge" :username="username"></smsRecharge>
|
||||
<smsSignature ref="signatureDialogRef" :username="username" @select="handleSelectTemplate" ></smsSignature>
|
||||
<el-dialog v-model="visibleMobile" :title="t('更换手机号')" width="600px" destroy-on-close >
|
||||
<el-form label-width="120px" :model="changeFormData" ref="changeFormRef" :rules="changeFormRules" class="page-form ml-[20px]">
|
||||
<el-form-item label="手机号" prop="mobile" v-if="formData.mobiles">
|
||||
<el-input placeholder="请输入手机号" disabled class="input-width" maxlength="11" show-word-limit v-model="formData.mobiles" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="新手机号" prop="new_mobile" v-if="formData.mobiles">
|
||||
<el-input placeholder="请输入新手机号" class="input-width" maxlength="11" show-word-limit v-model="changeFormData.new_mobile" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="new_mobile" v-if="!formData.mobiles">
|
||||
<el-input placeholder="请输入手机号" class="input-width" maxlength="11" show-word-limit v-model="changeFormData.new_mobile" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="验证码" prop="captcha_code" v-if="formData.mobiles">
|
||||
<div class="flex items-center">
|
||||
<el-input placeholder="请输入验证码" class="input-width" maxlength="4" show-word-limit v-model="changeFormData.captcha_code" clearable />
|
||||
<img :src="changeFormData.captcha_img" alt="验证码" class="w-[100px] h-[32px] cursor-pointer ml-[10px]" @click="getSmsCaptchaFn" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="动态码" prop="code" v-if="formData.mobiles">
|
||||
<div class="flex items-center">
|
||||
<el-input placeholder="请输入动态码" class="input-width" maxlength="4" show-word-limit v-model="changeFormData.code" clearable />
|
||||
<el-button class="ml-[10px]" @click="getSmsSendFn" :disabled="countdown > 0" :loading="sending">
|
||||
{{ countdown > 0 ? `${countdown}秒后重新获取` : '获取动态码' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visibleMobile = false">{{ t("cancel") }}</el-button>
|
||||
<el-button type="primary" @click="onSave()">{{ t("confirm") }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-card class="box-card !border-none mb-[15px] min-h-[100vh]" shadow="never"></el-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref ,computed, onMounted} from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getAccountIsLogin,getAccountInfo,editAccount ,editSms,getSmsCaptcha,getSmsSend,enableNiusms} from '@/app/api/notice'
|
||||
import { useRoute, useRouter} from 'vue-router'
|
||||
import smsNiuLogin from '@/app/views/setting/components/sms_niu_login.vue'
|
||||
import smsTemplate from '@/app/views/setting/components/sms_template.vue'
|
||||
import smsRechargeRecord from '@/app/views/setting/components/sms_recharge_record.vue'
|
||||
import smsRecharge from '@/app/views/setting/components/sms_recharge.vue'
|
||||
import smsSend from '@/app/views/setting/components/sms_send.vue'
|
||||
import smsSignature from '@/app/views/setting/components/sms_signature.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
const isLogin = ref(true)
|
||||
const formData = reactive({
|
||||
mobiles: '',
|
||||
sms_count: '',
|
||||
username: '',
|
||||
company: '',
|
||||
signature: '',
|
||||
status_name: ''
|
||||
})
|
||||
const isInit = ref(false) // 标记是否初始化完成
|
||||
const activeName = ref('template')
|
||||
const loading = ref(true)
|
||||
const isRecharge = ref(false)
|
||||
const isLoginStatus = ref(true)
|
||||
const is_enable = ref(0)
|
||||
const username = ref('')
|
||||
onMounted(() => {
|
||||
getAccountIsLoginFn()
|
||||
})
|
||||
const backRecharge = () => {
|
||||
isRecharge.value = false
|
||||
getAccountIsLoginFn()
|
||||
}
|
||||
const getAccountIsLoginFn =()=> {
|
||||
loading.value = true
|
||||
getAccountIsLogin().then(res => {
|
||||
isLoginStatus.value = res.data.is_login
|
||||
isLogin.value = res.data.is_login
|
||||
username.value = res.data.username
|
||||
is_enable.value = res.data.is_enable
|
||||
if (res.data.is_login) {
|
||||
getAccountInfo(username.value).then(res => {
|
||||
Object.assign(formData, res.data)
|
||||
loading.value = false
|
||||
isInit.value = true
|
||||
})
|
||||
} else {
|
||||
loading.value = false
|
||||
isInit.value = true
|
||||
}
|
||||
|
||||
}).catch(err => {
|
||||
loading.value = false
|
||||
isInit.value = true
|
||||
})
|
||||
}
|
||||
|
||||
const signatureDialogRef = ref(null)
|
||||
|
||||
const openDialog = () => {
|
||||
signatureDialogRef.value?.open()
|
||||
}
|
||||
|
||||
const beforeChangeIsEnable = (val) => {
|
||||
if (!isInit.value) return false
|
||||
|
||||
let enable = is_enable.value == 1 ? 0 : 1
|
||||
return new Promise((resolve, reject) => {
|
||||
enableNiusms({ is_enable: enable }).then(() => {
|
||||
resolve(true)
|
||||
}).catch(() => {
|
||||
reject(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleSelectTemplate = (val) => {
|
||||
loading.value = true
|
||||
editAccount(username.value,{signature: val.sign}).then(res=>{
|
||||
getAccountIsLoginFn()
|
||||
})
|
||||
}
|
||||
|
||||
// 更换手机号
|
||||
const changeFormRef = ref()
|
||||
const changeFormData = ref({
|
||||
new_mobile: '',
|
||||
captcha_key: '',
|
||||
captcha_code: '',
|
||||
captcha_img: '',
|
||||
code: '',
|
||||
key: ''
|
||||
})
|
||||
|
||||
const changeMobile = async () => {
|
||||
changeFormData.value.new_mobile = ''
|
||||
changeFormData.value.captcha_key = ''
|
||||
changeFormData.value.captcha_code = ''
|
||||
changeFormData.value.captcha_img = ''
|
||||
changeFormData.value.code = ''
|
||||
changeFormData.value.key = ''
|
||||
|
||||
try {
|
||||
await getSmsCaptchaFn()
|
||||
visibleMobile.value = true
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
const getSmsCaptchaFn = () => {
|
||||
return getSmsCaptcha().then(res => {
|
||||
changeFormData.value.captcha_key = res.data.captcha_key
|
||||
changeFormData.value.captcha_img = res.data.img
|
||||
}).catch(err => {
|
||||
})
|
||||
}
|
||||
|
||||
const visibleMobile = ref(false)
|
||||
const changeFormRules = computed(() => {
|
||||
return {
|
||||
new_mobile: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '请输入正确的手机号',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
captcha_code: [
|
||||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: '请输入动态码', trigger: 'blur' },
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
const sending = ref(false); // 发送中状态
|
||||
const countdown = ref(0); // 倒计时秒数
|
||||
|
||||
const getSmsSendFn = () => {
|
||||
if (countdown.value > 0 || sending.value) return; // 正在倒计时或发送中,直接返回
|
||||
changeFormRef.value.validateField(['captcha_code'], (valid) => {
|
||||
if (!valid) return;
|
||||
sending.value = true; // 标记为发送中
|
||||
const params = {
|
||||
mobile: formData.mobiles,
|
||||
captcha_key: changeFormData.value.captcha_key,
|
||||
captcha_code: changeFormData.value.captcha_code
|
||||
};
|
||||
getSmsSend(params).then((res) => {
|
||||
changeFormData.value.key = res.data.key;
|
||||
startCountdown(60); // 启动60秒倒计时
|
||||
}).catch((err) => {
|
||||
getSmsCaptchaFn()
|
||||
sending.value = false;
|
||||
}).finally(() => {
|
||||
sending.value = false; // 无论成功失败都重置发送状态
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 启动倒计时
|
||||
const startCountdown = (seconds) => {
|
||||
countdown.value = seconds;
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
await changeFormRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
let params = {}
|
||||
if (formData.mobiles) {
|
||||
params = {
|
||||
mobile: formData.mobiles,
|
||||
new_mobile: changeFormData.value.new_mobile,
|
||||
key: changeFormData.value.key,
|
||||
code: changeFormData.value.code
|
||||
}
|
||||
} else {
|
||||
params = {
|
||||
new_mobile: changeFormData.value.new_mobile,
|
||||
}
|
||||
}
|
||||
|
||||
editAccount(username.value, params).then(res => {
|
||||
visibleMobile.value = false
|
||||
getAccountIsLoginFn()
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const back = () => {
|
||||
router.push('/setting/sms/setting')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
77
admin/src/app/views/setting/sms_niu_pay_result.vue
Normal file
@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none mb-[15px]" shadow="never">
|
||||
<el-page-header :content="pageName" :icon="ArrowLeft" @back="back()" />
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card !border-none mb-[15px]" shadow="never">
|
||||
<el-result v-if="status === 'payment'" icon="success" title="支付成功">
|
||||
<template #extra>
|
||||
<el-button type="primary" @click="back()">{{ t('back') }}</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
|
||||
<el-result v-else-if="status === 'close'" icon="error" title="订单已关闭">
|
||||
<template #extra>
|
||||
<el-button type="primary" @click="back()">{{ t('back') }}</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
|
||||
<el-result v-else icon="info" title="支付状态查询中" sub-title="请稍候,正在确认支付状态..." />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onBeforeUnmount, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { t } from '@/lang'
|
||||
import { getOrderPayStatus } from '@/app/api/notice'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
const username = route.query.username || ''
|
||||
const back = () => {
|
||||
router.push('/setting/niusms/setting')
|
||||
}
|
||||
|
||||
// 当前支付状态:wait、payment、close
|
||||
const status = ref('wait')
|
||||
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
onMounted(() => {
|
||||
const orderNo = route.query.out_trade_no as string
|
||||
if (!orderNo) return
|
||||
|
||||
// 初始调用一次
|
||||
checkStatus(orderNo)
|
||||
|
||||
// 开始轮询
|
||||
timer = setInterval(() => {
|
||||
checkStatus(orderNo)
|
||||
}, 2000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (timer) clearInterval(timer)
|
||||
})
|
||||
|
||||
const checkStatus = async (orderNo: string) => {
|
||||
try {
|
||||
const res = await getOrderPayStatus(username, {
|
||||
out_trade_no: orderNo
|
||||
})
|
||||
status.value = res.data.order_status
|
||||
|
||||
if (res.data.order_status === 'payment' || res.data.order_status === 'close') {
|
||||
if (timer) clearInterval(timer)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取支付状态失败', err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
|
||||
<el-form class="page-form" :model="formData" label-width="150px" ref="formRef" :rules="formRules" v-loading="loading">
|
||||
<el-form class="page-form loading-box" :model="formData" label-width="150px" ref="formRef" :rules="formRules" v-loading="loading">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-sm">{{ t('websiteInfo') }}</h3>
|
||||
<h3 class="text-[16px] text-[#1D1F3A] font-bold mb-4">{{ pageName }}</h3>
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('websiteInfo') }}</h3>
|
||||
|
||||
<el-form-item :label="t('siteName')" prop="site_name">
|
||||
<el-input v-model.trim="formData.site_name" :placeholder="t('siteNamePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||
@ -38,38 +38,36 @@
|
||||
<el-form-item :label="t('desc')">
|
||||
<el-input v-model.trim="formData.desc" type="textarea" :rows="4" clearable :placeholder="t('descPlaceholder')" class="input-width" maxlength="100" show-word-limit />
|
||||
</el-form-item>
|
||||
|
||||
<div class="mt-[20px]" v-show="appType == 'site'">
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('frontEndInfo') }}</h3>
|
||||
<el-form-item :label="t('frontEndName')">
|
||||
<el-input v-model.trim="formData.front_end_name" :placeholder="t('frontEndNamePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('phone')">
|
||||
<el-input v-model.trim="formData.phone" :placeholder="t('phonePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('logo')">
|
||||
<upload-image v-model="formData.front_end_logo" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('icon')">
|
||||
<upload-image v-model="formData.front_end_icon" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="mt-[20px]" v-if="appType == 'admin'">
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('serviceInformation') }}</h3>
|
||||
<el-form-item :label="t('contactsTel')">
|
||||
<el-input v-model.trim="formData.tel" :placeholder="t('contactsTelPlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('wechatCode')">
|
||||
<upload-image v-model="formData.wechat_code" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('customerServiceCode')">
|
||||
<upload-image v-model="formData.enterprise_wechat" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card mt-[15px] !border-none" shadow="never" v-show="appType == 'site'">
|
||||
<h3 class="panel-title !text-sm">{{ t('frontEndInfo') }}</h3>
|
||||
<el-form-item :label="t('frontEndName')">
|
||||
<el-input v-model.trim="formData.front_end_name" :placeholder="t('frontEndNamePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('phone')">
|
||||
<el-input v-model.trim="formData.phone" :placeholder="t('phonePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('logo')">
|
||||
<upload-image v-model="formData.front_end_logo" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('icon')">
|
||||
<upload-image v-model="formData.front_end_icon" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card mt-[15px] !border-none" shadow="never" v-if="appType == 'admin'">
|
||||
<h3 class="panel-title !text-sm">{{ t('serviceInformation') }}</h3>
|
||||
|
||||
<el-form-item :label="t('contactsTel')">
|
||||
<el-input v-model.trim="formData.tel" :placeholder="t('contactsTelPlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('wechatCode')">
|
||||
<upload-image v-model="formData.wechat_code" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('customerServiceCode')">
|
||||
<upload-image v-model="formData.enterprise_wechat" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
</el-form>
|
||||
|
||||
<div class="fixed-footer-wrap">
|
||||
@ -88,7 +86,10 @@ import { FormInstance, FormRules } from 'element-plus'
|
||||
import { getAppType } from '@/utils/common'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import useSystemStore from '@/stores/modules/system'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
const loading = ref(true)
|
||||
const appType = ref(getAppType())
|
||||
const formData: any = reactive<Record<string, string>>({
|
||||
@ -164,4 +165,8 @@ const save = async (formEl: FormInstance | undefined) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.loading-box .el-loading-spinner){
|
||||
top: 33%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,51 +1,89 @@
|
||||
<template>
|
||||
<!--站点套餐-->
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<el-card class="box-card !border-none setting-card" shadow="never">
|
||||
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<div class="flex justify-between items-center">
|
||||
<el-form :inline="true" :model="siteGroupTableData.searchParam" ref="searchFormRef" @submit.native.prevent class="search-form">
|
||||
<el-form-item prop="keywords">
|
||||
<el-input v-model.trim="siteGroupTableData.searchParam.keywords" :placeholder="t('groupNamePlaceholder')" />
|
||||
</el-form-item>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
<el-button type="primary" class="w-[100px]" @click="addEvent">
|
||||
{{ t('addSiteGroup') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="siteGroupTableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('groupName')" prop="keywords">
|
||||
<el-input v-model.trim="siteGroupTableData.searchParam.keywords" :placeholder="t('groupNamePlaceholder')" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadSiteGroupList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadSiteGroupList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="flex justify-between items-center">
|
||||
<el-button type="primary" class="w-[100px]" @click="addEvent">
|
||||
{{ t('addSiteGroup') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<div>
|
||||
<div class="mt-[20px] table-wrap">
|
||||
<el-table :data="siteGroupTableData.data" size="large" v-loading="siteGroupTableData.loading">
|
||||
|
||||
<template #empty>
|
||||
<span>{{ !siteGroupTableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
|
||||
<el-table-column prop="group_name" :label="t('groupName')" />
|
||||
|
||||
<el-table-column prop="group_name" :label="t('appName')" :show-overflow-tooltip="true">
|
||||
<el-table-column prop="group_name" :label="t('groupName')" min-width="150" />
|
||||
<el-table-column :label="t('appName')" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<el-tag class="mr-1" size="small" v-for="name in row.app_name">{{ name }}</el-tag>
|
||||
<template v-if="row.app_list.length > 0">
|
||||
<el-tooltip effect="dark" placement="top-start" popper-class="tip-class">
|
||||
<template #content>
|
||||
<div class="flex flex-wrap gap-[8px] max-w-[315x]">
|
||||
<div v-for="(name, index) in row.app_list" :key="index" class="flex flex-col items-center">
|
||||
<img :src="name.icon" class="w-[54px] h-[54px] rounded-[4px]" />
|
||||
<span class="mt-1">{{ name.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex items-center overflow-hidden whitespace-nowrap ">
|
||||
<div v-for="(name, index) in row.app_list" :key="index">
|
||||
<img :src="name.icon" v-if="index < 4" class="w-[54px] h-[54px] mr-[8px] shrink-0 rounded-[4px]" />
|
||||
</div>
|
||||
<div class="flex items-end h-[65px]" v-if="row.app_list.length > 4">
|
||||
<span class="ml-1 text-sm">...</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else></template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="group_name" :label="t('addonName')" :show-overflow-tooltip="true">
|
||||
<el-table-column :label="t('addonName')" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<el-tag class="mr-1" size="small" v-for="name in row.addon_name">{{ name }}</el-tag>
|
||||
<template v-if="row.addon_list.length > 0">
|
||||
<el-tooltip effect="dark" placement="top-start" popper-class="tip-class">
|
||||
<template #content>
|
||||
<div class="flex flex-wrap gap-[8px] max-w-[315px]">
|
||||
<div v-for="(name, index) in row.addon_list" :key="index" class="flex flex-col items-center">
|
||||
<img :src="name.icon" class="w-[54px] h-[54px] rounded-[4px]" />
|
||||
<span class="mt-1">{{ name.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex items-center overflow-hidden whitespace-nowrap min-w-[250px]">
|
||||
<div v-for="(name, index) in row.addon_list" :key="index">
|
||||
<img :src="name.icon" v-if="index < 4" class="w-[54px] h-[54px] mr-[8px] shrink-0 rounded-[4px]" />
|
||||
</div>
|
||||
<div class="flex items-end h-[65px]" v-if="row.addon_list.length > 4">
|
||||
<span class="ml-1 text-sm">...</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else></template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="create_time" :label="t('createTime')"></el-table-column>
|
||||
<el-table-column prop="group_roles" :label="t('operation')" align="right" fixed="right" width="130">
|
||||
<el-table-column prop="create_time" :label="t('createTime')" min-width="120"></el-table-column>
|
||||
<el-table-column prop="group_roles" :label="t('operation')" align="right" fixed="right" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
|
||||
<el-button type="primary" link @click="deleteEvent(row.group_id)">{{ t('delete') }}</el-button>
|
||||
@ -69,11 +107,9 @@ import { ref, reactive } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getSiteGroupList, deleteSiteGroup } from '@/app/api/site'
|
||||
import { ElMessageBox, FormInstance } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const pageName = route.meta.title
|
||||
|
||||
const searchFormRef = ref<FormInstance>()
|
||||
|
||||
@ -150,4 +186,74 @@ const deleteEvent = (id: number) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-table tr td) {
|
||||
height: 100px !important;
|
||||
}
|
||||
:deep(.el-input__wrapper){
|
||||
box-shadow: none !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid #D1D5DB !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
:deep(.el-select__wrapper){
|
||||
box-shadow: none !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid #D1D5DB !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
:deep(.el-button){
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
/* 设置 el-select 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-select__placeholder.is-transparent) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 设置 el-select 选中后的颜色 */
|
||||
:deep(.search-form .el-select__placeholder) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-input 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-input__inner::placeholder) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-input 输入内容后的颜色 */
|
||||
:deep(.search-form .el-input__inner) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-date-picker 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-date-editor .el-range-input::placeholder) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 设置 el-date-picker 的输入内容颜色 */
|
||||
:deep(.search-form .el-date-editor .el-range-input) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
}
|
||||
:deep(.setting-card .el-card__body){
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.no-transform-arrow .el-popper__arrow) {
|
||||
transform: none !important;
|
||||
}
|
||||
:deep(.el-popper__arrow){
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
.tip-class[data-popper-placement^="top"] > .el-popper__arrow:before {
|
||||
right: 35px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<!--站点套餐编辑-->
|
||||
<div class="main-container" v-loading="loading">
|
||||
<div class="main-container" >
|
||||
|
||||
<el-card class="card !border-none" shadow="never">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<el-page-header :content="pageName" :icon="ArrowLeft" @back="back()" />
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card mt-[15px] !border-none" shadow="never">
|
||||
<el-form :model="formData" label-width="110px" ref="formRef" :rules="formRules" class="page-form">
|
||||
<el-form :model="formData" label-width="110px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
|
||||
<el-form-item :label="t('groupName')" prop="group_name">
|
||||
<el-input v-model.trim="formData.group_name" :placeholder="t('groupNamePlaceholder')" clearable :disabled="formData.uid" class="input-width" maxlength="20" :show-word-limit="true" />
|
||||
</el-form-item>
|
||||
@ -82,14 +82,13 @@
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="fixed-footer-wrap">
|
||||
<div class="fixed-footer">
|
||||
<el-button type="primary" @click="confirm(formRef)">{{ t('save') }}</el-button>
|
||||
<el-button @click="back()">{{ t('cancel') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<div class="fixed-footer-wrap">
|
||||
<div class="fixed-footer">
|
||||
<el-button type="primary" @click="confirm(formRef)">{{ t('save') }}</el-button>
|
||||
<el-button @click="back()">{{ t('cancel') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -162,7 +161,10 @@ if (route.query.id) {
|
||||
}
|
||||
|
||||
const back = () => {
|
||||
router.push('/admin/site/group')
|
||||
router.push({
|
||||
path: '/admin/site/manage',
|
||||
query: { type: 'group' }
|
||||
})
|
||||
}
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!--站点详情-->
|
||||
<div class="main-container" v-loading="loading">
|
||||
<div class="main-container">
|
||||
|
||||
<el-card class="card !border-none" shadow="never">
|
||||
<el-page-header :icon="ArrowLeft" @back="back()">
|
||||
@ -13,7 +13,7 @@
|
||||
</el-page-header>
|
||||
</el-card>
|
||||
|
||||
<el-form class="page-form mt-[15px]" :model="formData" label-width="90px" ref="formRef">
|
||||
<el-form class="page-form mt-[15px]" :model="formData" label-width="90px" ref="formRef" v-loading="loading">
|
||||
<el-card class="box-card !border-none relative" shadow="never">
|
||||
<el-form-item :label="t('siteName')">
|
||||
<div class="input-width">{{ formData.site_name }}</div>
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
<el-form-item :label="t('siteLogo')">
|
||||
<el-image v-if="formData.logo" class="w-20 h-20" :src="img(formData.logo)" fit="contain"></el-image>
|
||||
<img class="w-20 h-20" v-else src="@/app/assets/images/site_logo.png" alt="" >
|
||||
<img class="w-20 h-20 rounded-[4px]" v-else src="@/app/assets/images/site_default.png" alt="">
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('siteDomain')">
|
||||
@ -188,7 +188,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
|
||||
const back = () => {
|
||||
tabbarStore.removeTab(route.path)
|
||||
router.push({ path: '/admin/site/list' })
|
||||
router.push({ path: '/admin/site/manage' })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,68 +1,69 @@
|
||||
<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-button type="primary" class="w-[100px]" @click="addEvent">
|
||||
{{ t('addSite') }}
|
||||
</el-button>
|
||||
<el-button class="w-[100px]" @click="toSiteLink()">
|
||||
{{ t('toSite') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card class="box-card !border-none setting-card" shadow="never">
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="siteTableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('siteInfo')" prop="keywords">
|
||||
<el-input v-model.trim="siteTableData.searchParam.keywords" :placeholder="t('siteNamePlaceholder')" />
|
||||
</el-form-item>
|
||||
<div class="flex justify-between items-start">
|
||||
<el-form :inline="true" :model="siteTableData.searchParam" ref="searchFormRef" class="search-form w-[80%]">
|
||||
<el-form-item prop="keywords">
|
||||
<el-input v-model.trim="siteTableData.searchParam.keywords" :placeholder="t('siteNamePlaceholder')" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('siteDomain')" prop="site_domain">
|
||||
<el-input v-model.trim="siteTableData.searchParam.site_domain" :placeholder="t('siteDomainPlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="site_domain" v-if="isShow">
|
||||
<el-input v-model.trim="siteTableData.searchParam.site_domain" :placeholder="t('siteDomainPlaceholder')" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('app')" prop="app_id">
|
||||
<el-select v-model="siteTableData.searchParam.app" clearable @change="appChangeFn" :placeholder="t('appIdPlaceholder')" class="input-width">
|
||||
<el-option :label="t('selectPlaceholder')" value="all" />
|
||||
<el-option :label="item['title']" :value="item['key']" v-for="(item, index) in Object.values(addonList)" :key="index"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="app">
|
||||
<el-select v-model="siteTableData.searchParam.app" clearable @change="appChangeFn" :placeholder="t('appIdPlaceholder')" class="input-width">
|
||||
<el-option :label="t('selectPlaceholder')" value="all" />
|
||||
<el-option :label="item['title']" :value="item['key']" v-for="(item, index) in Object.values(addonList)" :key="index"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('groupId')" prop="group_id">
|
||||
<el-select v-model="siteTableData.searchParam.group_id" clearable :placeholder="t('groupIdPlaceholder')" class="input-width">
|
||||
<el-option :label="t('selectPlaceholder')" value="" />
|
||||
<el-option :label="item['group_name']" :value="item['group_id']" v-for="(item, index) in groupList.all" :key="index"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="group_id">
|
||||
<el-select v-model="siteTableData.searchParam.group_id" clearable :placeholder="t('groupIdPlaceholder')" class="input-width">
|
||||
<el-option :label="t('selectPlaceholder')" value="" />
|
||||
<el-option :label="item['group_name']" :value="item['group_id']" v-for="(item, index) in groupList.all" :key="index"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('status')" prop="status">
|
||||
<el-select v-model="siteTableData.searchParam.status" clearable :placeholder="t('groupIdPlaceholder')" class="input-width">
|
||||
<el-option :label="t('selectPlaceholder')" value="" />
|
||||
<el-option :label="item" :value="index" v-for="(item, index) in statusList" :key="index"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="status">
|
||||
<el-select v-model="siteTableData.searchParam.status" clearable :placeholder="t('请选择状态')" class="input-width">
|
||||
<el-option :label="t('selectPlaceholder')" value="" />
|
||||
<el-option :label="item" :value="index" v-for="(item, index) in statusList" :key="index"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('createTime')" prop="create_time">
|
||||
<el-date-picker v-model="siteTableData.searchParam.create_time" type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
|
||||
:end-placeholder="t('endDate')" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="create_time" v-if="isShow">
|
||||
<el-date-picker v-model="siteTableData.searchParam.create_time" type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
|
||||
:end-placeholder="t('endDate')" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('expireTime')" prop="expire_time">
|
||||
<el-date-picker v-model="siteTableData.searchParam.expire_time" type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
|
||||
:end-placeholder="t('endDate')" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="expire_time" v-if="isShow">
|
||||
<el-date-picker v-model="siteTableData.searchParam.expire_time" type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
|
||||
:end-placeholder="t('endDate')" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadSiteList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadSiteList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
<el-button type="primary" link @click="isShow = !isShow">{{ isShow ? t('收起') : t('更多') }} <span class="iconfont iconjiantouxia ml-[4px] !text-[10px] mr-[10px] transition-transform duration-300" :class="{ 'rotate-180': isShow }"></span></el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="flex justify-end items-center w-[20%]">
|
||||
<div>
|
||||
<el-button type="primary" class="w-[100px]" @click="addEvent">
|
||||
{{ t('addSite') }}
|
||||
</el-button>
|
||||
<el-button class="w-[100px]" @click="toSiteLink()">
|
||||
{{ t('toSite') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
|
||||
<div class="mt-[20px]">
|
||||
@ -75,8 +76,8 @@
|
||||
<el-table-column :label="t('siteInfo')" width="300" align="left">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center">
|
||||
<img class="w-[50px] h-[50px] mr-[10px]" v-if="row.logo" :src="img(row.logo)" alt="">
|
||||
<img class="w-[50px] h-[50px] mr-[10px]" v-else src="@/app/assets/images/site_logo.png" alt="">
|
||||
<img class="w-[54px] h-[54px] mr-[10px] rounded-[4px]" v-if="row.logo" :src="img(row.logo)" alt="">
|
||||
<img class="w-[54px] h-[54px] mr-[10px] rounded-[4px]" v-else src="@/app/assets/images/site_default.png" alt="">
|
||||
<div class="flex flex-col">
|
||||
<span>{{ row.site_name || '' }}</span>
|
||||
</div>
|
||||
@ -95,7 +96,7 @@
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="group_name" :label="t('groupId')" width="150" :show-overflow-tooltip="true" />
|
||||
<el-table-column prop="site_domain" :label="t('siteDomain')" width="150" :show-overflow-tooltip="true" />
|
||||
<el-table-column prop="site_domain" :label="t('siteDomain')" width="250" :show-overflow-tooltip="true" />
|
||||
<el-table-column prop="create_time" :label="t('createTime')" width="200" :show-overflow-tooltip="true" />
|
||||
<el-table-column prop="expire_time" :label="t('expireTime')" width="200" :show-overflow-tooltip="true">
|
||||
<template #default="{ row }">
|
||||
@ -115,11 +116,11 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('operation')" min-width="210" align="right" fixed="right">
|
||||
<el-table-column :label="t('operation')" min-width="150" align="right" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="openClose(row.status, row.site_id)" v-if="row.status == 1 || row.status == 3">{{ row.status == 1 ? t('closeTxt') : t('openTxt') }}</el-button>
|
||||
<el-button type="primary" link @click="toSiteLink(row.site_id)">{{ t('toSite') }}</el-button>
|
||||
<el-dropdown>
|
||||
<!-- <el-dropdown>
|
||||
<span class="el-dropdown-link ml-[12px] h-[20px] leading-[24px] text-[var(--el-color-primary)]">
|
||||
更多
|
||||
</span>
|
||||
@ -130,10 +131,14 @@
|
||||
<el-button type="primary" class="mt-[5px] !ml-[0]" link @click="infoEvent(row)">{{ t('info') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-dropdown> -->
|
||||
<div class="manage-option text-right">
|
||||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
|
||||
<el-button type="primary" link @click="deleteEvent(row)">{{ t('delete') }}</el-button>
|
||||
<el-button type="primary" link @click="infoEvent(row)">{{ t('info') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="siteTableData.page" v-model:page-size="siteTableData.limit"
|
||||
@ -153,19 +158,23 @@ import { getToken, img } from '@/utils/common'
|
||||
import { t } from '@/lang'
|
||||
import { getSiteList, getSiteGroupAll, getStatusList, closeSite, openSite, deleteSite } from '@/app/api/site'
|
||||
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useRouter } from 'vue-router'
|
||||
import EditSite from '@/app/views/site/components/edit-site.vue'
|
||||
import { getInstalledAddonList } from '@/app/api/addon'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import {deleteUser} from "@/app/api/user";
|
||||
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
const prop = defineProps({
|
||||
status: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const groupList = ref({
|
||||
all: []
|
||||
})
|
||||
|
||||
const isShow = ref(false)
|
||||
const statusList = ref([])
|
||||
|
||||
const siteTableData = reactive({
|
||||
@ -184,7 +193,8 @@ const siteTableData = reactive({
|
||||
expire_time: []
|
||||
}
|
||||
})
|
||||
siteTableData.searchParam.status = route.query.id || ''
|
||||
// siteTableData.searchParam.status = route.query.id || ''
|
||||
siteTableData.searchParam.status = prop.status || ''
|
||||
const setGroupList = async () => {
|
||||
const obj = await (await getSiteGroupAll({})).data
|
||||
|
||||
@ -303,8 +313,10 @@ const toSiteLink = (siteId:number = 0) => {
|
||||
window.localStorage.setItem('site.comparisonSiteIdStorage', siteId)
|
||||
window.open(`${location.origin}/site/`)
|
||||
} else {
|
||||
window.open(`${location.origin}/home/index`)
|
||||
// window.open(`${location.origin}/home/index`)
|
||||
router.push({ path: '/home/index' })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const openClose = (i, site_id) => {
|
||||
@ -344,4 +356,106 @@ const deleteEvent = (data: any) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-table tr td) {
|
||||
height: 100px !important;
|
||||
}
|
||||
:deep(.el-input__wrapper){
|
||||
box-shadow: none !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid #D1D5DB !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
:deep(.el-select__wrapper){
|
||||
box-shadow: none !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid #D1D5DB !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
:deep(.el-button){
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
/* 设置 el-select 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-select__placeholder.is-transparent) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 设置 el-select 选中后的颜色 */
|
||||
:deep(.search-form .el-select__placeholder) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-input 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-input__inner::placeholder) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-input 输入内容后的颜色 */
|
||||
:deep(.search-form .el-input__inner) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-date-picker 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-date-editor .el-range-input::placeholder) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 设置 el-date-picker 的输入内容颜色 */
|
||||
:deep(.search-form .el-date-editor .el-range-input) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
}
|
||||
.manage-option {
|
||||
line-height: 50px;
|
||||
padding: 0 30px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100vw;
|
||||
bottom: 0;
|
||||
background-color: #f4f6f9;
|
||||
transition: all .3s;
|
||||
box-shadow: 0 4px 4px rgba(220, 220, 220, .3);
|
||||
opacity: 0;
|
||||
z-index: 999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 当行被 hover 时 */
|
||||
:deep(.el-table__row:hover) {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 当行被 hover 时,其下的单元格允许溢出 */
|
||||
:deep(.el-table__row:hover .el-table__cell) {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* 当行被 hover 时,显示 manage-option 并调整其位置 */
|
||||
:deep(.el-table__row:hover .manage-option) {
|
||||
opacity: 1;
|
||||
bottom: -51px;
|
||||
}
|
||||
|
||||
:deep(.el-table__fixed-body-wrapper:hover),
|
||||
:deep(.el-table__fixed-body-wrapper .el-table__row:hover) {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
:deep(.el-table__fixed-body-wrapper .el-table__row:hover .el-table__cell) {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
:deep(.setting-card .el-card__body){
|
||||
padding: 0 !important;
|
||||
}
|
||||
:deep(.el-scrollbar__view){
|
||||
margin-bottom: 50px !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
35
admin/src/app/views/site/manage.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<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-tabs v-model="activeName" class="mt-[20px]" tab-position="top">
|
||||
<el-tab-pane :label="t('站点列表')" name="list">
|
||||
<siteList :status='statusId'></siteList>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('站点套餐')" name="group">
|
||||
<group></group>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { useRoute } from 'vue-router'
|
||||
import siteList from '@/app/views/site/list.vue'
|
||||
import group from '@/app/views/site/group.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
const activeName = route.query.type || 'list'
|
||||
const statusId = route.query.id || ''
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
@ -6,15 +6,13 @@
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
<div>
|
||||
<el-button type="primary" class="w-[100px]" @click="userEditRef.setFormData()">
|
||||
{{ t('addUser') }}
|
||||
</el-button>
|
||||
<el-button type="primary" class="w-[100px]" @click="userEditRef.setFormData()">{{ t('addUser') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="userTableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('userName')" prop="username">
|
||||
<el-form :inline="true" :model="userTableData.searchParam" ref="searchFormRef" class="search-form">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model.trim="userTableData.searchParam.username" :placeholder="t('userNamePlaceholder')" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item :label="t('createTime')" prop="create_time">
|
||||
@ -22,7 +20,7 @@
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
|
||||
:end-placeholder="t('endDate')" />
|
||||
</el-form-item> -->
|
||||
<el-form-item :label="t('loginTime')" prop="last_time">
|
||||
<el-form-item prop="last_time">
|
||||
<el-date-picker v-model="userTableData.searchParam.last_time" type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
|
||||
:end-placeholder="t('endDate')" />
|
||||
@ -41,9 +39,9 @@
|
||||
</template>
|
||||
<el-table-column :label="t('headImg')" width="100" align="left">
|
||||
<template #default="{ row }">
|
||||
<div class="w-[35px] h-[35px] flex items-center justify-center">
|
||||
<img v-if="row.head_img" :src="img(row.head_img)" class="w-[35px] rounded-full" />
|
||||
<img v-else src="@/app/assets/images/member_head.png" class="w-[35px] rounded-full" />
|
||||
<div class="w-[54px] h-[54px] flex items-center justify-center">
|
||||
<img v-if="row.head_img" :src="img(row.head_img)" class="w-[54px] rounded-full" />
|
||||
<img v-else src="@/app/assets/images/member_head.png" class="w-[54px] rounded-full" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@ -73,6 +71,13 @@
|
||||
<el-button type="primary" link @click="detailEvent(row.uid, 'userCreateSiteLimit')" >{{ t('userCreateSiteLimit') }}</el-button>
|
||||
<el-button type="primary" link @click="deleteEvent(row.uid)" >{{ t('delete') }}</el-button>
|
||||
</template>
|
||||
<!-- <div class="manage-option text-right ">
|
||||
<template v-if="!row.is_super_admin">
|
||||
<el-button type="primary" link @click="editEvent(row.uid)" >{{ t('edit') }}</el-button>
|
||||
<el-button type="primary" link @click="detailEvent(row.uid, 'userCreateSiteLimit')" >{{ t('userCreateSiteLimit') }}</el-button>
|
||||
<el-button type="primary" link @click="deleteEvent(row.uid)" >{{ t('delete') }}</el-button>
|
||||
</template>
|
||||
</div> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -184,4 +189,99 @@ const deleteEvent = (uid: number) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-table tr td) {
|
||||
height: 100px !important;
|
||||
}
|
||||
:deep(.el-input__wrapper){
|
||||
box-shadow: none !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid #D1D5DB !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
:deep(.el-select__wrapper){
|
||||
box-shadow: none !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid #D1D5DB !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
:deep(.el-button){
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
/* 设置 el-select 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-select__placeholder.is-transparent) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 设置 el-select 选中后的颜色 */
|
||||
:deep(.search-form .el-select__placeholder) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-input 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-input__inner::placeholder) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-input 输入内容后的颜色 */
|
||||
:deep(.search-form .el-input__inner) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
/* 设置 el-date-picker 的 placeholder 颜色 */
|
||||
:deep(.search-form .el-date-editor .el-range-input::placeholder) {
|
||||
color: #C4C7DA;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 设置 el-date-picker 的输入内容颜色 */
|
||||
:deep(.search-form .el-date-editor .el-range-input) {
|
||||
color: #4F516D;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.manage-option {
|
||||
line-height: 50px;
|
||||
padding: 0 30px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100vw;
|
||||
bottom: 0;
|
||||
background-color: #f4f6f9;
|
||||
transition: all .3s;
|
||||
box-shadow: 0 4px 4px rgba(220, 220, 220, .3);
|
||||
opacity: 0;
|
||||
z-index: 999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 当行被 hover 时 */
|
||||
:deep(.el-table__row:hover) {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 当行被 hover 时,其下的单元格允许溢出 */
|
||||
:deep(.el-table__row:hover .el-table__cell) {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* 当行被 hover 时,显示 manage-option 并调整其位置 */
|
||||
:deep(.el-table__row:hover .manage-option) {
|
||||
opacity: 1;
|
||||
bottom: -51px;
|
||||
}
|
||||
|
||||
:deep(.el-table__fixed-body-wrapper:hover),
|
||||
:deep(.el-table__fixed-body-wrapper .el-table__row:hover) {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
:deep(.el-table__fixed-body-wrapper .el-table__row:hover .el-table__cell) {
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -99,7 +99,7 @@ const router = useRouter()
|
||||
const pageName = route.meta.title
|
||||
|
||||
const back = () => {
|
||||
router.push('/tools/addon')
|
||||
router.push('/tools/addon/list')
|
||||
}
|
||||
const form = ref({
|
||||
title: '',
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeName" class="mt-[20px]">
|
||||
<!-- <el-tabs v-model="activeName" class="mt-[20px]">
|
||||
<el-tab-pane :label="t('developmentProcess')" name="developmentProcess">
|
||||
<el-steps :active="5" direction="vertical">
|
||||
<el-step>
|
||||
@ -136,8 +136,77 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-tabs> -->
|
||||
<el-steps :active="5" direction="vertical" class="mt-[20px]">
|
||||
<el-step>
|
||||
<template #title>
|
||||
<p class="text-[14px] font-[700]">
|
||||
{{ t("step1") }}
|
||||
</p>
|
||||
</template>
|
||||
<template #description>
|
||||
<span class="text-[#999]">{{ t("describe1") }}</span>
|
||||
<div class="mt-[20px] mb-[40px] h-[32px]">
|
||||
<el-button type="primary" @click="router.push({ path: '/tools/addon_edit' })">{{t("btn1") }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-step>
|
||||
|
||||
<el-step>
|
||||
<template #title>
|
||||
<p class="text-[14px] font-[700]">
|
||||
{{ t("step2") }}
|
||||
</p>
|
||||
</template>
|
||||
<template #description>
|
||||
<span class="text-[#999]">{{ t("describe2") }}</span>
|
||||
<div class="mt-[20px] mb-[40px] h-[32px]">
|
||||
<el-button type="primary" plain @click="router.push({ path: '/tools/addon/list' })">{{t("btn2") }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-step>
|
||||
|
||||
<el-step>
|
||||
<template #title>
|
||||
<p class="text-[14px] font-[700]">
|
||||
{{ t("step3") }}
|
||||
</p>
|
||||
</template>
|
||||
<template #description>
|
||||
<span class="text-[#999]">{{ t("describe3") }}</span>
|
||||
<div class="mt-[20px] mb-[40px] h-[32px]">
|
||||
<el-button type="primary" plain @click="linkEvent('https://www.kancloud.cn/niucloud/niucloud-admin-develop/3225439')">{{t("btn3") }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-step>
|
||||
|
||||
<el-step>
|
||||
<template #title>
|
||||
<p class="text-[14px] font-[700]">
|
||||
{{ t("step4") }}
|
||||
</p>
|
||||
</template>
|
||||
<template #description>
|
||||
<span class="text-[#999]">{{ t("describe4") }}</span>
|
||||
<div class="mt-[20px] mb-[40px] h-[32px]">
|
||||
<el-button type="primary" plain @click="router.push({ path: '/tools/addon/list' })">{{t("btn4") }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-step>
|
||||
<el-step>
|
||||
<template #title>
|
||||
<p class="text-[14px] font-[700]">
|
||||
{{ t("step5") }}
|
||||
</p>
|
||||
</template>
|
||||
<template #description>
|
||||
<span class="text-[#999]">{{ t("describe5") }}</span>
|
||||
<div class="mt-[20px] mb-[40px] h-[32px]">
|
||||
<el-button type="primary" plain @click="linkEvent('https://www.niucloud.com/app')">{{t("btn5") }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-step>
|
||||
</el-steps>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
162
admin/src/app/views/tools/addon/list.vue
Normal file
@ -0,0 +1,162 @@
|
||||
<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="addonTableData.searchParam" ref="searchFormRef" class="search-form">
|
||||
<el-form-item :label="t('title')" prop="search">
|
||||
<el-input v-model.trim="addonTableData.searchParam.search" :placeholder="t('titlePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadAddonList">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="mt-[20px]">
|
||||
<el-table :data="addonTableData.data" size="large" v-loading="addonTableData.loading">
|
||||
<template #empty>
|
||||
<span>{{ t('emptyData') }}</span>
|
||||
</template>
|
||||
|
||||
<el-table-column :label="t('title')" align="left" min-width="320">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center justify-between">
|
||||
<el-image v-if="row.icon" class="w-[45px] h-[45px]" :src="row.icon.indexOf('data:image') != -1 ? row.icon : img(row.icon)" fit="contain">
|
||||
<template #error>
|
||||
<img class="w-[45px] h-[45px]" src="@/app/assets/images/category_default.png" alt="">
|
||||
</template>
|
||||
</el-image>
|
||||
<img v-else class="w-[45px] h-[45px]" src="@/app/assets/images/category_default.png" alt="">
|
||||
<div class="flex-1 w-[236px] pl-[15px] truncate">
|
||||
{{ row.title }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="key" :label="t('key')" min-width="200" />
|
||||
<el-table-column prop="type_name" :label="t('type')" align="center" min-width="200" />
|
||||
<el-table-column prop="author" :label="t('author')" min-width="200" />
|
||||
<el-table-column prop="version" :label="t('version')" align="center" min-width="200" />
|
||||
<el-table-column :label="t('status')" align="center" min-width="200">
|
||||
<template #default="{ row }">
|
||||
{{ Object.keys(row.install_info).length ? '已安装' : '未安装' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" width="180" :show-overflow-tooltip="true">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="editEvent(row.key)">{{ t('edit') }}</el-button>
|
||||
<el-button v-if="Object.keys(row.install_info).length" type="primary" link @click="addonDevelopBuildFn(row)">{{ t('step4') }}</el-button>
|
||||
<el-button v-if="!Object.keys(row.install_info).length" type="primary" link @click="deleteEvent(row.key)">{{ t('delete') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- <div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="addonTableData.page" v-model:page-size="addonTableData.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="addonTableData.total"
|
||||
@size-change="loadAddonList()" @current-change="loadAddonList" />
|
||||
</div> -->
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { getToken, img } from '@/utils/common'
|
||||
import { t } from '@/lang'
|
||||
import { getAddonDevelop, deleteAddonDevelop, addonDevelopBuild, addonDevelopDownload } from '@/app/api/tools'
|
||||
import { ElMessageBox, FormInstance } from 'element-plus'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
|
||||
const addonTableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {
|
||||
search: ''
|
||||
}
|
||||
})
|
||||
|
||||
const searchFormRef = ref<FormInstance>()
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
|
||||
formEl.resetFields()
|
||||
loadAddonList()
|
||||
}
|
||||
/**
|
||||
* 获取站点列表
|
||||
*/
|
||||
const loadAddonList = (page: number = 1) => {
|
||||
addonTableData.loading = true
|
||||
addonTableData.page = page
|
||||
getAddonDevelop({
|
||||
page: addonTableData.page,
|
||||
limit: addonTableData.limit,
|
||||
...addonTableData.searchParam
|
||||
}).then(res => {
|
||||
addonTableData.loading = false
|
||||
addonTableData.data = res.data
|
||||
addonTableData.total = res.data.total
|
||||
}).catch(() => {
|
||||
addonTableData.loading = false
|
||||
})
|
||||
}
|
||||
loadAddonList()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const editEvent = (key: any) => {
|
||||
router.push({ path: '/tools/addon_edit', query: { key } })
|
||||
}
|
||||
|
||||
// 打包插件
|
||||
const addonDevelopBuildFn = (data: any) => {
|
||||
addonTableData.loading = true
|
||||
addonDevelopBuild(data.key).then(res => {
|
||||
addonTableData.loading = false
|
||||
addonDevelopDownload(data.key).then(res => {
|
||||
ElMessageBox.alert(`插件打包成功,插件包所在位置:网站根目录${res.data}下请手动进行下载`, t('warning'))
|
||||
}).catch()
|
||||
}).catch(() => {
|
||||
addonTableData.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
const deleteEvent = (key: any) => {
|
||||
ElMessageBox.confirm(t('codeDeleteTips'), t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
addonTableData.loading = true
|
||||
deleteAddonDevelop(key).then(() => {
|
||||
loadAddonList()
|
||||
}).catch(() => {
|
||||
addonTableData.loading = false
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
@ -6,7 +6,7 @@
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="type">
|
||||
<el-tabs v-model="type" tab-position="top">
|
||||
<el-tab-pane :label="t(tab)" v-for="(tab, index) in attachmentType" :name="tab" :key="index">
|
||||
<attachment scene="attachment" :type="tab" />
|
||||
</el-tab-pane>
|
||||
@ -44,7 +44,7 @@ const type = ref(attachmentType[0])
|
||||
|
||||
.el-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
|
||||
|
||||
@ -23,9 +23,7 @@
|
||||
</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">
|
||||
|
||||
@ -35,7 +33,19 @@
|
||||
|
||||
<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="content" :label="t('操作')" width="120" align="left">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.content=='手动备份'" class="multi-hidden">
|
||||
<el-tag type="primary">{{ row.content }}</el-tag>
|
||||
</span>
|
||||
<span v-else-if="row.content=='自动备份'" class="multi-hidden">
|
||||
<el-tag type="success">{{ row.content }}</el-tag>
|
||||
</span>
|
||||
<span v-else class="multi-hidden">
|
||||
<el-tag type="warning">{{ row.content }}</el-tag>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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" />
|
||||
@ -53,7 +63,9 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="mt-[10px] flex items-center">
|
||||
<el-button @click="batchDelete" size="small">{{ t('batchDelete') }}</el-button>
|
||||
</div>
|
||||
<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"
|
||||
@ -64,7 +76,7 @@
|
||||
|
||||
<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">
|
||||
<el-steps :active="numberOfSteps" align-center class="number-of-steps" finish-status="success" process-status="process" v-if="active !='error' && active !='complete'">
|
||||
<template v-if="iSBackupRecovery == 1">
|
||||
<!-- 手动备份 -->
|
||||
<el-step :title="t('testDirectoryPermissions')" />
|
||||
@ -149,14 +161,37 @@
|
||||
</div>
|
||||
|
||||
<!-- 执行任务 -->
|
||||
<div class="h-[370px] mt-[30px]" v-if="active == 'execute'">
|
||||
<div class="h-[370px] mt-[30px]" v-show="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 class="mt-[50px]" v-show="active == 'complete'">
|
||||
<el-result icon="success" :title="iSBackupRecovery == 1 ? t('backupCompleteTips') : t('restoreCompleteTips')" :sub-title="iSBackupRecovery == 1 ?`备份耗时${formattedDuration},成功备份完成。` : `恢复耗时${formattedDuration},成功恢复完成。`">
|
||||
<template #icon>
|
||||
<img src="@/app/assets/images/success_icon.png" alt="">
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-button @click="handleReturn" class="!w-[90px]">返回</el-button>
|
||||
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
|
||||
</div>
|
||||
<!-- 失败 -->
|
||||
<div class="mt-[50px]" v-show="active == 'error'">
|
||||
<el-result icon="success" :title="iSBackupRecovery == 1 ? t('备份失败') : t('恢复失败')" :sub-title="backupErrorMessage" >
|
||||
<template #icon>
|
||||
<img src="@/app/assets/images/error_icon.png" alt="">
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-button @click="handleReturn" class="!w-[90px]">错误信息</el-button>
|
||||
<el-button @click="showDialog=false" type="primary" class="!w-[90px]">完成</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
@ -188,7 +223,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, nextTick, watch, h } from 'vue'
|
||||
import { ref, reactive, nextTick, watch, h,computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
@ -226,6 +261,8 @@ 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
|
||||
@ -272,7 +309,8 @@ const manualBackupEvent = () => {
|
||||
).then(() => {
|
||||
// if (repeat.value) return
|
||||
// repeat.value = true
|
||||
|
||||
backupContents = []
|
||||
numberOfSteps.value = 0
|
||||
iSBackupRecovery.value = 1
|
||||
showDialog.value = true
|
||||
uploading.value = true
|
||||
@ -281,6 +319,17 @@ const manualBackupEvent = () => {
|
||||
checkPermissionFn()
|
||||
})
|
||||
}
|
||||
// 计时器相关
|
||||
const buildStartTime = ref<number | null>(null)
|
||||
const buildDuration = ref<number>(0)
|
||||
let buildTimer: number | null = null
|
||||
const formattedDuration = computed(() => {
|
||||
const seconds = buildDuration.value
|
||||
const mins = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
return mins > 0 ? `${mins}分${secs}秒` : `${secs}秒`
|
||||
})
|
||||
const backupErrorMessage = ref('')
|
||||
|
||||
// 进入执行备份
|
||||
const manualBackupFn = (task: any = '') => {
|
||||
@ -296,13 +345,33 @@ const manualBackupFn = (task: any = '') => {
|
||||
if (task == '') {
|
||||
terminalRef.value.execute('clear')
|
||||
terminalRef.value.execute('开始执行')
|
||||
const storedTime = localStorage.getItem('manual_back_start_time')
|
||||
if (storedTime) {
|
||||
buildStartTime.value = Number(storedTime)
|
||||
} else {
|
||||
const now = Date.now()
|
||||
buildStartTime.value = now
|
||||
localStorage.setItem('manual_back_start_time', String(now))
|
||||
}
|
||||
buildDuration.value = Math.floor((Date.now() - buildStartTime.value) / 1000)
|
||||
buildTimer && clearInterval(buildTimer)
|
||||
buildTimer = setInterval(() => {
|
||||
if (buildStartTime.value) {
|
||||
buildDuration.value = Math.floor((Date.now() - buildStartTime.value) / 1000)
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
if (data.content && !backupContents.includes(data.content)) {
|
||||
backupContents.push(data.content)
|
||||
terminalRef.value.pushMessage({ content: `${ data.content }` })
|
||||
}
|
||||
terminalRef.value.pushMessage({ content: `${ data.content }` })
|
||||
if (data.task == 'end') {
|
||||
numberOfSteps.value = 2
|
||||
setTimeout(() => {
|
||||
numberOfSteps.value = 3
|
||||
active.value = 'complete'
|
||||
buildTimer && clearInterval(buildTimer) // 清除计时器
|
||||
localStorage.removeItem('manual_back_start_time')
|
||||
loadList()
|
||||
repeat.value = false
|
||||
}, 1500)
|
||||
@ -312,6 +381,18 @@ const manualBackupFn = (task: any = '') => {
|
||||
loadList()
|
||||
repeat.value = false
|
||||
}, 2000)
|
||||
backupErrorMessage.value = data.content
|
||||
active.value = 'error'
|
||||
// 停止计时器
|
||||
if (buildTimer) {
|
||||
clearInterval(buildTimer)
|
||||
buildTimer = null
|
||||
}
|
||||
// 保证 duration 也被最后更新一次
|
||||
if (buildStartTime.value) {
|
||||
buildDuration.value = Math.floor((Date.now() - buildStartTime.value) / 1000)
|
||||
}
|
||||
localStorage.removeItem('manual_back_start_time')
|
||||
} else {
|
||||
// 延迟2秒请求,等待恢复数据加载完成
|
||||
setTimeout(() => {
|
||||
@ -413,6 +494,8 @@ const restoreEvent = (data: any) => {
|
||||
).then(() => {
|
||||
// if (repeat.value) return
|
||||
// repeat.value = true
|
||||
numberOfSteps.value = 0
|
||||
restoreContents = []
|
||||
iSBackupRecovery.value = 2
|
||||
currentId.value = data.id
|
||||
active.value = 'check'
|
||||
@ -459,13 +542,33 @@ const restoreUpgradeBackupFn = (id: any, task: any = '') => {
|
||||
uploading.value = false
|
||||
terminalRef.value.execute('clear')
|
||||
terminalRef.value.execute('开始执行')
|
||||
const storedTime = localStorage.getItem('manual_back_start_time')
|
||||
if (storedTime) {
|
||||
buildStartTime.value = Number(storedTime)
|
||||
} else {
|
||||
const now = Date.now()
|
||||
buildStartTime.value = now
|
||||
localStorage.setItem('manual_back_start_time', String(now))
|
||||
}
|
||||
buildDuration.value = Math.floor((Date.now() - buildStartTime.value) / 1000)
|
||||
buildTimer && clearInterval(buildTimer)
|
||||
buildTimer = setInterval(() => {
|
||||
if (buildStartTime.value) {
|
||||
buildDuration.value = Math.floor((Date.now() - buildStartTime.value) / 1000)
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
if (data.content && !restoreContents.includes(data.content)) {
|
||||
restoreContents.push(data.content)
|
||||
terminalRef.value.pushMessage({ content: `${ data.content }` })
|
||||
}
|
||||
terminalRef.value.pushMessage({ content: `${ data.content }` })
|
||||
if (data.task == 'end') {
|
||||
numberOfSteps.value = 2
|
||||
setTimeout(() => {
|
||||
numberOfSteps.value = 3
|
||||
active.value = 'complete'
|
||||
buildTimer && clearInterval(buildTimer) // 清除计时器
|
||||
localStorage.removeItem('manual_back_start_time')
|
||||
loadList()
|
||||
repeat.value = false
|
||||
}, 1500)
|
||||
@ -475,9 +578,22 @@ const restoreUpgradeBackupFn = (id: any, task: any = '') => {
|
||||
loadList()
|
||||
repeat.value = false
|
||||
}, 2000)
|
||||
backupErrorMessage.value = data.content
|
||||
active.value = 'error'
|
||||
// 停止计时器
|
||||
if (buildTimer) {
|
||||
clearInterval(buildTimer)
|
||||
buildTimer = null
|
||||
}
|
||||
// 保证 duration 也被最后更新一次
|
||||
if (buildStartTime.value) {
|
||||
buildDuration.value = Math.floor((Date.now() - buildStartTime.value) / 1000)
|
||||
}
|
||||
localStorage.removeItem('manual_back_start_time')
|
||||
} else {
|
||||
// 延迟2秒请求,等待恢复数据加载完成
|
||||
setTimeout(() => {
|
||||
restoreContents = []
|
||||
restoreUpgradeBackupFn(id, data.task)
|
||||
}, 2000)
|
||||
}
|
||||
@ -564,9 +680,14 @@ const restoreTaskList = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
const isBack = ref(false)
|
||||
const handleReturn = () => {
|
||||
active.value = 'execute'
|
||||
isBack.value = true
|
||||
}
|
||||
|
||||
const dialogClose = (done: () => {}) => {
|
||||
if (active.value == 'execute') {
|
||||
if (active.value == 'execute' && !isBack.value) {
|
||||
ElMessageBox.confirm(
|
||||
t('showDialogCloseTips'),
|
||||
t('warning'),
|
||||
@ -579,6 +700,11 @@ const dialogClose = (done: () => {}) => {
|
||||
terminalRef.value.execute('clear')
|
||||
interrupt.value = true // 执行一半,中途取消,需要恢复初始状态
|
||||
done()
|
||||
localStorage.removeItem('manual_back_start_time')
|
||||
buildTimer && clearInterval(buildTimer)
|
||||
buildTimer = null
|
||||
buildStartTime.value = null
|
||||
buildDuration.value = 0
|
||||
}).catch(() => {
|
||||
})
|
||||
} else {
|
||||
@ -742,8 +868,27 @@ const batchDelete = () => {
|
||||
|
||||
.el-step__icon {
|
||||
background: var(--el-color-primary);
|
||||
color: #fff;
|
||||
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||||
|
||||
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-finish {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
|
||||
.el-step__icon {
|
||||
background: var(--el-color-primary)!important;
|
||||
color: #fff !important;
|
||||
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||||
|
||||
i {
|
||||
color: #fff;
|
||||
@ -764,7 +909,9 @@ const batchDelete = () => {
|
||||
.el-step__icon {
|
||||
padding: 10px;
|
||||
border: 1px solid var(--el-color-primary);
|
||||
box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||||
background: var(--el-color-primary)!important;
|
||||
color: #fff !important;
|
||||
// box-shadow: 0 0 0 4px var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
|
||||
@ -772,6 +919,26 @@ const batchDelete = () => {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-result__icon) {
|
||||
color: unset !important; // 清除默认颜色
|
||||
}
|
||||
:deep(.el-result__title p){
|
||||
font-size: 25px;
|
||||
color: #1D1F3A;
|
||||
font-weight: 500;
|
||||
}
|
||||
:deep(.el-result__subtitle p){
|
||||
font-size: 15px;
|
||||
color: #4F516D;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 5;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
/* 多行超出隐藏 */
|
||||
.multi-hidden {
|
||||
word-break: break-all;
|
||||
|
||||
@ -2,159 +2,153 @@
|
||||
<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 class="bg-[#fff] w-[100%] overflow-hidden">
|
||||
<div class="flex items-center justify-between mb-[20px]">
|
||||
<div class="text-[#1D1F3A] text-[16px] font-bold ml-[4px]">云编译</div>
|
||||
<div class="flex ml-[20px]">
|
||||
<div class="btn-time w-[181px] h-[36px] rounded-[4px] text-[#9699B6] text-[12px] ml-[10px]">
|
||||
<span>云编译执行时间大约</span>
|
||||
<span class="text-[14px] text-[#DA203E] mx-[3px]">5</span>
|
||||
<span>分钟</span>
|
||||
</div>
|
||||
<el-button class="w-[98px] !h-[36px]" type="primary" @click="handleCloudBuild" :loading="cloudBuildRef?.loading">云编译</el-button>
|
||||
</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>
|
||||
<div class="panel-title bg-[#F4F5F7] border-[#E6E6E6] border-solid border-b-[1px] h-[40px] flex items-center p-[10px]">
|
||||
<span class="text-[16px] font-500 text-[#1D1F3A]">云编译</span>
|
||||
<span class="text-[12px] text-[#9699B6] ml-[10px]">云编译不需要本地安装node环境即可进行,针对使用者方便快捷</span>
|
||||
</div>
|
||||
<div class="mt-[20px] flex mb-[14px] items-center">
|
||||
<span class="flex ml-[20px] font-500 text-[16px] items-center text-[#1D1F3A]">
|
||||
<!-- <i class="w-[3px] h-[12px] bg-primary mr-[6px] block"></i> -->
|
||||
温馨提示
|
||||
</span>
|
||||
<span class="text-[14px] text-[#606266] ml-[7px]"> 以下情况可以进行云编译</span>
|
||||
<span class="text-[12px] text-[#9699B6] ml-[10px]"> 以下情况可以进行云编译</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">
|
||||
<!-- <div class="text-[14px] text-[#606266] ml-[13px] mb-[18px]">云编译不需要本地安装node环境即可进行,针对使用者方便快捷</div> -->
|
||||
<div class="ml-[40px] text-[14px] text-[#4F516D] mb-[18px]">1、系统或插件,每次安装或升级完成后,需要云编译</div>
|
||||
<div class="ml-[40px] text-[14px] text-[#4F516D] mb-[18px]">2、开发者编写完前端代码之后,可以使用云编译进行源码编译</div>
|
||||
<div class="ml-[40px] text-[14px] text-[#4F516D] mb-[18px]">3、由于云编译不是针对某个插件进行编译,而是系统整体编译,因此如果同时需要安装多个插件时,往往需要安装到最后一个插件才整体进行云编译</div>
|
||||
<div class="mt-[21px] flex mb-[21px] text-[16px] text-[#1D1F3A] font-500 items-center">
|
||||
<span class="flex ml-[20px] items-center">
|
||||
<i class="w-[3px] h-[12px] bg-primary mr-[6px] block"></i>
|
||||
<!-- <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>
|
||||
<el-timeline-item :hollow="true">
|
||||
<!-- <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]">
|
||||
</template> -->
|
||||
<div class="text-[16px] text-[#1D1F3A]">编译admin代码</div>
|
||||
<div class="p-[10px] bg-[#F9F9FB] mt-[10px] text-[#4F516D] text-[14px] w-[1085px] border-[#F1F1F8] border-solid border-[1px] h-[40px] flex items-center rounded-[4px]">
|
||||
<span>云编译会将admin端的vue代码编译为对应的html文件,同时将生成的代码下载到系统 niucloud 下的</span>
|
||||
<span class="text-[#FF9D31] mx-[3px]">public/admin</span>
|
||||
<span class="text-[#F09000] mx-[3px] font-bold">public/admin</span>
|
||||
<span>目录中。后台的访问路径将变为</span>
|
||||
<span class="text-primary ml-[3px]">https://域名/admin</span>
|
||||
<span class="text-primary ml-[3px] font-500">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]">
|
||||
<el-timeline-item :hollow="true">
|
||||
<div class="text-[16px] text-[#1D1F3A]">编译uniapp代码</div>
|
||||
<div class="p-[10px] bg-[#F9F9FB] mt-[10px] text-[#4F516D] text-[14px] w-[1085px] border-[#F1F1F8] border-solid border-[1px] h-[40px] flex items-center rounded-[4px]">
|
||||
<span>云编泽会将uniapp端的vue代码编译为对应的html文件,同时将生成的代码下载到系统 niucloud下的</span>
|
||||
<span class="text-[#FF9D31] mx-[3px]">public/wap</span>
|
||||
<span class="text-[#F09000] mx-[3px] font-bold">public/wap</span>
|
||||
<span>目录中,这样手机端网页的访问路径将变为</span>
|
||||
<span class="text-primary ml-[3px]"> https://域名/wap</span>
|
||||
<span class="text-primary ml-[3px] font-500"> 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]">
|
||||
<el-timeline-item :hollow="true">
|
||||
<div class="text-[16px] text-[#1D1F3A]">编译web代码</div>
|
||||
<div class="p-[10px] bg-[#F9F9FB] mt-[10px] text-[#4F516D] text-[14px] w-[1085px] border-[#F1F1F8] border-solid border-[1px] h-[40px] flex items-center rounded-[4px]">
|
||||
<span>云编泽会将web端的vue代码编译为对应的html文件,同时将生成的代码下载到系统 niucloud下的</span>
|
||||
<span class="text-[#FF9D31] mx-[3px]">public/web</span>
|
||||
<span class="text-[#F09000] mx-[3px] font-bold">public/web</span>
|
||||
<span>目录中,这样电脑端网页的访问路径将变为</span>
|
||||
<span class="text-primary ml-[3px]"> https://域名/web</span>
|
||||
<span class="text-primary ml-[3px] font-500"> 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 class="mt-[10px]">
|
||||
<div class="panel-title bg-[#F4F5F7] border-[#E6E6E6] border-solid border-b-[1px] h-[40px] flex items-center p-[10px]">
|
||||
<span class="text-[16px] font-500 text-[#1D1F3A]">本地编译</span>
|
||||
</div>
|
||||
<div class="mt-[21px] flex mb-[21px] text-[16px] items-center">
|
||||
<div class="mt-[20px] flex mb-[14px] text-[16px] items-center text-[#1D1F3A]">
|
||||
<span class="flex ml-[20px] items-center">
|
||||
<i class="w-[3px] h-[12px] bg-primary mr-[6px] block"></i>
|
||||
<!-- <i class="w-[3px] h-[12px] bg-primary mr-[6px] block"></i> -->
|
||||
温馨提示
|
||||
</span>
|
||||
</div>
|
||||
<div class="ml-[40px] text-[14px] text-[#606266] mb-[18px]">
|
||||
<div class="ml-[40px] text-[14px] text-[#4F516D] mb-[18px]">
|
||||
<span>1、如果本地安装了Node环境,可以进行本地编译,要求</span>
|
||||
<span class="text-[#D43030] ml-[3px]">Node版本>18</span>
|
||||
<span class="text-[#DA203E] ml-[3px] font-500">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、由于云编译配置的访问路径时固定的,针对客户有独立部署admin,wap,web等个性化端口名称配置需求,需要进行本地编译</div>
|
||||
<div class="mt-[34px] flex mb-[21px] text-[16px] items-center">
|
||||
<div class="ml-[40px] text-[14px] text-[#4F516D] mb-[18px]">2、默认本地编译流程与云编译相同,执行本地编译命令后,会将编译后的代码移动到系统niucloud下的public下的对应端口目录下</div>
|
||||
<div class="ml-[40px] text-[14px] text-[#4F516D] mb-[18px]">3、由于云编译配置的访问路径是固定的,针对客户有独立部署admin,wap,web等个性化端口名称配置需求,需要进行本地编译</div>
|
||||
<div class="mt-[20px] flex mb-[14px] text-[16px] items-center text-[#1D1F3A]">
|
||||
<span class="flex ml-[20px] items-center">
|
||||
<i class="w-[3px] h-[12px] bg-primary mr-[6px] block"></i>
|
||||
<!-- <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 class="ml-[40px] text-[#374151] text-[14px] italic">
|
||||
<span class="text-[16px] italic">#安装依赖:</span>
|
||||
<span class="italic">进入admin端与uniapp端以及web端目录都可执行</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 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 class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]">
|
||||
<span class="text-[14px] text-[#374151]">npm install</span>
|
||||
<span class="iconfont iconfuzhiV6xx1 !text-[#252B3A] cursor-pointer" @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>
|
||||
<div class="ml-[40px] text-[14px] text-[#374151] italic">
|
||||
<span class="text-[16px] italic">#后台admin端口打包:</span>
|
||||
<span>进入admin目录下执行,执行后编译代码默认移动到系统的niucloud下的</span>
|
||||
<span class="text-[#FF9D31] mx-[3px]">public/admin</span>
|
||||
<span class="text-[#F09000] 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 class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]">
|
||||
<span class="text-[14px] text-[#374151]">npm run build</span>
|
||||
<span class="iconfont iconfuzhiV6xx1 !text-[#252B3A] cursor-pointer" @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>
|
||||
<div class="ml-[40px] text-[14px] text-[#374151] italic">
|
||||
<span class="text-[16px] italic">#使用uniapp打包H5:</span>
|
||||
<span>进入uniapp目录下执行,执行后编译代码默认移动到系统niucloud下的</span>
|
||||
<span class="text-[#FF9D31] mx-[3px]">public/wap</span>
|
||||
<span class="text-[#F09000] 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 class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]">
|
||||
<span class="text-[14px] text-[#374151]">npm run build:h5</span>
|
||||
<span class="iconfont iconfuzhiV6xx1 !text-[#252B3A] cursor-pointer" @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>
|
||||
<div class="ml-[40px] text-[14px] text-[#374151] italic">
|
||||
<span class="text-[16px] italic">#使用uniapp打包微信小程序:</span>
|
||||
<span>进入uniapp目录下执行,执行后编译代码默认移动到系统niucloud下的</span>
|
||||
<span class="text-[#FF9D31] mx-[3px]">uni-app/dist/build/mp-weixin</span>
|
||||
<span class="text-[#F09000] 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 class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]">
|
||||
<span class="text-[14px] text-[#374151]">npm run build:mp-weixin</span>
|
||||
<span class="iconfont iconfuzhiV6xx1 !text-[#252B3A] cursor-pointer" @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>
|
||||
<div class="ml-[40px] text-[14px] text-[#374151] italic">
|
||||
<span class="text-[16px] italic">#前台web(pc)端打包::</span>
|
||||
<span>进入web目录下执行,执行后编译代码默认移动到系统niucloud下的</span>
|
||||
<span class="text-[#FF9D31] mx-[3px]">public/web</span>
|
||||
<span class="text-[#F09000] 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 class="ml-[40px] w-[1085px] h-[40px] bg-[#F9F9FB] rounded-[4px] mt-[10px] flex items-center justify-between border-[#F1F1F8] border-solid border-[1px] px-[10px]">
|
||||
<span class="text-[14px] text-[#374151]">npm run generate</span>
|
||||
<span class="iconfont iconfuzhiV6xx1 !text-[#252B3A] cursor-pointer" @click="copyEvent('npm run build')"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -215,4 +209,18 @@ watch(copied, () => {
|
||||
line-height: 36px;
|
||||
text-align: center;
|
||||
}
|
||||
:deep(.el-button){
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
:deep(.el-timeline-item__node--normal){
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
}
|
||||
:deep(.el-timeline-item__tail){
|
||||
left: 6px !important;
|
||||
}
|
||||
:deep(.el-timeline-item__node.is-hollow){
|
||||
background: #9699B6 !important;
|
||||
border-width: 3px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeName" class="mt-[20px]">
|
||||
<!-- <el-tabs v-model="activeName" class="mt-[20px]">
|
||||
<el-tab-pane :label="t('codeGeneration')" name="codeGeneration">
|
||||
<el-steps :active="5" direction="vertical">
|
||||
<el-step>
|
||||
@ -134,7 +134,71 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-tabs> -->
|
||||
|
||||
<el-steps :active="5" direction="vertical" class="mt-[20px]">
|
||||
<el-step>
|
||||
<template #title>
|
||||
<p class="text-[14px] font-[700]">
|
||||
{{ t("step1") }}
|
||||
</p>
|
||||
</template>
|
||||
<template #description>
|
||||
<span class="text-[#999]">{{ t("describe1") }}</span>
|
||||
<div class="mt-[20px] mb-[40px] h-[32px]">
|
||||
<el-button type="primary" class="w-[100px]" @click="addEvent">{{ t('btn1') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-step>
|
||||
|
||||
<el-step>
|
||||
<template #title>
|
||||
<p class="text-[14px] font-[700]">
|
||||
{{ t("step2") }}
|
||||
</p>
|
||||
</template>
|
||||
<template #description>
|
||||
<span class="text-[#999]">{{ t("describe2") }}</span>
|
||||
<div class="mt-[20px] mb-[40px] h-[32px]"></div>
|
||||
</template>
|
||||
</el-step>
|
||||
|
||||
<el-step>
|
||||
<template #title>
|
||||
<p class="text-[14px] font-[700]">
|
||||
{{ t("step3") }}
|
||||
</p>
|
||||
</template>
|
||||
<template #description>
|
||||
<span class="text-[#999]">{{ t("describe3") }}</span>
|
||||
<div class="mt-[20px] mb-[40px] h-[32px]"></div>
|
||||
</template>
|
||||
</el-step>
|
||||
|
||||
<el-step>
|
||||
<template #title>
|
||||
<p class="text-[14px] font-[700]">
|
||||
{{ t("step4") }}
|
||||
</p>
|
||||
</template>
|
||||
<template #description>
|
||||
<span class="text-[#999]">{{ t("describe4") }}</span>
|
||||
<div class="mt-[20px] mb-[40px] h-[32px]"></div>
|
||||
</template>
|
||||
</el-step>
|
||||
|
||||
<el-step>
|
||||
<template #title>
|
||||
<p class="text-[14px] font-[700]">
|
||||
{{ t("step5") }}
|
||||
</p>
|
||||
</template>
|
||||
<template #description>
|
||||
<span class="text-[#999]">{{ t("describe5") }}</span>
|
||||
<div class="mt-[20px] mb-[40px] h-[32px]"></div>
|
||||
</template>
|
||||
</el-step>
|
||||
</el-steps>
|
||||
|
||||
<add-table ref="addCodeDialog" />
|
||||
|
||||
|
||||
258
admin/src/app/views/tools/code/list.vue
Normal file
@ -0,0 +1,258 @@
|
||||
<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="codeTableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('addonName')" prop="addon_name">
|
||||
<el-select v-model="codeTableData.searchParam.addon_name" placeholder="全部" filterable remote clearable :remote-method="getAddonDevelopFn">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="系统" value="2" />
|
||||
<el-option :label="item.title" :value="item.key" v-for="item in addonList" :key="item.key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('tableName')" prop="table_name">
|
||||
<el-input v-model.trim="codeTableData.searchParam.table_name" :placeholder="t('tableNamePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadGenerateTableList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<div>
|
||||
<el-table :data="codeTableData.data" size="large" v-loading="codeTableData.loading">
|
||||
|
||||
<template #empty>
|
||||
<span>{{ !codeTableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
|
||||
<el-table-column prop="table_name" :show-overflow-tooltip="true" :label="t('tableName')" min-width="120" />
|
||||
<el-table-column prop="title" :show-overflow-tooltip="true" :label="t('addonName')" min-width="120" />
|
||||
<el-table-column prop="table_content" :show-overflow-tooltip="true" :label="t('tableContent')" min-width="120" />
|
||||
|
||||
<el-table-column prop="edit_type" :label="t('editType')" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.edit_type == 1 ? t('popup') : t('page') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('createTime')" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.create_time || '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" width="330">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
|
||||
<el-button type="primary" link @click="generatePreviewFn(row.id)">{{ t('preview') }}</el-button>
|
||||
<el-button type="primary" link @click="generatorCheckFileFn(row.id)">{{ t('saveAndSync') }}</el-button>
|
||||
<el-button type="primary" link @click="generateCreateFn(row.id, 2)">{{ t('download') }}</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="codeTableData.page" v-model:page-size="codeTableData.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="codeTableData.total"
|
||||
@size-change="loadGenerateTableList()" @current-change="loadGenerateTableList" />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { img ,setTablePageStorage } from '@/utils/common'
|
||||
import { t } from '@/lang'
|
||||
import { getGenerateTableList, deleteGenerateTable, generateCreate, generatePreview, generatorCheckFile, getAddonDevelop } from '@/app/api/tools'
|
||||
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
|
||||
const codeTableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {
|
||||
table_name: '',
|
||||
table_content: '',
|
||||
addon_name: ''
|
||||
}
|
||||
})
|
||||
|
||||
const searchFormRef = ref<FormInstance>()
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
|
||||
formEl.resetFields()
|
||||
loadGenerateTableList()
|
||||
}
|
||||
/**
|
||||
* 获取代码生成列表
|
||||
*/
|
||||
const loadGenerateTableList = (page: number = 1) => {
|
||||
codeTableData.loading = true
|
||||
codeTableData.page = page
|
||||
|
||||
getGenerateTableList({
|
||||
page: codeTableData.page,
|
||||
limit: codeTableData.limit,
|
||||
...codeTableData.searchParam
|
||||
}).then(res => {
|
||||
codeTableData.loading = false
|
||||
codeTableData.data = res.data.data
|
||||
codeTableData.total = res.data.total
|
||||
setTablePageStorage(codeTableData.page, codeTableData.limit, codeTableData.searchParam);
|
||||
}).catch(() => {
|
||||
codeTableData.loading = false
|
||||
})
|
||||
}
|
||||
loadGenerateTableList()
|
||||
/**
|
||||
* 删除代码生成
|
||||
*/
|
||||
const deleteEvent = (id: number) => {
|
||||
ElMessageBox.confirm(t('codeDeleteTips'), t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
deleteGenerateTable(id).then(() => {
|
||||
loadGenerateTableList()
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @param data
|
||||
*/
|
||||
const editEvent = (data: any) => {
|
||||
router.push('/tools/code/edit?id=' + data.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步校验
|
||||
*/
|
||||
const generatorCheckFileFn = ((id: any) => {
|
||||
generatorCheckFile({ id }).then((res: any) => {
|
||||
codeTableData.loading = false
|
||||
ElMessageBox.confirm(
|
||||
res.msg != '2' ? t('saveAndSyncText') : t('saveAndSyncText1'),
|
||||
t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel')
|
||||
}
|
||||
).then(() => {
|
||||
generateCreateFn(id, 3)
|
||||
}).catch(() => {
|
||||
})
|
||||
}).catch(() => {
|
||||
codeTableData.loading = false
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 同步or下载
|
||||
*/
|
||||
const generateCreateFn = (id: any, generate_type: any) => {
|
||||
codeTableData.loading = true
|
||||
generateCreate({ id, generate_type }).then(res => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '操作成功'
|
||||
})
|
||||
if (generate_type != 3) {
|
||||
codeTableData.loading = false
|
||||
window.open(img(res.data.file), '_blank')
|
||||
} else {
|
||||
loadGenerateTableList()
|
||||
}
|
||||
}).catch(() => {
|
||||
codeTableData.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
*代码预览
|
||||
*/
|
||||
const previewList = ref<Array<any>>([])
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const treeData = ref([])
|
||||
const codeLoading = ref(false)
|
||||
const code = ref('')
|
||||
const treeKey = ref('')
|
||||
const generatePreviewFn = (id: number) => {
|
||||
dialogVisible.value = true
|
||||
codeLoading.value = true
|
||||
code.value = ''
|
||||
treeData.value = []
|
||||
treeKey.value = ''
|
||||
generatePreview(id).then(res => {
|
||||
previewList.value = res.data
|
||||
treeData.value = listToTree(res.data.map((el:any) => el.file_dir + el.name))
|
||||
code.value = previewList.value[0].content
|
||||
codeLoading.value = false
|
||||
}).catch(() => {
|
||||
codeLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const nodeClick = (node: { path: any }) => {
|
||||
previewList.value.forEach(el => {
|
||||
if (node.path === el.file_dir + el.name) code.value = el.content
|
||||
})
|
||||
}
|
||||
const listToTree = (arr:any) => {
|
||||
const ret: any[] = []
|
||||
if (Array.isArray(arr)) {
|
||||
for (let i = 0; i < arr.length; ++i) {
|
||||
const path = arr[i].split('/')
|
||||
let _ret = ret
|
||||
for (let j = 0; j < path.length; ++j) {
|
||||
const name = path[j]
|
||||
let obj = null
|
||||
for (var k = 0; k < _ret.length; ++k) {
|
||||
const _obj = _ret[k]
|
||||
if (_obj.name === name) {
|
||||
obj = _obj
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!obj) {
|
||||
obj = { name: name, path: name.indexOf('.') < 0 ? '' : arr[i], key: 'k' + i + j + k }
|
||||
if (name.indexOf('.') < 0) obj.children = []
|
||||
if (obj.path === arr[0]) treeKey.value = obj.key
|
||||
_ret.push(obj)
|
||||
}
|
||||
if (obj.children) _ret = obj.children
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
@ -19,19 +19,36 @@
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-table :data="tableData.data" size="large" v-loading="tableData.loading" ref="tableRef">
|
||||
<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')" />
|
||||
<el-table-column prop="prev_version" :label="t('prevVersion')" />
|
||||
<el-table-column prop="current_version" :label="t('currentVersion')" />
|
||||
<el-table-column prop="complete_time" :label="t('completeTime')" />
|
||||
<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-[10px] flex items-center">
|
||||
<el-button @click="batchDelete" size="small">{{ t('batchDelete') }}</el-button>
|
||||
</div>
|
||||
<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"
|
||||
@ -41,14 +58,20 @@
|
||||
</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 { FormInstance } from 'element-plus'
|
||||
import {ElMessage, ElMessageBox, FormInstance} from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getUpgradeRecords } from '@/app/api/upgrade'
|
||||
import { getUpgradeRecords, delUpgradeRecords } from '@/app/api/upgrade'
|
||||
import 'vue-web-terminal/lib/theme/dark.css'
|
||||
|
||||
const route = useRoute()
|
||||
@ -92,6 +115,44 @@ const loadList = (page: number = 1) => {
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<el-form :model="formData" :rules="formRules" label-width="150px" ref="formRef" class="page-form" v-loading="loading">
|
||||
<el-form :model="formData" :rules="formRules" label-width="150px" ref="formRef" class="page-form p-[20px]" v-loading="loading">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-sm">{{ t('oplatformSetting') }}</h3>
|
||||
<!-- <h3 class="text-[16px] text-[#1D1F3A] font-bold mb-4">{{ pageName }}</h3> -->
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('oplatformSetting') }}</h3>
|
||||
|
||||
<el-form-item label="APPID" prop="app_id">
|
||||
<el-input v-model.trim="formData.app_id" :placeholder="t('appidPlaceholder')" class="input-width" clearable />
|
||||
@ -10,72 +11,73 @@
|
||||
<el-form-item label="APPSECRET" prop="app_secret">
|
||||
<el-input v-model.trim="formData.app_secret" :placeholder="t('appSecretPlaceholder')" class="input-width" clearable />
|
||||
</el-form-item>
|
||||
<div class="box-card mt-[20px] !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('messagesReceiving') }}</h3>
|
||||
|
||||
<el-form-item :label="t('empowerReceiveUrl')">
|
||||
<el-input v-model.trim="staticInfo.auth_serve_url" placeholder="Please input" class="!w-[500px]" :readonly="true">
|
||||
<template #append>
|
||||
<div class="cursor-pointer" @click="copyEvent(staticInfo.auth_serve_url)">{{ t('copy') }}
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('messageReceiveUrl')">
|
||||
<el-input v-model.trim="staticInfo.message_serve_url" placeholder="Please input" class="!w-[500px]" :readonly="true">
|
||||
<template #append>
|
||||
<div class="cursor-pointer" @click="copyEvent(staticInfo.message_serve_url)">{{ t('copy') }}
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('messageValidationToken')" prop="token">
|
||||
<el-input v-model.trim="formData.token" class="input-width" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('messageDecryptKey')" prop="aes_key">
|
||||
<el-input v-model.trim="formData.aes_key" class="input-width">
|
||||
<template #append>
|
||||
<div class="cursor-pointer" @click="copyEvent(formData.aes_key)">{{ t('copy') }}</div>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="form-tip">{{ t('messageDecryptKeyTips') }}<el-button type="primary" link @click="regenerate">{{ t('regenerate') }}</el-button></div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="box-card mt-[20px] !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('domainSetting') }}</h3>
|
||||
|
||||
<el-form-item :label="t('empowerStartDomain')">
|
||||
<el-input v-model.trim="staticInfo.auth_launch_domain" placeholder="Please input" class="input-width" :readonly="true">
|
||||
<template #append>
|
||||
<div class="cursor-pointer" @click="copyEvent(staticInfo.auth_launch_domain)">{{ t('copy') }}</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('wechatDomain')">
|
||||
<el-input v-model.trim="staticInfo.wechat_auth_domain" placeholder="Please input" class="input-width" :readonly="true">
|
||||
<template #append>
|
||||
<div class="cursor-pointer" @click="copyEvent(staticInfo.wechat_auth_domain)">{{ t('copy') }}</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="box-card mt-[20px] !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('developerWeappUpload') }}</h3>
|
||||
|
||||
<el-form-item :label="t('developAppid')" prop="develop_app_id">
|
||||
<el-input v-model.trim="formData.develop_app_id" :placeholder="t('developAppidPlaceholder')" class="input-width" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('uploadKey')" prop="develop_upload_private_key">
|
||||
<div class="input-width">
|
||||
<upload-file v-model="formData.develop_upload_private_key" api="sys/document/wechat" />
|
||||
</div>
|
||||
<div class="form-tip">{{ t('uploadIpTips') }}{{ staticInfo.upload_ip }}</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card mt-[15px] !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-sm">{{ t('messagesReceiving') }}</h3>
|
||||
|
||||
<el-form-item :label="t('empowerReceiveUrl')">
|
||||
<el-input v-model.trim="staticInfo.auth_serve_url" placeholder="Please input" class="!w-[500px]" :readonly="true">
|
||||
<template #append>
|
||||
<div class="cursor-pointer" @click="copyEvent(staticInfo.auth_serve_url)">{{ t('copy') }}
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('messageReceiveUrl')">
|
||||
<el-input v-model.trim="staticInfo.message_serve_url" placeholder="Please input" class="!w-[500px]" :readonly="true">
|
||||
<template #append>
|
||||
<div class="cursor-pointer" @click="copyEvent(staticInfo.message_serve_url)">{{ t('copy') }}
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('messageValidationToken')" prop="token">
|
||||
<el-input v-model.trim="formData.token" class="input-width" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('messageDecryptKey')" prop="aes_key">
|
||||
<el-input v-model.trim="formData.aes_key" class="input-width">
|
||||
<template #append>
|
||||
<div class="cursor-pointer" @click="copyEvent(formData.aes_key)">{{ t('copy') }}</div>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="form-tip">{{ t('messageDecryptKeyTips') }}<el-button type="primary" link @click="regenerate">{{ t('regenerate') }}</el-button></div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card mt-[15px] !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-sm">{{ t('domainSetting') }}</h3>
|
||||
|
||||
<el-form-item :label="t('empowerStartDomain')">
|
||||
<el-input v-model.trim="staticInfo.auth_launch_domain" placeholder="Please input" class="input-width" :readonly="true">
|
||||
<template #append>
|
||||
<div class="cursor-pointer" @click="copyEvent(staticInfo.auth_launch_domain)">{{ t('copy') }}</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('wechatDomain')">
|
||||
<el-input v-model.trim="staticInfo.wechat_auth_domain" placeholder="Please input" class="input-width" :readonly="true">
|
||||
<template #append>
|
||||
<div class="cursor-pointer" @click="copyEvent(staticInfo.wechat_auth_domain)">{{ t('copy') }}</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card mt-[15px] !border-none" shadow="never">
|
||||
<h3 class="panel-title !text-sm">{{ t('developerWeappUpload') }}</h3>
|
||||
|
||||
<el-form-item :label="t('developAppid')" prop="develop_app_id">
|
||||
<el-input v-model.trim="formData.develop_app_id" :placeholder="t('developAppidPlaceholder')" class="input-width" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('uploadKey')" prop="develop_upload_private_key">
|
||||
<div class="input-width">
|
||||
<upload-file v-model="formData.develop_upload_private_key" api="sys/document/wechat" />
|
||||
</div>
|
||||
<div class="form-tip">{{ t('uploadIpTips') }}{{ staticInfo.upload_ip }}</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
|
||||
<div class="fixed-footer-wrap">
|
||||
@ -93,7 +95,10 @@ import { getStatic, getConfig, editConfig } from '@/app/api/wxoplatform'
|
||||
import { ElMessage, FormInstance, FormRules } from 'element-plus'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { guid } from '@/utils/common'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
const loading = ref(true)
|
||||
const formData = ref({
|
||||
app_id: '',
|
||||
@ -179,4 +184,28 @@ watch(copied, () => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.setting-card .el-card__body){
|
||||
padding: 0 !important;
|
||||
}
|
||||
.fixed-footer-wrap {
|
||||
height: 48px;
|
||||
|
||||
.fixed-footer {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
right: -15px;
|
||||
bottom: 0;
|
||||
left: -15px;
|
||||
display: flex;
|
||||
height: inherit;
|
||||
background: var(--el-bg-color-overlay);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
--tw-shadow: var(--el-box-shadow);
|
||||
--tw-shadow-colored: var(--el-box-shadow);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition:var(--el-transition-duration) width ease-in-out,var(--el-transition-duration) padding-left ease-in-out,var(--el-transition-duration) padding-right ease-in-out;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<el-card class="box-card !border-none p-[20px]" shadow="never">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
<!-- <span class="text-page-title">{{ pageName }}</span> -->
|
||||
<el-button type="primary" class="w-[100px]" @click="commit()">
|
||||
{{ t('oneClickSync') }}
|
||||
</el-button>
|
||||
@ -36,13 +36,11 @@
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.commit_record.user_version">
|
||||
{{ row.commit_record.status_name }}
|
||||
<div v-if="row.commit_record.template_id == '0'" class="text-error">
|
||||
<div v-if="row.commit_record.status == 1 && row.commit_record.template_id == '0'" class="text-error">
|
||||
{{ t('syncTemplateError') }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
--
|
||||
</div>
|
||||
<div>--</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@ -54,8 +52,11 @@
|
||||
|
||||
<el-table-column :label="t('operation')" align="right" fixed="right" width="330">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="showCommitRecordDialog(row.group_id)">{{ t('commitRecord') }}</el-button>
|
||||
<el-button type="primary" link @click="commit(row.group_id)">{{ t('weappVersionUpdate') }}</el-button>
|
||||
<div class="flex flex-col items-end">
|
||||
<el-button type="primary" link @click="commit(row.group_id)">{{ t('weappVersionUpdate') }}</el-button>
|
||||
<el-button type="primary" link @click="showCommitRecordDialog(row.group_id)">{{ t('weappVersionUpdateRecord') }}</el-button>
|
||||
<el-button type="primary" link @click="syncSiteWeappFn(row.group_id)">{{ t('siteWeappSync') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -101,8 +102,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getWeappLastCommitRecord, weappCommit, getWeappCommitRecord, getSiteGroupCommitRecord } from '@/app/api/wxoplatform'
|
||||
import { getWeappLastCommitRecord, weappCommit, getWeappCommitRecord, getSiteGroupCommitRecord, syncSiteWeapp } from '@/app/api/wxoplatform'
|
||||
import { t } from '@/lang'
|
||||
import { ElMessageBox } from "element-plus";
|
||||
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
@ -192,10 +194,25 @@ const showCommitRecordDialog = (siteGroupId: number) => {
|
||||
loadCommitRecordList()
|
||||
commitRecordDialogShow.value = true
|
||||
}
|
||||
|
||||
const syncSiteWeappFn = (siteGroupId: number) => {
|
||||
ElMessageBox.confirm(t('syncSiteWeappTips'), t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
syncSiteWeapp({ site_group_id: siteGroupId })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-alert__title) {
|
||||
white-space: pre;
|
||||
}
|
||||
// :deep(.setting-card .el-card__body){
|
||||
// padding: 0 !important;
|
||||
// }
|
||||
</style>
|
||||
|
||||
@ -1,217 +1,223 @@
|
||||
{
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"info": "详情",
|
||||
"createTime": "创建时间",
|
||||
"sort": "排序",
|
||||
"status": "状态",
|
||||
"operation": "操作",
|
||||
"more": "更多",
|
||||
"statusNormal": "正常",
|
||||
"statusDeactivate": "停用",
|
||||
"startUsing": "启用",
|
||||
"confirm": "确认",
|
||||
"save": "保存",
|
||||
"back": "返回",
|
||||
"cancel": "取消",
|
||||
"search": "搜索",
|
||||
"reset": "重置",
|
||||
"refresh": "刷新",
|
||||
"refreshSuccess": "刷新成功",
|
||||
"select": "选择",
|
||||
"export": "导出列表",
|
||||
"exportPlaceholder": "确定要导出数据吗?",
|
||||
"exportTip": "批量导出数据",
|
||||
"exportConfirm": "确定并导出",
|
||||
"warning": "提示",
|
||||
"isShow": "是否显示",
|
||||
"show": "显示",
|
||||
"hidden": "隐藏",
|
||||
"icon": "图标",
|
||||
"userName": "用户名",
|
||||
"headImg": "头像",
|
||||
"accountNumber": "账号",
|
||||
"accountSettings": "账号设置",
|
||||
"realName": "名称",
|
||||
"realNamePlaceholder": "请输入用户名称",
|
||||
"password": "密码",
|
||||
"confirmPassword": "确认密码",
|
||||
"image": "图片",
|
||||
"video": "视频",
|
||||
"rename": "重命名",
|
||||
"lookOver": "查看",
|
||||
"selectAll": "全选",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"copy": "复制",
|
||||
"complete": "完成",
|
||||
"copySuccess": "复制成功",
|
||||
"notSupportCopy": "浏览器不支持一键复制,请手动进行复制",
|
||||
"selectPlaceholder": "全部",
|
||||
"provincePlaceholder": "请选择省",
|
||||
"cityPlaceholder": "请选择市",
|
||||
"districtPlaceholder": "请选择区/县",
|
||||
"emptyData": "暂无数据",
|
||||
"emptyQrCode": "暂无二维码",
|
||||
"fileErr": "无法显示",
|
||||
"upload": {
|
||||
"root": "上传",
|
||||
"selectimage": "选择图片",
|
||||
"selectvideo": "选择视频",
|
||||
"selecticon": "选择图标",
|
||||
"selectnews": "选择图文",
|
||||
"uploadimage": "上传图片",
|
||||
"uploadvideo": "上传视频",
|
||||
"addAttachmentCategory": "添加分组",
|
||||
"attachmentCategoryPlaceholder": "请输入分组名称",
|
||||
"attachmentEmpty": "暂无附件,请点击上传按钮上传",
|
||||
"iconEmpty": "暂无图标",
|
||||
"deleteCategoryTips": "确定要删除该分组吗?",
|
||||
"deleteAttachmentTips": "确定要删除所选附件吗?如所选附件已被使用删除将会受到影响,请谨慎操作!",
|
||||
"move": "移动",
|
||||
"moveCategory": "移动分组",
|
||||
"moveTo": "移动至",
|
||||
"placeholderimageName": "请输入图片名称",
|
||||
"placeholdervideoName": "请输入视频名称",
|
||||
"placeholdericonName": "请输入图标名称",
|
||||
"success": "上传成功",
|
||||
"triggerUpperLimit": "可选数量已达上限",
|
||||
"mediaEmpty": "暂无素材,请点击上传按钮上传"
|
||||
},
|
||||
"tabs": {
|
||||
"closeLeft": "关闭左侧",
|
||||
"closeRight": "关闭右侧",
|
||||
"closeOther": "关闭其他"
|
||||
},
|
||||
"layout": {
|
||||
"layoutSetting": "主题设置",
|
||||
"darkMode": "黑暗模式",
|
||||
"sidebarMode": "主题风格",
|
||||
"themeColor": "主题颜色",
|
||||
"detectionLoginOperation": "确定",
|
||||
"detectionLoginContent": "已检测到有其他账号登录,需要刷新后才能继续操作。",
|
||||
"detectionLoginTip": "提示",
|
||||
"layoutStyle": "布局风格",
|
||||
"tab": "开启标签栏"
|
||||
},
|
||||
"axios": {
|
||||
"unknownError": "未知错误",
|
||||
"400": "错误的请求",
|
||||
"401": "请重新登录",
|
||||
"403": "拒绝访问",
|
||||
"404": "请求错误",
|
||||
"405": "请求方法未允许",
|
||||
"408": "请求超时",
|
||||
"409": "请求跨域",
|
||||
"500": "服务器端出错,错误原因:",
|
||||
"501": "网络未实现",
|
||||
"502": "网络错误",
|
||||
"503": "服务不可用",
|
||||
"504": "网络超时",
|
||||
"505": "http版本不支持该请求",
|
||||
"timeout": "网络请求超时!",
|
||||
"requestError": "请求错误",
|
||||
"errNetwork": "网络请求错误",
|
||||
"baseUrlError": " 接口请求错误,请检查VITE_APP_BASE_URL参数配置或者伪静态配置, <a style='text-decoration: underline;' href='https://www.kancloud.cn/niucloud/niucloud-admin-develop/3213750' target='blank'>点击查看相关手册</a>"
|
||||
},
|
||||
"linkPlaceholder": "请选择跳转链接",
|
||||
"selectLinkTips": "链接选择",
|
||||
"diyLinkName": "链接名称",
|
||||
"diyLinkNamePlaceholder": "请输入链接名称",
|
||||
"diyLinkNameNotEmpty": "链接名称不能为空",
|
||||
"diyLinkUrl": "跳转路径",
|
||||
"diyLinkUrlPlaceholder": "请输入跳转路径",
|
||||
"diyLinkUrlNotEmpty": "跳转路径不能为空",
|
||||
"diyAppletId": "小程序AppID",
|
||||
"diyAppletIdPlaceholder": "请输入要打开的小程序的appid",
|
||||
"diyAppletIdNotEmpty": "小程序AppID不能为空",
|
||||
"diyAppletPage": "小程序路径",
|
||||
"diyAppletPagePlaceholder": "请输入要打开的小程序路径",
|
||||
"diyAppletPageNotEmpty": "小程序路径不能为空",
|
||||
"diyMakePhone": "电话号码",
|
||||
"diyMakePhonePlaceholder": "请输入电话号码",
|
||||
"diyMakePhoneNotEmpty": "电话号码不能为空",
|
||||
"returnToPreviousPage": "返回",
|
||||
"preview": "预览",
|
||||
"emptyApp": "暂未安装任何应用",
|
||||
"newInfo": "最新消息",
|
||||
"visitWap": "访问店铺",
|
||||
"mapSetting": "地图设置",
|
||||
"mapKey": "腾讯地图KEY",
|
||||
"indexTemplate": "首页模版",
|
||||
"indexSwitch": "切换首页",
|
||||
"indexWarning": "你确定要切换首页吗?",
|
||||
"originalPassword": "原始密码",
|
||||
"newPassword": "新密码",
|
||||
"passwordCopy": "确认密码",
|
||||
"passwordTip": "修改密码时必填.不修改密码时留空",
|
||||
"originalPasswordPlaceholder": "请输入原始密码",
|
||||
"passwordPlaceholder": "请输入新密码",
|
||||
"originalPasswordHint": "原始密码不能为空",
|
||||
"newPasswordHint": "请输入确认密码",
|
||||
"doubleCipherHint": "两次新密码不同",
|
||||
"confirmPasswordError": "两次新密码不同",
|
||||
"upgrade": {
|
||||
"upgradeButton": "立即升级",
|
||||
"title": "升级",
|
||||
"upgradingTips": "有正在执行的升级任务,",
|
||||
"clickView": "点击查看",
|
||||
"dirPermission": "目录读写权限",
|
||||
"path": "路径",
|
||||
"demand": "要求",
|
||||
"readable": "可读",
|
||||
"write": "可写",
|
||||
"upgradeSuccess": "升级成功",
|
||||
"localBuild": "本地编译",
|
||||
"cloudBuild": "重试",
|
||||
"rollback": "回滚",
|
||||
"showDialogCloseTips": "升级任务尚未完成,关闭将取消升级,是否要继续关闭?",
|
||||
"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>手动回退上一版本!",
|
||||
"knownToKnow": "我已知晓,不需要再次提示",
|
||||
"cloudBuildErrorTips": "一键云编译队列任务过多,请等待几分钟后重试!"
|
||||
},
|
||||
"cloudbuild": {
|
||||
"title": "云编译",
|
||||
"executingTips": "有正在执行的编译任务,",
|
||||
"clickView": "点击查看",
|
||||
"dirPermission": "目录读写权限",
|
||||
"path": "路径",
|
||||
"demand": "要求",
|
||||
"readable": "可读",
|
||||
"write": "可写",
|
||||
"cloudbuildSuccess": "编译完成",
|
||||
"showDialogCloseTips": "编译任务尚未完成,关闭将取消编译,是否要继续关闭?"
|
||||
},
|
||||
"formSelectContentTitle": "表单名称",
|
||||
"formSelectContentTitlePlaceholder": "请输入表单名称",
|
||||
"formSelectContentTypeName": "表单类型",
|
||||
"formSelectContentTypeNamePlaceholder": "请选择表单类型",
|
||||
"formSelectContentTypeAll": "全部",
|
||||
"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": "配置失败"
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"info": "详情",
|
||||
"createTime": "创建时间",
|
||||
"sort": "排序",
|
||||
"status": "状态",
|
||||
"operation": "操作",
|
||||
"more": "更多",
|
||||
"statusNormal": "正常",
|
||||
"statusDeactivate": "停用",
|
||||
"startUsing": "启用",
|
||||
"confirm": "确认",
|
||||
"save": "保存",
|
||||
"back": "返回",
|
||||
"cancel": "取消",
|
||||
"search": "搜索",
|
||||
"reset": "重置",
|
||||
"refresh": "刷新",
|
||||
"refreshSuccess": "刷新成功",
|
||||
"select": "选择",
|
||||
"export": "导出列表",
|
||||
"exportPlaceholder": "确定要导出数据吗?",
|
||||
"exportTip": "批量导出数据",
|
||||
"exportConfirm": "确定并导出",
|
||||
"warning": "提示",
|
||||
"isShow": "是否显示",
|
||||
"show": "显示",
|
||||
"hidden": "隐藏",
|
||||
"icon": "图标",
|
||||
"userName": "用户名",
|
||||
"headImg": "头像",
|
||||
"accountNumber": "账号",
|
||||
"accountSettings": "账号设置",
|
||||
"realName": "名称",
|
||||
"realNamePlaceholder": "请输入用户名称",
|
||||
"password": "密码",
|
||||
"confirmPassword": "确认密码",
|
||||
"image": "图片",
|
||||
"video": "视频",
|
||||
"rename": "重命名",
|
||||
"lookOver": "查看",
|
||||
"selectAll": "全选",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"copy": "复制",
|
||||
"complete": "完成",
|
||||
"copySuccess": "复制成功",
|
||||
"notSupportCopy": "浏览器不支持一键复制,请手动进行复制",
|
||||
"selectPlaceholder": "全部",
|
||||
"provincePlaceholder": "请选择省",
|
||||
"cityPlaceholder": "请选择市",
|
||||
"districtPlaceholder": "请选择区/县",
|
||||
"emptyData": "暂无数据",
|
||||
"emptyQrCode": "暂无二维码",
|
||||
"fileErr": "无法显示",
|
||||
"upload": {
|
||||
"root": "上传",
|
||||
"selectimage": "选择图片",
|
||||
"selectvideo": "选择视频",
|
||||
"selecticon": "选择图标",
|
||||
"selectnews": "选择图文",
|
||||
"uploadimage": "上传图片",
|
||||
"uploadvideo": "上传视频",
|
||||
"addAttachmentCategory": "添加分组",
|
||||
"attachmentCategoryPlaceholder": "请输入分组名称",
|
||||
"attachmentEmpty": "暂无附件,请点击上传按钮上传",
|
||||
"iconEmpty": "暂无图标",
|
||||
"deleteCategoryTips": "确定要删除该分组吗?",
|
||||
"deleteAttachmentTips": "确定要删除所选附件吗?如所选附件已被使用删除将会受到影响,请谨慎操作!",
|
||||
"move": "移动",
|
||||
"moveCategory": "移动分组",
|
||||
"moveTo": "移动至",
|
||||
"placeholderimageName": "请输入图片名称",
|
||||
"placeholdervideoName": "请输入视频名称",
|
||||
"placeholdericonName": "请输入图标名称",
|
||||
"success": "上传成功",
|
||||
"triggerUpperLimit": "可选数量已达上限",
|
||||
"mediaEmpty": "暂无素材,请点击上传按钮上传"
|
||||
},
|
||||
"tabs": {
|
||||
"closeLeft": "关闭左侧",
|
||||
"closeRight": "关闭右侧",
|
||||
"closeOther": "关闭其他"
|
||||
},
|
||||
"layout": {
|
||||
"layoutSetting": "主题设置",
|
||||
"darkMode": "黑暗模式",
|
||||
"sidebarMode": "主题风格",
|
||||
"themeColor": "主题颜色",
|
||||
"detectionLoginOperation": "确定",
|
||||
"detectionLoginContent": "已检测到有其他账号登录,需要刷新后才能继续操作。",
|
||||
"detectionLoginTip": "提示",
|
||||
"layoutStyle": "布局风格",
|
||||
"tab": "开启标签栏"
|
||||
},
|
||||
"axios": {
|
||||
"unknownError": "未知错误",
|
||||
"400": "错误的请求",
|
||||
"401": "请重新登录",
|
||||
"403": "拒绝访问",
|
||||
"404": "请求错误",
|
||||
"405": "请求方法未允许",
|
||||
"408": "请求超时",
|
||||
"409": "请求跨域",
|
||||
"500": "服务器端出错,错误原因:",
|
||||
"501": "网络未实现",
|
||||
"502": "网络错误",
|
||||
"503": "服务不可用",
|
||||
"504": "网络超时",
|
||||
"505": "http版本不支持该请求",
|
||||
"timeout": "网络请求超时!",
|
||||
"requestError": "请求错误",
|
||||
"errNetwork": "网络请求错误",
|
||||
"baseUrlError": " 接口请求错误,请检查VITE_APP_BASE_URL参数配置或者伪静态配置, <a style='text-decoration: underline;' href='https://www.kancloud.cn/niucloud/niucloud-admin-develop/3213750' target='blank'>点击查看相关手册</a>"
|
||||
},
|
||||
"linkPlaceholder": "请选择跳转链接",
|
||||
"selectLinkTips": "链接选择",
|
||||
"diyLinkName": "链接名称",
|
||||
"diyLinkNamePlaceholder": "请输入链接名称",
|
||||
"diyLinkNameNotEmpty": "链接名称不能为空",
|
||||
"diyLinkUrl": "跳转路径",
|
||||
"diyLinkUrlPlaceholder": "请输入跳转路径",
|
||||
"diyLinkUrlNotEmpty": "跳转路径不能为空",
|
||||
"diyAppletId": "小程序AppID",
|
||||
"diyAppletIdPlaceholder": "请输入要打开的小程序的appid",
|
||||
"diyAppletIdNotEmpty": "小程序AppID不能为空",
|
||||
"diyAppletPage": "小程序路径",
|
||||
"diyAppletPagePlaceholder": "请输入要打开的小程序路径",
|
||||
"diyAppletPageNotEmpty": "小程序路径不能为空",
|
||||
"diyMakePhone": "电话号码",
|
||||
"diyMakePhonePlaceholder": "请输入电话号码",
|
||||
"diyMakePhoneNotEmpty": "电话号码不能为空",
|
||||
"returnToPreviousPage": "返回",
|
||||
"preview": "预览",
|
||||
"emptyApp": "暂未安装任何应用",
|
||||
"newInfo": "最新消息",
|
||||
"visitWap": "访问店铺",
|
||||
"mapSetting": "地图设置",
|
||||
"mapKey": "腾讯地图KEY",
|
||||
"indexTemplate": "首页模版",
|
||||
"indexSwitch": "切换首页",
|
||||
"indexWarning": "你确定要切换首页吗?",
|
||||
"originalPassword": "原始密码",
|
||||
"newPassword": "新密码",
|
||||
"passwordCopy": "确认密码",
|
||||
"passwordTip": "修改密码时必填.不修改密码时留空",
|
||||
"originalPasswordPlaceholder": "请输入原始密码",
|
||||
"passwordPlaceholder": "请输入新密码",
|
||||
"originalPasswordHint": "原始密码不能为空",
|
||||
"newPasswordHint": "请输入确认密码",
|
||||
"doubleCipherHint": "两次新密码不同",
|
||||
"confirmPasswordError": "两次新密码不同",
|
||||
"upgrade": {
|
||||
"upgradeButton": "立即升级",
|
||||
"title": "升级",
|
||||
"upgradingTips": "有正在执行的升级任务,",
|
||||
"clickView": "点击查看",
|
||||
"dirPermission": "目录读写权限",
|
||||
"path": "路径",
|
||||
"demand": "要求",
|
||||
"readable": "可读",
|
||||
"write": "可写",
|
||||
"upgradeSuccess": "升级成功",
|
||||
"localBuild": "本地编译",
|
||||
"cloudBuild": "重试",
|
||||
"rollback": "回滚",
|
||||
"showDialogCloseTips": "升级任务尚未完成,关闭将取消升级,是否要继续关闭?",
|
||||
"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>手动回退上一版本!",
|
||||
"knownToKnow": "我已知晓,不需要再次提示",
|
||||
"cloudBuildErrorTips": "一键云编译队列任务过多,请等待几分钟后重试!",
|
||||
"isNeedBackup": "是否需要备份",
|
||||
"isNeedBackupTips": "检测到已存在备份,此次升级是否需要备份,不需要备份在升级出现异常时将会恢复最近的一次备份。",
|
||||
"isNeedBackupBtn": "查看备份记录",
|
||||
"option": "选项",
|
||||
"isNeedCloudbuild": "是否需要云编译",
|
||||
"cloudbuildTips": "此次升级的同时是否需要进行云编译"
|
||||
},
|
||||
"cloudbuild": {
|
||||
"title": "云编译",
|
||||
"executingTips": "有正在执行的编译任务,",
|
||||
"clickView": "点击查看",
|
||||
"dirPermission": "目录读写权限",
|
||||
"path": "路径",
|
||||
"demand": "要求",
|
||||
"readable": "可读",
|
||||
"write": "可写",
|
||||
"cloudbuildSuccess": "编译完成",
|
||||
"showDialogCloseTips": "编译任务尚未完成,关闭将取消编译,是否要继续关闭?"
|
||||
},
|
||||
"formSelectContentTitle": "表单名称",
|
||||
"formSelectContentTitlePlaceholder": "请输入表单名称",
|
||||
"formSelectContentTypeName": "表单类型",
|
||||
"formSelectContentTypeNamePlaceholder": "请选择表单类型",
|
||||
"formSelectContentTypeAll": "全部",
|
||||
"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": "配置失败"
|
||||
}
|
||||
|
||||
@ -1,38 +1,38 @@
|
||||
<template>
|
||||
<div :class="['layout-aside ease-in duration-200 flex border-t-[1px] border-solid border-[var(--el-color-info-light-8)] box-border', { 'bright': !dark }]">
|
||||
<div class="flex flex-col border-0 border-r-[1px] border-solid border-[var(--el-color-info-light-8)] box-border">
|
||||
<!-- <div class="w-full h-[64px] flex justify-center items-center w-[65px]flex-shrink-0">
|
||||
<div class="w-[40px] h-[40px] rounded-[50%] overflow-hidden">
|
||||
<el-image style="width: 100%; height: 100%" :src="img(logoUrl)" fit="contain">
|
||||
<template #error>
|
||||
<div class="flex justify-center items-center w-full h-[40px]"><img class="max-w-[100px]" src="@/app/assets/images/site_login_logo.png" alt="" object-fit="contain"></div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</div> -->
|
||||
<el-scrollbar class="w-[65px] one-menu">
|
||||
<div :class="['layout-aside ease-in duration-200 flex box-border', { 'bright': !dark}]">
|
||||
<div class="flex flex-col border-0 border-r-[1px] border-solid border-[var(--el-color-info-light-8)] box-border overflow-hidden">
|
||||
|
||||
<div :class="['w-[150px] one-menu hide-scrollbar', { 'expanded': systemStore.menuIsCollapse }]" >
|
||||
<!-- <div class="flex items-center justify-center bg-primary text-white h-[40px] text-[16px] rounded-[4px] mb-[10px]"><span class="text-[20px]">+</span><span v-if="systemStore.menuIsCollapse" class="ml-[10px]">应用市场</span></div> -->
|
||||
<div class="flex flex-col items-center">
|
||||
<template v-for="(item, index) in oneMenuData">
|
||||
<div v-if="item.meta.show" class="menu-item py-[10px] flex flex-col items-center justify-center w-full box-border cursor-pointer" :class="{'is-active':oneMenuActive===item.original_name}" @click="router.push({ name: item.name })">
|
||||
<div class="w-[35px] h-[35px] flex items-center justify-center mx-auto menu-icon" :class="{'is-active':oneMenuActive===item.original_name}">
|
||||
<div v-if="item.meta.show" class="menu-item my-[2px] p-2 flex w-full box-border cursor-pointer relative" :class="{'is-active':oneMenuActive===item.original_name,'hover-left': systemStore.menuIsCollapse, 'vertical': !systemStore.menuIsCollapse , 'horizontal': systemStore.menuIsCollapse }" :style="{ height: (systemStore.menuIsCollapse ) ? '40px' : '55px' }" @click="router.push({ name: item.name })">
|
||||
<div class="w-[20px] h-[20px] flex items-center justify-center menu-icon" :class="{'is-active':oneMenuActive===item.original_name}">
|
||||
<template v-if="item.meta.icon">
|
||||
<el-image class="w-[25px] h-[25px] overflow-hidden" :src="item.meta.icon" fit="fill" v-if="isUrl(item.meta.icon)"/>
|
||||
<icon :name="item.meta.icon" size="25px" v-else />
|
||||
<el-image class="w-[20px] h-[20px] overflow-hidden" :src="item.meta.icon" fit="fill" v-if="isUrl(item.meta.icon)"/>
|
||||
<icon :name="item.meta.icon" size="20px" color="#1D1F3A" v-else />
|
||||
</template>
|
||||
<icon v-else :name="'iconfont iconshezhi1'" />
|
||||
<icon v-else :name="'iconfont iconshezhi1'" color="#1D1F3A" />
|
||||
</div>
|
||||
<div class="text-center text-[13px] mt-[3px]">{{ item.meta.short_title || item.meta.title }}</div>
|
||||
|
||||
<div v-if="systemStore.menuIsCollapse" class="text-center text-[14px] mt-[3px] ml-[10px]">{{ item.meta.title || item.meta.short_title }}</div>
|
||||
<div v-else class="text-center text-[12px] mt-1">{{ item.meta.short_title || item.meta.title }}</div>
|
||||
<div v-if="systemStore.menuIsCollapse && item.name=='app_store' && recentlyUpdated.length>0" class="text-[11px] bg-[#DA203E] px-[10px] rounded-[12px] text-[#fff] ml-auto">更新</div>
|
||||
<div v-if="!systemStore.menuIsCollapse && item.name=='app_store' && recentlyUpdated.length>0" class="w-[7px] h-[7px] bg-[#DA203E] absolute flex items-center justify-center rounded-full top-[4px] right-[14px]"></div>
|
||||
<div v-if="systemStore.menuIsCollapse && item.original_name=='tool' && isNewVersion" class="text-[11px] bg-[#DA203E] px-[10px] rounded-[12px] text-[#fff] ml-auto">更新</div>
|
||||
<div v-if="!systemStore.menuIsCollapse && item.original_name=='tool' && isNewVersion" class="w-[7px] h-[7px] bg-[#DA203E] absolute flex items-center justify-center rounded-full top-[4px] right-[14px]"></div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="flex flex-col two-menu w-[185px] " v-if="twoMenuData.length">
|
||||
<!-- <div class="w-[175px] h-[64px] flex items-center justify-center text-[16px]">{{ route.matched[1].meta.title }}</div> -->
|
||||
<el-scrollbar class="flex-1">
|
||||
<el-menu :default-active="route.name" :default-openeds="defaultOpeneds" :router="true" class="aside-menu" :collapse="systemStore.menuIsCollapse">
|
||||
<menu-item v-for="(route, index) in twoMenuData" :routes="route" :key="index" />
|
||||
<!-- <div class="w-[185px] h-[64px] flex items-center justify-center text-[16px]">{{ route.matched[1].meta.title }}</div> -->
|
||||
<el-scrollbar class="flex-1" >
|
||||
<el-menu :default-active="route.name" :default-openeds="defaultOpeneds" :router="true" class="aside-menu">
|
||||
<menu-item v-for="(route, index) in twoMenuData" :routes="route" :key="index" :isNewVersion="isNewVersion" />
|
||||
</el-menu>
|
||||
<div class="h-[48px]"></div>
|
||||
</el-scrollbar>
|
||||
@ -40,7 +40,7 @@
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch, ref, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
@ -49,6 +49,9 @@ import useUserStore from '@/stores/modules/user'
|
||||
import { ADMIN_ROUTE,findFirstValidRoute } from "@/router/routers"
|
||||
import { img, isUrl } from '@/utils/common'
|
||||
import menuItem from './menu-item.vue'
|
||||
import { getAddonLocal} from '@/app/api/addon'
|
||||
import { getVersions } from "@/app/api/auth"
|
||||
import { getAuthInfo, getFrameworkVersionList } from "@/app/api/module"
|
||||
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
@ -58,6 +61,7 @@ const router = useRouter()
|
||||
const dark = computed(() => {
|
||||
return systemStore.dark
|
||||
})
|
||||
|
||||
const logoUrl = computed(() => {
|
||||
return userStore.siteInfo.icon ? userStore.siteInfo.icon : systemStore.website.icon
|
||||
})
|
||||
@ -71,6 +75,7 @@ routers.forEach(item => {
|
||||
item.name = findFirstValidRoute(item.children)
|
||||
}
|
||||
oneMenuData.value.push(item)
|
||||
|
||||
})
|
||||
|
||||
const oneMenuActive = ref(oneMenuData.value[0].name)
|
||||
@ -78,44 +83,129 @@ watch(route, () => {
|
||||
twoMenuData.value = route.matched[1].children ?? []
|
||||
oneMenuActive.value = route.matched[1].name == ADMIN_ROUTE.children[0].name ? route.matched[2].name : route.matched[1].name
|
||||
defaultOpeneds.value = twoMenuData.value.map(item => item.name)
|
||||
|
||||
}, { immediate: true })
|
||||
|
||||
const recentlyUpdated = ref([])
|
||||
const localListFn = () => {
|
||||
getAddonLocal({}).then((res) => {
|
||||
const data = res.data.list
|
||||
recentlyUpdated.value = []
|
||||
for (const i in data) {
|
||||
if (data[i].install_info && Object.keys(data[i].install_info)?.length) {
|
||||
if (data[i].install_info.version != data[i].version) {
|
||||
recentlyUpdated.value.push(data[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
localListFn()
|
||||
const frameworkVersionList = ref([])
|
||||
const isNewVersion = computed(() => {
|
||||
if (!newVersion.value || newVersion.value.version_no === version.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 将版本号转为字符串再处理
|
||||
const currentVersionStr = String(version.value);
|
||||
const latestVersionStr = String(newVersion.value.version_no);
|
||||
// 移除点号并转为数字比较
|
||||
const currentVersionNum = parseInt(currentVersionStr.replace(/\./g, ''), 10);
|
||||
const latestVersionNum = parseInt(latestVersionStr.replace(/\./g, ''), 10);
|
||||
return latestVersionNum > currentVersionNum;
|
||||
});
|
||||
const getFrameworkVersionListFn = () => {
|
||||
getFrameworkVersionList().then(({ data }) => {
|
||||
frameworkVersionList.value = data
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
getFrameworkVersionListFn()
|
||||
|
||||
const newVersion: any = computed(() => {
|
||||
return frameworkVersionList.value.length ? frameworkVersionList.value[0] : null
|
||||
})
|
||||
const version = ref('')
|
||||
const getVersionsInfo = () => {
|
||||
getVersions().then((res) => {
|
||||
version.value = res.data.version.version
|
||||
})
|
||||
}
|
||||
getVersionsInfo()
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.one-menu{
|
||||
padding: 10px;
|
||||
padding-top: 20px;
|
||||
width: 78px;
|
||||
overflow-y: auto;
|
||||
// transition: width 0.1s ease-out;
|
||||
&.expanded {
|
||||
width: 185px;
|
||||
padding: 15px;
|
||||
padding-top: 18px;
|
||||
}
|
||||
.menu-item{
|
||||
.menu-icon {
|
||||
background-color: transparent; /* 默认无背景色 */
|
||||
color: #8F9ABF;
|
||||
border-radius: 2px;
|
||||
justify-content: center;
|
||||
&.vertical {
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu-icon.is-active {
|
||||
background-color: var(--el-color-primary); /* 选中时背景色 */
|
||||
color: white; /* 选中时图标颜色变白 */
|
||||
border-radius: 4px; /* 可选:使图标背景为圆形 */
|
||||
&.horizontal {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.menu-icon {
|
||||
// background-color: transparent; /* 默认无背景色 */
|
||||
color: #1D1F3A;
|
||||
}
|
||||
|
||||
// .menu-icon.is-active {
|
||||
// background-color: var(--el-color-primary); /* 选中时背景色 */
|
||||
// color: white; /* 选中时图标颜色变白 */
|
||||
// border-radius: 4px; /* 可选:使图标背景为圆形 */
|
||||
// }
|
||||
|
||||
&:hover{
|
||||
color:var(--el-color-primary);
|
||||
background-color: #EAEBF0 !important;
|
||||
border-radius: 6px;
|
||||
// background-color: var(--el-color-primary-light-9) !important;
|
||||
// color:var(--el-color-primary);
|
||||
}
|
||||
&.is-active{
|
||||
// background-color: var(--el-color-primary) !important;
|
||||
// color: #fff !important;
|
||||
border: none;
|
||||
color:var(--el-color-primary);
|
||||
background-color: #EAEBF0 !important;
|
||||
border-radius: 6px;
|
||||
// background-color: var(--el-color-primary-light-9) !important;
|
||||
// border: none;
|
||||
// color:var(--el-color-primary);
|
||||
}
|
||||
span{
|
||||
font-size: 14px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
.menu-item.hover-left {
|
||||
justify-content: flex-start;
|
||||
padding-left: 5px;
|
||||
}
|
||||
&.expanded .menu-item .text-center {
|
||||
opacity: 1;
|
||||
}
|
||||
.el-menu{
|
||||
border: 0;
|
||||
}
|
||||
.el-scrollbar{
|
||||
height: calc(100vh - 65px);
|
||||
}
|
||||
|
||||
}
|
||||
.two-menu{
|
||||
.aside-menu:not(.el-menu--collapse) {
|
||||
@ -123,8 +213,8 @@ watch(route, () => {
|
||||
border: 0;
|
||||
padding-top: 15px;
|
||||
.el-menu-item{
|
||||
height: 36px;
|
||||
margin: 4px 10px;
|
||||
height: 40px;
|
||||
margin: 4px 15px;
|
||||
padding: 0 8px !important;
|
||||
border-radius: 2px;
|
||||
span{
|
||||
@ -132,13 +222,16 @@ watch(route, () => {
|
||||
font-size: 14px;
|
||||
}
|
||||
&.is-active{
|
||||
// background-color: var(--el-color-info-light-8) !important;
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
background-color: #EAEBF0 !important;
|
||||
border-radius: 6px;
|
||||
color: inherit;
|
||||
// background-color: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
&:hover{
|
||||
// background-color: var(--el-color-info-light-8) !important;
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
color: var(--el-color-primary);
|
||||
background-color: #EAEBF0 !important;
|
||||
border-radius: 6px;
|
||||
// background-color: var(--el-color-primary-light-9) !important;
|
||||
// color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
.el-sub-menu{
|
||||
@ -146,20 +239,21 @@ watch(route, () => {
|
||||
margin: 4px 0px;
|
||||
// margin-bottom: 8px;
|
||||
.el-sub-menu__title{
|
||||
margin: 0px 10px;
|
||||
height: 36px;
|
||||
margin: 0px 15px;
|
||||
height: 40px;
|
||||
padding-left: 8px;
|
||||
border-radius: 2px;
|
||||
span{
|
||||
height: 36px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
&:hover{
|
||||
// background-color: var(--el-color-info-light-8) !important;
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
color: var(--el-color-primary);
|
||||
background-color:#EAEBF0 !important;
|
||||
border-radius: 6px;
|
||||
// background-color: var(--el-color-primary-light-9) !important;
|
||||
// color: var(--el-color-primary);
|
||||
}
|
||||
.el-icon.el-sub-menu__icon-arrow{
|
||||
right: 5px;
|
||||
@ -191,9 +285,23 @@ watch(route, () => {
|
||||
font-size: var(--el-font-size-base);
|
||||
}
|
||||
}
|
||||
// :deep(.el-scrollbar__bar){
|
||||
// display: none !important;
|
||||
// }
|
||||
// .layout-aside .el-scrollbar__wrap--hidden-default, .layout-aside .el-scrollbar{
|
||||
// overflow: inherit !important;
|
||||
// }
|
||||
// 隐藏滚动条
|
||||
.hide-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
/* Chrome/Safari/Edge */
|
||||
}
|
||||
|
||||
.layout-aside .el-scrollbar__wrap--hidden-default, .layout-aside .el-scrollbar{
|
||||
overflow: inherit !important;
|
||||
.hide-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
/* IE/Edge */
|
||||
scrollbar-width: none;
|
||||
/* Firefox */
|
||||
}
|
||||
// .layout-aside .menu-item.is-active{
|
||||
// position: relative;
|
||||
|
||||
@ -2,19 +2,21 @@
|
||||
<template v-if="meta.show">
|
||||
<el-sub-menu v-if="routes.children" :index="String(routes.name)">
|
||||
<template #title>
|
||||
<div v-if="meta.icon " class="w-[13px] h-[13px] mr-[10rpx] relative flex justify-center items-center">
|
||||
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
|
||||
<div v-if="meta.icon && props.level != 2" class="w-[13px] h-[13px] mr-[10rpx] relative flex justify-center items-center">
|
||||
<icon v-if="meta.icon && props.level != 2" :name="meta.icon" color="#1D1F3A" class="absolute !w-auto" />
|
||||
</div>
|
||||
<span :class="['ml-[10px]', {'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}]">{{ meta.title }}</span>
|
||||
</template>
|
||||
<menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" />
|
||||
<menu-item v-for="(route, index) in routes.children" :routes="route" :level="props.level + 1" :key="index" :isNewVersion="props.isNewVersion" />
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-else :index="String(routes.name)" :route="routes.path">
|
||||
<template #title>
|
||||
<div v-if="meta.icon " class="w-[13px] h-[13px] mr-[10rpx] relative flex justify-center items-center">
|
||||
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
|
||||
<div v-if="meta.icon && props.level != 2" class="w-[13px] h-[13px] mr-[10rpx] relative flex justify-center items-center">
|
||||
<icon v-if="meta.icon && props.level != 2" color="#1D1F3A" :name="meta.icon" class="absolute !w-auto" />
|
||||
</div>
|
||||
<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 }}
|
||||
<div v-if="meta.view=='app/upgrade'&& props.isNewVersion" class="w-[7px] h-[7px] bg-[#DA203E] absolute flex items-center justify-center rounded-full top-[10px] right-[65px]"></div>
|
||||
</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
@ -22,21 +24,25 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ref, computed } from 'vue'
|
||||
import menuItem from './menu-item.vue'
|
||||
import { CollectionTag } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
routes: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
isNewVersion: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const meta = computed(() => props.routes.meta)
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@ -1,17 +1,30 @@
|
||||
<template>
|
||||
<el-container class="h-[64px] w-full layout-admin flex items-center justify-between px-[15px]" >
|
||||
<el-container class="h-[64px] w-full layout-admin flex items-center justify-between pr-[15px] border-b-[1px] border-solid border-[var(--el-color-info-light-8)]" >
|
||||
<!-- :class="['h-full px-[10px]',{'layout-header border-b border-color': !dark}]" -->
|
||||
<div class="flex items-center">
|
||||
<div class="max-w-[120px] flex justify-center items-center flex-shrink-0">
|
||||
<div class="max-w-[120px] h-[40px] overflow-hidden">
|
||||
<el-image style="width: 100%; height: 100%" :src="img(logoUrl)" fit="contain">
|
||||
<!-- <div class="navbar-item flex items-center h-full cursor-pointer" @click="toggleMenuCollapse">
|
||||
<icon name="element Expand" v-if="systemStore.menuIsCollapse" />
|
||||
<icon name="element Fold" v-else />
|
||||
</div> -->
|
||||
<div class="flex justify-center items-center flex-shrink-0" :class="{'w-[185px]': systemStore.menuIsCollapse,'w-[78px]': !systemStore.menuIsCollapse}">
|
||||
<div class="w-full h-[40px] overflow-hidden">
|
||||
<el-image style="width: 100%; height: 100%" :src="img(logoUrl)" fit="contain" v-if="!systemStore.menuIsCollapse">
|
||||
<template #error>
|
||||
<div class="flex justify-center items-center w-full h-full"><img class="max-w-[120px]" src="@/app/assets/images/logo.default.png" alt="" object-fit="contain"></div>
|
||||
<div class="flex justify-center items-center w-full h-full"><img class="max-w-[70px]" src="@/app/assets/images/logo.default.png" alt="" object-fit="contain"></div>
|
||||
</template>
|
||||
</el-image>
|
||||
<el-image style="width: 100%; height: 100%" :src="img(longLogoUrl)" fit="contain" v-else>
|
||||
<template #error>
|
||||
<div class="flex justify-center items-center w-full h-full"><img class="max-w-[180px]" src="@/app/assets/images/logo.default.png" alt="" object-fit="contain"></div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</div>
|
||||
<div class="left-panel flex items-center text-[14px] leading-[1]">
|
||||
<div class="navbar-item flex items-center h-full cursor-pointer" @click="toggleMenuCollapse">
|
||||
<icon name="element Fold" v-if="systemStore.menuIsCollapse" />
|
||||
<icon name="element Expand" v-else />
|
||||
</div>
|
||||
<!-- 刷新当前页 -->
|
||||
<div class="navbar-item flex items-center h-full cursor-pointer" @click="refreshRouter">
|
||||
<icon name="element Refresh" />
|
||||
@ -24,9 +37,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div>
|
||||
<el-input placeholder="搜索站点或应用" v-model.trim="keywords" />
|
||||
</div> -->
|
||||
|
||||
<div>
|
||||
<div class="right-panel h-full flex items-center justify-end">
|
||||
<div class="border-primary border-[1px] h-[30px] px-[15px] flex items-center rounded-[6px] mr-[10px] cursor-pointer" @click="toHome()">
|
||||
<span class="iconfont iconguanliduan !text-primary mr-1"></span>
|
||||
<span class="text-[14px] text-primary">客户端</span>
|
||||
</div>
|
||||
<div class="navbar-item flex items-center h-full cursor-pointer">
|
||||
<message />
|
||||
</div>
|
||||
<div class="navbar-item flex items-center h-full cursor-pointer">
|
||||
<layout-setting />
|
||||
</div>
|
||||
@ -36,9 +59,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" v-model="comparisonToken">
|
||||
<input type="hidden" v-model="comparisonSiteId">
|
||||
|
||||
<input type="hidden" v-model="comparisonToken" />
|
||||
<input type="hidden" v-model="comparisonSiteId" />
|
||||
|
||||
<el-dialog v-model="detectionLoginDialog" :title="t('layout.detectionLoginTip')" width="30%" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false">
|
||||
<span>{{ t('layout.detectionLoginContent') }}</span>
|
||||
@ -76,20 +99,25 @@ import { ref, computed } from 'vue'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
import useSystemStore from '@/stores/modules/system'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { img, isUrl } from '@/utils/common'
|
||||
|
||||
import { useRoute,useRouter } from 'vue-router'
|
||||
import { img, getToken } from '@/utils/common'
|
||||
import { t } from '@/lang'
|
||||
import storage from '@/utils/storage'
|
||||
import userInfo from './user-info.vue'
|
||||
import layoutSetting from './layout-setting.vue'
|
||||
import message from './message.vue'
|
||||
|
||||
const systemStore = useSystemStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
const logoUrl = computed(() => {
|
||||
return userStore.siteInfo.icon ? userStore.siteInfo.icon : systemStore.website.icon
|
||||
})
|
||||
const longLogoUrl = computed(() => {
|
||||
return userStore.siteInfo.logo ? userStore.siteInfo.logo : systemStore.website.logo
|
||||
})
|
||||
// 检测登录 start
|
||||
const detectionLoginDialog = ref(false)
|
||||
const comparisonToken = ref('')
|
||||
@ -126,6 +154,21 @@ const breadcrumb = computed(() => {
|
||||
return matched
|
||||
})
|
||||
storage.set({ key: 'currHeadMenuName', data: "" })
|
||||
systemStore.toggleMenuCollapse(storage.get('menuiscollapse') || false)
|
||||
const toggleMenuCollapse = () => {
|
||||
systemStore.toggleMenuCollapse(!systemStore.menuIsCollapse)
|
||||
}
|
||||
|
||||
const toHome = () => {
|
||||
if (!window.localStorage.getItem('site.token')) {
|
||||
window.localStorage.setItem('site.token', getToken())
|
||||
window.localStorage.setItem('site.comparisonTokenStorage', getToken())
|
||||
}
|
||||
if (!window.localStorage.getItem('site.userinfo')) {
|
||||
window.localStorage.setItem('site.userinfo', JSON.stringify(useUserStore().userInfo))
|
||||
}
|
||||
router.push({ path: '/home/index'})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -145,4 +188,12 @@ storage.set({ key: 'currHeadMenuName', data: "" })
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
// :deep(.el-input__wrapper) {
|
||||
// box-shadow: none !important;
|
||||
// border-radius: 4px !important;
|
||||
// background: #F7F7FA !important;
|
||||
// min-width: 638px;
|
||||
// height: 40px;
|
||||
// border-radius: 4px !important;
|
||||
// }
|
||||
</style>
|
||||
|
||||
63
admin/src/layout/admin/components/header/message.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<el-popover class="box-item" :width="322">
|
||||
<template #reference>
|
||||
<div class="relative">
|
||||
<icon name="iconfont iconFramec-1" />
|
||||
<span v-if="showRedDot" class="absolute top-[-3px] right-[-5px] w-[12px] h-[12px] rounded-full bg-[#DA203E] text-white text-[12px] flex justify-center items-center">1</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex items-center bg-[#F8FAFF] p-[10px] rounded-[8px]" v-if="showRedDot">
|
||||
<div class="w-[36px] h-[36px] rounded-full flex justify-center items-center">
|
||||
<img src="@/app/assets/images/index/cloud.png" alt="" class="w-[36px] h-[36px]" />
|
||||
</div>
|
||||
<div class="py-[3px] ml-[5px] flex-1">
|
||||
<div class="text-[16px] font-bold text-[#1D1F3A] mb-[5px]">云编译</div>
|
||||
<div class="text-[12px] text-[#4F516D] flex justify-between items-center">
|
||||
<span>有正在执行的编译任务</span>
|
||||
<span class="text-primary cursor-pointer ml-auto" @click="cloudBuildRef?.elNotificationClick()">点击查看</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-center" v-else>
|
||||
<img src="@/app/assets/images/index/message_empty.png" alt="">
|
||||
</div>
|
||||
</el-popover>
|
||||
<!-- <i class="iconfont iconFramec-1 cursor-pointer" title="消息" v-else></i> -->
|
||||
<cloud-build ref="cloudBuildRef"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import CloudBuild from "@/app/components/cloud-build/index.vue"
|
||||
|
||||
const cloudBuildRef = ref<any>(null)
|
||||
|
||||
const showRedDot = ref(false)
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const startPolling = () => {
|
||||
if (pollTimer) return
|
||||
|
||||
pollTimer = setInterval(() => {
|
||||
const startTime = localStorage.getItem('cloud_build_task')
|
||||
showRedDot.value = !!startTime
|
||||
if (!startTime) {
|
||||
stopPolling()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const stopPolling = () => {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
}
|
||||
startPolling()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.box-item .el-popover.el-popper){
|
||||
padding: 13px 16px !important;
|
||||
}
|
||||
</style>
|
||||
@ -8,36 +8,46 @@
|
||||
<icon name="element ArrowDown" class="ml-[5px]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="getUserInfoFn">
|
||||
<!-- <router-link to="/user/center"> -->
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconshezhi1 ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">账号设置</span>
|
||||
</div>
|
||||
<!-- </router-link> -->
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<router-link to="/tools/authorize">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconshouquanxinxi2 ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">授权信息</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePasswordDialog=true">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconxiugai ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">修改密码</span>
|
||||
<div class="p-[10px]">
|
||||
<div class="userinfo flex h-full items-center pb-[10px] border-b-[1px] border-solid border-[#e5e5e5]">
|
||||
<el-avatar v-if="userStore.userInfo.head_img" :size="45" :icon="UserFilled" :src="img(userStore.userInfo.head_img)"/>
|
||||
<img v-else src="@/app/assets/images/member_head.png" class="w-[45px] rounded-full" />
|
||||
<div>
|
||||
<div class="user-name pl-[8px] text-[14px]">{{ userStore.userInfo.username }}</div>
|
||||
<div class="pl-[8px] text-[13px] text-[#9699B6]">个人中心</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont icontuichudenglu ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">退出登录</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</div>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="getUserInfoFn" class="rounded-[4px]">
|
||||
<!-- <router-link to="/user/center"> -->
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconshezhi1 ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">账号设置</span>
|
||||
</div>
|
||||
<!-- </router-link> -->
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item class="rounded-[4px]">
|
||||
<router-link to="/tools/authorize">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconshouquanxinxi2 ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">授权信息</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePasswordDialog=true" class="rounded-[4px]">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconxiugai ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">修改密码</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout" class="rounded-[4px]">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont icontuichudenglu ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">退出登录</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dialog v-model="changePasswordDialog" width="450px" title="修改密码">
|
||||
@ -104,16 +114,17 @@ const saveInfo = reactive({
|
||||
});
|
||||
// 表单验证规则
|
||||
const formRules = reactive<FormRules>({
|
||||
original_password: [
|
||||
{ required: true, message: t("originalPasswordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password_copy: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
]
|
||||
});
|
||||
original_password: [
|
||||
{ required: true, message: t("originalPasswordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password_copy: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
]
|
||||
})
|
||||
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate((valid) => {
|
||||
@ -144,4 +155,5 @@ const submitForm = (formEl: FormInstance | undefined) => {
|
||||
.el-popper .el-dropdown-menu{
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -38,4 +38,8 @@ const appStore = useAppStore()
|
||||
const tabbarStore = useTabbarStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.bg-page {
|
||||
background-color: #F7F7FA;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -8,34 +8,43 @@
|
||||
<icon name="element ArrowDown" class="ml-[5px]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="toLink('/home/index')">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconqiehuan ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">切换站点</span>
|
||||
<div class="p-[10px]">
|
||||
<div class="userinfo flex h-full items-center pb-[10px] border-b-[1px] border-solid border-[#e5e5e5]">
|
||||
<el-avatar v-if="userStore.userInfo.head_img" :size="45" :icon="UserFilled" :src="img(userStore.userInfo.head_img)"/>
|
||||
<img v-else src="@/app/assets/images/member_head.png" class="w-[45px] rounded-full" />
|
||||
<div>
|
||||
<div class="user-name pl-[8px] text-[14px]">{{ userStore.userInfo.username }}</div>
|
||||
<div class="pl-[8px] text-[13px] text-[#9699B6]">个人中心</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="getUserInfoFn">
|
||||
<!-- <router-link to="/user/center"> -->
|
||||
</div>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="toLink('/home/index')">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconqiehuan ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">切换站点</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="getUserInfoFn">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconshezhi1 ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">账号设置</span>
|
||||
</div>
|
||||
<!-- </router-link> -->
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePasswordDialog=true">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconxiugai ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">修改密码</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont icontuichudenglu ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">退出登录</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePasswordDialog=true">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconxiugai ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">修改密码</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont icontuichudenglu ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">退出登录</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dialog v-model="changePasswordDialog" width="450px" title="修改密码">
|
||||
@ -67,7 +76,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { UserFilled } from '@element-plus/icons-vue'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import { setUserInfo } from '@/app/api/personal'
|
||||
@ -104,18 +113,20 @@ const saveInfo = reactive({
|
||||
password: '',
|
||||
password_copy: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = reactive<FormRules>({
|
||||
original_password: [
|
||||
{ required: true, message: t("originalPasswordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password_copy: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
]
|
||||
});
|
||||
original_password: [
|
||||
{ required: true, message: t("originalPasswordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password_copy: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
]
|
||||
})
|
||||
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate((valid) => {
|
||||
|
||||
@ -8,34 +8,42 @@
|
||||
<icon name="element ArrowDown" class="ml-[5px]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="toLink('/home/index')">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconqiehuan ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">切换站点</span>
|
||||
<div class="p-[10px]">
|
||||
<div class="userinfo flex h-full items-center pb-[10px] border-b-[1px] border-solid border-[#e5e5e5]">
|
||||
<el-avatar v-if="userStore.userInfo.head_img" :size="45" :icon="UserFilled" :src="img(userStore.userInfo.head_img)"/>
|
||||
<img v-else src="@/app/assets/images/member_head.png" class="w-[45px] rounded-full" />
|
||||
<div>
|
||||
<div class="user-name pl-[8px] text-[14px]">{{ userStore.userInfo.username }}</div>
|
||||
<div class="pl-[8px] text-[13px] text-[#9699B6]">个人中心</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="getUserInfoFn">
|
||||
<!-- <router-link to="/user/center"> -->
|
||||
</div>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="toLink('/home/index')">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconqiehuan ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">切换站点</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="getUserInfoFn">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconshezhi1 ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">账号设置</span>
|
||||
</div>
|
||||
<!-- </router-link> -->
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePasswordDialog=true">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconxiugai ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">修改密码</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont icontuichudenglu ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">退出登录</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePasswordDialog=true">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconxiugai ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">修改密码</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont icontuichudenglu ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">退出登录</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dialog v-model="changePasswordDialog" width="450px" title="修改密码">
|
||||
@ -106,16 +114,17 @@ const saveInfo = reactive({
|
||||
});
|
||||
// 表单验证规则
|
||||
const formRules = reactive<FormRules>({
|
||||
original_password: [
|
||||
{ required: true, message: t("originalPasswordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password_copy: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
]
|
||||
});
|
||||
original_password: [
|
||||
{ required: true, message: t("originalPasswordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password_copy: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
]
|
||||
})
|
||||
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate((valid) => {
|
||||
|
||||
@ -8,34 +8,43 @@
|
||||
<icon name="element ArrowDown" class="ml-[5px]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="toLink('/home/index')">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconqiehuan ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">切换站点</span>
|
||||
<div class="p-[10px]">
|
||||
<div class="userinfo flex h-full items-center pb-[10px] border-b-[1px] border-solid border-[#e5e5e5]">
|
||||
<el-avatar v-if="userStore.userInfo.head_img" :size="45" :icon="UserFilled" :src="img(userStore.userInfo.head_img)"/>
|
||||
<img v-else src="@/app/assets/images/member_head.png" class="w-[45px] rounded-full" />
|
||||
<div>
|
||||
<div class="user-name pl-[8px] text-[14px]">{{ userStore.userInfo.username }}</div>
|
||||
<div class="pl-[8px] text-[13px] text-[#9699B6]">个人中心</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="getUserInfoFn">
|
||||
<!-- <router-link to="/user/center"> -->
|
||||
</div>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="toLink('/home/index')">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconqiehuan ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">切换站点</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="getUserInfoFn">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconshezhi1 ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">账号设置</span>
|
||||
</div>
|
||||
<!-- </router-link> -->
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePasswordDialog=true">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconxiugai ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">修改密码</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont icontuichudenglu ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">退出登录</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePasswordDialog=true">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconxiugai ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">修改密码</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont icontuichudenglu ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">退出登录</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dialog v-model="changePasswordDialog" width="450px" title="修改密码">
|
||||
@ -106,16 +115,17 @@ const saveInfo = reactive({
|
||||
});
|
||||
// 表单验证规则
|
||||
const formRules = reactive<FormRules>({
|
||||
original_password: [
|
||||
{ required: true, message: t("originalPasswordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password_copy: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
]
|
||||
});
|
||||
original_password: [
|
||||
{ required: true, message: t("originalPasswordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
password_copy: [
|
||||
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
|
||||
]
|
||||
})
|
||||
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate((valid) => {
|
||||
|
||||
@ -8,34 +8,43 @@
|
||||
<icon name="element ArrowDown" class="ml-[5px]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="toLink('/home/index')">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconqiehuan ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">切换站点</span>
|
||||
<div class="p-[10px]">
|
||||
<div class="userinfo flex h-full items-center pb-[10px] border-b-[1px] border-solid border-[#e5e5e5]">
|
||||
<el-avatar v-if="userStore.userInfo.head_img" :size="45" :icon="UserFilled" :src="img(userStore.userInfo.head_img)"/>
|
||||
<img v-else src="@/app/assets/images/member_head.png" class="w-[45px] rounded-full" />
|
||||
<div>
|
||||
<div class="user-name pl-[8px] text-[14px]">{{ userStore.userInfo.username }}</div>
|
||||
<div class="pl-[8px] text-[13px] text-[#9699B6]">个人中心</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="getUserInfoFn">
|
||||
<!-- <router-link to="/user/center"> -->
|
||||
</div>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="toLink('/home/index')">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconqiehuan ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">切换站点</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="getUserInfoFn">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconshezhi1 ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">账号设置</span>
|
||||
</div>
|
||||
<!-- </router-link> -->
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePasswordDialog=true">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconxiugai ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">修改密码</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont icontuichudenglu ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">退出登录</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="changePasswordDialog=true">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont iconxiugai ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">修改密码</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<div class="flex items-center leading-[1] py-[5px]">
|
||||
<span class="iconfont icontuichudenglu ml-[4px] !text-[14px] mr-[10px]"></span>
|
||||
<span class="text-[14px]">退出登录</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dialog v-model="changePasswordDialog" width="450px" title="修改密码">
|
||||
|
||||
@ -40,7 +40,7 @@ const userStore = defineStore('user', {
|
||||
},
|
||||
login(form: object, app_type: any) {
|
||||
return new Promise((resolve, reject) => {
|
||||
login(form, app_type).then(async(res) => {
|
||||
login(form, app_type).then(async (res) => {
|
||||
if (app_type == 'admin' && Test.empty(res.data.userrole)) {
|
||||
storage.setPrefix('site')
|
||||
}
|
||||
|
||||