update admin

This commit is contained in:
全栈小学生 2023-10-23 17:03:20 +08:00
parent b948dd0561
commit 6c66a9546e
71 changed files with 3885 additions and 1381 deletions

View File

@ -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})
}

View File

@ -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})
}

View File

@ -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`)
}
/***************************************************** 地址管理 ****************************************************/
/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -7,7 +7,7 @@
"menuTypeDir": "目录",
"menuTypeMenu": "菜单",
"menuTypeButton": "按钮",
"menuDeleteTips": "确定要删除该菜单吗",
"menuDeleteTips": "删除菜单会删除当前菜单以及该菜单下所有子菜单,是否确认删除",
"addMenu": "添加菜单",
"updateMenu": "编辑菜单",
"routePath": "路由路径",

View File

@ -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": "取消"
}

View File

@ -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": "支付方式"
}

View File

@ -0,0 +1,10 @@
{
"refundNo": "退款编号",
"refundMoney": "退款金额",
"payType": "支付类型",
"status": "退款状态",
"createTime": "申请时间",
"refundNoPlaceholder": "请输入退款编号",
"startDate": "开始时间",
"endDate": "结束时间"
}

View File

@ -0,0 +1,16 @@
{
"refundNo": "退款编号",
"refundMoney": "退款金额",
"payType": "支付类型",
"status": "退款状态",
"createTime": "申请时间",
"refundNoPlaceholder": "请输入退款编号",
"startDate": "开始时间",
"endDate": "结束时间",
"outTradeNo": "交易流水号",
"refundTypeName": "退款方式",
"statusName": "退款状态",
"transfer": "转账",
"transferType": "转账方式",
"voucher": "凭证"
}

View File

@ -0,0 +1,8 @@
{
"app": "应用管理",
"descriptionLeft": "暂无安装任何应用,马上去",
"link": "插件管理",
"descriptionRight": "逛逛",
"niucloud": "Niucloud官网",
"appStore": "插件管理"
}

View File

@ -1,4 +1,5 @@
{
"changeApp": "切换应用",
"dataSummarize": "数据概况",
"todayData": "今日数据",
"memberNumb": "新增会员数",

View File

@ -0,0 +1,12 @@
{
"preview": "预览",
"weapp": "微信小程序",
"wechat": "微信公众号",
"link": "链接",
"copy": "复制",
"copySuccess": "复制成功",
"weappNotSet": "小程序未配置",
"developTitle": "开发环境配置",
"wapDomain": "wap域名WAP_DOMAIN",
"wapDomainPlaceholder": "请输入wap域名"
}

View 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官网我的授权 授权详情中查看"
}

View File

@ -1,17 +1,6 @@
{
"personal": "个人中心",
"editPersonal": "编辑",
"headImg": "头像",
"realName": "名称",
"originalPassword": "原始密码",
"password": "新密码",
"passwordCopy": "确认密码",
"passwordTip": "修改密码时必填.不修改密码时留空",
"realNamePlaceholder": "请输入用户名称",
"headImgPlaceholder": "请输入用户头像",
"originalPasswordPlaceholder": "请输入原始密码",
"passwordPlaceholder": "请输入新密码",
"save": "保存",
"cancel": "取消",
"originalPasswordHint": "原始密码不能为空",
"newPasswordHint": "请输入确认密码",
"doubleCipherHint": "两次新密码不同"
"realName": "名称"
}

View File

@ -0,0 +1,18 @@
{
"editPersonal": "编辑个人中心",
"headImg": "头像",
"realName": "名称",
"originalPassword": "原始密码",
"password": "新密码",
"passwordCopy": "确认密码",
"passwordTip": "修改密码时必填.不修改密码时留空",
"realNamePlaceholder": "请输入用户名称",
"headImgPlaceholder": "请输入用户头像",
"originalPasswordPlaceholder": "请输入原始密码",
"passwordPlaceholder": "请输入新密码",
"save": "保存",
"cancel": "返回",
"originalPasswordHint": "原始密码不能为空",
"newPasswordHint": "请输入确认密码",
"doubleCipherHint": "两次新密码不同"
}

View File

@ -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)
/**
* 云安装插件
*/

View File

@ -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;
};

View File

@ -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>

File diff suppressed because it is too large Load Diff

View File

@ -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);
}

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View 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>

View 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>

View File

@ -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]

View File

@ -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>

View File

@ -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}`;
// errorCorrectionLevelLH()
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 = '';

View 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>

View File

@ -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

View File

@ -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 {

View File

@ -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) => {

View File

@ -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

View 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>

View File

@ -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' : '' ]"

View File

@ -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()

View File

@ -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": "两次新密码不同"
}

View 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>

View 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>

View 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>

View File

@ -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) => {

View File

@ -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 {

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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){

View File

@ -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,

View File

@ -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);
}

View File

@ -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>

View 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>

View File

@ -10,7 +10,7 @@ const modules = import.meta.glob('./*/index.vue')
//
let themeStyle = {
'oneType': 'standard',
'oneType': 'default',
'twoType': 'profession',
'threeType': 'business'
}

View 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>

View File

@ -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) => {

View File

@ -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">

View File

@ -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({

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View 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>

View File

@ -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

View File

@ -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')

View File

@ -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";
}