update admin
@ -149,13 +149,4 @@ export function getDecoratePage(params: Record<string, any>) {
|
||||
*/
|
||||
export function changeTemplate(params: Record<string, any>) {
|
||||
return request.put(`diy/change`, params, {showSuccessMessage: true})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览数据
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export function getPreviewData(params: Record<string, any>) {
|
||||
return request.put(`diy/preview`, params, {showSuccessMessage: false})
|
||||
}
|
||||
@ -33,4 +33,39 @@ export function getAccountStat() {
|
||||
*/
|
||||
export function getAccountType() {
|
||||
return request.get(`pay/account/type`)
|
||||
}
|
||||
|
||||
/***************************************************** 退款信息 **************************************************/
|
||||
|
||||
/**
|
||||
* 退款列表
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export function getPayRefundPages(params: Record<string, any>) {
|
||||
return request.get(`pay/refund`, {params})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取退款详情
|
||||
* @param id
|
||||
*/
|
||||
export function getPayRefundInfo(refund_no: string) {
|
||||
return request.get(`pay/refund/${refund_no}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 退款方式
|
||||
* @param id
|
||||
*/
|
||||
export function getRefundType() {
|
||||
return request.get(`pay/refund/type`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 退款转账
|
||||
* @param id
|
||||
*/
|
||||
export function getRefundTransfer(params: Record<string, any>) {
|
||||
return request.post(`pay/refund/transfer`, params, {showSuccessMessage: true})
|
||||
}
|
||||
@ -323,6 +323,16 @@ export function getIconList(params: Record<string, any>) {
|
||||
return request.get(`sys/attachment/icon`, {params})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取evn
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export function getEnv() {
|
||||
return request.get(`sys/env`)
|
||||
}
|
||||
|
||||
/***************************************************** 地址管理 ****************************************************/
|
||||
|
||||
/**
|
||||
|
||||
BIN
admin/src/app/assets/images/login_logo_ico_white.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
admin/src/app/assets/images/nc_logo.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 67 KiB |
@ -7,7 +7,7 @@
|
||||
"menuTypeDir": "目录",
|
||||
"menuTypeMenu": "菜单",
|
||||
"menuTypeButton": "按钮",
|
||||
"menuDeleteTips": "确定要删除该菜单吗?",
|
||||
"menuDeleteTips": "删除菜单会删除当前菜单以及该菜单下所有子菜单,是否确认删除?",
|
||||
"addMenu": "添加菜单",
|
||||
"updateMenu": "编辑菜单",
|
||||
"routePath": "路由路径",
|
||||
|
||||
@ -154,5 +154,36 @@
|
||||
"UIDStyle": "UID样式",
|
||||
"accountStyle": "积分/余额样式",
|
||||
"accountNumberColor": "数值颜色",
|
||||
"accountNumberWeight": "数值粗细"
|
||||
"accountNumberWeight": "数值粗细",
|
||||
"goodsCategoryTitle":"商品分类",
|
||||
"customGoods":"手动选择",
|
||||
"goodsNum":"商品数量",
|
||||
"selectCategory":"选择分类",
|
||||
"categoryName": "分类名称",
|
||||
"categoryImage": "分类图片",
|
||||
"selectSource": "选择数据源",
|
||||
"goodsSelectPopupSelectGoodsButton": "选择商品",
|
||||
"goodsSelectPopupSelect": "已选",
|
||||
"goodsSelectPopupPiece": "个",
|
||||
"goodsSelectPopupSelectGoodsDialog": "商品选择",
|
||||
"goodsSelectPopupAllGoods": "全部商品",
|
||||
"goodsSelectPopupSelectedGoods": "已选商品",
|
||||
"goodsSelectPopupGoodsName": "商品名称",
|
||||
"goodsSelectPopupGoodsNamePlaceholder": "请输入商品名称",
|
||||
"goodsSelectPopupGoodsCategory": "商品分类",
|
||||
"goodsSelectPopupGoodsCategoryPlaceholder": "全部",
|
||||
"goodsSelectPopupGoodsType": "商品类型",
|
||||
"goodsSelectPopupGoodsTypePlaceholder": "请选择商品类型",
|
||||
"goodsSelectPopupGoodsInfo": "商品",
|
||||
"goodsSelectPopupPrice": "价格",
|
||||
"goodsSelectPopupStock": "库存",
|
||||
"goodsSelectPopupBeforeTip": "已选择",
|
||||
"goodsSelectPopupAfterTip": "个商品",
|
||||
"goodsSelectPopupClearGoods": "取消选择",
|
||||
"goodsSelectPopupGoodsMinTip": "所选商品数量不能少于",
|
||||
"goodsSelectPopupGoodsMaxTip": "所选商品数量不能超过",
|
||||
"confirm": "确定",
|
||||
"cancel": "取消"
|
||||
|
||||
|
||||
}
|
||||
@ -1,26 +1,27 @@
|
||||
{
|
||||
"id":"主键",
|
||||
"accountDetail":"账单详情",
|
||||
"detail":"详情",
|
||||
"type":"账单类型",
|
||||
"money":"账单金额",
|
||||
"moneyPlaceholder":"请输入账单金额",
|
||||
"tradeNo":"账单编号",
|
||||
"tradeNoPlaceholder":"请输入账单编号",
|
||||
"createTime":"账单日期",
|
||||
"totalPay":"累计收款(元)",
|
||||
"totalRefund":"累计退款(元)",
|
||||
"totalTransfer":"累计转账(元)",
|
||||
"accountType": "请选择退款类型",
|
||||
"startDate": "开始时间",
|
||||
"endDate": "结束时间",
|
||||
"transferNo" : "转账单号",
|
||||
"transferTime": "转账时间",
|
||||
"transferType": "转账类型",
|
||||
"transferMoney": "转账金额",
|
||||
"transferRemark": "转账说明",
|
||||
"outTradeNo": "单号",
|
||||
"refundMoney": "退款金额",
|
||||
"failReason": "退款说明",
|
||||
"body": "说明"
|
||||
"id": "主键",
|
||||
"accountDetail": "账单详情",
|
||||
"detail": "详情",
|
||||
"type": "账单类型",
|
||||
"money": "账单金额",
|
||||
"moneyPlaceholder": "请输入账单金额",
|
||||
"tradeNo": "账单编号",
|
||||
"tradeNoPlaceholder": "请输入账单编号",
|
||||
"createTime": "账单日期",
|
||||
"totalPay": "累计收款(元)",
|
||||
"totalRefund": "累计退款(元)",
|
||||
"totalTransfer": "累计转账(元)",
|
||||
"accountType": "请选择退款类型",
|
||||
"startDate": "开始时间",
|
||||
"endDate": "结束时间",
|
||||
"transferNo": "转账单号",
|
||||
"transferTime": "转账时间",
|
||||
"transferType": "转账类型",
|
||||
"transferMoney": "转账金额",
|
||||
"transferRemark": "转账说明",
|
||||
"outTradeNo": "单号",
|
||||
"refundMoney": "退款金额",
|
||||
"failReason": "退款说明",
|
||||
"body": "说明",
|
||||
"payType": "支付方式"
|
||||
}
|
||||
10
admin/src/app/lang/zh-cn/finance.pay_refund.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"refundNo": "退款编号",
|
||||
"refundMoney": "退款金额",
|
||||
"payType": "支付类型",
|
||||
"status": "退款状态",
|
||||
"createTime": "申请时间",
|
||||
"refundNoPlaceholder": "请输入退款编号",
|
||||
"startDate": "开始时间",
|
||||
"endDate": "结束时间"
|
||||
}
|
||||
16
admin/src/app/lang/zh-cn/finance.refund_detail.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"refundNo": "退款编号",
|
||||
"refundMoney": "退款金额",
|
||||
"payType": "支付类型",
|
||||
"status": "退款状态",
|
||||
"createTime": "申请时间",
|
||||
"refundNoPlaceholder": "请输入退款编号",
|
||||
"startDate": "开始时间",
|
||||
"endDate": "结束时间",
|
||||
"outTradeNo": "交易流水号",
|
||||
"refundTypeName": "退款方式",
|
||||
"statusName": "退款状态",
|
||||
"transfer": "转账",
|
||||
"transferType": "转账方式",
|
||||
"voucher": "凭证"
|
||||
}
|
||||
8
admin/src/app/lang/zh-cn/index.app_manage.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"app": "应用管理",
|
||||
"descriptionLeft": "暂无安装任何应用,马上去",
|
||||
"link": "插件管理",
|
||||
"descriptionRight": "逛逛",
|
||||
"niucloud": "Niucloud官网",
|
||||
"appStore": "插件管理"
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"changeApp": "切换应用",
|
||||
"dataSummarize": "数据概况",
|
||||
"todayData": "今日数据",
|
||||
"memberNumb": "新增会员数",
|
||||
|
||||
12
admin/src/app/lang/zh-cn/index.preview.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"preview": "预览",
|
||||
"weapp": "微信小程序",
|
||||
"wechat": "微信公众号",
|
||||
"link": "链接",
|
||||
"copy": "复制",
|
||||
"copySuccess": "复制成功",
|
||||
"weappNotSet": "小程序未配置",
|
||||
"developTitle": "开发环境配置",
|
||||
"wapDomain": "wap域名(WAP_DOMAIN)",
|
||||
"wapDomainPlaceholder": "请输入wap域名"
|
||||
}
|
||||
59
admin/src/app/lang/zh-cn/index.store.json
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"noPlug":"暂无应用",
|
||||
"install":"安装",
|
||||
"unload":"卸载",
|
||||
"installLabel":"已安装",
|
||||
"uninstalledLabel":"未安装",
|
||||
"version":"版本",
|
||||
"title":"名称",
|
||||
"desc":"描述",
|
||||
"plugDetail": "插件信息",
|
||||
"author": "作者",
|
||||
"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官网我的授权 授权详情中查看"
|
||||
}
|
||||
@ -1,17 +1,6 @@
|
||||
{
|
||||
"personal": "个人中心",
|
||||
"editPersonal": "编辑",
|
||||
"headImg": "头像",
|
||||
"realName": "名称",
|
||||
"originalPassword": "原始密码",
|
||||
"password": "新密码",
|
||||
"passwordCopy": "确认密码",
|
||||
"passwordTip": "修改密码时必填.不修改密码时留空",
|
||||
"realNamePlaceholder": "请输入用户名称",
|
||||
"headImgPlaceholder": "请输入用户头像",
|
||||
"originalPasswordPlaceholder": "请输入原始密码",
|
||||
"passwordPlaceholder": "请输入新密码",
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"originalPasswordHint": "原始密码不能为空",
|
||||
"newPasswordHint": "请输入确认密码",
|
||||
"doubleCipherHint": "两次新密码不同"
|
||||
"realName": "名称"
|
||||
}
|
||||
18
admin/src/app/lang/zh-cn/user.edit_center.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"editPersonal": "编辑个人中心",
|
||||
"headImg": "头像",
|
||||
"realName": "名称",
|
||||
"originalPassword": "原始密码",
|
||||
"password": "新密码",
|
||||
"passwordCopy": "确认密码",
|
||||
"passwordTip": "修改密码时必填.不修改密码时留空",
|
||||
"realNamePlaceholder": "请输入用户名称",
|
||||
"headImgPlaceholder": "请输入用户头像",
|
||||
"originalPasswordPlaceholder": "请输入原始密码",
|
||||
"passwordPlaceholder": "请输入新密码",
|
||||
"save": "保存",
|
||||
"cancel": "返回",
|
||||
"originalPasswordHint": "原始密码不能为空",
|
||||
"newPasswordHint": "请输入确认密码",
|
||||
"doubleCipherHint": "两次新密码不同"
|
||||
}
|
||||
@ -4,7 +4,7 @@
|
||||
<span class="text-[20px]">{{ t('localAppText') }}</span>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<div class="absolute right-0 top-[2px] flex items-center cursor-pointer z-[4] border border-inherit">
|
||||
<div class="absolute right-0 top-[2px] flex items-center cursor-pointer z-[4] border border-inherit">
|
||||
<div class="flex item-center justify-center px-[6px] py-[4px]"
|
||||
:class="{ 'bg-slate-200': showType == 'small' }" @click="showType = 'small'">
|
||||
<img src="@/app/assets/images/app_store/switch_icon_1.png" class=" w-[16px] h-[16px]">
|
||||
@ -14,8 +14,8 @@
|
||||
<img src="@/app/assets/images/app_store/switch_icon_2.png" class="w-[16px] h-[16px] ">
|
||||
</div>
|
||||
</div>
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<el-tab-pane :label="installLabel" name="installed">
|
||||
<div class="flex flex-wrap px-2 plug-list pb-10">
|
||||
<div v-for="(item, index) in localList.installed" :key="index + 'a'"
|
||||
@ -309,6 +309,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -379,6 +380,7 @@ const localList = ref({
|
||||
all: [],
|
||||
error: ''
|
||||
})
|
||||
|
||||
const localListFn = () => {
|
||||
loading.value = true
|
||||
getAddonLocal({}).then(res => {
|
||||
@ -402,6 +404,7 @@ const localListFn = () => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
localListFn()
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
@ -415,10 +418,13 @@ const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
}
|
||||
|
||||
const currAddon = ref('')
|
||||
|
||||
// 安装面板弹窗
|
||||
const installShowDialog = ref(false)
|
||||
|
||||
// 安装步骤
|
||||
const installStep = ref(1)
|
||||
|
||||
// 安装检测结果
|
||||
const installCheckResult = ref({})
|
||||
|
||||
@ -479,6 +485,7 @@ const getInstallTask = (first: boolean = true) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getInstallTask()
|
||||
|
||||
const checkInstallTask = () => {
|
||||
@ -506,6 +513,7 @@ const handleInstall = () => {
|
||||
}
|
||||
|
||||
const cloudInstalling = ref(false)
|
||||
|
||||
/**
|
||||
* 云安装插件
|
||||
*/
|
||||
|
||||
@ -74,10 +74,10 @@
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
var res = {code: true, message: ''};
|
||||
if (diyStore.value[index].list.length === 0) {
|
||||
res.code = false;
|
||||
res.message = t('selectAddonTips');
|
||||
}
|
||||
// if (diyStore.value[index].list.length === 0) {
|
||||
// res.code = false;
|
||||
// res.message = t('selectAddonTips');
|
||||
// }
|
||||
return res;
|
||||
};
|
||||
|
||||
|
||||
@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
内容
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('titleStyle') }}</h3>
|
||||
<el-form label-width="80px" class="px-[10px]">
|
||||
<el-form-item :label="t('textFontSize')">
|
||||
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] article-slider" :min="12" :max="20"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('textFontWeight')">
|
||||
<el-radio-group v-model="diyStore.editComponent.fontWeight">
|
||||
<el-radio :label="'normal'">{{t('fontWeightNormal')}}</el-radio>
|
||||
<el-radio :label="'bold'">{{t('fontWeightBold')}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('textColor')">
|
||||
<el-color-picker v-model="diyStore.editComponent.textColor"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {t} from '@/lang'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
import {ref, reactive} from 'vue'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = []; // 忽略公共属性
|
||||
|
||||
const showDialog = ref(false)
|
||||
|
||||
const showStyle = () => {
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
const selectStyle = ref(diyStore.editComponent.style)
|
||||
|
||||
const changeStyle = () => {
|
||||
switch (selectStyle.value) {
|
||||
case 'style-1':
|
||||
diyStore.editComponent.subTitle.control = false
|
||||
diyStore.editComponent.more.control = false
|
||||
diyStore.editComponent.styleName = "风格1"
|
||||
break;
|
||||
case 'style-2':
|
||||
diyStore.editComponent.subTitle.control = true
|
||||
diyStore.editComponent.more.control = true
|
||||
diyStore.editComponent.styleName = "风格2"
|
||||
break;
|
||||
}
|
||||
diyStore.editComponent.style = selectStyle.value
|
||||
showDialog.value = false
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.horz-blank-slider {
|
||||
.el-slider__input {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<div class="flex flex-wrap">
|
||||
<div class="page-item relative bg-no-repeat ml-[20px] mr-[40px] mt-[20px] bg-[#f7f7f7] w-[300px] pt-[80px] pb-[20px]" :class="{ 'cursor-pointer' : !item.isDisabledPop }" v-for="(item,key) in page" :key="key">
|
||||
<p class="absolute top-[46px] left-[50%] translate-x-[-50%] text-[14px] truncate w-[130px] text-center">{{item.use_template.title}}</p>
|
||||
<div class="page-item relative bg-no-repeat ml-[20px] mr-[40px] mt-[20px] bg-[#f7f7f7] w-[300px] pt-[80px] pb-[20px]"
|
||||
:class="{ 'cursor-pointer' : !item.isDisabledPop }" v-for="(item,key) in page" :key="key">
|
||||
<p class="absolute top-[46px] left-[50%] translate-x-[-50%] text-[14px] truncate w-[130px] text-center">
|
||||
{{item.use_template.title}}</p>
|
||||
|
||||
<div v-show="item.use_template.url" class="w-[282px] h-[493px] mx-auto">
|
||||
<iframe :id="'previewIframe_' + key" v-show="item.loadingIframe" class="w-[282px] h-[493px] mx-auto" :src="item.use_template.wapPreview" frameborder="0"></iframe>
|
||||
<iframe :id="'previewIframe_' + key" v-show="item.loadingIframe" class="w-[282px] h-[493px] mx-auto"
|
||||
:src="item.use_template.wapPreview" frameborder="0"></iframe>
|
||||
<div v-show="item.loadingDev" class="w-[282px] h-[493px] mx-auto bg-body pt-[20px] px-[20px]">
|
||||
<div class="font-bold text-xl mb-[40px]">{{t('developTitle')}}</div>
|
||||
<div class="mb-[20px] flex flex-col">
|
||||
@ -19,12 +22,17 @@
|
||||
<img class="max-w-full" v-if="item.use_template.cover" :src="img(item.use_template.cover)"/>
|
||||
</div>
|
||||
|
||||
<p class="text-[12px] text-[#999] mt-[10px] mx-auto truncate text-center w-[250px]">{{item.use_template.desc}}</p>
|
||||
<p class="text-[12px] text-[#999] mt-[10px] mx-auto truncate text-center w-[250px]">
|
||||
{{item.use_template.desc}}</p>
|
||||
|
||||
<div class="item-hide absolute inset-x-0 inset-y-0 bg-black bg-opacity-50 text-center" :class="{ 'disabled' : item.isDisabledPop }">
|
||||
<div class="item-hide absolute inset-x-0 inset-y-0 bg-black bg-opacity-50 text-center"
|
||||
:class="{ 'disabled' : item.isDisabledPop }">
|
||||
<div class="item-btn-box absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] flex flex-col flex-wrap">
|
||||
<el-button @click="show(key,item)">{{ t('changePage') }}</el-button>
|
||||
<el-button @click="toDecorate(item.use_template)" v-show="item.use_template.mode != 'other' || item.use_template.action == 'decorate'">{{ t('decorate') }}</el-button>
|
||||
<el-button @click="toDecorate(item.use_template)"
|
||||
v-show="item.use_template.mode != 'other' || item.use_template.action == 'decorate'">{{
|
||||
t('decorate') }}
|
||||
</el-button>
|
||||
<el-button @click="toPreview(item.use_template)">{{ t('preview') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -32,16 +40,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="showDialog" :title="t('changeTemplate')" width="400px" :close-on-press-escape="false" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
<el-dialog v-model="showDialog" :title="t('changeTemplate')" width="400px" :close-on-press-escape="false"
|
||||
:destroy-on-close="true" :close-on-click-modal="false">
|
||||
|
||||
<el-form :model="form" label-width="0px" v-if="formData.type">
|
||||
<el-form-item label="">
|
||||
<div>{{t('hopeBeforeTip')}}<span class="text-primary px-[5px]">{{ page[formData.type].title }}</span>{{t('hopeAfterTip')}}</div>
|
||||
<div>{{t('hopeBeforeTip')}}<span class="text-primary px-[5px]">{{ page[formData.type].title }}</span>{{t('hopeAfterTip')}}
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="">
|
||||
<el-select v-model="hope" class="w-full">
|
||||
<el-option :label="t('changeTemplateTip') + ' ' + page[formData.type].title + ' ' + t('template')" value="template"/>
|
||||
<el-option :label="t('changeTemplateTip') + ' ' + page[formData.type].title + ' ' + t('template')"
|
||||
value="template"/>
|
||||
<el-option :label="t('changeMyPageTip') + ' ' + page[formData.type].title" value="diy"/>
|
||||
<el-option :label="t('changeOtherPageTip') + ' ' + page[formData.type].title" value="other"/>
|
||||
</el-select>
|
||||
@ -55,7 +66,8 @@
|
||||
|
||||
<el-form-item label="" v-show="hope == 'diy'">
|
||||
<el-select v-model="formData.id" class="w-full">
|
||||
<el-option v-for="(item, index) in page[formData.type].my_page" :label="item.title" :value="item.id"/>
|
||||
<el-option v-for="(item, index) in page[formData.type].my_page" :label="item.title"
|
||||
:value="item.id"/>
|
||||
</el-select>
|
||||
<div class="mt-[10px]">
|
||||
<span class="cursor-pointer text-primary mr-[10px]" @click="toDiyList">{{ t('createPage') }}</span>
|
||||
@ -65,7 +77,8 @@
|
||||
|
||||
<el-form-item label="" v-show="hope == 'other'">
|
||||
<el-select v-model="formData.page" class="w-full">
|
||||
<el-option v-for="(item, index) in page[formData.type].other_page" :label="item.title" :value="item.page"/>
|
||||
<el-option v-for="(item, index) in page[formData.type].other_page" :label="item.title"
|
||||
:value="item.page"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@ -85,8 +98,8 @@
|
||||
import {reactive, ref, watch} from 'vue'
|
||||
import {t} from '@/lang'
|
||||
import {img} from '@/utils/common'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {useRouter} from 'vue-router'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getDecoratePage, getDiyList, changeTemplate} from '@/app/api/diy'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
@ -134,7 +147,7 @@
|
||||
|
||||
if (import.meta.env.MODE == 'development') {
|
||||
// 开发模式情况下,并且未配置wap域名,则获取缓存域名
|
||||
if (wapDomain.value){
|
||||
if (wapDomain.value) {
|
||||
page[key].wapUrl = wapDomain.value + '/wap';
|
||||
setDomain(key);
|
||||
}
|
||||
@ -157,7 +170,7 @@
|
||||
window.addEventListener('message', (event) => {
|
||||
try {
|
||||
let data = JSON.parse(event.data);
|
||||
if(['appOnLaunch','appOnReady'].indexOf(data.type) != -1){
|
||||
if (['appOnLaunch', 'appOnReady'].indexOf(data.type) != -1) {
|
||||
for (let key in page) {
|
||||
page[key].loadingDev = false; // 禁用开发环境配置
|
||||
page[key].loadingIframe = true; // 加载iframe
|
||||
@ -175,7 +188,7 @@
|
||||
}, false);
|
||||
|
||||
// 将数据发送到uniapp
|
||||
const postMessage = (key:string)=> {
|
||||
const postMessage = (key: string) => {
|
||||
var diyData = JSON.stringify({
|
||||
type: 'appOnReady',
|
||||
message: '加载完成'
|
||||
@ -267,17 +280,17 @@
|
||||
|
||||
// 跳转去预览
|
||||
const toPreview = (data: any) => {
|
||||
let query: any = {};
|
||||
if (data.id) {
|
||||
query.id = data.id;
|
||||
} else if (data.name) {
|
||||
query.name = data.name;
|
||||
}else if(data.url){
|
||||
query.url = data.url;
|
||||
let page = data.page;
|
||||
if (data.url) {
|
||||
page = data.url;
|
||||
} else if (data.id) {
|
||||
page += '?id=' + data.id;
|
||||
}
|
||||
let url = router.resolve({
|
||||
path: '/decorate/preview',
|
||||
query
|
||||
path: '/preview/wap',
|
||||
query: {
|
||||
page
|
||||
}
|
||||
});
|
||||
window.open(url.href);
|
||||
}
|
||||
|
||||
@ -1,134 +1,141 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-[20px]">{{pageName}}</span>
|
||||
<el-button type="primary" class="w-[100px]" @click="dialogVisible = true">
|
||||
{{ t('addDiyPage') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-[20px]">{{pageName}}</span>
|
||||
<el-button type="primary" class="w-[100px]" @click="dialogVisible = true">
|
||||
{{ t('addDiyPage') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="diyPageTableData.searchParam" ref="searchFormDiyPageRef">
|
||||
<el-form-item :label="t('title')" prop="title">
|
||||
<el-input v-model="diyPageTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('typeName')" prop="type">
|
||||
<el-select v-model="diyPageTableData.searchParam.type" :placeholder="t('pageTypePlaceholder')">
|
||||
<el-option :label="t('all')" value="" />
|
||||
<el-option v-for="(item, key) in pageType" :label="item.title" :value="key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadDiyPageList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormDiyPageRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="diyPageTableData.searchParam" ref="searchFormDiyPageRef">
|
||||
<el-form-item :label="t('title')" prop="title">
|
||||
<el-input v-model="diyPageTableData.searchParam.title" :placeholder="t('titlePlaceholder')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('typeName')" prop="type">
|
||||
<el-select v-model="diyPageTableData.searchParam.type" :placeholder="t('pageTypePlaceholder')">
|
||||
<el-option :label="t('all')" value=""/>
|
||||
<el-option v-for="(item, key) in pageType" :label="item.title" :value="key"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadDiyPageList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormDiyPageRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-table :data="diyPageTableData.data" size="large" v-loading="diyPageTableData.loading">
|
||||
<el-table :data="diyPageTableData.data" size="large" v-loading="diyPageTableData.loading">
|
||||
|
||||
<template #empty>
|
||||
<span>{{ !diyPageTableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
<template #empty>
|
||||
<span>{{ !diyPageTableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
|
||||
<el-table-column prop="title" :label="t('title')" min-width="120" />
|
||||
<el-table-column prop="type_name" :label="t('typeName')" min-width="80" />
|
||||
<!-- <el-table-column :label="t('status')" min-width="80">-->
|
||||
<!-- <template #default="{ row }">-->
|
||||
<!-- <span v-if="row.type == 'DIY_PAGE'">-</span>-->
|
||||
<!-- <template v-else>-->
|
||||
<!-- <span v-if="row.is_default == 1" class="text-primary">{{ t('isUse') }}</span>-->
|
||||
<!-- <span v-else>{{ t('unused') }}</span>-->
|
||||
<!-- </template>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<el-table-column prop="update_time" :label="t('updateTime')" min-width="120" />
|
||||
<el-table-column prop="title" :label="t('title')" min-width="120"/>
|
||||
<el-table-column prop="type_name" :label="t('typeName')" min-width="80"/>
|
||||
<!-- <el-table-column :label="t('status')" min-width="80">-->
|
||||
<!-- <template #default="{ row }">-->
|
||||
<!-- <span v-if="row.type == 'DIY_PAGE'">-</span>-->
|
||||
<!-- <template v-else>-->
|
||||
<!-- <span v-if="row.is_default == 1" class="text-primary">{{ t('isUse') }}</span>-->
|
||||
<!-- <span v-else>{{ t('unused') }}</span>-->
|
||||
<!-- </template>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<el-table-column prop="update_time" :label="t('updateTime')" min-width="120"/>
|
||||
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="toPreview(row)">{{ t('promote') }}</el-button>
|
||||
<el-button v-if="row.type == 'DIY_PAGE'" type="primary" link @click="openShare(row)">{{ t('shareSet') }}</el-button>
|
||||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
|
||||
<!-- <el-button v-if="row.type == 'DIY_PAGE' || row.is_default == 0" type="danger" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>-->
|
||||
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="toPreview(row)">{{ t('promote') }}</el-button>
|
||||
<el-button v-if="row.type == 'DIY_PAGE'" type="primary" link @click="openShare(row)">{{
|
||||
t('shareSet') }}
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
|
||||
<!-- <el-button v-if="row.type == 'DIY_PAGE' || row.is_default == 0" type="danger" link @click="deleteEvent(row.id)">{{ t('delete') }}</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="diyPageTableData.page" v-model:page-size="diyPageTableData.limit" layout="total, sizes, prev, pager, next, jumper" :total="diyPageTableData.total" @size-change="loadDiyPageList()" @current-change="loadDiyPageList" />
|
||||
</div>
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="diyPageTableData.page" v-model:page-size="diyPageTableData.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="diyPageTableData.total"
|
||||
@size-change="loadDiyPageList()" @current-change="loadDiyPageList"/>
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
</el-card>
|
||||
|
||||
<!--添加页面-->
|
||||
<el-dialog v-model="dialogVisible" :title="t('addPageTips')" width="25%">
|
||||
<!--添加页面-->
|
||||
<el-dialog v-model="dialogVisible" :title="t('addPageTips')" width="25%">
|
||||
|
||||
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules">
|
||||
<el-form-item :label="t('title')" prop="title">
|
||||
<el-input v-model="formData.title" :placeholder="t('titlePlaceholder')" clearable maxlength="12" show-word-limit class="w-full" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('addType')" prop="type">
|
||||
<el-select v-model="formData.type" :placeholder="t('pageTypePlaceholder')" class="w-full">
|
||||
<el-option v-for="(item, key) in pageType" :label="item.title" :value="key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('templateName')" prop="template" v-show="pageTypeData">
|
||||
<el-select v-model="formData.template" class="w-full">
|
||||
<el-option :label="t('emptyTemplate')" value="" />
|
||||
<el-option v-for="(item, key) in pageTypeData" :label="item.title" :value="key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules">
|
||||
<el-form-item :label="t('title')" prop="title">
|
||||
<el-input v-model="formData.title" :placeholder="t('titlePlaceholder')" clearable maxlength="12"
|
||||
show-word-limit class="w-full"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('addType')" prop="type">
|
||||
<el-select v-model="formData.type" :placeholder="t('pageTypePlaceholder')" class="w-full">
|
||||
<el-option v-for="(item, key) in pageType" :label="item.title" :value="key"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('templateName')" prop="template" v-show="pageTypeData">
|
||||
<el-select v-model="formData.template" class="w-full">
|
||||
<el-option :label="t('emptyTemplate')" value=""/>
|
||||
<el-option v-for="(item, key) in pageTypeData" :label="item.title" :value="key"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" @click="addEvent(formRef)">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 分享设置-->
|
||||
<el-dialog v-model="shareDialogVisible" :title="t('shareSet')" width="30%">
|
||||
<el-tabs v-model="tabShareType">
|
||||
<el-tab-pane :label="t('wechat')" name="wechat"></el-tab-pane>
|
||||
<el-tab-pane :label="t('weapp')" name="weapp"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-form :model="shareFormData[tabShareType]" label-width="90px" ref="shareFormRef" :rules="shareFormRules">
|
||||
<el-form-item :label="t('sharePage')">
|
||||
<span>{{ sharePage }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('shareTitle')" prop="title">
|
||||
<el-input v-model="shareFormData[tabShareType].title" :placeholder="t('shareTitlePlaceholder')" clearable maxlength="30" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('shareDesc')" prop="desc" v-if="tabShareType == 'wechat'">
|
||||
<el-input v-model="shareFormData[tabShareType].desc" :placeholder="t('shareDescPlaceholder')" type="textarea" rows="4" clearable maxlength="100" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('shareImageUrl')" prop="url">
|
||||
<upload-image v-model="shareFormData[tabShareType].url" :limit="1" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 分享设置-->
|
||||
<el-dialog v-model="shareDialogVisible" :title="t('shareSet')" width="30%">
|
||||
<el-tabs v-model="tabShareType">
|
||||
<el-tab-pane :label="t('wechat')" name="wechat"></el-tab-pane>
|
||||
<el-tab-pane :label="t('weapp')" name="weapp"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-form :model="shareFormData[tabShareType]" label-width="90px" ref="shareFormRef" :rules="shareFormRules">
|
||||
<el-form-item :label="t('sharePage')">
|
||||
<span>{{ sharePage }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('shareTitle')" prop="title">
|
||||
<el-input v-model="shareFormData[tabShareType].title" :placeholder="t('shareTitlePlaceholder')"
|
||||
clearable maxlength="30" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('shareDesc')" prop="desc" v-if="tabShareType == 'wechat'">
|
||||
<el-input v-model="shareFormData[tabShareType].desc" :placeholder="t('shareDescPlaceholder')"
|
||||
type="textarea" rows="4" clearable maxlength="100" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('shareImageUrl')" prop="url">
|
||||
<upload-image v-model="shareFormData[tabShareType].url" :limit="1"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="shareDialogVisible = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" @click="shareEvent(shareFormRef)">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getDiyPageList, deleteDiyPage, getDiyTemplate, editDiyPageShare } from '@/app/api/diy'
|
||||
import { ElMessageBox, FormInstance } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { getUrl } from '@/app/api/sys'
|
||||
import {reactive, ref, computed} from 'vue'
|
||||
import {t} from '@/lang'
|
||||
import {getDiyPageList, deleteDiyPage, getDiyTemplate, editDiyPageShare} from '@/app/api/diy'
|
||||
import {ElMessageBox, FormInstance} from 'element-plus'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import {getUrl} from '@/app/api/sys'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@ -139,25 +146,25 @@
|
||||
const formData = reactive({
|
||||
title: '',
|
||||
type: '',
|
||||
template:''
|
||||
template: ''
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
title: [
|
||||
{ required: true, message: t('titlePlaceholder'), trigger: 'blur' },
|
||||
{required: true, message: t('titlePlaceholder'), trigger: 'blur'},
|
||||
],
|
||||
type: [
|
||||
{ required: true, message: t('pageTypePlaceholder'), trigger: 'blur' },
|
||||
{required: true, message: t('pageTypePlaceholder'), trigger: 'blur'},
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const pageTypeData = computed(()=>{
|
||||
let data:any = '';
|
||||
const pageTypeData = computed(() => {
|
||||
let data: any = '';
|
||||
formData.template = '';
|
||||
if(formData.type){
|
||||
if (formData.type) {
|
||||
data = pageType[formData.type].template;
|
||||
}
|
||||
return data;
|
||||
@ -172,7 +179,7 @@
|
||||
if (valid) {
|
||||
dialogVisible.value = false;
|
||||
let url = `/decorate/edit?type=${formData.type}&title=${formData.title}`;
|
||||
if(formData.template) url += `&template=${formData.template}`;
|
||||
if (formData.template) url += `&template=${formData.template}`;
|
||||
router.push(url);
|
||||
}
|
||||
})
|
||||
@ -185,7 +192,7 @@
|
||||
getDomain();
|
||||
|
||||
// 获取自定义页面模板
|
||||
getDiyTemplate({ mode : '' }).then(res => {
|
||||
getDiyTemplate({mode: ''}).then(res => {
|
||||
for (let key in res.data) {
|
||||
pageType[key] = res.data[key]
|
||||
}
|
||||
@ -200,7 +207,7 @@
|
||||
searchParam: {
|
||||
"title": "",
|
||||
"type": '',
|
||||
'mode':''
|
||||
'mode': ''
|
||||
}
|
||||
})
|
||||
|
||||
@ -238,11 +245,11 @@
|
||||
// 删除自定义页面
|
||||
const deleteEvent = (id: number) => {
|
||||
ElMessageBox.confirm(t('diyPageDeleteTips'), t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning',
|
||||
}
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning',
|
||||
}
|
||||
).then(() => {
|
||||
deleteDiyPage(id).then(() => {
|
||||
loadDiyPageList()
|
||||
@ -253,15 +260,11 @@
|
||||
|
||||
// 跳转去预览
|
||||
const toPreview = (data: any) => {
|
||||
let query: any = {};
|
||||
if (data.id) {
|
||||
query.id = data.id;
|
||||
} else if (data.name) {
|
||||
query.name = data.name;
|
||||
}
|
||||
let url = router.resolve({
|
||||
path: '/decorate/preview',
|
||||
query
|
||||
path: '/preview/wap',
|
||||
query: {
|
||||
page: data.type_page + '?id=' + data.id
|
||||
}
|
||||
});
|
||||
window.open(url.href);
|
||||
}
|
||||
@ -290,8 +293,8 @@
|
||||
shareFormId.value = row.id;
|
||||
sharePage.value = row.title;
|
||||
let share = row.share ? JSON.parse(row.share) : {
|
||||
wechat: { title: '', desc: '', url: '' },
|
||||
weapp: { title: '', url: '' }
|
||||
wechat: {title: '', desc: '', url: ''},
|
||||
weapp: {title: '', url: ''}
|
||||
};
|
||||
if (share) {
|
||||
shareFormData.wechat = share.wechat;
|
||||
@ -318,7 +321,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined)=>{
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields();
|
||||
loadDiyPageList();
|
||||
@ -327,9 +330,9 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.copy {
|
||||
background: var(--el-color-primary) !important;
|
||||
color: var(--el-color-white) !important;
|
||||
}
|
||||
.copy {
|
||||
background: var(--el-color-primary) !important;
|
||||
color: var(--el-color-white) !important;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
@ -1,246 +1,255 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-[20px]">{{pageName}}</span>
|
||||
</div>
|
||||
<el-card class="box-card !border-none base-bg !px-[35px]" shadow="never">
|
||||
<el-row class="flex">
|
||||
<el-col :span="8" class="min-w-[100px]">
|
||||
<div class="statistic-card">
|
||||
<el-statistic :value="accountStat.pay ? Number.parseFloat(accountStat.pay).toFixed(2) : '0.00'"></el-statistic>
|
||||
<div class="statistic-footer">
|
||||
<div class="footer-item text-[14px] text-[#666]">
|
||||
<span>{{ t('totalPay') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-[20px]">{{ pageName }}</span>
|
||||
</div>
|
||||
<el-card class="box-card !border-none base-bg !px-[35px]" shadow="never">
|
||||
<el-row class="flex">
|
||||
<el-col :span="8" class="min-w-[100px]">
|
||||
<div class="statistic-card">
|
||||
<el-statistic :value="accountStat.refund ? Number.parseFloat(accountStat.refund).toFixed(2) : '0.00'"></el-statistic>
|
||||
<div class="statistic-footer">
|
||||
<div class="footer-item text-[14px] text-[#666]">
|
||||
<span>{{ t('totalRefund') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8" class="min-w-[100px]">
|
||||
<el-statistic
|
||||
:value="accountStat.pay ? Number.parseFloat(accountStat.pay).toFixed(2) : '0.00'"></el-statistic>
|
||||
<div class="statistic-footer">
|
||||
<div class="footer-item text-[14px] text-[#666]">
|
||||
<span>{{ t('totalPay') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8" class="min-w-[100px]">
|
||||
<div class="statistic-card">
|
||||
<el-statistic :value="accountStat.transfer ? Number.parseFloat(accountStat.transfer).toFixed(2) : '0.00'"></el-statistic>
|
||||
<div class="statistic-footer">
|
||||
<div class="footer-item text-[14px] text-[#666]">
|
||||
<span>{{ t('totalTransfer') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-statistic
|
||||
:value="accountStat.refund ? Number.parseFloat(accountStat.refund).toFixed(2) : '0.00'"></el-statistic>
|
||||
<div class="statistic-footer">
|
||||
<div class="footer-item text-[14px] text-[#666]">
|
||||
<span>{{ t('totalRefund') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8" class="min-w-[100px]">
|
||||
<div class="statistic-card">
|
||||
<el-statistic
|
||||
:value="accountStat.transfer ? Number.parseFloat(accountStat.transfer).toFixed(2) : '0.00'"></el-statistic>
|
||||
<div class="statistic-footer">
|
||||
<div class="footer-item text-[14px] text-[#666]">
|
||||
<span>{{ t('totalTransfer') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="siteAccountLogTable.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('type')" class="items-center">
|
||||
<el-select v-model="siteAccountLogTable.searchParam.type" class="m-2" :placeholder="t('accountType')" >
|
||||
<el-option
|
||||
v-for="(item, index) in accountType"
|
||||
:key="index"
|
||||
:label="item"
|
||||
:value="index"
|
||||
/>
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="siteAccountLogTable.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('type')" class="items-center">
|
||||
<el-select v-model="siteAccountLogTable.searchParam.type" class="m-2"
|
||||
:placeholder="t('accountType')">
|
||||
<el-option v-for="(item, index) in accountType" :key="index" :label="item" :value="index"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('tradeNo')" prop="trade_no">
|
||||
<el-input v-model="siteAccountLogTable.searchParam.trade_no" :placeholder="t('tradeNoPlaceholder')" />
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('tradeNo')" prop="trade_no">
|
||||
<el-input v-model="siteAccountLogTable.searchParam.trade_no"
|
||||
:placeholder="t('tradeNoPlaceholder')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('createTime')" prop="create_time">
|
||||
<el-date-picker v-model="siteAccountLogTable.searchParam.create_time" type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
|
||||
:end-placeholder="t('endDate')" />
|
||||
<el-date-picker v-model="siteAccountLogTable.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>
|
||||
<el-button type="primary" @click="loadSiteAccountLogList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadSiteAccountLogList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="mt-[10px]">
|
||||
<el-table :data="siteAccountLogTable.data" size="large" v-loading="siteAccountLogTable.loading">
|
||||
<template #empty>
|
||||
<span>{{ !siteAccountLogTable.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
<el-table-column prop="trade_no" :label="t('tradeNo')" min-width="120" />
|
||||
<el-table-column prop= "type_name" :label="t('type')" min-width="120" />
|
||||
<el-table-column prop="money" :label="t('money')" min-width="120" align="right" />
|
||||
<el-table-column :label="t('createTime')" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.create_time || '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<div class="mt-[10px]">
|
||||
<el-table :data="siteAccountLogTable.data" size="large" v-loading="siteAccountLogTable.loading">
|
||||
<template #empty>
|
||||
<span>{{ !siteAccountLogTable.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
<el-table-column prop="trade_no" :label="t('tradeNo')" min-width="120"/>
|
||||
<el-table-column prop="type_name" :label="t('type')" min-width="120"/>
|
||||
<el-table-column prop="type_name" :label="t('payType')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.type == 'pay'">{{ row.pay_info.type_name || '' }}</span>
|
||||
<span v-else-if="row.type == 'refund'">{{ row.pay_info.type_name || '' }}</span>
|
||||
<span v-else-if="row.type == 'transfer'">{{ row.pay_info.transfer_type_name || '' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="money" :label="t('money')" min-width="120" align="right"/>
|
||||
<el-table-column :label="t('createTime')" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.create_time || '' }}
|
||||
</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('detail') }}</el-button>
|
||||
</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('detail') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="siteAccountLogTable.page" v-model:page-size="siteAccountLogTable.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="siteAccountLogTable.total"
|
||||
@size-change="loadSiteAccountLogList()" @current-change="loadSiteAccountLogList" />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="siteAccountLogTable.page"
|
||||
v-model:page-size="siteAccountLogTable.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="siteAccountLogTable.total" @size-change="loadSiteAccountLogList()"
|
||||
@current-change="loadSiteAccountLogList"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-dialog v-model="showDialog" :title="t('accountDetail')" width="550px" :destroy-on-close="true">
|
||||
<el-form :model="formData" label-width="110px" ref="formRef" class="page-form">
|
||||
|
||||
<el-form-item :label="t('tradeNo')" >
|
||||
<div class="input-width"> {{ formData.trade_no }} </div>
|
||||
<el-form :model="formData" label-width="110px" ref="formRef" class="page-form">
|
||||
|
||||
<el-form-item :label="t('tradeNo')">
|
||||
<div class="input-width"> {{ formData.trade_no }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('type')" >
|
||||
<div class="input-width"> {{ formData.type_name }} </div>
|
||||
<el-form-item :label="t('type')">
|
||||
<div class="input-width"> {{ formData.type_name }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('money')" >
|
||||
<div class="input-width"> {{ formData.money }} </div>
|
||||
<el-form-item :label="t('money')">
|
||||
<div class="input-width"> {{ formData.money }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('createTime')" >
|
||||
<div class="input-width"> {{ formData.create_time }} </div>
|
||||
<el-form-item :label="t('createTime')">
|
||||
<div class="input-width"> {{ formData.create_time }}</div>
|
||||
</el-form-item>
|
||||
<div v-if="formData.type == 'transfer'">
|
||||
<el-form-item :label="t('transferNo')" >
|
||||
<div class="input-width"> {{ formData.pay_info.transfer_no }} </div>
|
||||
<el-form-item :label="t('transferNo')">
|
||||
<div class="input-width"> {{ formData.pay_info.transfer_no }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('transferTime')" >
|
||||
<div class="input-width"> {{ formData.pay_info.transfer_time }} </div>
|
||||
<el-form-item :label="t('transferTime')">
|
||||
<div class="input-width"> {{ formData.pay_info.transfer_time }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('transferType')" >
|
||||
<div class="input-width"> {{ formData.pay_info.transfer_type }} </div>
|
||||
<el-form-item :label="t('transferType')">
|
||||
<div class="input-width"> {{ formData.pay_info.transfer_type }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('transferMoney')" >
|
||||
<div class="input-width"> {{ formData.pay_info.money }} </div>
|
||||
<el-form-item :label="t('transferMoney')">
|
||||
<div class="input-width"> {{ formData.pay_info.money }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('transferRemark')" >
|
||||
<div class="input-width"> {{ formData.pay_info.remark }} </div>
|
||||
<el-form-item :label="t('transferRemark')">
|
||||
<div class="input-width"> {{ formData.pay_info.remark }}</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="formData.type == 'refund'">
|
||||
<el-form-item :label="t('outTradeNo')" >
|
||||
<div class="input-width"> {{ formData.pay_info.out_trade_no }} </div>
|
||||
<el-form-item :label="t('outTradeNo')">
|
||||
<div class="input-width"> {{ formData.pay_info.out_trade_no }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('createTime')" >
|
||||
<div class="input-width"> {{ formData.pay_info.create_time }} </div>
|
||||
<el-form-item :label="t('createTime')">
|
||||
<div class="input-width"> {{ formData.pay_info.create_time }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('refundMoney')" >
|
||||
<div class="input-width"> {{ formData.pay_info.money }} </div>
|
||||
<el-form-item :label="t('refundMoney')">
|
||||
<div class="input-width"> {{ formData.pay_info.money }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('failReason')" >
|
||||
<div class="input-width"> {{ formData.pay_info.fail_reason }} </div>
|
||||
<el-form-item :label="t('failReason')">
|
||||
<div class="input-width"> {{ formData.pay_info.fail_reason }}</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="formData.type == 'pay'">
|
||||
<el-form-item :label="t('outTradeNo')" >
|
||||
<div class="input-width"> {{ formData.pay_info.out_trade_no }} </div>
|
||||
<el-form-item :label="t('outTradeNo')">
|
||||
<div class="input-width"> {{ formData.pay_info.out_trade_no }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('createTime')" >
|
||||
<div class="input-width"> {{ formData.pay_info.create_time }} </div>
|
||||
<el-form-item :label="t('createTime')">
|
||||
<div class="input-width"> {{ formData.pay_info.create_time }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('money')" >
|
||||
<div class="input-width"> {{ formData.pay_info.money }} </div>
|
||||
<el-form-item :label="t('money')">
|
||||
<div class="input-width"> {{ formData.pay_info.money }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('body')" >
|
||||
<div class="input-width"> {{ formData.pay_info.body }} </div>
|
||||
<el-form-item :label="t('body')">
|
||||
<div class="input-width"> {{ formData.pay_info.body }}</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="showDialog = false">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="showDialog = false">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getAccountList, getAccountStat, getAccountType } from '@/app/api/pay'
|
||||
import { img } from '@/utils/common'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import {reactive, ref} from 'vue'
|
||||
import {t} from '@/lang'
|
||||
import {getAccountList, getAccountInfo, getAccountStat, getAccountType} from '@/app/api/pay'
|
||||
import {img} from '@/utils/common'
|
||||
import type {FormInstance} from 'element-plus'
|
||||
import {useRoute} from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title;
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
|
||||
let siteAccountLogTable = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam:{
|
||||
type: "",
|
||||
money: "",
|
||||
trade_no: "",
|
||||
create_time: ""
|
||||
}
|
||||
})
|
||||
|
||||
const searchFormRef = ref<FormInstance>()
|
||||
|
||||
/**
|
||||
* 获取站点账单记录列表
|
||||
*/
|
||||
const loadSiteAccountLogList = (page: number = 1) => {
|
||||
siteAccountLogTable.loading = true
|
||||
siteAccountLogTable.page = page
|
||||
|
||||
getAccountList({
|
||||
page: siteAccountLogTable.page,
|
||||
limit: siteAccountLogTable.limit,
|
||||
...siteAccountLogTable.searchParam
|
||||
}).then(res => {
|
||||
siteAccountLogTable.loading = false
|
||||
siteAccountLogTable.data = res.data.data
|
||||
siteAccountLogTable.total = res.data.total
|
||||
}).catch(() => {
|
||||
siteAccountLogTable.loading = false
|
||||
const siteAccountLogTable = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {
|
||||
type: '',
|
||||
money: '',
|
||||
trade_no: '',
|
||||
create_time: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
loadSiteAccountLogList()
|
||||
|
||||
const searchFormRef = ref<FormInstance>()
|
||||
|
||||
/**
|
||||
* 获取站点账单记录列表
|
||||
*/
|
||||
const loadSiteAccountLogList = (page: number = 1) => {
|
||||
siteAccountLogTable.loading = true
|
||||
siteAccountLogTable.page = page
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
getAccountList({
|
||||
page: siteAccountLogTable.page,
|
||||
limit: siteAccountLogTable.limit,
|
||||
...siteAccountLogTable.searchParam
|
||||
}).then(res => {
|
||||
siteAccountLogTable.loading = false
|
||||
siteAccountLogTable.data = res.data.data
|
||||
siteAccountLogTable.total = res.data.total
|
||||
}).catch(() => {
|
||||
siteAccountLogTable.loading = false
|
||||
})
|
||||
}
|
||||
loadSiteAccountLogList()
|
||||
}
|
||||
|
||||
const accountType = ref([])
|
||||
const checkAccountType = () => {
|
||||
getAccountType().then(res=>{
|
||||
accountType.value = res.data
|
||||
})
|
||||
}
|
||||
checkAccountType()
|
||||
const showDialog = ref(false)
|
||||
const formData = ref([]);
|
||||
const detailEvent = (info) => {
|
||||
showDialog.value = true
|
||||
formData.value = info
|
||||
}
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
loadSiteAccountLogList()
|
||||
}
|
||||
|
||||
const accountStat = ref([])
|
||||
const checkAccountStat = async () => {
|
||||
accountStat.value = await (await getAccountStat()).data
|
||||
}
|
||||
checkAccountStat()
|
||||
const accountType = ref([])
|
||||
const checkAccountType = () => {
|
||||
getAccountType().then(res => {
|
||||
accountType.value = res.data
|
||||
})
|
||||
}
|
||||
checkAccountType()
|
||||
const showDialog = ref(false)
|
||||
const formData = ref([])
|
||||
const detailEvent = (info: any) => {
|
||||
getAccountInfo(info.id).then(({data}) => {
|
||||
formData.value = data
|
||||
showDialog.value = true
|
||||
})
|
||||
}
|
||||
|
||||
const accountStat = ref([])
|
||||
const checkAccountStat = async () => {
|
||||
accountStat.value = await (await getAccountStat()).data
|
||||
}
|
||||
checkAccountStat()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
125
admin/src/app/views/finance/pay_refund.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-[20px]">{{pageName}}</span>
|
||||
</div>
|
||||
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="payRefundTable.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('refundNo')" prop="refund_no">
|
||||
<el-input v-model="payRefundTable.searchParam.refund_no" :placeholder="t('refundNoPlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('createTime')" prop="create_time">
|
||||
<el-date-picker v-model="payRefundTable.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>
|
||||
<el-button type="primary" @click="loadPayRefundList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="mt-[10px]">
|
||||
<el-table :data="payRefundTable.data" size="large" v-loading="payRefundTable.loading">
|
||||
<template #empty>
|
||||
<span>{{ !payRefundTable.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
<el-table-column prop="refund_no" :label="t('refundNo')" min-width="200" />
|
||||
<el-table-column prop="money" :label="t('refundMoney')" min-width="120" />
|
||||
<el-table-column prop="type_name" :label="t('payType')" min-width="120" />
|
||||
<el-table-column prop="status_name" :label="t('status')" min-width="120" />
|
||||
<el-table-column prop="create_time" :label="t('createTime')" min-width="160" />
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="infoEvent(row)">{{ t('info') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="payRefundTable.page" v-model:page-size="payRefundTable.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="payRefundTable.total"
|
||||
@size-change="loadPayRefundList()" @current-change="loadPayRefundList" />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getPayRefundPages } from '@/app/api/pay'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const pageName = route.meta.title
|
||||
|
||||
const payRefundTable = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {
|
||||
refund_no: '',
|
||||
create_time: []
|
||||
}
|
||||
})
|
||||
|
||||
const searchFormRef = ref<FormInstance>()
|
||||
|
||||
// 选中数据
|
||||
const selectData = ref<any[]>([])
|
||||
|
||||
// 字典数据
|
||||
|
||||
/**
|
||||
* 获取商品标签列表
|
||||
*/
|
||||
const loadPayRefundList = (page: number = 1) => {
|
||||
payRefundTable.loading = true
|
||||
payRefundTable.page = page
|
||||
|
||||
getPayRefundPages({
|
||||
page: payRefundTable.page,
|
||||
limit: payRefundTable.limit,
|
||||
...payRefundTable.searchParam
|
||||
}).then(res => {
|
||||
payRefundTable.loading = false
|
||||
payRefundTable.data = res.data.data
|
||||
payRefundTable.total = res.data.total
|
||||
}).catch(() => {
|
||||
payRefundTable.loading = false
|
||||
})
|
||||
}
|
||||
loadPayRefundList()
|
||||
|
||||
const infoEvent = (data) => {
|
||||
router.push('/member/refund/detail?refund_no=' + data.refund_no)
|
||||
}
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
loadPayRefundList()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 多行超出隐藏 */
|
||||
.multi-hidden {
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
||||
161
admin/src/app/views/finance/refund_detail.vue
Normal file
@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<div class="detail-head">
|
||||
<div class="left" @click="router.push({ path: '/member/refund' })">
|
||||
<span class="iconfont iconxiangzuojiantou !text-xs"></span>
|
||||
<span class="ml-[1px]">{{ t('returnToPreviousPage') }}</span>
|
||||
</div>
|
||||
<span class="adorn">|</span>
|
||||
<span class="right">{{ pageName }}</span>
|
||||
</div>
|
||||
<el-card class="box-card !border-none relative" shadow="never" v-if="formData">
|
||||
<div class="flex px-[20px] py-[20px] justify-between">
|
||||
<span>{{ t('refundMoney') }}:<span>¥{{ formData.money }}</span></span>
|
||||
<span>{{ t('refundNo') }}:<span>{{ formData.refund_no }}</span></span>
|
||||
</div>
|
||||
<el-table :data="refundList" size="large">
|
||||
<el-table-column prop="out_trade_no" :label="t('outTradeNo')" min-width="200" />
|
||||
<el-table-column prop="create_time" :label="t('createTime')" min-width="160" />
|
||||
<el-table-column prop="refund_type_name" :label="t('refundTypeName')" min-width="120" />
|
||||
<el-table-column :label="t('refundMoney')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<span>¥{{ row.money }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status_name" :label="t('statusName')" min-width="120" />
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="transferEvent(row)" v-if="row.status == 'wait'">{{ t('transfer') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="transferDialog" :title="title" width="500px" class="diy-dialog-wrap"
|
||||
:destroy-on-close="true">
|
||||
<el-form :model="transfeFormData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
|
||||
<el-form-item :label="t('transferType')">
|
||||
<el-radio-group v-model="transfeFormData.refund_type">
|
||||
<el-radio :label="item.value" v-for="(item, index) in refundTypeData" :key="index">{{ item.name }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('refundMoney')" >
|
||||
<span>{{ transfeFormData.refund_money }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('voucher')" v-if="transfeFormData.refund_type == 'offline'">
|
||||
<upload-image v-model="transfeFormData.voucher" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="transferDialog = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{
|
||||
t('confirm')
|
||||
}}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getPayRefundInfo, getRefundType, getRefundTransfer } from '@/app/api/pay'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { img, getAppType } from '@/utils/common'
|
||||
import { ElMessageBox, FormInstance } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const pageName = route.meta.title
|
||||
const refundNo: string = route.query.refund_no
|
||||
const loading = ref(true)
|
||||
const appType = getAppType()
|
||||
|
||||
const refundList = ref([])
|
||||
const formData: Record<string, any> | null = ref(null)
|
||||
|
||||
const setFormData = async (refundNo: string = '') => {
|
||||
loading.value = true
|
||||
formData.value = null
|
||||
await getPayRefundInfo(refundNo)
|
||||
.then(({ data }) => {
|
||||
formData.value = data
|
||||
refundList.value.push(data)
|
||||
})
|
||||
|
||||
.catch(() => {
|
||||
|
||||
})
|
||||
loading.value = false
|
||||
}
|
||||
if (refundNo) setFormData(refundNo)
|
||||
else loading.value = false
|
||||
|
||||
const refundTypeData = ref([])
|
||||
getRefundType().then((data) => {
|
||||
Object.keys(data.data).forEach((key: string) => {
|
||||
refundTypeData.value.push({ value: key, name: data.data[key] })
|
||||
})
|
||||
})
|
||||
|
||||
const transferDialog = ref(false)
|
||||
const transferEvent = (data) => {
|
||||
transferDialog.value = true
|
||||
transfeFormData.refund_no = data.refund_no
|
||||
transfeFormData.refund_money = data.money
|
||||
}
|
||||
|
||||
const initialFormData = {
|
||||
refund_no: '',
|
||||
refund_type: 'back',
|
||||
voucher: '',
|
||||
refund_money: 0.00
|
||||
}
|
||||
const transfeFormData: Record<string, any> = reactive({ ...initialFormData })
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
label_name: [
|
||||
{ required: true, message: t('labelNamePlaceholder'), trigger: 'blur' }
|
||||
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const confirm = async (formEl: FormInstance | undefined) => {
|
||||
if (loading.value || !formEl) return
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
const data = transfeFormData
|
||||
getRefundTransfer(data).then(res => {
|
||||
loading.value = false
|
||||
transferDialog.value = false
|
||||
refundList.value = []
|
||||
setFormData(refundNo)
|
||||
}).catch(err => {
|
||||
transferDialog.value = false
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// const transferEvent = () => {
|
||||
// const routeUrl = router.resolve({
|
||||
// path: '/member/refund',
|
||||
// query: { refund_id: formData.value.refund_id }
|
||||
// })
|
||||
// window.open(routeUrl.href, '_blank')
|
||||
// }
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
151
admin/src/app/views/index/app_manage.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div class="h-[480px] box-border pt-[20px] px-[20px]" v-loading="loading">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="font-600 text-[20px] text-[#222]">{{ t('app') }}</div>
|
||||
<el-button @click="toAppStore">
|
||||
<el-icon class="mr-[2px]"><Download /></el-icon>
|
||||
<span>{{t('appStore')}}</span>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="flex flex-wrap mt-[28px]">
|
||||
<template v-for="(item, index) in detail.appList" :key="index">
|
||||
<div class="app-item w-[284px] box-border p-[18px] pb-[24px] bg-[#fff] rounded-[8px] cursor-pointer mr-[24px] mb-[24px]"
|
||||
@click="itemPath(item)">
|
||||
<div class="flex items-center">
|
||||
<el-image class="w-[40px] h-[40px] rounded-[8px]" :src="img(item.icon)" fit="contain">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<img class="w-[40px] h-[40px] rounded-[8px]"
|
||||
src="@/app/assets/images/app_store/app_store_default.png"/>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<div class="flex-1 font-600 text-[14px] text-[#222] ml-[12px]">{{ item.title }}</div>
|
||||
</div>
|
||||
<div class="font-500 text-[13px] text-[#6D7278] mt-[14px]">{{ item.desc }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-empty class="mx-auto overview-empty" v-if="!detail.appList.length && !loading">
|
||||
<template #image>
|
||||
<div class="w-[230px] mx-auto">
|
||||
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="">
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<p class="flex items-center">
|
||||
<span>{{ t('descriptionLeft') }}</span>
|
||||
<el-link type="primary" @click="toAppStore" class="mx-[5px]">{{ t('link') }}</el-link>
|
||||
<span>{{ t('descriptionRight') }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</el-empty>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {reactive, ref, onMounted, computed} from 'vue'
|
||||
import {t} from '@/lang'
|
||||
import {getAuthaddon} from '@/app/api/auth'
|
||||
import {img} from '@/utils/common'
|
||||
import {useRouter} from 'vue-router'
|
||||
import storage from '@/utils/storage'
|
||||
import {findFirstValidRoute} from '@/router/routers'
|
||||
import {UserFilled} from '@element-plus/icons-vue'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
const router = useRouter()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const loading = ref(true)
|
||||
const detail = reactive({
|
||||
appList: []
|
||||
})
|
||||
const appLink: any = ref({})
|
||||
|
||||
const getAuthaddonFn = () => {
|
||||
loading.value = true
|
||||
getAuthaddon().then(res => {
|
||||
res.data.forEach((item: any, index) => {
|
||||
if (item.type == 'app') {
|
||||
detail.appList.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
userStore.routers.forEach((item, index) => {
|
||||
if (item.children && item.children.length) {
|
||||
item.name = findFirstValidRoute(item.children)
|
||||
appLink.value[item.meta.app] = findFirstValidRoute(item.children)
|
||||
} else {
|
||||
appLink.value[item.meta.app] = item.name
|
||||
}
|
||||
})
|
||||
loading.value = false
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
getAuthaddonFn()
|
||||
|
||||
const itemPath = (data: any) => {
|
||||
storage.set({key: 'menuAppStorage', data: data.key})
|
||||
storage.set({key: 'plugMenuTypeStorage', data: ''})
|
||||
|
||||
const appMenuList = userStore.appMenuList
|
||||
appMenuList.push(data.key)
|
||||
userStore.setAppMenuList(appMenuList)
|
||||
|
||||
let name: any = appLink.value[data.key];
|
||||
|
||||
router.push({name: name})
|
||||
|
||||
}
|
||||
|
||||
const goAppManage = () => {
|
||||
router.push('/app_manage')
|
||||
}
|
||||
|
||||
const goRouter = () => {
|
||||
window.open('https://www.niucloud.com/product')
|
||||
}
|
||||
|
||||
// 跳转至开发者
|
||||
const toAppStore = () => {
|
||||
router.push('/app_manage/app_store')
|
||||
}
|
||||
|
||||
const goNiucloud = () => {
|
||||
window.open('https://www.niucloud.com')
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
userStore.logout();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main-container {
|
||||
background: linear-gradient(180deg, rgba(253, 253, 253, 0.24) 0%, #FAFAFA 100%);
|
||||
min-height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
.overview-top {
|
||||
background-image: url('@/app/assets/images/index/overview.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.app-item {
|
||||
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.overview-empty .el-empty__image {
|
||||
width: auto !important;
|
||||
}
|
||||
</style>
|
||||
102
admin/src/app/views/index/edit_personal.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="main-container w-full p-5 bg-white" v-loading="loading">
|
||||
<div class="flex justify-between items-center h-[32px] mb-4">
|
||||
<span class="text-[20px]">{{ t('editPersonal') }}</span>
|
||||
</div>
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<el-form :model="saveInfo" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
|
||||
<el-form-item :label="t('headImg')">
|
||||
<upload-image v-model="saveInfo.head_img" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('userName')">
|
||||
<el-input v-model="saveInfo.username" clearable class="input-width" :readonly="true"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('realName')">
|
||||
<el-input v-model="saveInfo.real_name" :placeholder="t('realNamePlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="flex justify-center mt-[50px]">
|
||||
<el-button type="primary" @click="submitForm(formRef)">{{ t('save') }}</el-button>
|
||||
<el-button type="primary" @click="returnFn(formRef)">{{ t('cancel') }}</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
|
||||
import { img } from '@/utils/common'
|
||||
import { getUserInfo,setUserInfo } from '@/app/api/personal'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
// 提交信息
|
||||
let saveInfo = reactive({
|
||||
head_img: '',
|
||||
real_name: '',
|
||||
username: ''
|
||||
});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const loading = ref(true);
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
const getUserInfoFn = () => {
|
||||
loading.value = true;
|
||||
getUserInfo().then(res => {
|
||||
loading.value = false;
|
||||
|
||||
let data = res.data;
|
||||
saveInfo.head_img = data.head_img;
|
||||
saveInfo.real_name = data.real_name;
|
||||
saveInfo.username = data.username;
|
||||
}).catch(() => {
|
||||
loading.value = false;
|
||||
})
|
||||
}
|
||||
getUserInfoFn();
|
||||
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (loading.value || !formEl) return
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
|
||||
setUserInfo(saveInfo).then((res: any) => {
|
||||
loading.value = false;
|
||||
}).catch((err: any) => {
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
}
|
||||
const returnFn = ()=>{
|
||||
router.push('/user/center')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.personal-body){
|
||||
background-color: #fff;
|
||||
.el-form-item__content{
|
||||
.el-input{
|
||||
width: 250px;
|
||||
}
|
||||
.el-form-item__content{
|
||||
justify-content: space-between;
|
||||
}
|
||||
.el-button{
|
||||
margin-left: auto;
|
||||
}
|
||||
.personal-option{
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -65,7 +65,7 @@ const getAuthaddonFn = () => {
|
||||
}
|
||||
getAuthaddonFn()
|
||||
const installApp = () => {
|
||||
router.push('app/app_store')
|
||||
router.push('tools/app_store')
|
||||
}
|
||||
const itemPath = (key: any) => {
|
||||
let name = userStore.addonIndexRoute[key]
|
||||
|
||||
@ -1,33 +1,22 @@
|
||||
<template>
|
||||
<div class="main-container attachment-container" v-loading="loading">
|
||||
<div class="main-container w-full p-5 bg-white" v-loading="loading">
|
||||
<div class="flex justify-between items-center h-[32px] mb-4">
|
||||
<span class="text-[20px]">{{ t('personal') }}</span>
|
||||
<span class="text-[14px] text-[#999] cursor-pointer" @click="toEditPersonal">{{ t('editPersonal') }}</span>
|
||||
</div>
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<el-form :model="saveInfo" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
|
||||
<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" />
|
||||
<el-image class="w-[70px] h-[70px]" :src="img(saveInfo.head_img)" fit="contain" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('userName')">
|
||||
<el-input v-model="saveInfo.username" clearable class="input-width" :readonly="true"/>
|
||||
<div>{{saveInfo.username}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('realName')">
|
||||
<el-input v-model="saveInfo.real_name" :placeholder="t('realNamePlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('originalPassword')">
|
||||
<el-input v-model="saveInfo.original_password" type="password" :placeholder="t('originalPasswordPlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('password')">
|
||||
<el-input v-model="saveInfo.password" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
<div class="form-tip">{{t('passwordTip')}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('passwordCopy')">
|
||||
<el-input v-model="saveInfo.password_copy" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
<div>{{saveInfo.real_name}}</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<div class="fixed-footer-wrap">
|
||||
<div class="fixed-footer">
|
||||
<el-button type="primary" @click="submitForm(formRef)">{{ t('save') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -37,7 +26,9 @@ import { t } from '@/lang'
|
||||
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
|
||||
import { img } from '@/utils/common'
|
||||
import { getUserInfo,setUserInfo } from '@/app/api/personal'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
// 提交信息
|
||||
let saveInfo = reactive({
|
||||
head_img: '',
|
||||
@ -72,35 +63,11 @@ const getUserInfoFn = () => {
|
||||
}
|
||||
getUserInfoFn();
|
||||
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (loading.value || !formEl) return
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
let msg = "";
|
||||
if (saveInfo.password && !saveInfo.original_password) msg = t('originalPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && !saveInfo.password_copy) msg = t('newPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && saveInfo.password_copy && saveInfo.password != saveInfo.password_copy) msg = t('doubleCipherHint');
|
||||
if (msg) {
|
||||
ElNotification({
|
||||
type: 'error',
|
||||
message: msg,
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
|
||||
setUserInfo(saveInfo).then((res: any) => {
|
||||
loading.value = false;
|
||||
}).catch((err: any) => {
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
// 跳转至开发者
|
||||
const toEditPersonal = () => {
|
||||
router.push('/user/edit_center')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div class="main-container w-[375px] mx-auto my-[20px] relative">
|
||||
<div class="main-container w-[375px] mx-auto mt-[20px] mb-[40px] relative">
|
||||
|
||||
<div class="flex h-full">
|
||||
<iframe v-show="loadingIframe" class="w-[375px]" :src="wapPreview" frameborder="0" id="previewIframe"></iframe>
|
||||
<iframe v-show="loadingIframe" class="w-[375px]" :src="wapPreview" frameborder="0"
|
||||
id="previewIframe"></iframe>
|
||||
<div v-show="loadingDev" class="w-[375px] border border-slate-100 bg-body pt-[20px] px-[20px]">
|
||||
<div class="font-bold text-xl mb-[40px]">{{t('developTitle')}}</div>
|
||||
<div class="mb-[20px] flex flex-col">
|
||||
@ -22,7 +23,8 @@
|
||||
<el-form-item :label="t('link')" v-show="wapPreview">
|
||||
<el-input readonly :value="wapPreview">
|
||||
<template #append>
|
||||
<el-button @click="copyEvent(wapPreview)" class="bg-primary copy">{{ t('copy') }}</el-button>
|
||||
<el-button @click="copyEvent(wapPreview)" class="bg-primary copy">{{ t('copy') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
@ -63,7 +65,6 @@
|
||||
import {img} from '@/utils/common'
|
||||
import QRCode from "qrcode";
|
||||
import storage from '@/utils/storage'
|
||||
import {getPreviewData} from '@/app/api/diy'
|
||||
|
||||
const wapUrl = ref('')
|
||||
const wapDomain = ref('')
|
||||
@ -77,9 +78,7 @@
|
||||
|
||||
var time = new Date().getTime();
|
||||
const route = useRoute();
|
||||
route.query.id = route.query.id || 0;
|
||||
route.query.name = route.query.name || '';
|
||||
route.query.url = route.query.url || ''; // 路径优先级最高
|
||||
route.query.page = route.query.page || ''; // 页面路径
|
||||
|
||||
getUrl().then((res: any) => {
|
||||
wapUrl.value = res.data.wap_url;
|
||||
@ -120,8 +119,9 @@
|
||||
}
|
||||
|
||||
const setDomain = () => {
|
||||
if (route.query.url) {
|
||||
wapPreview.value = `${wapUrl.value}${route.query.url}`;
|
||||
if (route.query.page) {
|
||||
wapPreview.value = `${wapUrl.value}${route.query.page}`;
|
||||
// errorCorrectionLevel:密度容错率L(低)H(高)
|
||||
QRCode.toDataURL(wapPreview.value, {errorCorrectionLevel: 'L', margin: 0, width: 100}).then(url => {
|
||||
wapImage.value = url
|
||||
})
|
||||
@ -129,25 +129,6 @@
|
||||
setTimeout(() => {
|
||||
if (difference.value == 0) initLoad();
|
||||
}, 1000 * 2);
|
||||
} else {
|
||||
getPreviewData({
|
||||
id: route.query.id,
|
||||
name: route.query.name,
|
||||
}).then((res: any) => {
|
||||
let data = res.data;
|
||||
wapPreview.value = `${wapUrl.value}/${data.page}`;
|
||||
|
||||
QRCode.toDataURL(wapPreview.value, {errorCorrectionLevel: 'L', margin: 0, width: 100}).then(url => {
|
||||
wapImage.value = url
|
||||
})
|
||||
timeIframe.value = new Date().getTime();
|
||||
postMessage();
|
||||
setTimeout(() => {
|
||||
if (difference.value == 0) {
|
||||
initLoad();
|
||||
}
|
||||
}, 1000 * 2);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +136,7 @@
|
||||
window.addEventListener('message', (event) => {
|
||||
try {
|
||||
let data = JSON.parse(event.data);
|
||||
if(['appOnLaunch','appOnReady'].indexOf(data.type) != -1){
|
||||
if (['appOnLaunch', 'appOnReady'].indexOf(data.type) != -1) {
|
||||
loadingDev.value = false;
|
||||
loadingIframe.value = true;
|
||||
var loadTime = new Date().getTime();
|
||||
@ -168,7 +149,7 @@
|
||||
}, false);
|
||||
|
||||
// 将数据发送到uniapp
|
||||
const postMessage = ()=> {
|
||||
const postMessage = () => {
|
||||
let data = JSON.stringify({
|
||||
type: 'appOnReady',
|
||||
message: '加载完成'
|
||||
@ -177,7 +158,7 @@
|
||||
};
|
||||
|
||||
// 初始化加载状态
|
||||
const initLoad = ()=>{
|
||||
const initLoad = () => {
|
||||
loadingDev.value = true;
|
||||
loadingIframe.value = false;
|
||||
wapPreview.value = '';
|
||||
814
admin/src/app/views/index/store.vue
Normal file
@ -0,0 +1,814 @@
|
||||
<template>
|
||||
<div class="main-container w-full p-5 bg-white" v-loading="loading">
|
||||
<div class="flex justify-between items-center h-[32px] mb-4">
|
||||
<span class="text-[20px]">{{ t('localAppText') }}</span>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<!-- <div class="absolute right-0 top-[2px] flex items-center cursor-pointer z-[4] border border-inherit">
|
||||
<div class="flex item-center justify-center px-[6px] py-[4px]"
|
||||
:class="{ 'bg-slate-200': showType == 'small' }" @click="showType = 'small'">
|
||||
<img src="@/app/assets/images/app_store/switch_icon_1.png" class=" w-[16px] h-[16px]">
|
||||
</div>
|
||||
<div class="flex item-center justify-center px-[6px] py-[4px]"
|
||||
:class="{ 'bg-slate-200': showType == 'large' }" @click="showType = 'large'">
|
||||
<img src="@/app/assets/images/app_store/switch_icon_2.png" class="w-[16px] h-[16px] ">
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<el-tab-pane :label="installLabel" name="installed">
|
||||
<div class="flex flex-wrap px-2 plug-list pb-10">
|
||||
<div v-for="(item, index) in localList.installed" :key="index + 'a'"
|
||||
class="flex items-center cursor-pointer w-[295px] relative plug-item mr-4 mb-4"
|
||||
@click="getAddonDetialFn(item)" v-if="showType == 'small'">
|
||||
<div class="p-3">
|
||||
<img class="w-[44px] h-[44px] rounded-sm" v-if="item.icon" :src="item.icon" alt="">
|
||||
<img class="w-[44px] h-[44px] rounded-sm" v-else src="@/app/assets/images/icon-addon.png"
|
||||
alt="">
|
||||
</div>
|
||||
<div class="flex items-center w-[220px] border-b py-3 justify-between">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[14px] truncate w-[160px]">{{ item.title }}</span>
|
||||
<span class="text-xs text-gray-400 truncate w-[160px] mt-[4px]">{{ item.desc }}</span>
|
||||
</div>
|
||||
<el-button size="small" round class="!text-primary !border-primary !bg-transparent"
|
||||
@click.stop="uninstallAddonFn(item.key)">{{ t('unload')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap plug-list pb-10 plug-large" v-if="showType == 'large'">
|
||||
<div class="app-item cursor-pointer mr-4 mt-[20px] pb-2 bg-[#f7f7f7]"
|
||||
v-for="(item, index) in localList.installed" :key="index + 'a'"
|
||||
@click="getAddonDetialFn(item)">
|
||||
<div class="flex justify-center items-center">
|
||||
<img class="w-[240px] h-[120px]" v-if="item.cover" :src="item.cover" />
|
||||
<img v-else class="w-[240px] h-[120px]"
|
||||
src="@/app/assets/images/app_store/app_store_default.png" />
|
||||
</div>
|
||||
<div class="flex w-[240px] h-[46px]">
|
||||
<div class="text-left mt-2 w-[190px]">
|
||||
<p class="app-text text-[14px] text-[#222] pl-2">{{ item.title }}</p>
|
||||
<p class="app-text text-[12px] text-[#999] pl-2">{{ item.desc }}</p>
|
||||
</div>
|
||||
<div class="flex items-center pr-2">
|
||||
<el-button size="small" round class="!text-primary !border-primary !bg-transparent"
|
||||
@click.stop="uninstallAddonFn(item.key)">{{ t('unload')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<el-empty class="mx-auto overview-empty" v-if="!localList.installed.length && !loading">
|
||||
<template #image>
|
||||
<div class="w-[230px] mx-auto">
|
||||
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="">
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<p class="flex items-center">{{t('installed-empty')}}</p>
|
||||
</template>
|
||||
</el-empty>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="uninstalledLabel" name="uninstalled">
|
||||
<div class="flex flex-wrap px-2 plug-list pb-10">
|
||||
|
||||
<div v-for="(item, index) in localList.uninstalled" :key="index + 'a'"
|
||||
class="flex items-center cursor-pointer w-[295px] relative plug-item mr-4 mb-4"
|
||||
@click="getAddonDetialFn(item)" v-if="showType == 'small'">
|
||||
<div class="p-3">
|
||||
<img v-if="item.icon" class="w-[44px] h-[44px] rounded-sm" :src="item.icon" alt="">
|
||||
<img v-else class="w-[44px] h-[44px] rounded-sm" src="@/app/assets/images/icon-addon.png"
|
||||
alt="">
|
||||
</div>
|
||||
<div class="flex items-center w-[220px] border-b py-3 justify-between">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[14px] truncate w-[160px]">{{ item.title }}</span>
|
||||
<span class="text-xs text-gray-400 truncate w-[160px] mt-[4px]">{{ item.desc }}</span>
|
||||
</div>
|
||||
<el-button v-if="item.is_download" size="small" round
|
||||
class="!text-primary !border-primary !bg-transparent"
|
||||
@click.stop="installAddonFn(item.key)">{{ t('install')
|
||||
}}</el-button>
|
||||
<el-button v-else size="small" :loading="downloading == item.key"
|
||||
:disabled="downloading != ''" round
|
||||
class="!text-primary !border-primary !bg-transparent" @click.stop="downEvent(item)">{{
|
||||
t('down') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap plug-list pb-10 plug-large" v-if="showType == 'large'">
|
||||
<div class="app-item cursor-pointer mr-4 mt-[20px] pb-2 bg-[#f7f7f7]"
|
||||
v-for="(item, index) in localList.uninstalled" :key="index + 'a'"
|
||||
@click="getAddonDetialFn(item)">
|
||||
<div class="flex justify-center items-center">
|
||||
<img v-if="item.cover && !item.is_download" class="w-[240px] h-[120px]"
|
||||
:src="img(item.cover)" />
|
||||
<img v-else-if="item.cover && item.is_download" class="w-[240px] h-[120px]"
|
||||
:src="item.cover" />
|
||||
<img v-else class="w-[240px] h-[120px]"
|
||||
src="@/app/assets/images/app_store/app_store_default.png" />
|
||||
</div>
|
||||
<div class="flex w-[240px] h-[46px]">
|
||||
<div class="text-left mt-2 w-[190px]">
|
||||
<p class="app-text text-[14px] text-[#222] pl-2">{{ item.title }}</p>
|
||||
<p class="app-text text-[12px] text-[#999] pl-2">{{ item.desc }}</p>
|
||||
</div>
|
||||
<div class="flex items-center pr-2">
|
||||
<el-button v-if="item.is_download" size="small" round
|
||||
class="!text-primary !border-primary !bg-transparent"
|
||||
@click.stop="installAddonFn(item.key)">{{ t('install')
|
||||
}}</el-button>
|
||||
<el-button v-else size="small" :loading="downloading == item.key"
|
||||
:disabled="downloading != ''" round
|
||||
class="!text-primary !border-primary !bg-transparent"
|
||||
@click.stop="downEvent(item)">{{ t('down') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty class="mx-auto overview-empty" v-if="!localList.uninstalled.length && !loading">
|
||||
<template #image>
|
||||
<div class="w-[230px] mx-auto">
|
||||
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="">
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<p class="flex items-center">
|
||||
<span>{{ t('descriptionLeft') }}</span>
|
||||
<el-link type="primary" @click="goRouter" class="mx-[5px]">{{ t('link') }}</el-link>
|
||||
<span>{{ t('descriptionRight') }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</el-empty>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="allLabel" name="buy">
|
||||
|
||||
<div class="flex flex-wrap px-2 plug-list pb-10">
|
||||
|
||||
<template v-if="authinfo">
|
||||
<div v-for="(item, index) in localList.all" :key="index + 'a'"
|
||||
class="flex items-center cursor-pointer w-[295px] relative plug-item mr-4 mb-4"
|
||||
@click="getAddonDetialFn(item)" v-if="showType == 'small'">
|
||||
<div class="p-3">
|
||||
<img v-if="item.icon" class="w-[44px] h-[44px] rounded-sm" :src="item.icon" alt="">
|
||||
<img v-else class="w-[44px] h-[44px] rounded-sm" src="@/app/assets/images/icon-addon.png"
|
||||
alt="">
|
||||
</div>
|
||||
<div class="flex items-center w-[220px] border-b py-3 justify-between">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[14px] truncate w-[160px]">{{ item.title }}</span>
|
||||
<span class="text-xs text-gray-400 truncate w-[160px] mt-[4px]">{{ item.desc }}</span>
|
||||
</div>
|
||||
<el-button v-if="item.install_info && Object.keys(item.install_info)?.length" size="small"
|
||||
round class="!text-primary !border-primary !bg-transparent"
|
||||
@click.stop="uninstallAddonFn(item.key)">{{ t('unload')
|
||||
}}</el-button>
|
||||
<el-button v-else-if="item.is_download && item.install_info <= 0" size="small" round
|
||||
class="!text-primary !border-primary !bg-transparent"
|
||||
@click.stop="installAddonFn(item.key)">{{ t('install')
|
||||
}}</el-button>
|
||||
<el-button v-else size="small" :loading="downloading == item.key"
|
||||
:disabled="downloading != ''" round
|
||||
class="!text-primary !border-primary !bg-transparent" @click.stop="downEvent(item)">{{
|
||||
t('down') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap plug-list pb-10 plug-large" v-if="showType == 'large'">
|
||||
<div class="app-item cursor-pointer mr-4 mt-[20px] pb-2 bg-[#f7f7f7]"
|
||||
v-for="(item, index) in localList.all" :key="index + 'a'" @click="getAddonDetialFn(item)">
|
||||
<div class="flex justify-center items-center">
|
||||
<img v-if="item.icon && !item.is_download" class="w-[240px] h-[120px]"
|
||||
:src="img(item.icon)" />
|
||||
<img v-else-if="item.icon && item.is_download" class="w-[240px] h-[120px]"
|
||||
:src="item.icon" />
|
||||
<img v-else class="w-[240px] h-[120px]"
|
||||
src="@/app/assets/images/app_store/app_store_default.png" />
|
||||
</div>
|
||||
<div class="flex w-[240px] h-[46px]">
|
||||
<div class="text-left mt-2 w-[190px]">
|
||||
<p class="app-text text-[14px] text-[#222] pl-2">{{ item.title }}</p>
|
||||
<p class="app-text text-[12px] text-[#999] pl-2">{{ item.desc }}</p>
|
||||
</div>
|
||||
<div class="flex items-center pr-2">
|
||||
<el-button v-if="item.install_info && Object.keys(item.install_info)?.length"
|
||||
size="small" round class="!text-primary !border-primary !bg-transparent"
|
||||
@click.stop="uninstallAddonFn(item.key)">{{ t('unload')
|
||||
}}</el-button>
|
||||
<el-button v-else-if="item.is_download && item.install_info <= 0" size="small" round
|
||||
class="!text-primary !border-primary !bg-transparent"
|
||||
@click.stop="installAddonFn(item.key)">{{ t('install')
|
||||
}}</el-button>
|
||||
<el-button v-else size="small" round :loading="downloading == item.key"
|
||||
:disabled="downloading != ''"
|
||||
class="!text-primary !border-primary !bg-transparent"
|
||||
@click.stop="downEvent(item)">{{ t('down') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="!localList.all.length && !loading && !authinfo" class="mx-auto overview-empty flex flex-col items-center pt-14 pb-6">
|
||||
<div class="mb-[20px] text-sm text-[#888]">检测到当前账号尚未绑定授权,请先绑定授权!</div>
|
||||
<div class="flex flex-1 flex-wrap justify-center relative" >
|
||||
<el-button class="w-[154px] !h-[48px] mt-[8px]" type="primary" @click="authCodeApproveFn">授权码认证</el-button>
|
||||
<el-popover ref="getAuthCodeDialog" placement="bottom" :width="478" trigger="click" class="mt-[8px]">
|
||||
<div class="px-[18px] py-[8px]">
|
||||
<p class="leading-[32px] text-[14px]">您在官方应用市场购买任意一款应用,即可获得授权码。输入正确授权码认证通过后,即可支持在线升级和其它相关服务</p>
|
||||
<div class="flex justify-end mt-[36px]">
|
||||
<el-button class="w-[182px] !h-[48px]" plain @click="market">去应用市场逛逛</el-button>
|
||||
<el-button class="w-[100px] !h-[48px]" plain @click="getAuthCodeDialog.hide()">关闭</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-button class="w-[154px] !h-[48px] mt-[8px] !text-[var(--el-color-primary)] hover:!text-[var(--el-color-primary)] !bg-transparent" plain type="primary">如何获取授权码?</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="authCodeApproveDialog" title="授权码认证" width="400px">
|
||||
<el-form :model="formData" label-width="0" ref="formRef" :rules="formRules" class="page-form">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<el-form-item prop="auth_code">
|
||||
<el-input v-model="formData.auth_code" :placeholder="t('authCodePlaceholder')" class="input-width" clearable size="large" />
|
||||
</el-form-item>
|
||||
|
||||
<div class="mt-[20px]">
|
||||
<el-form-item prop="auth_secret">
|
||||
<el-input v-model="formData.auth_secret" clearable :placeholder="t('authSecretPlaceholder')" class="input-width" size="large" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-[10px] text-info">{{ t('authInfoTips') }}</div>
|
||||
|
||||
<div class="mt-[20px]">
|
||||
<el-button type="primary" class="w-full" size="large" :loading="saveLoading" @click="save(formRef)">{{ t('confirm') }}</el-button>
|
||||
</div>
|
||||
<div class="mt-[10px] text-right">
|
||||
<el-button type="primary" link @click="market">{{ t('notHaveAuth') }}</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 详情 -->
|
||||
<el-dialog v-model="appStoreShowDialog" :title="t('plugDetail')" width="500px" :destroy-on-close="true">
|
||||
<el-form :model="appStoreInfo" label-width="120px" ref="formRef" class="page-form">
|
||||
<el-form-item :label="t('title')">
|
||||
<div class="input-width"> {{ appStoreInfo.title }} </div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('desc')">
|
||||
<div class="input-width"> {{ appStoreInfo.desc }} </div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('author')">
|
||||
<div class="input-width"> {{ appStoreInfo.author }} </div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('version')">
|
||||
<div class="input-width"> {{ appStoreInfo.version }} </div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="appStoreShowDialog = false">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 安装弹窗 -->
|
||||
<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-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 class="min-h-[150px]">
|
||||
<div class="bg-[#fff] my-3" v-if="installCheckResult.dir">
|
||||
<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">
|
||||
<span>{{ t('path') }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span>{{ t('demand') }}</span>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span>{{ t('status') }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="pb-[10px] items pl-[15px]"
|
||||
v-for="item in installCheckResult.dir.is_readable">
|
||||
<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 in installCheckResult.dir.is_write">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="flex justify-end">
|
||||
<el-tooltip effect="dark" :content="t('installTips')" placement="top">
|
||||
<el-button type="default" :disabled="!installCheckResult.is_pass || cloudInstalling"
|
||||
:loading="localInstalling" @click="handleInstall">{{
|
||||
t('localInstall')
|
||||
}}</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="dark" :content="t('cloudInstallTips')" placement="top">
|
||||
<el-button type="primary" :disabled="!installCheckResult.is_pass || localInstalling"
|
||||
:loading="cloudInstalling" @click="handleCloudInstall">{{
|
||||
t('cloudInstall')
|
||||
}}</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="installStep == 2" class="h-[50vh] mt-[20px]">
|
||||
<terminal name="my-terminal" :context="currAddon" :init-log="null" :show-header="false"
|
||||
:show-log-time="true" />
|
||||
</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-for="item in installAfterTips" class="mb-[10px]">
|
||||
<el-alert :title="item" type="warning" :closable="false" />
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, computed, h } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getAddonLocal, uninstallAddon, installAddon, preInstallCheck, cloudInstallAddon, getAddonInstalltask, getAddonCloudInstallLog } from '@/app/api/addon'
|
||||
import { downloadVersion, getAuthinfo, setAuthinfo } from '@/app/api/module'
|
||||
import { TabsPaneContext, ElMessageBox, ElNotification, FormInstance, FormRules } from 'element-plus'
|
||||
import { img } from '@/utils/common'
|
||||
import { Terminal, api as terminalApi } from 'vue-web-terminal'
|
||||
import { useRouter } from 'vue-router'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const router = useRouter()
|
||||
const activeName = ref('installed')
|
||||
const loading = ref<Boolean>(false)
|
||||
const showType = ref('small')
|
||||
const downloading = ref('')
|
||||
const installAfterTips = ref<string[]>([])
|
||||
const userStore = useUserStore()
|
||||
|
||||
const downEvent = (param: Record<string, any>) => {
|
||||
if (downloading.value) return
|
||||
downloading.value = param.key
|
||||
|
||||
downloadVersion({ addon: param.key, version: param.version }).then(() => {
|
||||
installAddonFn(param.key)
|
||||
localListFn()
|
||||
downloading.value = ''
|
||||
}).catch(() => {
|
||||
downloading.value = ''
|
||||
})
|
||||
}
|
||||
|
||||
const installLabel = computed(() => {
|
||||
let text = t('installLabel')
|
||||
localList.value.installed.length && (text += ` (${localList.value.installed.length})`)
|
||||
return text
|
||||
})
|
||||
|
||||
const uninstalledLabel = computed(() => {
|
||||
let text = t('uninstalledLabel')
|
||||
localList.value.uninstalled.length && (text += ` (${localList.value.uninstalled.length})`)
|
||||
return text
|
||||
})
|
||||
|
||||
const allLabel = computed(() => {
|
||||
let text = t('buyLabel')
|
||||
localList.value.all.length && (text += ` (${localList.value.all.length})`)
|
||||
return text
|
||||
})
|
||||
|
||||
const authCode = ref('')
|
||||
getAuthinfo().then(res => {
|
||||
if (res.data.data && res.data.data.auth_code) {
|
||||
authCode.value = res.data.data.auth_code
|
||||
}
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
/**
|
||||
* 本地下载的插件列表
|
||||
*/
|
||||
const localList = ref({
|
||||
installed: [],
|
||||
uninstalled: [],
|
||||
all: [],
|
||||
error: ''
|
||||
})
|
||||
|
||||
const localListFn = () => {
|
||||
loading.value = true
|
||||
getAddonLocal({}).then(res => {
|
||||
const data = res.data.list
|
||||
localList.value.error = res.data.error
|
||||
localList.value.installed = []
|
||||
localList.value.uninstalled = []
|
||||
localList.value.all = []
|
||||
for (const i in data) {
|
||||
if (data[i].is_local == false) localList.value.all.push(data[i])
|
||||
|
||||
if (data[i].install_info && Object.keys(data[i].install_info)?.length) {
|
||||
localList.value.installed.push(data[i])
|
||||
} else {
|
||||
if (data[i].is_download == true) localList.value.uninstalled.push(data[i])
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
localListFn()
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
if (tab.paneName == 'buy' && localList.value.error != '') {
|
||||
ElMessage({
|
||||
message: localList.value.error,
|
||||
grouping: true,
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const currAddon = ref('')
|
||||
|
||||
// 安装面板弹窗
|
||||
const installShowDialog = ref(false)
|
||||
|
||||
// 安装步骤
|
||||
const installStep = ref(1)
|
||||
|
||||
// 安装检测结果
|
||||
const installCheckResult = ref({})
|
||||
|
||||
/**
|
||||
* 安装
|
||||
* @param key
|
||||
*/
|
||||
const installAddonFn = (key: string) => {
|
||||
currAddon.value = key
|
||||
installStep.value = 1
|
||||
installShowDialog.value = true
|
||||
installAfterTips.value = []
|
||||
|
||||
preInstallCheck(key).then(res => {
|
||||
installCheckResult.value = res.data
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取正在进行的安装任务
|
||||
*/
|
||||
let notificationEl = null
|
||||
const getInstallTask = (first: boolean = true) => {
|
||||
getAddonInstalltask().then(res => {
|
||||
if (res.data) {
|
||||
if (first) {
|
||||
installLog = []
|
||||
currAddon.value = res.data.addon
|
||||
if (!installShowDialog.value) {
|
||||
notificationEl = ElNotification.success({
|
||||
title: t('warning'),
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: h('div', {}, [
|
||||
t('installingTips'),
|
||||
h('span', { class: 'text-primary cursor-pointer', onClick: checkInstallTask }, [t('installPercent')])
|
||||
]),
|
||||
duration: 0,
|
||||
showClose: false
|
||||
})
|
||||
}
|
||||
}
|
||||
if (res.data.error) {
|
||||
return
|
||||
}
|
||||
if (res.data.mode == 'cloud') {
|
||||
getCloudInstallLog()
|
||||
}
|
||||
setTimeout(() => {
|
||||
getInstallTask(false)
|
||||
}, 2000)
|
||||
} else {
|
||||
if (!first) {
|
||||
installStep.value = 3
|
||||
localListFn()
|
||||
userStore.getAppList()
|
||||
notificationEl.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getInstallTask()
|
||||
|
||||
const checkInstallTask = () => {
|
||||
installShowDialog.value = true
|
||||
installStep.value = 2
|
||||
}
|
||||
|
||||
const localInstalling = ref(false)
|
||||
/**
|
||||
* 安装插件
|
||||
*/
|
||||
const handleInstall = () => {
|
||||
if (!installCheckResult.value.is_pass || localInstalling.value) return
|
||||
localInstalling.value = true
|
||||
|
||||
installAddon({ addon: currAddon.value }).then(res => {
|
||||
installStep.value = 3
|
||||
localListFn()
|
||||
userStore.getAppList()
|
||||
localInstalling.value = false
|
||||
if (res.data.length) installAfterTips.value = res.data
|
||||
}).catch((res) => {
|
||||
localInstalling.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const cloudInstalling = ref(false)
|
||||
|
||||
/**
|
||||
* 云安装插件
|
||||
*/
|
||||
const handleCloudInstall = () => {
|
||||
if (!authCode.value) {
|
||||
authElMessageBox()
|
||||
return
|
||||
}
|
||||
|
||||
if (!installCheckResult.value.is_pass || cloudInstalling.value) return
|
||||
cloudInstalling.value = true
|
||||
|
||||
cloudInstallAddon({ addon: currAddon.value }).then(res => {
|
||||
installStep.value = 2
|
||||
terminalApi.execute('my-terminal', 'clear')
|
||||
terminalApi.pushMessage('my-terminal', { content: '开始安装插件', class: 'info' })
|
||||
getInstallTask()
|
||||
cloudInstalling.value = false
|
||||
}).catch((res) => {
|
||||
cloudInstalling.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const authElMessageBox = () => {
|
||||
ElMessageBox.confirm(
|
||||
t('authTips'),
|
||||
t('warning'),
|
||||
{
|
||||
distinguishCancelAndClose: true,
|
||||
confirmButtonText: t('toBind'),
|
||||
cancelButtonText: t('toNiucloud')
|
||||
}
|
||||
).then(() => {
|
||||
router.push({ path: '/app/authorize' })
|
||||
}).catch((action: string) => {
|
||||
if (action === 'cancel') {
|
||||
window.open('https://www.niucloud.com/product')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let installLog: string[] = []
|
||||
const getCloudInstallLog = () => {
|
||||
getAddonCloudInstallLog(currAddon.value)
|
||||
.then(res => {
|
||||
const data = res.data.data ?? []
|
||||
if (data[0] && data[0].length && installShowDialog.value == true) {
|
||||
data[0].forEach(item => {
|
||||
if (!installLog.includes(item.action)) {
|
||||
terminalApi.pushMessage('my-terminal', { content: `正在执行:${item.action}` })
|
||||
installLog.push(item.action)
|
||||
|
||||
if (item.code == 0) {
|
||||
terminalApi.pushMessage('my-terminal', { content: item.msg, class: 'error' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notificationEl?.close()
|
||||
})
|
||||
}
|
||||
|
||||
watch(currAddon, (nval) => {
|
||||
installCheckResult.value = {}
|
||||
})
|
||||
|
||||
/**
|
||||
* 卸载
|
||||
* @param key
|
||||
*/
|
||||
const uninstallAddonFn = (key: string) => {
|
||||
uninstallAddon({ addon: key }).then(res => {
|
||||
storage.set({key: 'menuAppStorage', data: ""})
|
||||
localListFn()
|
||||
userStore.getAppList()
|
||||
loading.value = false
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const market = () => {
|
||||
window.open('https://www.niucloud.com/product')
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装弹窗关闭提示
|
||||
* @param done
|
||||
*/
|
||||
const installShowDialogClose = (done: () => {}) => {
|
||||
if (installStep.value == 2) {
|
||||
ElMessageBox.confirm(
|
||||
t('installShowDialogCloseTips'),
|
||||
t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
done()
|
||||
}).catch(() => { })
|
||||
} else done()
|
||||
}
|
||||
|
||||
// 插件详情
|
||||
const appStoreShowDialog = ref(false)
|
||||
const appStoreInfo = ref<AnyObject>({})
|
||||
const getAddonDetialFn = (data: AnyObject) => {
|
||||
appStoreShowDialog.value = true
|
||||
appStoreInfo.value = data
|
||||
}
|
||||
|
||||
// 授权
|
||||
let authCodeApproveDialog = ref(false);
|
||||
const authinfo = ref("");
|
||||
let getAuthCodeDialog = ref(null);
|
||||
const saveLoading = ref(false);
|
||||
const checkAppMange = () => {
|
||||
getAuthinfo()
|
||||
.then((res) => {
|
||||
if (res.data.data && res.data.data.length != 0) {
|
||||
authinfo.value = res.data.data;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
authCodeApproveDialog.value = false;
|
||||
});
|
||||
};
|
||||
checkAppMange();
|
||||
const authCodeApproveFn = ()=>{
|
||||
authCodeApproveDialog.value = true;
|
||||
}
|
||||
|
||||
const formData = reactive<Record<string, string>>({
|
||||
auth_code: "",
|
||||
auth_secret: "",
|
||||
});
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = reactive<FormRules>({
|
||||
auth_code: [
|
||||
{ required: true, message: t("authCodePlaceholder"), trigger: "blur" },
|
||||
],
|
||||
auth_secret: [
|
||||
{ required: true, message: t("authSecretPlaceholder"), trigger: "blur" },
|
||||
],
|
||||
});
|
||||
|
||||
const save = async (formEl: FormInstance | undefined) => {
|
||||
if (saveLoading.value || !formEl) return;
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
saveLoading.value = true;
|
||||
|
||||
setAuthinfo(formData)
|
||||
.then(() => {
|
||||
saveLoading.value = false;
|
||||
checkAppMange();
|
||||
})
|
||||
.catch(() => {
|
||||
saveLoading.value = false;
|
||||
authCodeApproveDialog.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const goRouter = () => {
|
||||
window.open('https://www.niucloud.com/product')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.demo-tabs>.el-tabs__content {
|
||||
padding: 32px;
|
||||
color: #6b778c;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.plug-item {
|
||||
.plug-item-operate {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
font-size: var(--el-font-size-extra-small);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.t-container) {
|
||||
box-shadow: none !important;
|
||||
|
||||
.t-window {
|
||||
padding: 10px 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.switch-btn.active {
|
||||
border-color: var(--el-color-primary);
|
||||
color: #fff;
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.plug-large {
|
||||
.plug-item-operate {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
font-size: var(--el-font-size-extra-small);
|
||||
}
|
||||
}
|
||||
|
||||
.app-text {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
-o-text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// 插件安装-弹窗-表格样式
|
||||
.table-head-bg {
|
||||
background: #f5f7f9;
|
||||
}
|
||||
|
||||
html.dark .table-head-bg {
|
||||
background: #141414;
|
||||
}
|
||||
</style>
|
||||
@ -17,7 +17,7 @@
|
||||
<img class="w-[50px] h-[50px] inline-block" v-if="formData.headimg" :src="img(formData.headimg)" alt="">
|
||||
<img class="w-[50px] h-[50px] inline-block" v-else src="@/app/assets/images/default_headimg.png" alt="">
|
||||
<el-icon @click="editMemberInfo('headimg')" class="-bottom-[2px] -right-[4px] cursor-pointer">
|
||||
<EditPen color="#273CE2" />
|
||||
<EditPen :color="theme.theme" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
@ -40,7 +40,7 @@
|
||||
</span>
|
||||
<el-tooltip effect="dark" :content="t('adjust')" placement="top">
|
||||
<el-icon @click="adjustPoint(formData)" class="ml-2 cursor-pointer" :size="12">
|
||||
<EditPen color="#273CE2" />
|
||||
<EditPen :color="theme.theme" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="dark" :content="t('detail')" placement="top">
|
||||
@ -71,7 +71,7 @@
|
||||
</span>
|
||||
<el-tooltip effect="dark" :content="t('adjust')" placement="top">
|
||||
<el-icon @click="adjustBalance(formData)" class="ml-2 cursor-pointer" :size="12">
|
||||
<EditPen color="#273CE2" />
|
||||
<EditPen :color="theme.theme" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="dark" :content="t('detail')" placement="top">
|
||||
@ -172,7 +172,7 @@
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('memberLabel') }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ formData.member_label_name.toString() || t('notAvailable') }}<el-icon @click="editMemberInfo('member_label')" class="-bottom-[2px] -right-[4px] cursor-pointer">
|
||||
<EditPen color="#273CE2" />
|
||||
<EditPen :color="theme.theme" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
@ -180,7 +180,7 @@
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('birthday') }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ formData.birthday || t('notAvailable') }}<el-icon @click="editMemberInfo('birthday')" class="-bottom-[2px] -right-[4px] cursor-pointer">
|
||||
<EditPen color="#273CE2" />
|
||||
<EditPen :color="theme.theme" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
@ -188,7 +188,7 @@
|
||||
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('sex') }}</span>
|
||||
<span class="text-[14px] text-[#666666]">
|
||||
{{ formData.sex == 1 && t('manSex') || formData.sex == 2 && t('girlSex') || t('secrecySex') }}<el-icon @click="editMemberInfo('sex')" class="-bottom-[2px] -right-[4px] cursor-pointer">
|
||||
<EditPen color="#273CE2" />
|
||||
<EditPen :color="theme.theme" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
@ -248,6 +248,9 @@ import BalanceEdit from '@/app/views/member/components/member-balance-edit.vue'
|
||||
import EditMember from '@/app/views/member/components/edit-member.vue'
|
||||
import colorGradient from '../../../../uniapp/src/uni_modules/vk-uview-ui/libs/function/colorGradient'
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
import storage from '@/utils/storage'
|
||||
const theme = storage.get('theme') ?? {}
|
||||
console.log(theme)
|
||||
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
|
||||
@ -133,7 +133,7 @@ const validKey = (rule, value, callback) => {
|
||||
}
|
||||
const validVersion = (rule, value, callback) => {
|
||||
if (value !== '') {
|
||||
const reg = /^([1-9]\d|[1-9])(\.([0-9]){1}){2}$/;
|
||||
const reg = /^([0-9]\d|[0-9])(\.([0-9]){1}){2}$/;
|
||||
if (!reg.test(value)) {
|
||||
return callback(new Error(t('versionPlaceholderErr')));
|
||||
} else {
|
||||
|
||||
@ -534,7 +534,7 @@ const onSave = async (code: number) => {
|
||||
if (!['select', 'radio', 'checkbox'].includes(el.view_type)) el.dict_type = ''
|
||||
return el
|
||||
}))
|
||||
console.log(JSON.parse(data.table_column))
|
||||
|
||||
data.relations = JSON.stringify(data.relations)
|
||||
loading.value = true
|
||||
editGenerateTable(data).then((res: any) => {
|
||||
|
||||
@ -122,7 +122,6 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
// loading.value = true
|
||||
|
||||
const data = formData
|
||||
console.log(data)
|
||||
|
||||
// save(data).then(res => {
|
||||
// loading.value = false
|
||||
|
||||
100
admin/src/app/views/tools/index.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="box-border px-[30px] pt-[60px]" v-loading="loading">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="font-600 text-[20px] text-[#222]">工具管理</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap mt-[28px]">
|
||||
<template v-for="(item, index) in menus" :key="index">
|
||||
<div class="app-item w-[284px] box-border p-[15px] bg-[#fff] rounded-[8px] cursor-pointer mr-[24px] mb-[24px]" @click="toLink(item)">
|
||||
<div class="flex items-center">
|
||||
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="40px" :title="item.meta.title" />
|
||||
<img v-else class="w-[40px] h-[40px] rounded-[8px]" src="@/app/assets/images/app_store/app_store_default.png"/>
|
||||
<div class="flex-1 font-600 text-[14px] text-[#222] ml-[12px]">{{ item.meta.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-empty v-if="!menus.length && !loading" class="mx-auto overview-empty">
|
||||
<template #image>
|
||||
<div class="w-[230px] mx-auto">
|
||||
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="">
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<p class="flex items-center">
|
||||
<span>{{ t('descriptionLeft') }}</span>
|
||||
<el-link type="primary" @click="goRouter" class="mx-[5px]">{{ t('link') }}</el-link>
|
||||
<span>{{ t('descriptionRight') }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</el-empty>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {reactive, ref, onMounted, computed} from 'vue'
|
||||
import {t} from '@/lang'
|
||||
import {getAuthaddon} from '@/app/api/auth'
|
||||
import {img} from '@/utils/common'
|
||||
import {useRouter} from 'vue-router'
|
||||
import storage from '@/utils/storage'
|
||||
import {findFirstValidRoute} from '@/router/routers'
|
||||
import {UserFilled} from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const loading = ref(true)
|
||||
const detail = reactive({
|
||||
appList: []
|
||||
})
|
||||
const appLink: any = ref({})
|
||||
|
||||
const menus = computed(() => {
|
||||
let obj = []
|
||||
loading.value = true;
|
||||
userStore.routers.forEach((item, index) => {
|
||||
if (item.meta.key == 'tool' && item.children && item.children.length) {
|
||||
item.children.forEach((childItem,childIndex) => {
|
||||
if(childItem.meta.show == 1){
|
||||
obj.push(childItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
loading.value = false;
|
||||
return obj
|
||||
})
|
||||
|
||||
const toLink = (data)=>{
|
||||
router.push({ name: data.name })
|
||||
}
|
||||
|
||||
const goRouter = () => {
|
||||
window.open('https://www.niucloud.com/product')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main-container {
|
||||
background: linear-gradient(180deg, rgba(253, 253, 253, 0.24) 0%, #FAFAFA 100%);
|
||||
min-height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
.overview-top {
|
||||
background-image: url('@/app/assets/images/index/overview.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.app-item {
|
||||
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.overview-empty .el-empty__image {
|
||||
width: auto !important;
|
||||
}
|
||||
</style>
|
||||
@ -19,7 +19,7 @@
|
||||
<el-dialog v-model="showDialog" :title="t('selectLinkTips')" width="40%" :close-on-press-escape="false" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
|
||||
<div class="flex items-start">
|
||||
<el-scrollbar class="w-[140px] border-r h-[350px]">
|
||||
<el-scrollbar class="w-[140px] border-r" height="360px">
|
||||
<div v-for="(item,index) in link" :key="index"
|
||||
class="h-[40px] leading-[40px] cursor-pointer hover:bg-primary-light-9 px-[10px] hover:text-primary"
|
||||
:class="[ item.name == parentLinkName ? 'bg-primary-light-9 text-primary' : '' ]"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { nextTick } from 'vue'
|
||||
import {nextTick} from 'vue'
|
||||
|
||||
class Language {
|
||||
private i18n: any;
|
||||
@ -40,6 +40,18 @@ class Language {
|
||||
data[`${file}.${key}`] = messages.default[key]
|
||||
})
|
||||
|
||||
// 查询插件的公共语言包
|
||||
if (app) {
|
||||
try {
|
||||
var messagesCommon = await import( `@/${app}/lang/${locale}/common.json`)
|
||||
Object.keys(messagesCommon.default).forEach(key => {
|
||||
data[`${file}.${key}`] = messagesCommon.default[key]
|
||||
})
|
||||
} catch (e) {
|
||||
// console.log('未找到插件公共语言包')
|
||||
}
|
||||
}
|
||||
|
||||
this.i18n.global.mergeLocaleMessage(locale, data)
|
||||
this.setI18nLanguage(locale)
|
||||
return nextTick()
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
"copy": "复制",
|
||||
"complete": "完成",
|
||||
"copySuccess": "复制成功",
|
||||
"changeApp": "切换应用",
|
||||
"notSupportCopy": "浏览器不支持一键复制,请手动进行复制",
|
||||
"selectPlaceholder": "全部",
|
||||
"provincePlaceholder": "请选择省",
|
||||
@ -127,5 +128,20 @@
|
||||
"generateMax":"超过最多输入字符数",
|
||||
"generateMin":"少于最少输入字符数",
|
||||
"generateBetween":"请输入正确的字符信息",
|
||||
"emptyAppData":"暂无应用"
|
||||
"emptyAppData":"暂无应用",
|
||||
|
||||
"appMarketPlace": "应用",
|
||||
"developerCenter": "开发",
|
||||
"niucloud": "Niucloud官网",
|
||||
"exit": "退出",
|
||||
|
||||
"originalPassword": "原始密码",
|
||||
"newPassword": "新密码",
|
||||
"passwordCopy": "确认密码",
|
||||
"passwordTip": "修改密码时必填.不修改密码时留空",
|
||||
"originalPasswordPlaceholder": "请输入原始密码",
|
||||
"passwordPlaceholder": "请输入新密码",
|
||||
"originalPasswordHint": "原始密码不能为空",
|
||||
"newPasswordHint": "请输入确认密码",
|
||||
"doubleCipherHint": "两次新密码不同"
|
||||
}
|
||||
15
admin/src/layout/app_manage/components/aside/index.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { img } from '@/utils/common'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
153
admin/src/layout/app_manage/components/header/index.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<el-container class="h-[60px] bg-[#2B303B] flex items-center justify-between px-[15px] text-white">
|
||||
<div class="flex items-center text-[14px] leading-[1]">
|
||||
<span class="iconfont icontuodong !text-[24px] mr-[6px]"></span>
|
||||
<span class="cursor-pointer" @click="goAppManage">{{ t('appMarketPlace') }}</span>
|
||||
<template v-if="app_debug">
|
||||
<span class="mx-2 text-[#4F5563] mx-[15px]">|</span>
|
||||
<span class="cursor-pointer" @click="goDeveloperCenter">{{ t('developerCenter') }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex items-center mr-[20px] min-w-[200px] text-[14px]">
|
||||
<span class="cursor-pointer mr-[15px]" @click="goNiucloud">{{ t('niucloud') }}</span>
|
||||
<el-dropdown>
|
||||
<div class="userinfo flex h-full items-center">
|
||||
<el-avatar :size="25" :icon="UserFilled" />
|
||||
<div class="user-name pl-[8px] text-[#fff]">{{ userStore.userInfo.username }}</div>
|
||||
<icon name="element-ArrowDown" class="ml-[5px] !text-[#fff]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="usercenter"><router-link to="/user/center">账号设置</router-link></el-dropdown-item>
|
||||
<el-dropdown-item command="usercenter"><router-link to="/tools/authorize">授权信息</router-link></el-dropdown-item>
|
||||
<el-dropdown-item command="usercenter" @click="changePasswordDialog=true">修改密码</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<span class="mx-2 text-[#4F5563]">|</span>
|
||||
<span class="cursor-pointer" @click="logout">{{t('exit')}}</span>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="changePasswordDialog" width="450px" title="修改密码" :before-close="handleClose">
|
||||
<div>
|
||||
<el-form :model="saveInfo" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
|
||||
<el-form-item :label="t('originalPassword')" prop="original_password">
|
||||
<el-input v-model="saveInfo.original_password" type="password" :placeholder="t('originalPasswordPlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('newPassword')" prop="password">
|
||||
<el-input v-model="saveInfo.password" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
<div class="form-tip">{{t('passwordTip')}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('passwordCopy')" prop="password_copy">
|
||||
<el-input v-model="saveInfo.password_copy" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="changePasswordDialog = false">{{t('cancel')}}</el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)">{{t('save')}}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
|
||||
import { t } from '@/lang'
|
||||
import {getEnv} from '@/app/api/sys'
|
||||
import { setUserInfo } from '@/app/api/personal'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
const userStore = useUserStore()
|
||||
|
||||
const router = useRouter()
|
||||
let app_debug = ref(false)
|
||||
|
||||
const goAppManage = () => {
|
||||
router.push('/app_manage')
|
||||
}
|
||||
const getEnvFn = () => {
|
||||
getEnv().then(res => {
|
||||
app_debug.value = res.data.app_debug;
|
||||
}).catch(() => {})
|
||||
}
|
||||
getEnvFn();
|
||||
|
||||
|
||||
const goRouter = () => {
|
||||
window.open('https://www.niucloud.com/product')
|
||||
}
|
||||
|
||||
// 跳转至开发者
|
||||
const goDeveloperCenter = () => {
|
||||
router.push('/tools/addon')
|
||||
}
|
||||
|
||||
const goNiucloud = () => {
|
||||
window.open('https://www.niucloud.com')
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
userStore.logout();
|
||||
}
|
||||
|
||||
// 修改密码 --- start
|
||||
let changePasswordDialog = ref(false)
|
||||
const formRef = ref<FormInstance>();
|
||||
// 提交信息
|
||||
let saveInfo = reactive({
|
||||
original_password: '',
|
||||
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" },
|
||||
]
|
||||
});
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
let msg = "";
|
||||
if (saveInfo.password && !saveInfo.original_password) msg = t('originalPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && !saveInfo.password_copy) msg = t('newPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && saveInfo.password_copy && saveInfo.password != saveInfo.password_copy) msg = t('doubleCipherHint');
|
||||
if (msg) {
|
||||
ElNotification({
|
||||
type: 'error',
|
||||
message: msg,
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
setUserInfo(saveInfo).then((res: any) => {
|
||||
changePasswordDialog.value = false;
|
||||
}).catch((err: any) => {
|
||||
changePasswordDialog.value = false;
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
}
|
||||
// 修改密码 --- end
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout-header {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
box-shadow: 0px 0px 4px 0px rgba(0, 145, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
37
admin/src/layout/app_manage/index.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<el-container class="w-screen h-screen min-w-[1200px] flex flex-col">
|
||||
<layout-aside></layout-aside>
|
||||
<el-container class="overview-top">
|
||||
<el-header class="h-[60px]">
|
||||
<layout-header></layout-header>
|
||||
</el-header>
|
||||
<el-main class="w-[1200px] m-auto">
|
||||
<el-scrollbar class="main-height">
|
||||
<router-view></router-view>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
|
||||
</el-container>
|
||||
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import layoutHeader from './components/header/index.vue'
|
||||
import layoutAside from './components/aside/index.vue'
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.overview-top {
|
||||
background-image: url('@/app/assets/images/index/overview.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
.main-height{
|
||||
height: calc(100vh - 120px);
|
||||
}
|
||||
.box-shadow{
|
||||
box-shadow: 0 0 10px 1px rgba(151, 151, 151, 0.1);
|
||||
}
|
||||
</style>
|
||||
@ -14,8 +14,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { img } from '@/utils/common'
|
||||
|
||||
const props = defineProps(['isShowHover','data','hoverType'])
|
||||
console.log();
|
||||
let data = ref([]);
|
||||
if(props.data){
|
||||
props.data.forEach((item,index) => {
|
||||
|
||||
@ -2,21 +2,21 @@
|
||||
<div :class="['flex', { 'two-type': sidebar == 'twoType' }, { 'three-type': sidebar == 'threeType' }]" v-if="isLoad">
|
||||
<div class="w-[124px] overflow-hidden">
|
||||
<!-- , { 'bright': !dark } -->
|
||||
<el-aside :class="['h-screen layout-aside w-[124px] pb-[30px] px-[8px] bg-[#202033] ease-in duration-200']">
|
||||
<el-aside :class="['h-screen layout-aside w-[124px] pb-[30px] px-[8px] bg-[#282c34] ease-in duration-200']">
|
||||
<div class="h-full flex flex-col relative">
|
||||
<div class="group flex items-center justify-center h-[64px] cursor-pointer" v-if="!globalAppKey" @mouseenter="threefloatMenuHover">
|
||||
<span class="iconfont iconyun1 !text-[32px] !w-auto text-[#fff]"></span>
|
||||
<app-menu :isShowHover="threefloatMenu" :data="applyList" @child-click="toLink" hoverType='threefloatMenu'></app-menu>
|
||||
<!-- <app-menu :isShowHover="threefloatMenu" :data="applyList" @child-click="toLink" hoverType='threefloatMenu'></app-menu>-->
|
||||
</div>
|
||||
|
||||
<template v-for="(item, index) in menus" :key="index">
|
||||
<template v-if="globalAppKey == item.meta.app && item.meta.parentTitle">
|
||||
<div class="group flex items-center justify-center h-[64px] cursor-pointer" @mouseenter="threefloatMenuHover">
|
||||
<img v-if="item.meta.parentIcon" :src="img(item.meta.parentIcon)" class="w-[40px] h-[40px]" alt="">
|
||||
<img v-if="item.meta.parentIcon" :src="img(item.meta.parentIcon)" class="w-[40px] h-[40px] rounded-full" alt="">
|
||||
<div class="flex items-center justify-center w-[30px] h-[30px]" v-else>
|
||||
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px" />
|
||||
</div>
|
||||
<app-menu :isShowHover="threefloatMenu" :data="applyList" @child-click="toLink" hoverType='threefloatMenu'></app-menu>
|
||||
<!-- <app-menu :isShowHover="threefloatMenu" :data="applyList" @child-click="toLink" hoverType='threefloatMenu'></app-menu>-->
|
||||
</div>
|
||||
<div v-for="(appItem, appIndex) in item.children" :key="appIndex" @click="toLink(appItem)"
|
||||
:class="['rounded-sm flex items-center px-[8px] mb-[4px] h-[40px] cursor-pointer text-[#b9b9bf] hover:bg-[var(--el-color-primary)] hover:!text-[#fff] menu-item hover:text-color whitespace-nowrap', { 'bg-[var(--el-color-primary)] !text-[#fff] menu-item-active ': localMenuKey == appItem.meta.key}]">
|
||||
@ -122,7 +122,7 @@
|
||||
<el-sub-menu :index="String(threeMenu.meta.title)"
|
||||
v-if="threeMenu.children && threeMenu.meta.show">
|
||||
<template #title>
|
||||
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
|
||||
<span class="text-[14px] !pl-[10px]">{{ threeMenu.meta.title }}</span>
|
||||
</template>
|
||||
<template v-for="(fourMenu, fourIndex) in threeMenu.children"
|
||||
:key="fourIndex">
|
||||
@ -348,6 +348,7 @@ const isTwoMenuFn = (item) => {
|
||||
|
||||
return bool;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@ -524,7 +525,6 @@ const isTwoMenuFn = (item) => {
|
||||
|
||||
.el-sub-menu__title:hover {
|
||||
background-color: var(--el-color-primary);
|
||||
;
|
||||
}
|
||||
|
||||
.el-sub-menu__title {
|
||||
|
||||
@ -19,10 +19,10 @@
|
||||
<div class="right-panel h-full flex items-center justify-end">
|
||||
<!-- 预览 只有站点时展示-->
|
||||
|
||||
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'site'"></i>
|
||||
<!-- 切换首页 -->
|
||||
<div class="navbar-item flex items-center h-full cursor-pointer" v-if="appType == 'site'" @click="checkIndexList">
|
||||
<icon name="iconfont-iconqiehuan" :title="t('indexSwitch')" />
|
||||
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')"></i>
|
||||
<!-- 切换应用 -->
|
||||
<div class="navbar-item flex items-center h-full cursor-pointer" @click="changeApp">
|
||||
<icon name="iconfont-iconqiehuan" :title="t('changeApp')" class="!text-xs" />
|
||||
</div>
|
||||
<!-- 切换语言 -->
|
||||
<div class="navbar-item !px-[0] flex items-center h-full cursor-pointer">
|
||||
@ -131,6 +131,9 @@ const toggleMenuCollapse = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const changeApp = ()=>{
|
||||
router.push({ path: '/app_manage' })
|
||||
}
|
||||
// 刷新路由
|
||||
const refreshRouter = () => {
|
||||
if (!appStore.routeRefreshTag) return
|
||||
|
||||
@ -9,14 +9,20 @@
|
||||
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarStyle') }}</div>
|
||||
<div class="">
|
||||
<el-radio-group v-model="sidebarStyle" class="ml-4">
|
||||
<el-radio label="oneType" size="large">样式一</el-radio>
|
||||
<el-radio label="twoType" size="large">样式二</el-radio>
|
||||
<el-radio label="threeType" size="large">样式三</el-radio>
|
||||
<el-radio label="oneType" size="large">
|
||||
<img class="w-[35px] h-[35px]" src="@/app/assets/images/one_type.png" alt="">
|
||||
</el-radio>
|
||||
<el-radio label="twoType" size="large">
|
||||
<img class="w-[35px] h-[35px]" src="@/app/assets/images/two_type.png" alt="">
|
||||
</el-radio>
|
||||
<el-radio label="threeType" size="large">
|
||||
<img class="w-[35px] h-[35px]" src="@/app/assets/images/three_type.png" alt="">
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 风格切换 -->
|
||||
<div class="setting-item flex items-baseline justify-between mb-[10px]">
|
||||
<!-- <div class="setting-item flex items-baseline justify-between mb-[10px]">
|
||||
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarMode') }}</div>
|
||||
<div class="">
|
||||
<el-radio-group v-model="sidebar" class="ml-4">
|
||||
@ -31,7 +37,7 @@
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- 黑暗模式 -->
|
||||
<div class="setting-item flex items-center justify-between mb-[10px]">
|
||||
<div class="title text-base text-tx-secondary">{{ t('layout.darkMode') }}</div>
|
||||
|
||||
@ -1,21 +1,54 @@
|
||||
<template>
|
||||
<el-dropdown @command="clickEvent" :tabindex="1">
|
||||
<div class="userinfo flex h-full items-center">
|
||||
<el-avatar :size="25" :icon="UserFilled" />
|
||||
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
|
||||
<icon name="element-ArrowDown" class="ml-[5px]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="usercenter"><router-link to="/user/center">个人中心</router-link></el-dropdown-item>
|
||||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div>
|
||||
<el-dropdown @command="clickEvent" :tabindex="1">
|
||||
<div class="userinfo flex h-full items-center">
|
||||
<el-avatar :size="25" :icon="UserFilled" />
|
||||
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
|
||||
<icon name="element-ArrowDown" class="ml-[5px]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="usercenter"><router-link to="/user/center">账号设置</router-link></el-dropdown-item>
|
||||
<el-dropdown-item command="usercenter" @click="changePasswordDialog=true">修改密码</el-dropdown-item>
|
||||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
<el-dialog v-model="changePasswordDialog" title="修改密码" width="450px" :before-close="handleClose">
|
||||
<div>
|
||||
<el-form :model="saveInfo" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
|
||||
<el-form-item :label="t('originalPassword')" prop="original_password">
|
||||
<el-input v-model="saveInfo.original_password" type="password" :placeholder="t('originalPasswordPlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('newPassword')" prop="password">
|
||||
<el-input v-model="saveInfo.password" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
<div class="form-tip">{{t('passwordTip')}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('passwordCopy')" prop="password_copy">
|
||||
<el-input v-model="saveInfo.password_copy" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="changePasswordDialog = false">{{t('cancel')}}</el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)">{{t('save')}}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UserFilled } from '@element-plus/icons-vue'
|
||||
import { computed, reactive, ref, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
|
||||
import { t } from '@/lang'
|
||||
import {getEnv} from '@/app/api/sys'
|
||||
import { setUserInfo } from '@/app/api/personal'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
@ -27,6 +60,56 @@ const clickEvent = (command: string) => {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 修改密码 --- start
|
||||
let changePasswordDialog = ref(false)
|
||||
const formRef = ref<FormInstance>();
|
||||
// 提交信息
|
||||
let saveInfo = reactive({
|
||||
original_password: '',
|
||||
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" },
|
||||
]
|
||||
});
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
let msg = "";
|
||||
if (saveInfo.password && !saveInfo.original_password) msg = t('originalPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && !saveInfo.password_copy) msg = t('newPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && saveInfo.password_copy && saveInfo.password != saveInfo.password_copy) msg = t('doubleCipherHint');
|
||||
if (msg) {
|
||||
ElNotification({
|
||||
type: 'error',
|
||||
message: msg,
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
setUserInfo(saveInfo).then((res: any) => {
|
||||
changePasswordDialog.value = false;
|
||||
}).catch((err: any) => {
|
||||
changePasswordDialog.value = false;
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
}
|
||||
// 修改密码 --- end
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { img } from '@/utils/common'
|
||||
|
||||
const props = defineProps(['isShowHover','data','hoverType'])
|
||||
let data = ref([]);
|
||||
if(props.data){
|
||||
@ -1,69 +1,74 @@
|
||||
<template>
|
||||
<div :class="['flex', { 'two-type': sidebar == 'twoType' }, { 'three-type': sidebar == 'threeType' }]" v-if="isLoad">
|
||||
<div class="w-[65px] overflow-hidden">
|
||||
<el-aside
|
||||
:class="['h-screen layout-aside w-[65px] pb-[30px] bg-[#F7F8FA] ease-in duration-200', { 'bright': !dark }]">
|
||||
<div class="h-full flex flex-col relative">
|
||||
<div class="group flex items-center justify-center h-[64px] cursor-pointer cut-style" @mouseenter="onefloatMenuHover">
|
||||
<span class="iconfont icontuodong !text-[30px] "></span>
|
||||
<app-menu :isShowHover="onefloatMenu" :data="applyList" @child-click="toLink" hoverType='onefloatMenu'></app-menu>
|
||||
<div class="w-[64px] overflow-hidden">
|
||||
<!-- , { 'bright': !dark } -->
|
||||
<el-aside :class="['h-screen layout-aside w-[64px] pb-[30px] bg-[#282c34] ease-in duration-200']">
|
||||
<div class="h-full flex flex-col relative">
|
||||
<div class="group flex items-center justify-center h-[64px] cursor-pointer" v-if="!globalAppKey" @mouseenter="threefloatMenuHover">
|
||||
<span class="iconfont iconyun1 !text-[32px] !w-auto text-[#fff]"></span>
|
||||
<!-- <app-menu :isShowHover="threefloatMenu" :data="applyList" @child-click="toLink" hoverType='threefloatMenu'></app-menu>-->
|
||||
</div>
|
||||
<div @click="homeClick"
|
||||
class="flex items-center justify-center h-[56px] cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] menu-item hover:text-color whitespace-nowrap">
|
||||
<span class="iconfont iconshouye !text-[24px] "></span>
|
||||
</div>
|
||||
<div class="mb-[20px]">
|
||||
<template v-for="(item, index) in menus" :key="index">
|
||||
<div v-if="item.meta.app == '' && item.meta.attr == 'common'" @click="toLink(item)"
|
||||
:class="['flex items-center justify-center h-[56px] cursor-pointer text-[#6d7278] hover:bg-[#f1f2f6] menu-item hover:text-color whitespace-nowrap', { 'bg-[#f1f2f6] text-color menu-item-active ': (item.path == currentRoute.path || (currentRoute.path == '/admin' && item.path == '/index') || (currentRoute.meta.app && item.path == '/index')) }]">
|
||||
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px" :title="item.meta.title" />
|
||||
|
||||
<template v-for="(item, index) in menus" :key="index">
|
||||
<template v-if="globalAppKey == item.meta.app && item.meta.parentTitle">
|
||||
<div class="group flex items-center justify-center h-[64px] cursor-pointer" @mouseenter="threefloatMenuHover">
|
||||
<img v-if="item.meta.parentIcon" :src="img(item.meta.parentIcon)" class="w-[40px] h-[40px] rounded-full" alt="">
|
||||
<div class="flex items-center justify-center w-[30px] h-[30px]" v-else>
|
||||
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px" />
|
||||
</div>
|
||||
<!-- <app-menu :isShowHover="threefloatMenu" :data="applyList" @child-click="toLink" hoverType='threefloatMenu'></app-menu>-->
|
||||
</div>
|
||||
<div v-for="(appItem, appIndex) in item.children" :key="appIndex" @click="toLink(appItem)"
|
||||
:class="['rounded-[5px] flex justify-center flex-col items-center h-[54px] w-[54px] m-[5px] cursor-pointer text-[#fff] hover:bg-[var(--el-color-primary)] hover:!text-[#fff] menu-item hover:text-color whitespace-nowrap', { 'bg-[var(--el-color-primary)] !text-[#fff] menu-item-active ': localMenuKey == appItem.meta.key}]">
|
||||
<icon v-if="appItem.meta.icon" :name="appItem.meta.icon" class="!w-auto" size="16px" :title="appItem.meta.title" />
|
||||
<span class="text-[14px] leading-1">{{appItem.meta.shortTitle}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-for="(item, index) in menus" :key="index">
|
||||
<div v-if="!item.meta.app && (item.meta.attr == 'common' || item.meta.attr == 'system')" @click="toLink(item)"
|
||||
:class="['rounded-[5px] flex justify-center flex-col items-center m-[5px] h-[54px] w-[54px] cursor-pointer text-[#fff] hover:bg-[var(--el-color-primary)] hover:!text-[#fff] menu-item hover:text-color whitespace-nowrap', { 'bg-[var(--el-color-primary)] !text-[#fff] menu-item-active ': (item.path == currentRoute.path || (currentRoute.path == '/admin' && item.path == '/index') || (currentRoute.meta.app && item.path == '/index')) }]">
|
||||
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="16px" :title="item.meta.title" />
|
||||
<span class="text-[14px] leading-1">{{item.meta.shortTitle}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-aside>
|
||||
</div>
|
||||
|
||||
|
||||
<template v-for="(item, index) in menus" :key="index">
|
||||
<div v-if="isTwoMenuFn(item)" class="w-[189px] box-border border-r-[1px] border-solid second-menu">
|
||||
<div class="group flex flex-col items-center justify-center h-[64px] border-b-[1px] border-solid second-head cursor-pointer relative">
|
||||
{{ item.meta.app ? item.meta.parentTitle : item.meta.title }}
|
||||
<div v-if="isTwoMenuFn(item)" class="w-[201px] box-border border-r-[1px] border-solid second-menu">
|
||||
<div class="group flex flex-col items-center justify-center h-[60px] border-b-[1px] border-solid second-head cursor-pointer relative" @mouseenter="twofloatMenuHover">
|
||||
{{ item.meta.title }}
|
||||
</div>
|
||||
|
||||
<el-scrollbar class="overflow-y-auto menus-wrap">
|
||||
<el-scrollbar class="overflow-y-auto menus-wrap p-[10px]">
|
||||
<el-menu class="apply-menu !border-0" :router="true" unique-opened="true" :default-active="String(route.name)">
|
||||
<template v-for="(twoMenu, twoIndex) in item.children">
|
||||
<el-sub-menu :index="String(twoMenu.meta.title)" v-if="twoMenu.children && twoMenu.meta.show">
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center">
|
||||
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
|
||||
class="absolute !w-auto" size="18px" />
|
||||
<div class="w-[16px] h-[16px] mr-[3px] relative flex items-center">
|
||||
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon" class="!w-auto" size="16px" />
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
|
||||
<span class="text-[14px]">{{ twoMenu.meta.title }}</span>
|
||||
</template>
|
||||
<template v-for="(threeMenu, threeIndex) in twoMenu.children" :key="threeIndex">
|
||||
<!-- 三级菜单 -->
|
||||
<el-sub-menu :index="String(threeMenu.meta.title)" class="three-menu"
|
||||
v-if="threeMenu.children && threeMenu.meta.show">
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center">
|
||||
<span class="iconfont icondian !text-[25px]"></span>
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title }}</span>
|
||||
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
|
||||
</template>
|
||||
<template v-for="(fourMenu, fourIndex) in threeMenu.children" :key="fourIndex">
|
||||
<el-sub-menu :index="String(fourMenu.meta.title)"
|
||||
v-if="fourMenu.children && fourMenu.meta.show">
|
||||
<template #title>
|
||||
<div
|
||||
class="w-[16px] h-[16px] relative flex items-center justify-center">
|
||||
<span class="iconfont icondian !text-[25px]"></span>
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ fourMenu.meta.title }}</span>
|
||||
<span class="text-[14px]">{{ fourMenu.meta.title }}</span>
|
||||
</template>
|
||||
<template v-for="(fiveMenu, fiveIndex) in fourMenu.children"
|
||||
:key="fiveIndex">
|
||||
<el-menu-item v-if="fiveMenu.meta.show" class="!h-[52px] !pl-[55px]"
|
||||
<el-menu-item v-if="fiveMenu.meta.show" class="!pl-[55px]"
|
||||
:index="String(fiveMenu.name)" @click="toLink(fiveMenu)">
|
||||
<template #title>
|
||||
<span class="text-[14px]">{{ fiveMenu.meta.title }}</span>
|
||||
@ -71,7 +76,7 @@
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-else-if="fourMenu.meta.show" class="!h-[52px] !pl-[35px]"
|
||||
<el-menu-item v-else-if="fourMenu.meta.show" class="!pl-[40px]"
|
||||
:index="String(fourMenu.name)" @click="toLink(fourMenu)">
|
||||
<template #title>
|
||||
<span class="text-[14px]">{{ fourMenu.meta.title }}</span>
|
||||
@ -81,173 +86,85 @@
|
||||
</el-sub-menu>
|
||||
|
||||
<!-- 二级菜单 -->
|
||||
<el-menu-item v-else-if="threeMenu.meta.show" class="!h-[52px] !pl-[52px]"
|
||||
<el-menu-item v-else-if="threeMenu.meta.show" class="!pl-[40px]"
|
||||
:index="String(threeMenu.name)" @click="toLink(threeMenu)">
|
||||
<template #title>
|
||||
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
</el-sub-menu>
|
||||
|
||||
<el-menu-item v-else-if="twoMenu.meta.show && twoMenu.meta.key != 'official_market'"
|
||||
class="!pl-[25px] text-[#333]" :index="String(twoMenu.name)" @click="toLink(twoMenu)">
|
||||
class="!pl-[20px] text-[#333]" :index="String(twoMenu.name)" @click="toLink(twoMenu)">
|
||||
<template #title>
|
||||
<div v-if="twoMenu.meta.icon" class="w-[16px] h-[16px] relative flex items-center">
|
||||
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
|
||||
class="absolute !w-auto" size="18px" />
|
||||
<div class="w-[16px] h-[16px] mr-[3px] relative flex items-center">
|
||||
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon" class="!w-auto" size="16px" />
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
|
||||
<span class="text-[14px]">{{ twoMenu.meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
|
||||
<div class="flex items-center !px-[25px] h-[56px] cursor-pointer text-[#333] el-menu-item"
|
||||
v-else-if="twoMenu.meta.show && twoMenu.meta.key == 'official_market'"
|
||||
@click="toLink(twoMenu)">
|
||||
<div v-if="twoMenu.meta.icon" class="w-[16px] h-[16px] relative flex items-center">
|
||||
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon" class="absolute !w-auto"
|
||||
size="18px" />
|
||||
<div class="w-[16px] h-[16px] mr-[3px] relative flex items-center">
|
||||
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon" class="!w-auto" size="16px" />
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
|
||||
<span class="text-[14px]">{{ twoMenu.meta.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 系统菜单 -->
|
||||
<template v-if="applyTypeList.includes(localMenuKey) || otherTypeList.includes(localMenuKey)">
|
||||
<div class="!border-0 !border-t-[1px] border-solid mx-[25px] bg-[#f7f7f7] my-[5px]"></div>
|
||||
<el-menu-item v-if="!item.children" class="!pl-[20px] text-[#333]" :index="String(item.name)" @click="toLink(item)">
|
||||
<template #title>
|
||||
<span class="text-[14px]">{{ item.meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<!-- 插件菜单 -->
|
||||
<template v-if="plugMenuType && localMenuKey == 'app_center'">
|
||||
<template v-for="(twoMenu, twoIndex) in menus">
|
||||
<el-sub-menu :index="String(twoMenu.meta.title)"
|
||||
v-if="twoMenu.meta.attr == 'system' && !twoMenu.meta.app && twoMenu.children">
|
||||
<el-sub-menu :index="String(twoMenu.meta.title)" v-if="twoMenu.meta.app && twoMenu.meta.app == plugMenuType && twoMenu.children">
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center">
|
||||
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
|
||||
class="absolute !w-auto" size="18px" />
|
||||
<div class="w-[16px] h-[16px] mr-[3px] relative flex items-center">
|
||||
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon" class="!w-auto" size="16px" />
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
|
||||
<span class="text-[14px]">{{ twoMenu.meta.title }}</span>
|
||||
</template>
|
||||
<template v-for="(threeMenu, threeIndex) in twoMenu.children" :key="threeIndex">
|
||||
<!-- 三级菜单 -->
|
||||
<el-sub-menu :index="String(threeMenu.meta.title)"
|
||||
v-if="threeMenu.meta.app && threeMenu.children">
|
||||
v-if="threeMenu.children && threeMenu.meta.show">
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center justify-center">
|
||||
<span class="iconfont iconyuanquan_huaban1 !text-[20px]"></span>
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title }}</span>
|
||||
<span class="text-[14px] pl-[20px]">{{ threeMenu.meta.title }}</span>
|
||||
</template>
|
||||
<template v-for="(fourMenu, fourIndex) in threeMenu.children"
|
||||
:key="fourIndex">
|
||||
<!-- 三级菜单 -->
|
||||
<el-sub-menu :index="String(fourMenu.meta.title)"
|
||||
v-if="fourMenu.children && fourMenu.meta.show">
|
||||
<template #title>
|
||||
<div
|
||||
class="w-[16px] h-[16px] relative flex items-center justify-center">
|
||||
<span class="iconfont icondian !text-[25px]"></span>
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ fourMenu.meta.title
|
||||
}}</span>
|
||||
</template>
|
||||
<template v-for="(fiveMenu, fiveIndex) in fourMenu.children"
|
||||
:key="fiveIndex">
|
||||
<el-menu-item v-if="fiveMenu.meta.show"
|
||||
class="!h-[52px] !pl-[55px]" :index="String(fiveMenu.name)"
|
||||
@click="toLink(fiveMenu)">
|
||||
<template #title>
|
||||
<span class="text-[14px]">{{ fiveMenu.meta.title
|
||||
}}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-else-if="fourMenu.meta.show"
|
||||
class="!ml-[30px] !h-[52px] !pl-[35px]"
|
||||
:index="String(fourMenu.name)" @click="toLink(fourMenu)">
|
||||
<el-menu-item v-if="fourMenu.meta.show"
|
||||
class="!pl-[55px]"
|
||||
:index="String(fourMenu.name)"
|
||||
@click="toLink(fourMenu)">
|
||||
<template #title>
|
||||
<span class="text-[14px]">{{ fourMenu.meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-if="threeMenu.meta.show" class="!h-[52px] !pl-[52px]"
|
||||
<el-menu-item v-else-if="threeMenu.meta.show"
|
||||
class="!pl-[40px]"
|
||||
:index="String(threeMenu.name)" @click="toLink(threeMenu)">
|
||||
<template #title>
|
||||
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
<!-- 插件菜单 -->
|
||||
<template v-if="otherTypeList.includes(localMenuKey) && twoMenu.meta.key == 'app_center' && plugMenuType">
|
||||
<template v-for="(twoMenu, twoIndex) in menus">
|
||||
<el-sub-menu :index="String(twoMenu.meta.title)"
|
||||
v-if="twoMenu.meta.app && twoMenu.meta.app == plugMenuType && twoMenu.children">
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center justify-center">
|
||||
<span class="iconfont iconyuanquan_huaban1 !text-[20px]"></span>
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
|
||||
</template>
|
||||
<template v-for="(threeMenu, threeIndex) in twoMenu.children"
|
||||
:key="threeIndex">
|
||||
<!-- 三级菜单 -->
|
||||
<el-sub-menu :index="String(threeMenu.meta.title)"
|
||||
v-if="threeMenu.children && threeMenu.meta.show">
|
||||
<template #title>
|
||||
<div
|
||||
class="w-[16px] h-[16px] relative flex items-center justify-center">
|
||||
<span class="iconfont icondian !text-[25px]"></span>
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ threeMenu.meta.title
|
||||
}}</span>
|
||||
</template>
|
||||
<template v-for="(fourMenu, fourIndex) in threeMenu.children"
|
||||
:key="fourIndex">
|
||||
<el-menu-item v-if="fourMenu.meta.show"
|
||||
class="!h-[52px] !pl-[55px]"
|
||||
:index="String(fourMenu.name)"
|
||||
@click="toLink(fourMenu)">
|
||||
<template #title>
|
||||
<span class="text-[14px]">{{ fourMenu.meta.title
|
||||
}}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-else-if="threeMenu.meta.show"
|
||||
class="!ml-[30px] !h-[52px] !pl-[35px]"
|
||||
:index="String(threeMenu.name)" @click="toLink(threeMenu)">
|
||||
<template #title>
|
||||
<span class="text-[14px]">{{ threeMenu.meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
<el-menu-item
|
||||
v-else-if="twoMenu.meta.app && twoMenu.meta.app == plugMenuType"
|
||||
class="!pl-[25px] text-[#333]" :index="String(twoMenu.name)"
|
||||
@click="toLink(twoMenu)">
|
||||
<template #title>
|
||||
<div v-if="twoMenu.meta.icon"
|
||||
class="w-[16px] h-[16px] relative flex items-center">
|
||||
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
|
||||
class="absolute !w-auto" size="18px" />
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-else-if="twoMenu.meta.attr == 'system' && !twoMenu.meta.app"
|
||||
class="!pl-[25px] text-[#333]" :index="String(twoMenu.name)"
|
||||
<el-menu-item v-else-if="twoMenu.meta.app && twoMenu.meta.app == plugMenuType"
|
||||
class="!pl-[20px] text-[#333]" :index="String(twoMenu.name)"
|
||||
@click="toLink(twoMenu)">
|
||||
<template #title>
|
||||
<div v-if="twoMenu.meta.icon"
|
||||
class="w-[16px] h-[16px] relative flex items-center">
|
||||
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon"
|
||||
class="absolute !w-auto" size="18px" />
|
||||
<div class="w-[16px] h-[16px] mr-[3px] relative flex items-center">
|
||||
<icon v-if="twoMenu.meta.icon" :name="twoMenu.meta.icon" class="!w-auto" size="16px" />
|
||||
</div>
|
||||
<span class="ml-[11px] text-[15px]">{{ twoMenu.meta.title }}</span>
|
||||
<span class="text-[14px]">{{ twoMenu.meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
@ -284,6 +201,7 @@ globalAppKey.value = storage.get('menuAppStorage')
|
||||
localMenuKey.value = storage.get('menuAppStorage')
|
||||
const isLoad = ref(false);
|
||||
|
||||
|
||||
// 应用跳转 start
|
||||
const applyList = ref([])
|
||||
const applyTypeList = ref([])
|
||||
@ -295,15 +213,10 @@ const getApplelist = async () => {
|
||||
if (item.type == 'app') { applyTypeList.value.push(item.key) }
|
||||
if (item.type == 'addon') { otherTypeList.value.push(item.key) }
|
||||
})
|
||||
otherTypeList.value = otherTypeList.value.concat(['member', 'app_center'])
|
||||
isLoad.value = true;
|
||||
}
|
||||
getApplelist()
|
||||
|
||||
const homeClick = () => {
|
||||
const key = storage.get('menuAppStorage')
|
||||
key ? router.push({ name: appLink.value[key] }) : router.push({ path: '/' })
|
||||
}
|
||||
// 应用跳转 end
|
||||
|
||||
// 菜单
|
||||
@ -333,7 +246,7 @@ const menus = computed(() => {
|
||||
}
|
||||
|
||||
// 用于插件的卸载或安装
|
||||
if(!applyList.value.length){
|
||||
if(!applyList.value.length && !globalAppKey.value){
|
||||
storage.set({ key: 'menuAppStorage', data: '' })
|
||||
globalAppKey.value = ""
|
||||
}
|
||||
@ -341,6 +254,20 @@ const menus = computed(() => {
|
||||
storage.set({ key: 'menuAppStorage', data: applyTypeList.value[0] })
|
||||
globalAppKey.value = applyTypeList.value[0]
|
||||
}
|
||||
menus.forEach((item,index)=>{
|
||||
if(globalAppKey.value && item.meta.app == globalAppKey.value){
|
||||
item.children.forEach((childItem,childIndex) => {
|
||||
menus.push(childItem);
|
||||
|
||||
if(childItem.children){
|
||||
let parentKey = childItem.meta.key;
|
||||
childItem.children.forEach((grandItem,grandIndex) => {
|
||||
grandItem.parentKey = parentKey;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
return menus
|
||||
})
|
||||
|
||||
@ -363,26 +290,36 @@ watch(route, () => {
|
||||
|
||||
const data = route.matched[1]
|
||||
currentRoute.value = route.matched[1]
|
||||
localMenuKey.value = data.meta.key
|
||||
|
||||
if(route.meta.app && route.meta.app == globalAppKey.value){
|
||||
menus.value.forEach((item,index)=>{
|
||||
if(item.children && item.name != route.name){
|
||||
item.children.forEach((childItem,childIndex) => {
|
||||
if(childItem.name == route.name) localMenuKey.value = childItem.parentKey;
|
||||
});
|
||||
}else if(item.name == route.name){
|
||||
localMenuKey.value = item.name;
|
||||
}
|
||||
})
|
||||
}else{
|
||||
localMenuKey.value = data.meta.key
|
||||
}
|
||||
|
||||
if(otherTypeList.value.includes(localMenuKey.value) && plugMenuType.value){
|
||||
localMenuKey.value = "app_center"
|
||||
}
|
||||
|
||||
systemStore.$patch(state => {
|
||||
state.menuDrawer = false
|
||||
})
|
||||
}, { immediate: true })
|
||||
|
||||
|
||||
|
||||
let onefloatMenu = ref(true);
|
||||
let threefloatMenu = ref(true);
|
||||
const onefloatMenuHover = ()=>{
|
||||
onefloatMenu.value = true;
|
||||
}
|
||||
const threefloatMenuHover = ()=>{
|
||||
threefloatMenu.value = true;
|
||||
}
|
||||
const toLink = (data, type) => {
|
||||
if(type == 'onefloatMenu') onefloatMenu.value = false;
|
||||
|
||||
if(type == 'threefloatMenu') threefloatMenu.value = false;
|
||||
if (!data.meta && data.type == 'app' || data.meta.key != 'official_market') {
|
||||
let name = data.name;
|
||||
if(data.type == 'app'){
|
||||
@ -397,6 +334,8 @@ const toLink = (data, type) => {
|
||||
userStore.setAppMenuList(appMenuList)
|
||||
|
||||
name = appLink.value[data.key];
|
||||
}else if(data.meta.app){
|
||||
name = getLinkName(data);
|
||||
}
|
||||
router.push({ name: name })
|
||||
} else {
|
||||
@ -404,6 +343,13 @@ const toLink = (data, type) => {
|
||||
}
|
||||
}
|
||||
|
||||
const getLinkName = (res)=>{
|
||||
if(res.children && res.children.length){
|
||||
return getLinkName(res.children[0])
|
||||
}
|
||||
return res.name
|
||||
}
|
||||
|
||||
// 主题风格
|
||||
const sidebar = computed(() => {
|
||||
return systemStore.sidebar
|
||||
@ -412,14 +358,15 @@ const sidebar = computed(() => {
|
||||
// 控制二级菜单的显示
|
||||
const isTwoMenuFn = (item) => {
|
||||
let bool = (otherTypeList.value.includes(localMenuKey.value) && globalAppKey.value == item.meta.app)
|
||||
|| (!otherTypeList.value.includes(localMenuKey.value) && (item.meta.key == localMenuKey.value || item.meta.app == localMenuKey.value))
|
||||
|| (!otherTypeList.value.includes(localMenuKey.value) && (item.meta.key == localMenuKey.value || item.meta.app == localMenuKey.value) && !item.meta.app)
|
||||
|| (item.meta.app && !otherTypeList.value.includes(localMenuKey.value) && item.meta.key == localMenuKey.value && localMenuKey.value.indexOf('index') == -1)
|
||||
|
||||
return bool;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.layout-aside {
|
||||
|
||||
&.bright {
|
||||
background-color: #F5F7F9;
|
||||
|
||||
@ -450,13 +397,34 @@ const isTwoMenuFn = (item) => {
|
||||
}
|
||||
|
||||
.second-menu .el-sub-menu .el-sub-menu__title {
|
||||
padding-left: 25px !important;
|
||||
padding-left: 20px !important;
|
||||
padding-right: 25px !important;
|
||||
height: 50px !important;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 5px;
|
||||
&:hover{
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.el-icon.el-sub-menu__icon-arrow {
|
||||
right: 25px;
|
||||
right: 20px;
|
||||
font-weight: bolder;
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.second-menu .el-menu-item{
|
||||
height: 50px !important;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 5px;
|
||||
&.is-active{
|
||||
background-color: var(--el-menu-hover-bg-color);
|
||||
}
|
||||
&:hover{
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@ -500,9 +468,7 @@ const isTwoMenuFn = (item) => {
|
||||
}
|
||||
|
||||
.layout-aside {
|
||||
// background-color: #12192D;
|
||||
background-color: #2b303b;
|
||||
|
||||
.menu-item {
|
||||
color: #fff;
|
||||
|
||||
@ -584,6 +550,7 @@ const isTwoMenuFn = (item) => {
|
||||
|
||||
.el-menu-item {
|
||||
color: #fff;
|
||||
height: 50px !important;
|
||||
}
|
||||
|
||||
.el-menu-item:hover,
|
||||
@ -19,10 +19,10 @@
|
||||
<div class="right-panel h-full flex items-center justify-end">
|
||||
<!-- 预览 只有站点时展示-->
|
||||
|
||||
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'site'"></i>
|
||||
<!-- 切换首页 -->
|
||||
<div class="navbar-item flex items-center h-full cursor-pointer" v-if="appType == 'site'" @click="checkIndexList">
|
||||
<icon name="iconfont-iconqiehuan" :title="t('indexSwitch')" />
|
||||
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" ></i>
|
||||
<!-- 切换应用 -->
|
||||
<div class="navbar-item flex items-center h-full cursor-pointer" @click="changeApp">
|
||||
<icon name="iconfont-iconqiehuan" :title="t('changeApp')" class="!text-xs" />
|
||||
</div>
|
||||
<!-- 切换语言 -->
|
||||
<div class="navbar-item !px-[0] flex items-center h-full cursor-pointer">
|
||||
@ -119,6 +119,9 @@ watch(screenWidth, () => {
|
||||
}
|
||||
})
|
||||
|
||||
const changeApp = ()=>{
|
||||
router.push({ path: '/app_manage' })
|
||||
}
|
||||
// 菜单栏展开折叠
|
||||
const toggleMenuCollapse = () => {
|
||||
systemStore.$patch((state) => {
|
||||
@ -178,6 +181,7 @@ const submitIndex = () => {
|
||||
.layout-header {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
height: 60px;
|
||||
box-shadow: 0px 0px 4px 0px rgba(0, 145, 255, 0.1);
|
||||
}
|
||||
|
||||
@ -9,14 +9,20 @@
|
||||
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarStyle') }}</div>
|
||||
<div class="">
|
||||
<el-radio-group v-model="sidebarStyle" class="ml-4">
|
||||
<el-radio label="oneType" size="large">样式一</el-radio>
|
||||
<el-radio label="twoType" size="large">样式二</el-radio>
|
||||
<el-radio label="threeType" size="large">样式三</el-radio>
|
||||
<el-radio label="oneType" size="large">
|
||||
<img class="w-[35px] h-[35px]" src="@/app/assets/images/one_type.png" alt="">
|
||||
</el-radio>
|
||||
<el-radio label="twoType" size="large">
|
||||
<img class="w-[35px] h-[35px]" src="@/app/assets/images/two_type.png" alt="">
|
||||
</el-radio>
|
||||
<el-radio label="threeType" size="large">
|
||||
<img class="w-[35px] h-[35px]" src="@/app/assets/images/three_type.png" alt="">
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 风格切换 -->
|
||||
<div class="setting-item flex items-baseline justify-between mb-[10px]">
|
||||
<!-- <div class="setting-item flex items-baseline justify-between mb-[10px]">
|
||||
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarMode') }}</div>
|
||||
<div class="">
|
||||
<el-radio-group v-model="sidebar" class="ml-4">
|
||||
@ -31,7 +37,7 @@
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- 黑暗模式 -->
|
||||
<div class="setting-item flex items-center justify-between mb-[10px]">
|
||||
<div class="title text-base text-tx-secondary">{{ t('layout.darkMode') }}</div>
|
||||
112
admin/src/layout/default/components/header/user-info.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dropdown @command="clickEvent" :tabindex="1">
|
||||
<div class="userinfo flex h-full items-center">
|
||||
<el-avatar :size="25" :icon="UserFilled" />
|
||||
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
|
||||
<icon name="element-ArrowDown" class="ml-[5px]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="usercenter"><router-link to="/user/center">账号设置</router-link></el-dropdown-item>
|
||||
<el-dropdown-item command="usercenter" @click="changePasswordDialog=true">修改密码</el-dropdown-item>
|
||||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dialog v-model="changePasswordDialog" title="修改密码" width="450px" :before-close="handleClose">
|
||||
<div>
|
||||
<el-form :model="saveInfo" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
|
||||
<el-form-item :label="t('originalPassword')" prop="original_password">
|
||||
<el-input v-model="saveInfo.original_password" type="password" :placeholder="t('originalPasswordPlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('newPassword')" prop="password">
|
||||
<el-input v-model="saveInfo.password" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
<div class="form-tip">{{t('passwordTip')}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('passwordCopy')" prop="password_copy">
|
||||
<el-input v-model="saveInfo.password_copy" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="changePasswordDialog = false">{{t('cancel')}}</el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)">{{t('save')}}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UserFilled } from '@element-plus/icons-vue'
|
||||
import { computed, reactive, ref, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
|
||||
import { t } from '@/lang'
|
||||
import {getEnv} from '@/app/api/sys'
|
||||
import { setUserInfo } from '@/app/api/personal'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const clickEvent = (command: string) => {
|
||||
switch (command) {
|
||||
case 'logout':
|
||||
userStore.logout()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 修改密码 --- start
|
||||
let changePasswordDialog = ref(false)
|
||||
const formRef = ref<FormInstance>();
|
||||
// 提交信息
|
||||
let saveInfo = reactive({
|
||||
original_password: '',
|
||||
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" },
|
||||
]
|
||||
});
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
let msg = "";
|
||||
if (saveInfo.password && !saveInfo.original_password) msg = t('originalPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && !saveInfo.password_copy) msg = t('newPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && saveInfo.password_copy && saveInfo.password != saveInfo.password_copy) msg = t('doubleCipherHint');
|
||||
if (msg) {
|
||||
ElNotification({
|
||||
type: 'error',
|
||||
message: msg,
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
setUserInfo(saveInfo).then((res: any) => {
|
||||
changePasswordDialog.value = false;
|
||||
}).catch((err: any) => {
|
||||
changePasswordDialog.value = false;
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
}
|
||||
// 修改密码 --- end
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@ -10,7 +10,7 @@ const modules = import.meta.glob('./*/index.vue')
|
||||
|
||||
// 主题样式
|
||||
let themeStyle = {
|
||||
'oneType': 'standard',
|
||||
'oneType': 'default',
|
||||
'twoType': 'profession',
|
||||
'threeType': 'business'
|
||||
}
|
||||
|
||||
10
admin/src/layout/preview/index.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<el-container class="w-screen h-screen min-w-[1200px]">
|
||||
<router-view></router-view>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@ -14,8 +14,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { img } from '@/utils/common'
|
||||
|
||||
const props = defineProps(['isShowHover','data','hoverType'])
|
||||
console.log();
|
||||
let data = ref([]);
|
||||
if(props.data){
|
||||
props.data.forEach((item,index) => {
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
<template>
|
||||
<div :class="['flex', { 'two-type': sidebar == 'twoType' }, { 'three-type': sidebar == 'threeType' }]" v-if="isLoad">
|
||||
|
||||
|
||||
<template v-for="(item, index) in menus" :key="index">
|
||||
<div v-if="isTwoMenuFn(item)" class="w-[210px] box-border border-r-[1px] border-solid second-menu">
|
||||
<div class="group flex flex-col items-center justify-center h-[64px] border-b-[1px] border-solid second-head cursor-pointer relative" @mouseenter="twofloatMenuHover">
|
||||
<div class="flex items-center">
|
||||
<img v-if="item.meta.parentIcon" :src="img(item.meta.parentIcon)" class="w-[40px] h-[40px] mr-[8px]" alt="">
|
||||
<img v-if="item.meta.parentIcon" :src="img(item.meta.parentIcon)" class="w-[40px] h-[40px] mr-[8px] rounded-full" alt="">
|
||||
<div class="flex items-center justify-center w-[30px] h-[30px]" v-else>
|
||||
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto" size="24px" />
|
||||
</div>
|
||||
<span>{{ item.meta.app ? item.meta.parentTitle : item.meta.title }}</span>
|
||||
</div>
|
||||
<app-menu :isShowHover="twofloatMenu" :data="applyList" @child-click="toLink" hoverType='twofloatMenu'></app-menu>
|
||||
<!-- <app-menu :isShowHover="twofloatMenu" :data="applyList" @child-click="toLink" hoverType='twofloatMenu'></app-menu>-->
|
||||
</div>
|
||||
|
||||
<el-scrollbar class="overflow-y-auto menus-wrap">
|
||||
<el-menu class="apply-menu !border-0" :router="true" unique-opened="true" :default-active="String(route.name)">
|
||||
<el-menu class="apply-menu !border-0" :router="true" :unique-opened="true" :default-active="String(route.name)">
|
||||
<template v-if="applyTypeList.length">
|
||||
<template v-for="(twoMenu, twoIndex) in item.children">
|
||||
<el-sub-menu :index="String(twoMenu.meta.title)" v-if="twoMenu.children && twoMenu.meta.show">
|
||||
@ -472,6 +472,7 @@ const isTwoMenuFn = (item) => {
|
||||
|| (!applyTypeList.value.length && (item.meta.key == localMenuKey.value || item.meta.app == localMenuKey.value))
|
||||
return bool;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@ -18,11 +18,10 @@
|
||||
<el-col :span="12">
|
||||
<div class="right-panel h-full flex items-center justify-end">
|
||||
<!-- 预览 只有站点时展示-->
|
||||
|
||||
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'site'"></i>
|
||||
<!-- 切换首页 -->
|
||||
<div class="navbar-item flex items-center h-full cursor-pointer" v-if="appType == 'site'" @click="checkIndexList">
|
||||
<icon name="iconfont-iconqiehuan" :title="t('indexSwitch')" />
|
||||
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')"></i>
|
||||
<!-- 切换应用 -->
|
||||
<div class="navbar-item flex items-center h-full cursor-pointer" @click="changeApp">
|
||||
<icon name="iconfont-iconqiehuan" :title="t('changeApp')" class="!text-xs" />
|
||||
</div>
|
||||
<!-- 切换语言 -->
|
||||
<div class="navbar-item !px-[0] flex items-center h-full cursor-pointer">
|
||||
@ -163,6 +162,10 @@ const checkIndexList = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const changeApp = ()=>{
|
||||
router.push({ path: '/app_manage' })
|
||||
}
|
||||
|
||||
const index_path = ref('')
|
||||
const submitIndex = () => {
|
||||
setIndexList({
|
||||
|
||||
@ -9,14 +9,20 @@
|
||||
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarStyle') }}</div>
|
||||
<div class="">
|
||||
<el-radio-group v-model="sidebarStyle" class="ml-4">
|
||||
<el-radio label="oneType" size="large">样式一</el-radio>
|
||||
<el-radio label="twoType" size="large">样式二</el-radio>
|
||||
<el-radio label="threeType" size="large">样式三</el-radio>
|
||||
<el-radio label="oneType" size="large">
|
||||
<img class="w-[35px] h-[35px]" src="@/app/assets/images/one_type.png" alt="">
|
||||
</el-radio>
|
||||
<el-radio label="twoType" size="large">
|
||||
<img class="w-[35px] h-[35px]" src="@/app/assets/images/two_type.png" alt="">
|
||||
</el-radio>
|
||||
<el-radio label="threeType" size="large">
|
||||
<img class="w-[35px] h-[35px]" src="@/app/assets/images/three_type.png" alt="">
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 风格切换 -->
|
||||
<div class="setting-item flex items-baseline justify-between mb-[10px]">
|
||||
<!-- <div class="setting-item flex items-baseline justify-between mb-[10px]">
|
||||
<div class="title text-base text-tx-secondary whitespace-nowrap">{{ t('layout.sidebarMode') }}</div>
|
||||
<div class="">
|
||||
<el-radio-group v-model="sidebar" class="ml-4">
|
||||
@ -31,7 +37,7 @@
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- 黑暗模式 -->
|
||||
<div class="setting-item flex items-center justify-between mb-[10px]">
|
||||
<div class="title text-base text-tx-secondary">{{ t('layout.darkMode') }}</div>
|
||||
|
||||
@ -1,21 +1,53 @@
|
||||
<template>
|
||||
<el-dropdown @command="clickEvent" :tabindex="1">
|
||||
<div class="userinfo flex h-full items-center">
|
||||
<el-avatar :size="25" :icon="UserFilled" />
|
||||
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
|
||||
<icon name="element-ArrowDown" class="ml-[5px]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="usercenter"><router-link to="/user/center">个人中心</router-link></el-dropdown-item>
|
||||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div>
|
||||
<el-dropdown @command="clickEvent" :tabindex="1">
|
||||
<div class="userinfo flex h-full items-center">
|
||||
<el-avatar :size="25" :icon="UserFilled" />
|
||||
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
|
||||
<icon name="element-ArrowDown" class="ml-[5px]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="usercenter"><router-link to="/user/center">账号设置</router-link></el-dropdown-item>
|
||||
<el-dropdown-item command="usercenter" @click="changePasswordDialog=true">修改密码</el-dropdown-item>
|
||||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
<el-dialog v-model="changePasswordDialog" title="修改密码" width="450px" :before-close="handleClose">
|
||||
<div>
|
||||
<el-form :model="saveInfo" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
|
||||
<el-form-item :label="t('originalPassword')" prop="original_password">
|
||||
<el-input v-model="saveInfo.original_password" type="password" :placeholder="t('originalPasswordPlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('newPassword')" prop="password">
|
||||
<el-input v-model="saveInfo.password" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
<div class="form-tip">{{t('passwordTip')}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('passwordCopy')" prop="password_copy">
|
||||
<el-input v-model="saveInfo.password_copy" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="changePasswordDialog = false">{{t('cancel')}}</el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)">{{t('save')}}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UserFilled } from '@element-plus/icons-vue'
|
||||
import { computed, reactive, ref, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
|
||||
import { t } from '@/lang'
|
||||
import {getEnv} from '@/app/api/sys'
|
||||
import { setUserInfo } from '@/app/api/personal'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
@ -27,6 +59,56 @@ const clickEvent = (command: string) => {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 修改密码 --- start
|
||||
let changePasswordDialog = ref(false)
|
||||
const formRef = ref<FormInstance>();
|
||||
// 提交信息
|
||||
let saveInfo = reactive({
|
||||
original_password: '',
|
||||
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" },
|
||||
]
|
||||
});
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
let msg = "";
|
||||
if (saveInfo.password && !saveInfo.original_password) msg = t('originalPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && !saveInfo.password_copy) msg = t('newPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && saveInfo.password_copy && saveInfo.password != saveInfo.password_copy) msg = t('doubleCipherHint');
|
||||
if (msg) {
|
||||
ElNotification({
|
||||
type: 'error',
|
||||
message: msg,
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
setUserInfo(saveInfo).then((res: any) => {
|
||||
changePasswordDialog.value = false;
|
||||
}).catch((err: any) => {
|
||||
changePasswordDialog.value = false;
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
}
|
||||
// 修改密码 --- end
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
<template>
|
||||
<el-dropdown @command="clickEvent" :tabindex="1">
|
||||
<div class="userinfo flex h-full items-center">
|
||||
<el-avatar :size="25" :icon="UserFilled" />
|
||||
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
|
||||
<icon name="element-ArrowDown" class="ml-[5px]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="usercenter"><router-link to="/user/center">个人中心</router-link></el-dropdown-item>
|
||||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UserFilled } from '@element-plus/icons-vue'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const clickEvent = (command: string) => {
|
||||
switch (command) {
|
||||
case 'logout':
|
||||
userStore.logout()
|
||||
break
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
63
admin/src/layout/tools/components/aside/index.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="m-[25px] w-[200px] bg-[#fff] aside-shadow app-aside-wrap">
|
||||
<el-menu :router="true" unique-opened="true" :default-active="String(route.name)">
|
||||
<template v-for="(item, index) in menus" :key="index">
|
||||
<el-menu-item v-if="item.meta.key != 'official_market'" @click="toLink(item)" :index="String(item.name)">
|
||||
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto mr-[6px]" size="16px" :title="item.meta.title" />
|
||||
<span>{{ item.meta.title }}</span>
|
||||
</el-menu-item>
|
||||
<div class="el-menu-item" v-else @click="toLink(item)">
|
||||
<icon v-if="item.meta.icon" :name="item.meta.icon" class="!w-auto mr-[6px]" size="16px" :title="item.meta.title" />
|
||||
<span class="text-[14px]">{{ item.meta.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { img } from '@/utils/common'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
const loading = ref(true)
|
||||
|
||||
const menus = computed(() => {
|
||||
let obj = []
|
||||
loading.value = true;
|
||||
userStore.routers.forEach((item, index) => {
|
||||
if (item.meta.key == 'tool' && item.children && item.children.length) {
|
||||
item.children.forEach((childItem,childIndex) => {
|
||||
if(childItem.meta.show == 1){
|
||||
obj.push(childItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
loading.value = false;
|
||||
return obj
|
||||
})
|
||||
|
||||
const toLink = (data)=>{
|
||||
if(data.meta.key != 'official_market'){
|
||||
router.push({ name: data.name })
|
||||
}else{
|
||||
window.open('https://www.niucloud.com/product/', '_blank')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.aside-shadow{
|
||||
box-shadow: 0 0 10px 1px rgba(0,0,0,0.1);
|
||||
}
|
||||
.app-aside-wrap{
|
||||
.el-menu-item{
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
152
admin/src/layout/tools/components/header/index.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<el-container class="h-[60px] bg-[#2B303B] flex items-center justify-between px-[15px] text-white">
|
||||
<div class="flex items-center text-[14px]">
|
||||
<span class="iconfont icontuodong !text-[24px] mr-[6px]"></span>
|
||||
<span class="cursor-pointer" @click="goAppManage">{{ t('appMarketPlace') }}</span>
|
||||
<template v-if="app_debug">
|
||||
<span class="mx-2 text-[#4F5563] mx-[15px]">|</span>
|
||||
<span class="cursor-pointer" @click="goDeveloperCenter">{{ t('developerCenter') }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex items-center mr-[20px] min-w-[200px] text-[14px]">
|
||||
<span class="cursor-pointer mr-[15px]" @click="goNiucloud">{{ t('niucloud') }}</span>
|
||||
<el-dropdown>
|
||||
<div class="userinfo flex h-full items-center">
|
||||
<el-avatar :size="25" :icon="UserFilled" />
|
||||
<div class="user-name pl-[8px] text-[#fff]">{{ userStore.userInfo.username }}</div>
|
||||
<icon name="element-ArrowDown" class="ml-[5px] !text-[#fff]" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="usercenter"><router-link to="/user/center">账号设置</router-link></el-dropdown-item>
|
||||
<el-dropdown-item command="usercenter"><router-link to="/tools/authorize">授权信息</router-link></el-dropdown-item>
|
||||
<el-dropdown-item command="usercenter" @click="changePasswordDialog=true">修改密码</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<span class="mx-2 text-[#4F5563]">|</span>
|
||||
<span class="cursor-pointer" @click="logout">{{t('exit')}}</span>
|
||||
</div>
|
||||
<el-dialog v-model="changePasswordDialog" title="修改密码" :before-close="handleClose">
|
||||
<div>
|
||||
<el-form :model="saveInfo" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
|
||||
<el-form-item :label="t('originalPassword')" prop="original_password">
|
||||
<el-input v-model="saveInfo.original_password" type="password" :placeholder="t('originalPasswordPlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('newPassword')" prop="password">
|
||||
<el-input v-model="saveInfo.password" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
<div class="form-tip">{{t('passwordTip')}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('passwordCopy')" prop="password_copy">
|
||||
<el-input v-model="saveInfo.password_copy" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="changePasswordDialog = false">{{t('cancel')}}</el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)">{{t('save')}}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
|
||||
import { t } from '@/lang'
|
||||
import {getEnv} from '@/app/api/sys'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import { setUserInfo } from '@/app/api/personal'
|
||||
const userStore = useUserStore()
|
||||
|
||||
const router = useRouter()
|
||||
let app_debug = ref(false)
|
||||
|
||||
const goAppManage = () => {
|
||||
router.push('/app_manage')
|
||||
}
|
||||
const getEnvFn = () => {
|
||||
getEnv().then(res => {
|
||||
app_debug.value = res.data.app_debug;
|
||||
}).catch(() => {})
|
||||
}
|
||||
getEnvFn();
|
||||
|
||||
const goRouter = () => {
|
||||
window.open('https://www.niucloud.com/product')
|
||||
}
|
||||
|
||||
// 跳转至开发者
|
||||
const goDeveloperCenter = () => {
|
||||
router.push('/tools/addon')
|
||||
}
|
||||
|
||||
const goNiucloud = () => {
|
||||
window.open('https://www.niucloud.com')
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
userStore.logout();
|
||||
}
|
||||
|
||||
|
||||
// 修改密码 --- start
|
||||
let changePasswordDialog = ref(false)
|
||||
const formRef = ref<FormInstance>();
|
||||
// 提交信息
|
||||
let saveInfo = reactive({
|
||||
original_password: '',
|
||||
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" },
|
||||
]
|
||||
});
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
let msg = "";
|
||||
if (saveInfo.password && !saveInfo.original_password) msg = t('originalPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && !saveInfo.password_copy) msg = t('newPasswordHint');
|
||||
if (saveInfo.password && saveInfo.original_password && saveInfo.password_copy && saveInfo.password != saveInfo.password_copy) msg = t('doubleCipherHint');
|
||||
if (msg) {
|
||||
ElNotification({
|
||||
type: 'error',
|
||||
message: msg,
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
setUserInfo(saveInfo).then((res: any) => {
|
||||
changePasswordDialog.value = false;
|
||||
}).catch((err: any) => {
|
||||
changePasswordDialog.value = false;
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
}
|
||||
// 修改密码 --- end
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout-header {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
box-shadow: 0px 0px 4px 0px rgba(0, 145, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
38
admin/src/layout/tools/index.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<el-container class="w-screen h-screen min-w-[1200px] flex flex-col">
|
||||
<el-header class="h-[60px]">
|
||||
<layout-header></layout-header>
|
||||
</el-header>
|
||||
<el-container class="overview-top">
|
||||
<layout-aside></layout-aside>
|
||||
|
||||
<el-main class="p-0 box-shadow w-[1200px] m-[25px] ml-0 bg-[#fff]">
|
||||
<el-scrollbar class="main-height">
|
||||
<router-view></router-view>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
|
||||
</el-container>
|
||||
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import layoutHeader from './components/header/index.vue'
|
||||
import layoutAside from './components/aside/index.vue'
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.overview-top {
|
||||
background-image: url('@/app/assets/images/index/overview.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
.main-height{
|
||||
height: calc(100vh - 120px);
|
||||
}
|
||||
.box-shadow{
|
||||
box-shadow: 0 0 10px 1px rgba(151, 151, 151, 0.1);
|
||||
}
|
||||
</style>
|
||||
@ -1,7 +1,7 @@
|
||||
import { createRouter, createWebHistory, RouteLocationRaw, RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import { STATIC_ROUTES, NO_LOGIN_ROUTES, ROOT_ROUTER, ADMIN_ROUTE, DECORATE_ROUTER, findFirstValidRoute } from './routers'
|
||||
import { STATIC_ROUTES, NO_LOGIN_ROUTES, ROOT_ROUTER, ADMIN_ROUTE, DECORATE_ROUTER,PREVIEW_ROUTER,APP_MANAGE_ROUTER,TOOL_ROUTER, findFirstValidRoute } from './routers'
|
||||
import { language } from '@/lang'
|
||||
import useSystemStore from '@/stores/modules/system'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
@ -48,8 +48,14 @@ router.beforeEach(async (to, from, next) => {
|
||||
await userStore.getAuthMenus()
|
||||
|
||||
// 设置首页路由
|
||||
const currApp = storage.get('menuAppStorage')
|
||||
let currApp = storage.get('menuAppStorage')
|
||||
const firstRoute = findFirstValidRoute(userStore.routers)
|
||||
|
||||
if (!currApp) {
|
||||
await userStore.getAppList()
|
||||
currApp = userStore.globalAppKey
|
||||
}
|
||||
|
||||
if (currApp) {
|
||||
ROOT_ROUTER.redirect = { name: userStore.addonIndexRoute[currApp] ?? firstRoute }
|
||||
} else {
|
||||
@ -67,6 +73,24 @@ router.beforeEach(async (to, from, next) => {
|
||||
return
|
||||
}
|
||||
|
||||
// 手机页面预览
|
||||
if (route.path == PREVIEW_ROUTER.path) {
|
||||
PREVIEW_ROUTER.children = route.children
|
||||
router.addRoute(PREVIEW_ROUTER)
|
||||
return
|
||||
}
|
||||
|
||||
// 应用管理
|
||||
if (route.path == APP_MANAGE_ROUTER.path) {
|
||||
APP_MANAGE_ROUTER.children = route.children
|
||||
router.addRoute(APP_MANAGE_ROUTER)
|
||||
return
|
||||
}
|
||||
if (route.path == TOOL_ROUTER.path) {
|
||||
TOOL_ROUTER.children = route.children
|
||||
router.addRoute(TOOL_ROUTER)
|
||||
return
|
||||
}
|
||||
if (!route.children) {
|
||||
router.addRoute(ADMIN_ROUTE.children[0].name, route)
|
||||
return
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { RouteRecordRaw, RouterView } from 'vue-router'
|
||||
import {RouteRecordRaw, RouterView} from 'vue-router'
|
||||
import Default from '@/layout/index.vue'
|
||||
import Decorate from '@/layout/decorate/index.vue'
|
||||
import { Key } from '@element-plus/icons-vue'
|
||||
import Preview from '@/layout/preview/index.vue'
|
||||
import AppManage from '@/layout/app_manage/index.vue'
|
||||
import Tools from '@/layout/tools/index.vue'
|
||||
import {Key} from '@element-plus/icons-vue'
|
||||
|
||||
// 静态路由
|
||||
export const STATIC_ROUTES: Array<RouteRecordRaw> = [
|
||||
@ -15,7 +18,7 @@ export const STATIC_ROUTES: Array<RouteRecordRaw> = [
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
component: Default,
|
||||
component: AppManage,
|
||||
children: [
|
||||
{
|
||||
path: 'center',
|
||||
@ -24,6 +27,14 @@ export const STATIC_ROUTES: Array<RouteRecordRaw> = [
|
||||
title: '个人中心'
|
||||
},
|
||||
component: () => import('@/app/views/index/personal.vue')
|
||||
},
|
||||
{
|
||||
path: 'edit_center',
|
||||
meta: {
|
||||
type: 1,
|
||||
title: '编辑个人中心'
|
||||
},
|
||||
component: () => import('@/app/views/index/edit_personal.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -62,6 +73,29 @@ export const DECORATE_ROUTER: RouteRecordRaw = {
|
||||
children: []
|
||||
}
|
||||
|
||||
// 页面预览路由
|
||||
export const PREVIEW_ROUTER: RouteRecordRaw = {
|
||||
path: '/preview',
|
||||
component: Preview,
|
||||
name: Symbol('preview'),
|
||||
children: []
|
||||
}
|
||||
|
||||
// 切换应用路由
|
||||
export const APP_MANAGE_ROUTER: RouteRecordRaw = {
|
||||
path: '/app_manage',
|
||||
component: AppManage,
|
||||
name: Symbol('app_manage'),
|
||||
children: []
|
||||
}
|
||||
|
||||
// 切换应用路由
|
||||
export const TOOL_ROUTER: RouteRecordRaw = {
|
||||
path: '/tools',
|
||||
component: Tools,
|
||||
name: Symbol('tools'),
|
||||
children: []
|
||||
}
|
||||
const modules = import.meta.glob('@/app/views/**/*.vue')
|
||||
const addonModules = import.meta.glob('@/**/views/**/*.vue')
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3883393 */
|
||||
src: url('//at.alicdn.com/t/c/font_3883393_yevzijodb3.woff2?t=1695808853045') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_3883393_yevzijodb3.woff?t=1695808853045') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_3883393_yevzijodb3.ttf?t=1695808853045') format('truetype');
|
||||
src: url('//at.alicdn.com/t/c/font_3883393_t7tf6zkctc.woff2?t=1697534072773') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_3883393_t7tf6zkctc.woff?t=1697534072773') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_3883393_t7tf6zkctc.ttf?t=1697534072773') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@ -13,6 +13,126 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconfapiaoguanli:before {
|
||||
content: "\e683";
|
||||
}
|
||||
|
||||
.iconhuishouzhan:before {
|
||||
content: "\e6d7";
|
||||
}
|
||||
|
||||
.iconshangpinliebiao1:before {
|
||||
content: "\e6d8";
|
||||
}
|
||||
|
||||
.iconshangpinfenlei:before {
|
||||
content: "\e6d9";
|
||||
}
|
||||
|
||||
.iconfenleishezhi:before {
|
||||
content: "\e6da";
|
||||
}
|
||||
|
||||
.iconluxianguanli:before {
|
||||
content: "\e6db";
|
||||
}
|
||||
|
||||
.iconjiudianguanli:before {
|
||||
content: "\e6dc";
|
||||
}
|
||||
|
||||
.iconshangjiadizhiku:before {
|
||||
content: "\e6dd";
|
||||
}
|
||||
|
||||
.iconkaxiangguanli:before {
|
||||
content: "\e6de";
|
||||
}
|
||||
|
||||
.iconjiudiandingdan:before {
|
||||
content: "\e6df";
|
||||
}
|
||||
|
||||
.iconjingdianguanli:before {
|
||||
content: "\e6e0";
|
||||
}
|
||||
|
||||
.icondingdanshezhi:before {
|
||||
content: "\e6e3";
|
||||
}
|
||||
|
||||
.iconjiaoyishezhi:before {
|
||||
content: "\e6e4";
|
||||
}
|
||||
|
||||
.icondingdanhexiao:before {
|
||||
content: "\e6e5";
|
||||
}
|
||||
|
||||
.icondingdanweiquan:before {
|
||||
content: "\e6e6";
|
||||
}
|
||||
|
||||
.icona-dingdanliebiao:before {
|
||||
content: "\e6e7";
|
||||
}
|
||||
|
||||
.iconyuyuexiangmu:before {
|
||||
content: "\e68e";
|
||||
}
|
||||
|
||||
.iconshangpinpinpai:before {
|
||||
content: "\e6c9";
|
||||
}
|
||||
|
||||
.iconyuyueshezhi:before {
|
||||
content: "\e6cb";
|
||||
}
|
||||
|
||||
.iconxiangmuguanli:before {
|
||||
content: "\e6cc";
|
||||
}
|
||||
|
||||
.iconyouhuiquan:before {
|
||||
content: "\e6cd";
|
||||
}
|
||||
|
||||
.iconpaisongshezhi:before {
|
||||
content: "\e685";
|
||||
}
|
||||
|
||||
.icontuikuanweiquan:before {
|
||||
content: "\e6ce";
|
||||
}
|
||||
|
||||
.iconyingxiaozhongxin:before {
|
||||
content: "\e6cf";
|
||||
}
|
||||
|
||||
.iconjishiguanli:before {
|
||||
content: "\e684";
|
||||
}
|
||||
|
||||
.iconxiangmufenlei:before {
|
||||
content: "\e6d0";
|
||||
}
|
||||
|
||||
.iconwuliugenzong:before {
|
||||
content: "\e6d1";
|
||||
}
|
||||
|
||||
.iconshangpinbiaoqian:before {
|
||||
content: "\e6d2";
|
||||
}
|
||||
|
||||
.iconshangpinpinglun:before {
|
||||
content: "\e6d3";
|
||||
}
|
||||
|
||||
.iconshangpinfuwu:before {
|
||||
content: "\e6d4";
|
||||
}
|
||||
|
||||
.icongonggao:before {
|
||||
content: "\e629";
|
||||
}
|
||||
|
||||