update admin

This commit is contained in:
全栈小学生 2023-06-21 16:42:54 +08:00
parent e1d72b448d
commit 21d960df44
66 changed files with 2605 additions and 1133 deletions

View File

@ -12,7 +12,6 @@ import useSystemStore from '@/stores/modules/system'
import useAppStore from '@/stores/modules/app'
import { useDark, useToggle } from '@vueuse/core'
import { setThemeColor } from '@/utils/common'
import { language } from '@/lang'
import { useRoute } from 'vue-router'
const route = useRoute()
@ -20,15 +19,14 @@ const route = useRoute()
//
const systemStore = useSystemStore()
const locale = computed(() => (systemStore.lang === 'zh-cn' ? zhCn : en))
language.loadLocaleMessages(route.path, systemStore.lang)
const toggleDark = useToggle(useDark())
watch(route, () => {
useAppStore().$patch(state => {
state.route = route.path
state.route = route.meta.view || route.path
})
})
}, { immediate: true })
onMounted(() => {
//

View File

@ -2,13 +2,22 @@ import request from '@/utils/request'
/***************************************************** 自定义页面 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getDiyPageList(params: Record<string, any>) {
return request.get(`diy/diy`, {params})
}
/**
*
* @param params
* @returns
*/
export function getDiyPageList(params: Record<string, any>) {
return request.get(`diy/diy`, { params })
export function getDiyList(params: Record<string, any>) {
return request.get(`diy/list`, {params})
}
/**
@ -26,7 +35,7 @@ export function getDiyPageInfo(id: number) {
* @returns
*/
export function addDiyPage(params: Record<string, any>) {
return request.post('diy/diy', params, { showSuccessMessage: true })
return request.post('diy/diy', params, {showSuccessMessage: true})
}
/**
@ -34,7 +43,7 @@ export function addDiyPage(params: Record<string, any>) {
* @param params
*/
export function editDiyPage(params: Record<string, any>) {
return request.put(`diy/diy/${params.id}`, params, { showSuccessMessage: true })
return request.put(`diy/diy/${params.id}`, params, {showSuccessMessage: true})
}
/**
@ -42,7 +51,7 @@ export function editDiyPage(params: Record<string, any>) {
* @param params
*/
export function setUseDiyPage(params: Record<string, any>) {
return request.put(`diy/use`, params, { showSuccessMessage: true })
return request.put(`diy/use`, params, {showSuccessMessage: true})
}
/**
@ -50,7 +59,7 @@ export function setUseDiyPage(params: Record<string, any>) {
* @param params
*/
export function editDiyPageShare(params: Record<string, any>) {
return request.put(`diy/diy/share`, params, { showSuccessMessage: true })
return request.put(`diy/diy/share`, params, {showSuccessMessage: true})
}
/**
@ -59,28 +68,28 @@ export function editDiyPageShare(params: Record<string, any>) {
* @returns
*/
export function deleteDiyPage(id: number) {
return request.delete(`diy/diy/${id}`, { showSuccessMessage: true })
return request.delete(`diy/diy/${id}`, {showSuccessMessage: true})
}
/**
*
*/
export function initPage(params: Record<string, any>) {
return request.get(`diy/init`, { params })
return request.get(`diy/init`, {params})
}
/**
*
*/
export function getLink(params: Record<string, any>) {
return request.get(`diy/link`, { params })
return request.get(`diy/link`, {params})
}
/**
*
*/
export function getDiyBottom(params: Record<string, any>) {
return request.get(`diy/bottom`, { params })
return request.get(`diy/bottom`, {params})
}
/**
@ -89,14 +98,14 @@ export function getDiyBottom(params: Record<string, any>) {
* @returns
*/
export function setDiyBottom(params: Record<string, any>) {
return request.post('diy/bottom', params, { showSuccessMessage: true })
return request.post('diy/bottom', params, {showSuccessMessage: true})
}
/**
*
*/
export function getDiyTemplate(params: Record<string, any>) {
return request.get(`diy/template`, { params })
return request.get(`diy/template`, {params})
}
/**
@ -105,7 +114,7 @@ export function getDiyTemplate(params: Record<string, any>) {
* @returns
*/
export function getDiyRouteList(params: Record<string, any>) {
return request.get(`diy/route`, { params })
return request.get(`diy/route`, {params})
}
/**
@ -113,7 +122,7 @@ export function getDiyRouteList(params: Record<string, any>) {
* @param params
*/
export function getDiyRouteInfo(params: Record<string, any>) {
return request.get(`diy/route/info`, { params });
return request.get(`diy/route/info`, {params});
}
/**
@ -121,5 +130,32 @@ export function getDiyRouteInfo(params: Record<string, any>) {
* @param params
*/
export function editDiyRouteShare(params: Record<string, any>) {
return request.put(`diy/route/share`, params, { showSuccessMessage: true })
return request.put(`diy/route/share`, params, {showSuccessMessage: true})
}
/**
*
* @param params
* @returns
*/
export function getDecoratePage(params: Record<string, any>) {
return request.get(`diy/decorate`, {params})
}
/**
*
* @param params
* @returns
*/
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

@ -216,4 +216,39 @@ export function getLogList(params: Record<string, any>) {
*/
export function getLogInfo(id: number) {
return request.get(`site/log/${id}`)
}
/***************************************************** 账单列表 **************************************************/
/**
*
* @param params
* @returns
*/
export function getAccountList(params: Record<string, any>) {
return request.get(`site/account`, { params })
}
/**
*
* @param params
* @returns
*/
export function getAccountInfo(id: number) {
return request.get(`site/account/${id}`)
}
/**
*
* @returns
*/
export function getAccountStat() {
return request.get(`site/account/stat`)
}
/**
*
* @returns
*/
export function getAccountType() {
return request.get(`site/account/type`)
}

View File

@ -244,6 +244,26 @@ export function moveAttachment(params: Record<string, any>) {
return request.put(`sys/attachment/batchmove`, params)
}
/**
* menu菜单
*/
export function getAuthMenu() {
return request.get(`auth/site/showmenu`)
}
/**
*
*/
export function getShortcutMenu() {
return request.get(`sys/config/shortcut_menu`)
}
/**
*
*/
export function setShortcutMenu(params: Record<string, any>) {
return request.put(`sys/config/shortcut_menu`, params, { showSuccessMessage: true })
}
/***************************************************** 地址管理 ****************************************************/
@ -265,6 +285,21 @@ export function getAreatree(level: number = 1) {
return request.get(`sys/area/tree/${level}`)
}
/**
*
*/
export function getAddressInfo(params: any) {
return request.get(`sys/area/get_info`, { params })
}
/**
*
*/
export function getContraryAddress(params: any) {
return request.get(`sys/area/contrary`, { params })
}
/***************************************************** 存储设置 ****************************************************/
/**
@ -345,7 +380,7 @@ export function setTransferInfo(params: Record<string, any>) {
* @returns
*/
export function getCronList(params: any) {
return request.get(`sys/cron`, { params })
return request.get(`sys/schedule/list`, { params })
}
/**
@ -481,5 +516,17 @@ export function getMap() {
return request.get(`sys/config/map`)
}
/***************************************************** 首页 ****************************************************/
/**
*
*/
export function getIndexList() {
return request.get(`sys/config/site_index`)
}
/**
*
*/
export function setIndexList(params: Record<string, any>) {
return request.put(`sys/config/site_index`, params, { showSuccessMessage: true })
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -44,7 +44,6 @@ class Language {
this.setI18nLanguage(locale)
return nextTick()
} catch (e) {
console.log(e)
this.setI18nLanguage(locale)
return nextTick()
}

View File

@ -1,25 +0,0 @@
{
"menuName": "菜单名称",
"menuType": "类型",
"authId": "权限标识",
"menuTypeDir": "目录",
"menuTypeMenu": "菜单",
"menuTypeButton": "按钮",
"menuDeleteTips": "确定要删除该菜单吗?",
"addMenu": "添加菜单",
"updateMenu": "编辑菜单",
"routePath": "路由路径",
"viewPath": "组件路径",
"parentMenu": "父级菜单",
"menuIcon": "菜单图标",
"sort":"权重",
"menuKey":"菜单标识",
"menuNamePlaceholder": "请输入菜单名称",
"menuKeyPlaceholder": "请输入菜单标识",
"menuKeyValidata":"菜单标识只能使用字母数字下划线并且开头不能为数字",
"routePathPlaceholder": "请输入路由路径",
"viewPathPlaceholder": "请输入组件路径",
"authIdPlaceholder": "请输入权限标识",
"selectIconPlaceholder": "请选择菜单图标",
"topLevel": "顶级"
}

View File

@ -90,7 +90,9 @@
"503": "服务不可用",
"504": "网络超时",
"505": "http版本不支持该请求",
"timeout": "网络请求超时!"
"timeout": "网络请求超时!",
"requestError": "请求错误",
"errNetwork": "网络请求错误"
},
"linkPlaceholder": "请选择跳转链接",
"selectLinkTips": "链接选择",
@ -105,5 +107,8 @@
"emptyApp": "暂未安装任何应用",
"newInfo": "最新消息",
"mapSetting": "地图设置",
"mapKey": "腾讯地图KEY"
"mapKey": "腾讯地图KEY",
"indexTemplate": "首页模版",
"indexSwitch": "切换首页",
"indexWarning": "你确定要切换首页吗?"
}

View File

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

View File

@ -1,9 +1,26 @@
{
"developTitle": "开发环境配置",
"wapDomain": "wap域名WAP_DOMAIN",
"wapDomainPlaceholder": "请输入wap域名",
"pageSet": "页面设置",
"tabEditContent": "内容",
"tabEditStyle": "样式",
"pageStyle": "页面样式",
"pageContent": "页面内容",
"pageName": "页面名称",
"pageNamePlaceholder": "请输入页面名称",
"pageBgColor": "页面颜色",
"bgUrl": "背景图片",
"marginSet": "边距设置",
"componentStyleTitle": "组件样式",
"bottomBgColor": "底部背景",
"bottomBgTips": "底部背景包含边距和圆角",
"componentBgColor": "组件背景",
"marginTop": "上边距",
"marginBottom": "下边距",
"marginBoth": "左右边距",
"topRounded": "上圆角",
"bottomRounded": "下圆角",
"warmPrompt": "温馨提示",
"leavePageTitleTips": "确定离开此页面?",
"leavePageContentTips": "系统可能不会保存您所做的更改。",
@ -27,7 +44,12 @@
"imageAdsTips": "建议上传尺寸相同的图片推荐尺寸750*350",
"addImageAd": "添加图片",
"imageUrlTip": "请上传图片",
"imageHeight": "图片高度",
"imageHeightPlaceholder": "请输入图片高度",
"imageHeightRegNum": "图片高度格式错误,请输入数字",
"articleData": "文章数据",
"articleStyle": "文章样式",
"articleBgColor": "文章背景",
"dataSources": "数据来源",
"defaultSources": "默认",
"manualSelectionSources": "手动选择",
@ -65,6 +87,7 @@
"addGraphicNav": "添加导航",
"blankHeightSet": "高度设置",
"blankHeight": "空白高度",
"styleSet": "风格设置",
"titleStyle": "标题样式",
"selectStyle": "风格选择",
"titleContent": "标题内容",
@ -73,6 +96,7 @@
"textAlign": "对齐方式",
"textAlignLeft": "居左",
"textAlignCenter": "居中",
"textSet": "文字设置",
"textFontSize": "文字大小",
"textFontWeight": "文字粗细",
"fontWeightBold": "加粗",
@ -84,5 +108,6 @@
"moreContent": "“更多”按钮内容",
"more": "文字",
"morePlaceholder": "请输入文字",
"moreIsShow": "是否显示"
"moreIsShow": "是否显示",
"memberStyle": "会员样式"
}

View File

@ -1,12 +1,19 @@
{
"decorate": "装修",
"pageDecorate": "页面装修",
"changeTemplate": "切换模板",
"templateName": "模板名称",
"preview": "预览",
"weapp": "微信小程序",
"wechat": "微信公众号",
"link": "链接",
"copy": "复制",
"copySuccess": "复制成功",
"weappNotSet": "小程序未配置",
"hopeBeforeTip": "我希望把",
"hopeAfterTip": "切换成其他样式",
"changeTemplateTip": "选择",
"template": "模板",
"changeMyPageTip": "选择微页面作为",
"createPage": "创建微页面",
"myPage": "我的微页面",
"refreshPage": "刷新",
"placeholderTemplate": "请选择一个模板",
"placeholderMyPage": "请选择一个微页面",
"developTitle": "开发环境配置",
"wapDomain": "wap域名WAP_DOMAIN",
"wapDomainPlaceholder": "请输入wap域名"

View File

@ -1,11 +1,12 @@
{
"title": "页面名称",
"typeName": "页面模板",
"addType": "页面类型",
"addPageTips": "创建新页面",
"pageTemplatePlaceholder": "请选择页面模板",
"pageTypePlaceholder": "请选择页面模板",
"nameMax": "名称不能超过12个字符",
"templateName": "模板名称",
"empty": "空白",
"templateName": "已有模板",
"emptyTemplate": "空模板",
"status": "状态",
"updateTime": "更新时间",
"use": "使用",

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,36 @@
{
"title": "页面名称",
"typeName": "页面模板",
"addPageTips": "创建新页面",
"pageTemplatePlaceholder": "请选择页面模板",
"nameMax": "名称不能超过12个字符",
"templateName": "模板名称",
"empty": "空白",
"status": "状态",
"updateTime": "更新时间",
"use": "使用",
"isUse": "使用中",
"unused": "未使用",
"all": "全部",
"basicRoute": "基础页面",
"diyPage": "自定义页面",
"wapUrl": "wap链接",
"weappUrl": "小程序链接",
"shareLink": "分享链接",
"copy": "复制",
"copySuccess": "复制成功",
"titlePlaceholder": "请输入页面名称",
"addDiyPage": "添加页面",
"diyPageDeleteTips": "确定要删除该自定义页面吗?",
"promote": "推广",
"share": "分享",
"shareSet": "分享设置",
"sharePage": "分享页面",
"wechat": "微信公众号",
"weapp": "微信小程序",
"shareTitle": "分享标题",
"shareTitlePlaceholder": "请输入分享标题",
"shareDesc": "分享描述",
"shareDescPlaceholder": "请输入分享描述",
"shareImageUrl": "分享图片"
}

View File

@ -0,0 +1,33 @@
{
"id":"主键",
"accountDetail":"账单详情",
"detail":"详情",
"idPlaceholder":"请输入主键",
"siteId":"站点id",
"siteIdPlaceholder":"请输入站点id",
"type":"账单类型",
"money":"账单金额",
"moneyPlaceholder":"请输入账单金额",
"tradeNo":"账单编号",
"tradeNoPlaceholder":"请输入账单编号",
"createTime":"添加时间",
"createTimePlaceholder":"请输入添加时间",
"addSiteAccountLog":"添加站点账单记录",
"updateSiteAccountLog":"编辑站点账单记录",
"siteAccountLogDeleteTips":"确定要删除该站点账单记录吗?",
"totalPay":"累计收款(元)",
"totalRefund":"累计退款(元)",
"totalTransfer":"累计转账(元)",
"accountType": "请选择退款类型",
"startDate": "开始时间",
"endDate": "结束时间",
"transferNo" : "转账单号",
"transferTime": "转账时间",
"transferType": "转账类型",
"transferMoney": "转账金额",
"transferRemark": "转账说明",
"outTradeNo": "单号",
"refundMoney": "退款金额",
"failReason": "退款说明",
"body": "说明"
}

View File

@ -40,5 +40,28 @@
"accumulative":"累计",
"officialAccount": "Niucloud官方公众号",
"officialAccountDesc": "微信扫码关注",
"WeCom": "添加企业微信群"
"WeCom": "添加企业微信群",
"path": "地址",
"menuName": "名称",
"menuNamePlaceholder": "模版名称",
"menuBgColor": "背景颜色",
"menuImg": "选择图标",
"menuDesc": "描述",
"addShortcutMenu": "添加快捷模版",
"appTemplate": "应用模块",
"siteType": "站点类型",
"periodTime": "有效期",
"renew": "续费",
"selectModel": "选择模版",
"addMenu": "添加模块",
"shortcutLink": "链接",
"emptyMenu": "暂无快捷模块",
"select": "选择",
"custom": "自定义",
"accessSite": "访问站点",
"pathSelect": "选择链接",
"bgColorPlaceholder": "请选择背景色",
"iconPlaceholder": "请选择图标",
"pathPlaceholder": "请选择链接",
"descPlaceholder": "输入描述语…"
}

View File

@ -17,6 +17,5 @@
"weappTempKey" : "模板编号",
"smsId":"短信模版ID",
"smsIdPlaceholder":"短信模版ID",
"noticeType":"消息类型"
"noticeType":"消息类型"
}

View File

@ -34,6 +34,8 @@
"columnComment": "字段描述",
"columnType": "类型",
"fieldAttribute": "字段属性",
"addAndEdit":"添加编辑",
"listSearch":"列表查询",
"isPk":"是否主键",
"isRequired":"是否必填",
"isInsert":"是否添加",
@ -41,7 +43,7 @@
"isLists":"列表展示",
"isSearch":"是否搜索",
"isQuery":"是否查询",
"queryType":"查询方式",
"queryType":"搜索方式",
"viewType":"显示方式",
"pkRepeatTip": "只能添加一个主键",
"formInput":"文本框",

View File

@ -13,5 +13,8 @@
"nextTime":"下次执行时间",
"task":"任务命令",
"data":"附加参数",
"statusDesc":"最后执行结果"
"statusDesc":"最后执行结果",
"key": "key",
"timeClass": "时间类型",
"executeTime": "执行时间"
}

View File

@ -14,7 +14,12 @@
<icon :name="meta.icon" class="absolute top-[50%] -translate-y-[50%]" />
</div>
<template #title>
<span :class="['ml-[10px]', {'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}]">{{ meta.title }}</span>
<div class="relative">
<span :class="['ml-[10px]', {'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}]">{{ meta.title }}</span>
<div class="absolute top-[50%] -translate-y-[50%] right-[-288%]" @click="checkIndexList">
<img v-if="routes.path == '/site/siteindex'" class="w-[12px] h-[12px]" src="@/assets/images/index/model_tag.png"/>
</div>
</div>
</template>
</el-menu-item>
<el-menu-item v-else :index="String(routes.name)" :route="routePath">
@ -23,13 +28,35 @@
</template>
</el-menu-item>
</template>
<el-dialog v-model="showDialog" :title="t('indexTemplate')" width="550px" :destroy-on-close="true" >
<div class="flex flex-wrap">
<div v-for="(items, index) in indexList" :key="index" v-if="index_path == ''">
<div @click="index_path = items.view_path" class="index-item py-[5px] px-[10px] mr-[10px] rounded-[3px] cursor-pointer" :class="items.is_use == 1 ? 'selected' : '' ">
<span >{{ items.name }}</span>
</div>
</div>
<div v-for="(itemTo, indexTo) in indexList" :key="indexTo" v-else>
<div @click="index_path = itemTo.view_path" class="index-item py-[5px] px-[10px] mr-[10px] rounded-[3px] cursor-pointer" :class="index_path == itemTo.view_path ? 'selected' : '' ">
<span >{{ itemTo.name }}</span>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="submitIndex">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { t } from '@/lang'
import { getIndexList, setIndexList } from '@/api/sys'
import { useRoute, useRouter } from 'vue-router'
import { CollectionTag } from '@element-plus/icons-vue'
import { computed } from 'vue'
import { ref, computed } from 'vue'
import menuItem from './menu-item.vue'
const router = useRouter()
const props = defineProps({
routes: {
type: Object,
@ -45,6 +72,26 @@ const meta = computed(() => props.routes.meta)
const resolvePath = (path: string) => {
return `${props.routePath}/${path}`
}
const indexList = ref();
const showDialog = ref(false)
const checkIndexList = () => {
getIndexList().then(res => {
showDialog.value = true
indexList.value = res.data
})
}
const index_path = ref('');
const submitIndex = () => {
setIndexList({
view_path: index_path.value
}).then(() => {
showDialog.value = false
router.go(0)
})
}
</script>
<style lang="scss">
@ -59,4 +106,8 @@ const resolvePath = (path: string) => {
.el-alert .el-alert__description{
margin-top: 0;
}
.selected {
color: #fff;
background-color: #2C3EEF;
}
</style>

View File

@ -23,7 +23,12 @@
<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')"/>
</div>
<!-- 切换语言 -->
<div class="navbar-item flex items-center h-full cursor-pointer">
<switch-lang />
@ -55,6 +60,26 @@
</span>
</template>
</el-dialog>
<el-dialog v-model="showDialog" :title="t('indexTemplate')" width="550px" :destroy-on-close="true" >
<div class="flex flex-wrap">
<div v-for="(items, index) in indexList" :key="index" v-if="index_path == ''">
<div @click="index_path = items.view_path" class="index-item py-[5px] px-[10px] mr-[10px] rounded-[3px] cursor-pointer" :class="items.is_use == 1 ? 'selected' : '' ">
<span >{{ items.name }}</span>
</div>
</div>
<div v-for="(itemTo, indexTo) in indexList" :key="indexTo" v-else>
<div @click="index_path = itemTo.view_path" class="index-item py-[5px] px-[10px] mr-[10px] rounded-[3px] cursor-pointer" :class="index_path == itemTo.view_path ? 'selected' : '' ">
<span >{{ itemTo.name }}</span>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="submitIndex">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</el-container>
</template>
@ -70,6 +95,9 @@ import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
import useUserStore from '@/stores/modules/user'
import { getIndexList, setIndexList } from '@/api/sys'
import { ElMessageBox } from 'element-plus'
const router = useRouter()
const appType = storage.get('app_type')
const { toggle: toggleFullscreen, isFullscreen } = useFullscreen()
@ -138,7 +166,7 @@ const toggleMenuCollapse = () => {
//
const refreshRouter = () => {
if (!appStore.routeRefrehTag) return
if (!appStore.routeRefreshTag) return
appStore.refreshRouterView()
}
@ -153,6 +181,25 @@ const breadcrumb = computed(() => {
const backFn = () => {
router.go(-1)
}
const indexList = ref();
const showDialog = ref(false)
const checkIndexList = () => {
getIndexList().then(res => {
showDialog.value = true
indexList.value = res.data
})
}
const index_path = ref('');
const submitIndex = () => {
setIndexList({
view_path: index_path.value
}).then(() => {
showDialog.value = false
router.go(0)
})
}
</script>
<style lang="scss" scoped>
@ -167,4 +214,16 @@ const backFn = () => {
&:hover {
background-color: var(--el-bg-color-page);
}
}</style>
}
.index-item {
background-color: var(--el-bg-color-page);
&:hover {
color: #fff;
background-color: #2C3EEF;
}
}
.selected {
color: #fff;
background-color: #2C3EEF;
}
</style>

View File

@ -11,7 +11,7 @@
<el-main :class="['main-wrap h-full p-0',{'bg-page': dark}]">
<el-scrollbar>
<div class="p-[10px]">
<router-view v-slot="{ Component, route }" v-if="appStore.routeRefrehTag">
<router-view v-slot="{ Component, route }" v-if="appStore.routeRefreshTag">
<keep-alive :include="tabbarStore.tabNames">
<component :is="Component" :key="route.fullPath" />
</keep-alive>

View File

@ -1,4 +1,4 @@
import { createRouter, createWebHistory, RouteLocationRaw } from 'vue-router'
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, SITE_ROUTE, DECORATE_ROUTER, findFirstValidRoute } from './routers'
@ -24,6 +24,17 @@ router.push = (to: RouteLocationRaw) => {
return originPush(route)
}
/**
* resolve方法
*/
const originResolve = router.resolve
router.resolve = (to: RouteLocationRaw, currentLocation?: RouteLocationNormalizedLoaded) => {
const route = typeof to == 'string' ? urlToRouteRaw(to) : to
const paths = route.path.split('/').filter((item: string) => { return item })
route.path = ['admin', 'site', 'decorate'].indexOf(paths[0]) == -1 ? `/${getAppType()}${route.path}` : route.path
return originResolve(route, currentLocation)
}
// 全局前置守卫
router.beforeEach(async (to, from, next) => {
NProgress.configure({ showSpinner: false })
@ -39,7 +50,7 @@ router.beforeEach(async (to, from, next) => {
setWindowTitle(title)
// 加载语言包
await language.loadLocaleMessages(to.path, useSystemStore().lang);
await language.loadLocaleMessages((to.meta.view || to.path), useSystemStore().lang);
let matched: any = to.matched;

View File

@ -122,7 +122,8 @@ const createRoute = function (route: Route, parentRoute: RouteRecordRaw | null =
icon: route.icon,
type: route.menu_type,
show: route.is_show,
app: route.app_type
app: route.app_type,
view: route.view_path
}
}
if (route.menu_type == 0) {

View File

@ -4,7 +4,7 @@ import NProgress from 'nprogress'
interface App {
route: string,
routeRefrehTag: boolean,
routeRefreshTag: boolean,
pageReturn: boolean
}
@ -12,17 +12,17 @@ const useAppStore = defineStore('app', {
state: (): App => {
return {
route: '',
routeRefrehTag: true,
routeRefreshTag: true,
pageReturn: false
}
},
actions: {
refreshRouterView() {
this.routeRefrehTag = false
this.routeRefreshTag = false
NProgress.start()
nextTick(() => {
this.routeRefrehTag = true
this.routeRefreshTag = true
NProgress.done()
})
}

View File

@ -15,6 +15,7 @@ const useDiyStore = defineStore('diy', {
name: '', // 页面标识
type: '', // 页面模板
typeName: '', // 页面模板名称
templateName : '', // 页面模板标识
isDefault: 0, // 是否默认页面
predefineColors: [
'#F4391c',

View File

@ -33,9 +33,9 @@ const useSystemStore = defineStore('user', {
this.userInfo = res.data.userinfo
setToken(res.data.token)
storage.set({ key: 'userinfo', data: res.data.userinfo })
storage.set({ key: 'siteId', data: res.data.site_id })
storage.set({ key: 'siteId', data: res.data.site_info.site_code })
storage.set({ key: 'siteInfo', data: res.data.site_info })
storage.set({ key: 'comparisonSiteIdStorage', data: res.data.site_id })
storage.set({ key: 'comparisonSiteIdStorage', data: res.data.site_info.site_code })
storage.set({ key: 'comparisonTokenStorage', data: res.data.token })
resolve(res)
})

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3883393 */
src: url('//at.alicdn.com/t/c/font_3883393_pwtqr51nps.woff2?t=1686032889350') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_pwtqr51nps.woff?t=1686032889350') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_pwtqr51nps.ttf?t=1686032889350') format('truetype');
src: url('//at.alicdn.com/t/c/font_3883393_pgas1v4bw2a.woff2?t=1686965237342') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_pgas1v4bw2a.woff?t=1686965237342') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_pgas1v4bw2a.ttf?t=1686965237342') format('truetype');
}
.iconfont {
@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
.iconqiehuan:before {
content: "\e61e";
}
.iconxiangyoujiantou:before {
content: "\e660";
}

View File

@ -23,7 +23,7 @@ class Request {
constructor() {
this.instance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_URL,
baseURL: import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`,
timeout: 30000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded;',
@ -111,7 +111,7 @@ class Request {
* @param err
*/
private handleNetworkError(err: any) {
let errMessage = t('axios.requestError')
let errMessage = ''
if (err.response && err.response.status) {
const errStatus = err.response.status
@ -158,8 +158,9 @@ class Request {
break
}
}
if (err.message.includes('timeout')) errMessage = t('axios.timeout')
ElMessage({ message: errMessage, type: 'error' })
err.message.includes('timeout') && (errMessage = t('axios.timeout'))
err.code == 'ERR_NETWORK' && (errMessage = err.config.baseURL + t('axios.errNetwork'))
errMessage && ElMessage({ message: errMessage, type: 'error' })
}
private handleAuthError(code: number) {

View File

@ -125,7 +125,7 @@
}
const blurImageHeight = ()=> {
diyStore.editComponent.imageHeight = parseFloat(diyStore.editComponent.imageHeight).toFixed(2);
diyStore.editComponent.imageHeight = parseInt(diyStore.editComponent.imageHeight);
}
const imageBoxRef = ref()

View File

@ -155,6 +155,13 @@ const diyStore = useDiyStore()
const route = useRoute();
const router = useRouter()
route.query.id = route.query.id || 0;
route.query.name = route.query.name || '';
route.query.type = route.query.type || ''; //
route.query.template = route.query.template || ''; //
route.query.title = route.query.title || '';
route.query.back = route.query.back || '/diy/list';
const wapUrl = ref('')
const wapDomain = ref('')
const wapPreview = ref('')
@ -163,7 +170,7 @@ const loadingIframe = ref(false) // 加载iframe
const loadingDev = ref(false) //
const timeFrame = ref(0)
const backPath = route.query.back || '/diy/list'
const backPath = route.query.back
const component = ref([])
const componentType: string[] = reactive([])
const page = ref('')
@ -172,12 +179,6 @@ const siteId = ref(0)
const activeNames = ref(componentType)
const handleChange = (val: string[]) => { }
route.query.id = route.query.id || 0;
route.query.name = route.query.name || '';
route.query.template = route.query.template || ''; //
route.query.template_name = route.query.template_name || ''; //
route.query.title = route.query.title || '';
//
const originData = reactive({
id: diyStore.id,
@ -245,8 +246,8 @@ watch(
initPage({
id: route.query.id,
name: route.query.name,
type: route.query.type,
template: route.query.template,
template_name: route.query.template_name,
title: route.query.title
}).then(res => {
let data = res.data;
@ -255,6 +256,7 @@ initPage({
diyStore.name = data.name;
diyStore.type = data.type;
diyStore.typeName = data.type_name;
diyStore.templateName = data.template;
diyStore.isDefault = data.is_default;
if (data.value) {
let sources = JSON.parse(data.value);
@ -387,11 +389,13 @@ const save = () => {
name: diyStore.name,
title: diyStore.global.title,
type: diyStore.type,
template: diyStore.templateName,
is_default: diyStore.isDefault,
is_change: isChange.value ? 0 : 1,
value: JSON.stringify({
global: toRaw(diyStore.global),
value: toRaw(diyStore.value)
})
}),
};
const save = diyStore.id ? editDiyPage : addDiyPage

View File

@ -1,196 +1,355 @@
<template>
<div class="main-container">
<div class="flex flex-wrap">
<div class="page-item relative bg-no-repeat 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="flex h-[700px] bg-body p-[20px]" v-show="loading">
<iframe v-show="loadingIframe" class="w-[375px] border border-slate-100" :src="wapPreview" frameborder="0" id="previewIframe" @load="loadIframe"></iframe>
<div v-show="loadingDev" class="w-[375px] border border-slate-100 pt-[20px] px-[20px]">
<div class="font-bold text-xl mb-[40px]">{{t('developTitle')}}</div>
<div class="mb-[20px] flex flex-col">
<text class="mb-[10px]">{{ t('wapDomain') }}</text>
<el-input v-model="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable />
<div v-show="item.use_template.url" class="w-[282px] h-[500px] mx-auto">
<iframe v-show="item.loadingIframe" class="w-[282px] h-[500px] mx-auto"
:src="item.use_template.wapPreview"
frameborder="0" @load="loadIframe(key)"></iframe>
<div v-show="item.loadingDev"
class="w-[282px] h-[500px] 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">
<text class="mb-[10px]">{{ t('wapDomain') }}</text>
<el-input v-model="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable/>
</div>
<el-button type="primary" @click="saveDomain()">{{ t('confirm') }}</el-button>
</div>
<el-button type="primary" @click="save" >{{ t('confirm') }}</el-button>
</div>
<div class="w-[500px] ml-[40px]">
<el-button type="primary" @click="toDecorate()">{{ t('decorate') }}</el-button>
<div class="info-wrap mt-[20px]">
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('preview')">
<el-radio-group v-model="previewMode">
<el-radio :label="'weapp'">{{t('weapp')}}</el-radio>
<el-radio :label="'wechat'">{{t('wechat')}}</el-radio>
</el-radio-group>
</el-form-item>
<template v-if="previewMode == 'wechat'">
<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>
</template>
</el-input>
</el-form-item>
<el-form-item label=" " v-show="wapImage">
<el-image :src="wapImage"/>
</el-form-item>
</template>
<template v-if="previewMode == 'weapp'">
<el-form-item label=" " v-if="weappConfig.qr_code">
<el-image class="w-[100px] h-[100px]" :src="img(weappConfig.qr_code)"/>
</el-form-item>
<el-form-item label=" " v-else>
<span class="text-gray-400">{{t('weappNotSet')}}</span>
</el-form-item>
</template>
</el-form>
</div>
<div v-show="!item.use_template.wapPreview" class="overflow-hidden w-[282px] h-[500px] mx-auto">
<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>
<div class="item-hide absolute inset-x-0 inset-y-0 bg-black bg-opacity-50 text-center rounded-[40px]"
: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)">切换</el-button>
<el-button v-show="item.use_template.mode == 'diy'" @click="toDecorate(item.use_template)">装修
</el-button>
<el-button @click="toPreview(item.use_template)">预览</el-button>
</div>
</div>
</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-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>
</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('changeMyPageTip') + ' ' + page[formData.type].title" value="diy"/>
</el-select>
</el-form-item>
<el-form-item label="" v-show="hope == 'template'">
<el-select v-model="formData.template" class="w-full">
<el-option v-for="(item, key) in page[formData.type].template" :label="item.title" :value="key"/>
</el-select>
</el-form-item>
<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-select>
<div class="mt-[10px]">
<span class="cursor-pointer text-primary mr-[10px]" @click="toDiyList">{{ t('createPage') }}</span>
<span class="cursor-pointer text-primary" @click="refreshMyPage">{{ t('refreshPage') }}</span>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel')}}</el-button>
<el-button type="primary" @click="save">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import {ref, reactive, watch} from 'vue'
import {reactive, ref, watch, computed} from 'vue'
import {t} from '@/lang'
import { useRouter} from 'vue-router'
import {getWeappConfig} from '@/api/weapp'
import {getUrl} from '@/api/sys'
import {useClipboard} from '@vueuse/core'
import {ElMessage} from 'element-plus'
import {img} from '@/utils/common'
import QRCode from "qrcode";
import {useRoute, useRouter} from 'vue-router'
import {TabsPaneContext, ElMessage, ElMessageBox} from 'element-plus'
import {getDecoratePage, getDiyList, changeTemplate} from '@/api/diy'
import storage from '@/utils/storage'
const wapUrl = ref('')
const page: any = reactive({})
const showDialog = ref(false)
const router = useRouter()
const hope = ref('template')
const wapDomain = ref('')
const wapImage = ref('')
const wapPreview = ref('')
const loading = ref(false)
const loadingIframe = ref(false) // iframe
const loadingDev = ref(false) //
const timeFrame = ref(0)
//
const formData = reactive({
type: '',
mode: '',
template: '',
id: ''
})
var time = new Date().getTime();
const refreshData = () => {
formData.type = '';
formData.mode = '';
formData.template = '';
formData.id = '';
getDecoratePage({}).then((res => {
for (let key in res.data) {
page[key] = res.data[key]
}
getUrl().then((res:any)=> {
wapDomain.value = res.data.wap_domain;
wapUrl.value = res.data.wap_url;
setDomain();
for (let key in page) {
if (page[key].use_template.url) {
page[key].loadingIframe = false; // iframe
page[key].loadingDev = false; //
page[key].isDisabledPop = false; //
//
if(import.meta.env.MODE == 'production') return;
wapDomain.value = page[key].domain_url.wap_domain;
page[key].wapUrl = page[key].domain_url.wap_url;
// envwap
if (wapDomain.value) return;
// wap
if (import.meta.env.MODE == 'development' && !wapDomain.value && storage.get('wap_domain')) {
page[key].wapUrl = storage.get('wap_domain')
page[key].loadingIframe = true; // iframe
page[key].loadingDev = false; //
page[key].isDisabledPop = false; //
}
let wap_domain_storage = storage.get('wap_domain');
if(wap_domain_storage){
wapUrl.value = wap_domain_storage
setDomain();
return;
}
setDomain(key);
page[key].timeFrame = new Date().getTime();
}
}
timeFrame.value = new Date().getTime();
});
const save = ()=> {
wapUrl.value = wapDomain.value + '/wap'
setDomain();
storage.set({ key : 'wap_domain', data :wapUrl.value });
loadingIframe.value = true;
loadingDev.value = false;
}));
}
const setDomain = ()=> {
let siteInfo = storage.get('siteInfo');
let siteId = 0;
if (siteInfo) siteId = siteInfo.site_id;
wapPreview.value = `${wapUrl.value}/pages/index/index?mode=preview&site_id=${siteId}`;
QRCode.toDataURL(wapPreview.value, {errorCorrectionLevel: 'L', margin: 0, width: 100}).then(url => {
wapImage.value = url
refreshData();
// iframe
const loadIframe = (key: string) => {
if (storage.get('wap_domain')) return;
if (!page[key].use_template.wapPreview) return;
var loadTime = new Date().getTime();
var difference = loadTime - page[key].timeFrame;
// 1000wap
if (difference < 1000) {
page[key].loadingDev = true;
page[key].isDisabledPop = true;
page[key].loadingIframe = false;
} else {
page[key].loadingDev = false;
page[key].isDisabledPop = false;
page[key].loadingIframe = true;
}
}
const saveDomain = () => {
let wapUrl = wapDomain.value + '/wap';
storage.set({key: 'wap_domain', data: wapUrl});
for (let key in page) {
if (page[key].use_template.url) {
page[key].wapUrl = wapUrl;
setDomain(key);
}
}
setTimeout(() => {
for (let key in page) {
if (page[key].use_template.url) {
page[key].loadingIframe = true; // iframe
page[key].loadingDev = false; //
page[key].isDisabledPop = false; //
}
}
}, 100 * 3);
}
const setDomain = (key: string) => {
page[key].use_template.wapPreview = page[key].wapUrl + page[key].use_template.url;
}
const show = (key: string, data: any) => {
//
showDialog.value = true;
hope.value = data.use_template.hope;
formData.type = key;
formData.mode = data.use_template.mode;
if (hope.value == 'template') {
formData.template = data.use_template.template;
} else if (hope.value == 'diy') {
formData.id = data.use_template.id;
}
}
//
const toDecorate = (data: any) => {
let query: any = {
back: '/diy/index'
};
if (data.id) {
query.id = data.id;
} else if (data.name) {
query.name = data.name;
}
let url = router.resolve({
path: '/decorate/edit',
query
});
window.open(url.href);
}
//
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
});
window.open(url.href);
}
//
const toDiyList = (data: any) => {
let url = router.resolve({
path: '/site/diy/list'
});
window.open(url.href);
}
//
const refreshMyPage = () => {
getDiyList({type: formData.type, mode: 'diy'}).then((res) => {
let isExist = true; //
for (let i = 0; i < res.data.length; i++) {
if (formData.id == res.data[i].id) {
isExist = false;
break;
}
}
if (isExist) {
formData.id = '';
}
page[formData.type].my_page = {};
Object.assign(page[formData.type].my_page, res.data);
})
}
// iframe
const loadIframe = ()=> {
if (!wapPreview.value) return
var loadTime = new Date().getTime();
var difference = loadTime - timeFrame.value;
// 1000wap
if (difference < 1000) {
loadingDev.value = true;
loadingIframe.value = false;
wapPreview.value = ''
wapImage.value = ''
} else {
loadingDev.value = false;
loadingIframe.value = true;
watch(
() => hope.value,
(newValue, oldValue) => {
if (newValue == 'template') {
formData.id = '';
} else if (newValue == 'diy') {
formData.mode = 'diy';
formData.template = '';
}
}
loading.value = true
)
watch(
() => formData.template,
(newValue, oldValue) => {
if (newValue) {
formData.mode = page[formData.type].template[newValue].mode;
}
}
)
const isRepeat = ref(false)
const save = () => {
if (hope.value == 'template') {
if (formData.template == '') {
ElMessage({
type: 'warning',
message: `${t('placeholderTemplate')}`,
});
return;
}
} else if (hope.value == 'diy') {
if (formData.id == '') {
ElMessage({
type: 'warning',
message: `${t('placeholderMyPage')}`,
});
return;
}
}
if (isRepeat.value) return
isRepeat.value = true
changeTemplate({
...formData
}).then((res) => {
isRepeat.value = false;
showDialog.value = false;
refreshData();
})
}
const router = useRouter()
const weappConfig = reactive({
qr_code: ''
})
const previewMode = ref('weapp')
/**
* 获取微信配置
*/
getWeappConfig().then((res: any) => {
if (res.code == 1) {
let data = res.data;
weappConfig.qr_code = data.qr_code;
}
})
const url = router.resolve({
path: '/decorate/edit',
query: {name: 'DIY_INDEX'}
});
const toDecorate = () => {
router.push('/decorate/edit?name=DIY_INDEX&back=/diy/index')
}
/**
* 复制
*/
const {copy, isSupported, copied} = useClipboard()
const copyEvent = (text: string) => {
if (!isSupported.value) {
ElMessage({
message: t('notSupportCopy'),
type: 'warning'
})
}
copy(text)
}
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t('copySuccess'),
type: 'success'
})
}
})
</script>
<style lang="scss">
.copy {
background: var(--el-color-primary) !important;
color: var(--el-color-white) !important;
}
</style>
<style lang="scss" scoped>
.page-item {
background-image: url(assets/images/iphone_bg.png);
background-color: var(--el-bg-color);
background-size: 100%;
.item-hide {
display: none;
.item-btn-box {
button {
height: 35px;
width: 100px;
& ~ button {
margin-top: 15px;
margin-left: 0;
}
}
}
}
&:hover {
.item-hide:not(.disabled) {
display: block !important;
}
}
}
</style>

View File

@ -9,14 +9,14 @@
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="diyPageTableData.searchParam" ref="searchFormDiyPageRef" v-if="tabValue == 'diy'">
<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('pageTemplatePlaceholder')">
<el-select v-model="diyPageTableData.searchParam.type" :placeholder="t('pageTypePlaceholder')">
<el-option :label="t('all')" value="" />
<el-option v-for="(item, key) in pageTemplate" :label="item.title" :value="key" />
<el-option v-for="(item, key) in pageType" :label="item.title" :value="key" />
</el-select>
</el-form-item>
<el-form-item>
@ -24,84 +24,40 @@
<el-button @click="resetForm(searchFormDiyPageRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
<el-form :inline="true" :model="diyRouteTableData.searchParam" ref="searchFormDiyRouteRef" v-if="tabValue == 'route'">
<el-form-item :label="t('title')" prop="title">
<el-input v-model="diyRouteTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadDiyRouteList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormDiyRouteRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div>
<el-tabs v-model="tabValue" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane :label="t('diyPage')" name="diy">
<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="promoteEvent(row)">{{ t('promote') }}</el-button>
<el-button v-if="row.type != 'DIY_PAGE' && row.is_default == 0" type="primary" link @click="setUse(row)">{{ t('use') }}</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>
</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-tab-pane>
<!-- 基础页面路径 -->
<el-tab-pane :label="t('basicRoute')" name="route">
<el-table :data="diyRouteTableData.data" size="large" v-loading="diyRouteTableData.loading">
<template #empty>
<span>{{ !diyRouteTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="title" :label="t('title')" min-width="70" />
<el-table-column prop="page" :label="t('wapUrl')" min-width="170">
<template #default="{ row }">
<span class="mr-[10px]">{{ wapDomain + row.page }}</span>
<el-button type="primary" link @click="copyEvent(wapDomain + row.page)">{{ t('copy') }}</el-button>
</template>
</el-table-column>
<el-table-column prop="page" :label="t('weappUrl')" min-width="120">
<template #default="{ row }">
<span class="mr-[10px]">{{ row.page }}</span>
<el-button type="primary" link @click="copyEvent(row.page)">{{ t('copy') }}</el-button>
</template>
</el-table-column>
<el-table-column :label="t('share')" fixed="right" min-width="80">
<template #default="{ row }">
<el-button v-if="row.is_share == 1" type="primary" link @click="openShare(row)">{{ t('shareSet') }}</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<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="danger" 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-card>
@ -113,16 +69,16 @@
<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('typeName')" prop="template">
<el-select v-model="formData.template" :placeholder="t('pageTemplatePlaceholder')" class="w-full">
<el-option v-for="(item, key) in pageTemplate" :label="item.title" :value="key" />
<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_name" v-show="pageTemplateData">
<el-radio-group v-model="formData.template_name">
<el-radio label="">{{ t('empty') }}</el-radio>
<el-radio v-for="(item,key) in pageTemplateData" :label="key">{{item.title}}</el-radio>
</el-radio-group>
<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>
@ -136,7 +92,6 @@
<!-- 分享设置-->
<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>
@ -164,345 +119,217 @@
</template>
</el-dialog>
<!-- 推广-->
<el-dialog v-model="promoteDialogVisible" :title="t('promote')" width="30%">
<el-form label-width="90px">
<el-form-item :label="t('shareLink')">
<el-input readonly :value="promoteWapDomain">
<template #append>
<el-button @click="copyEvent(promoteWapDomain)" class="bg-primary copy">{{ t('copy') }}</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label=" ">
<el-image :src="wapImage" />
</el-form-item>
</el-form>
<!-- <el-tabs v-model="tabPromote">-->
<!-- <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-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, computed } from 'vue'
import { t } from '@/lang'
import { getDiyPageList, deleteDiyPage, setUseDiyPage, getDiyTemplate, getDiyRouteList, getDiyRouteInfo, editDiyPageShare, editDiyRouteShare } from '@/api/diy'
import { TabsPaneContext, ElMessage, ElMessageBox, FormInstance } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { useClipboard } from '@vueuse/core'
import QRCode from "qrcode";
import { getUrl } from '@/api/sys'
import { reactive, ref, watch, computed } from 'vue'
import { t } from '@/lang'
import { getDiyPageList, deleteDiyPage, getDiyTemplate, editDiyPageShare } from '@/api/diy'
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import QRCode from "qrcode";
import { getUrl } from '@/api/sys'
const pageTemplate: any = reactive({})
const router = useRouter()
const route = useRoute()
const pageName = route.meta.title;
const pageType: any = reactive({}) //
const router = useRouter()
const route = useRoute()
const pageName = route.meta.title;
//
const formData = reactive({
title: '',
template: '',
template_name:''
})
//
const formRules = computed(() => {
return {
template: [
{ required: true, message: t('pageTemplatePlaceholder'), trigger: 'blur' },
],
title: [
{ required: true, message: t('titlePlaceholder'), trigger: 'blur' },
]
}
})
const pageTemplateData = computed(()=>{
let data:any = '';
formData.template_name = '';
if(formData.template){
data = pageTemplate[formData.template].template;
//
// if(Object.keys(data).length) formData.template_name =Object.keys(data)[0];
}
return data;
})
const formRef = ref<FormInstance>()
const dialogVisible = ref(false)
const addEvent = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
dialogVisible.value = false;
router.push(`/decorate/edit?template=${formData.template}&template_name=${formData.template_name}&title=${formData.title}`);
}
})
}
let diyRouteTableData = reactive({
loading: true,
data: [],
searchParam: {
"title": "",
"type":""
}
})
const wapDomain = ref('')
const getDomain = async () => {
wapDomain.value = (await getUrl()).data.wap_url;
};
getDomain();
/**
* 获取自定义路由列表
*/
const loadDiyRouteList = () => {
diyRouteTableData.loading = true
getDiyRouteList({
...diyRouteTableData.searchParam
}).then(res => {
diyRouteTableData.loading = false
diyRouteTableData.data = res.data
}).catch(() => {
diyRouteTableData.loading = false
})
}
loadDiyRouteList()
//
getDiyTemplate({}).then(res => {
for (let key in res.data) {
pageTemplate[key] = res.data[key]
}
})
let diyPageTableData: any = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
"title": "",
"template": '',
}
})
const tabValue = ref('diy')
const handleClick = (tab: TabsPaneContext, event: Event) => {
tabValue.value = tab.props.name;
if (tabValue.value == 'diy') {
loadDiyPageList()
} else {
loadDiyRouteList()
}
}
const searchFormDiyRouteRef = ref<FormInstance>()
const searchFormDiyPageRef = ref<FormInstance>()
//
const loadDiyPageList = (page: number = 1) => {
diyPageTableData.loading = true
diyPageTableData.page = page
getDiyPageList({
page: diyPageTableData.page,
limit: diyPageTableData.limit,
...diyPageTableData.searchParam
}).then(res => {
diyPageTableData.loading = false
diyPageTableData.data = res.data.data
diyPageTableData.total = res.data.total
}).catch(() => {
diyPageTableData.loading = false
})
}
loadDiyPageList()
//
const editEvent = (data: any) => {
let url = router.resolve({
path: '/decorate/edit',
query: { id: data.id }
});
window.open(url.href);
}
//
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('diyPageDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}
).then(() => {
deleteDiyPage(id).then(() => {
loadDiyPageList()
}).catch(() => {
})
})
}
// 使
const setUse = (data: any) => {
setUseDiyPage({ id: data.id }).then(() => {
loadDiyPageList()
})
}
/**
* 复制
*/
const { copy, isSupported, copied } = useClipboard()
const copyEvent = (text: string) => {
if (!isSupported.value) {
ElMessage({
message: t('notSupportCopy'),
type: 'warning'
})
}
copy(text)
}
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t('copySuccess'),
type: 'success'
})
}
})
const tabShareType = ref('wechat')
const sharePage = ref('')
const shareFormId = ref(0)
const diyRouteData = reactive({
title: '',
name: '',
page: '',
is_share: 0,
sort: 0
})
const shareFormData = reactive({
wechat: {
//
const formData = reactive({
title: '',
desc: '',
url: ''
},
weapp: {
title: '',
url: ''
}
})
const shareDialogVisible = ref(false)
const shareFormRules = computed(() => {
return {}
})
type: '',
template:''
})
const shareFormRef = ref<FormInstance>()
const openShare = async (row: any) => {
if (tabValue.value == 'route') {
//
let info = (await getDiyRouteInfo({
name: row.name
})).data;
if (info.title) {
row.id = info.id;
row.title = info.title
row.name = info.name
row.page = info.page
row.is_share = info.is_share
row.sort = info.sort
row.share = info.share
//
const formRules = computed(() => {
return {
title: [
{ required: true, message: t('titlePlaceholder'), trigger: 'blur' },
],
type: [
{ required: true, message: t('pageTypePlaceholder'), trigger: 'blur' },
]
}
})
diyRouteData.title = row.title
diyRouteData.name = row.name
diyRouteData.page = row.page
diyRouteData.is_share = row.is_share
diyRouteData.sort = row.sort
const pageTypeData = computed(()=>{
let data:any = '';
formData.template = '';
if(formData.type){
data = pageType[formData.type].template;
}
return data;
})
const formRef = ref<FormInstance>()
const dialogVisible = ref(false)
const addEvent = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
dialogVisible.value = false;
let url = `/decorate/edit?type=${formData.type}&title=${formData.title}`;
if(formData.template) url += `&template=${formData.template}`;
router.push(url);
}
})
}
shareFormId.value = row.id;
sharePage.value = row.title;
let share = row.share ? JSON.parse(row.share) : {
wechat: { title: '', desc: '', url: '' },
weapp: { title: '', url: '' }
const wapDomain = ref('')
const getDomain = async () => {
wapDomain.value = (await getUrl()).data.wap_url;
};
if (share) {
shareFormData.wechat = share.wechat;
shareFormData.weapp = share.weapp;
getDomain();
//
getDiyTemplate({ mode : 'diy' }).then(res => {
for (let key in res.data) {
pageType[key] = res.data[key]
}
})
let diyPageTableData: any = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
"title": "",
"template": '',
}
})
const searchFormDiyPageRef = ref<FormInstance>()
//
const loadDiyPageList = (page: number = 1) => {
diyPageTableData.loading = true
diyPageTableData.page = page
getDiyPageList({
page: diyPageTableData.page,
limit: diyPageTableData.limit,
...diyPageTableData.searchParam
}).then(res => {
diyPageTableData.loading = false
diyPageTableData.data = res.data.data
diyPageTableData.total = res.data.total
}).catch(() => {
diyPageTableData.loading = false
})
}
shareDialogVisible.value = true;
}
loadDiyPageList()
const shareEvent = async (formEl: FormInstance | undefined) => {
if (!formEl) return
//
const editEvent = (data: any) => {
let url = router.resolve({
path: '/decorate/edit',
query: {id: data.id}
});
window.open(url.href);
}
await formEl.validate(async (valid) => {
if (valid) {
let save = tabValue.value == 'diy' ? editDiyPageShare : editDiyRouteShare
save({
id: shareFormId.value,
share: JSON.stringify(shareFormData),
...diyRouteData
}).then(() => {
if (tabValue.value == 'diy') {
loadDiyPageList()
} else {
loadDiyRouteList()
//
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('diyPageDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}
shareDialogVisible.value = false;
).then(() => {
deleteDiyPage(id).then(() => {
loadDiyPageList()
}).catch(() => {
})
})
}
//
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
});
window.open(url.href);
}
const tabShareType = ref('wechat')
const sharePage = ref('')
const shareFormId = ref(0)
const shareFormData = reactive({
wechat: {
title: '',
desc: '',
url: ''
},
weapp: {
title: '',
url: ''
}
})
}
const promoteDialogVisible = ref(false)
const tabPromote = ref('wechat')
const promoteWapDomain = ref('')
const wapImage = ref('')
const promoteEvent = (data: any) => {
promoteWapDomain.value = wapDomain.value + '/pages/index/diy?id=' + data.id;
QRCode.toDataURL(promoteWapDomain.value, { errorCorrectionLevel: 'L', margin: 0, width: 100 }).then(url => {
wapImage.value = url
const shareDialogVisible = ref(false)
const shareFormRules = computed(() => {
return {}
})
promoteDialogVisible.value = true;
}
const shareFormRef = ref<FormInstance>()
const openShare = async (row: any) => {
shareFormId.value = row.id;
sharePage.value = row.title;
let share = row.share ? JSON.parse(row.share) : {
wechat: { title: '', desc: '', url: '' },
weapp: { title: '', url: '' }
};
if (share) {
shareFormData.wechat = share.wechat;
shareFormData.weapp = share.weapp;
}
shareDialogVisible.value = true;
}
const resetForm = (formEl: FormInstance | undefined)=>{
if (!formEl) return
formEl.resetFields();
loadDiyPageList();
}
const shareEvent = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
editDiyPageShare({
id: shareFormId.value,
share: JSON.stringify(shareFormData),
}).then(() => {
loadDiyPageList()
shareDialogVisible.value = false;
}).catch(() => {
})
}
})
}
const resetForm = (formEl: FormInstance | undefined)=>{
if (!formEl) return
formEl.resetFields();
loadDiyPageList();
}
</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,18 +0,0 @@
<template>
<div class="main-container"></div>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
import {useRouter} from 'vue-router'
const router = useRouter()
const url = router.resolve({
path: '/decorate/edit',
query: {name: 'DIY_MEMBER_INDEX'}
});
router.push('/decorate/edit?name=DIY_MEMBER_INDEX')
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,196 @@
<template>
<div class="main-container w-[375px] mx-auto my-[20px] relative">
<div class="flex h-full" v-show="loading">
<iframe v-show="loadingIframe" class="w-[375px] border border-slate-100 bg-gray-100" :src="wapPreview" frameborder="0" id="previewIframe" @load="loadIframe"></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">
<text class="mb-[10px]">{{ t('wapDomain') }}</text>
<el-input v-model="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable/>
</div>
<el-button type="primary" @click="save">{{ t('confirm') }}</el-button>
</div>
<div class="w-[400px] absolute bg-body top-[10%] -right-[450px]" v-if="loadingIframe">
<div class="info-wrap mt-[20px]">
<el-form label-width="40px" class="px-[20px]">
<el-form-item :label="t('preview')">
<el-radio-group v-model="previewMode">
<el-radio :label="'weapp'">{{t('weapp')}}</el-radio>
<el-radio :label="'wechat'">{{t('wechat')}}</el-radio>
</el-radio-group>
</el-form-item>
<template v-if="previewMode == 'wechat'">
<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>
</template>
</el-input>
</el-form-item>
<el-form-item label=" " v-show="wapImage">
<el-image :src="wapImage"/>
</el-form-item>
</template>
<template v-if="previewMode == 'weapp'">
<el-form-item label=" " v-if="weappConfig.qr_code">
<el-image class="w-[100px] h-[100px]" :src="img(weappConfig.qr_code)"/>
</el-form-item>
<el-form-item label=" " v-else>
<span class="text-gray-400">{{t('weappNotSet')}}</span>
</el-form-item>
</template>
</el-form>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref, reactive, watch} from 'vue'
import {t} from '@/lang'
import {useRoute, useRouter} from 'vue-router'
import {getWeappConfig} from '@/api/weapp'
import {getUrl} from '@/api/sys'
import {useClipboard} from '@vueuse/core'
import {ElMessage} from 'element-plus'
import {img} from '@/utils/common'
import QRCode from "qrcode";
import storage from '@/utils/storage'
import {getPreviewData} from '@/api/diy'
const wapUrl = ref('')
const wapDomain = ref('')
const wapImage = ref('')
const wapPreview = ref('')
const loading = ref(false)
const loadingIframe = ref(false) // iframe
const loadingDev = ref(false) //
const timeFrame = ref(0)
var time = new Date().getTime();
const route = useRoute();
route.query.id = route.query.id || 0;
route.query.name = route.query.name || '';
getUrl().then((res: any) => {
wapDomain.value = res.data.wap_domain;
wapUrl.value = res.data.wap_url;
setDomain();
//
if (import.meta.env.MODE == 'production') return;
// envwap
if (wapDomain.value) return;
let wap_domain_storage = storage.get('wap_domain');
if (wap_domain_storage) {
wapUrl.value = wap_domain_storage
setDomain();
return;
}
timeFrame.value = new Date().getTime();
});
const save = () => {
wapUrl.value = wapDomain.value + '/wap';
setDomain();
storage.set({key: 'wap_domain', data: wapUrl.value});
loadingIframe.value = true;
loadingDev.value = false;
}
const setDomain = () => {
let siteInfo = storage.get('siteInfo');
let siteCode = '';
if (siteInfo) siteCode = siteInfo.site_code;
getPreviewData({
id: route.query.id,
name: route.query.name,
}).then((res: any) => {
let data = res.data;
wapPreview.value = `${wapUrl.value}/${data.page}&mode=preview&site_id=${siteCode}`;
QRCode.toDataURL(wapPreview.value, {errorCorrectionLevel: 'L', margin: 0, width: 100}).then(url => {
wapImage.value = url
})
})
}
// iframe
const loadIframe = () => {
if (!wapPreview.value) return;
var loadTime = new Date().getTime();
var difference = loadTime - timeFrame.value;
// 1000wap
if (difference < 1000) {
loadingDev.value = true;
loadingIframe.value = false;
wapPreview.value = '';
wapImage.value = '';
} else {
loadingDev.value = false;
loadingIframe.value = true;
}
loading.value = true
}
const weappConfig = reactive({
qr_code: ''
})
const previewMode = ref('weapp')
//
getWeappConfig().then((res: any) => {
if (res.code == 1) {
let data = res.data;
weappConfig.qr_code = data.qr_code;
}
})
//
const {copy, isSupported, copied} = useClipboard()
const copyEvent = (text: string) => {
if (!isSupported.value) {
ElMessage({
message: t('notSupportCopy'),
type: 'warning'
})
}
copy(text)
}
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t('copySuccess'),
type: 'success'
})
}
})
</script>
<style lang="scss">
body {
background: #edf0f3;
}
.copy {
background: var(--el-color-primary) !important;
color: var(--el-color-white) !important;
}
</style>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,258 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-[24px]">{{pageName}}</span>
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="diyRouteTableData.searchParam" ref="searchFormDiyRouteRef">
<el-form-item :label="t('title')" prop="title">
<el-input v-model="diyRouteTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadDiyRouteList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormDiyRouteRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<el-table :data="diyRouteTableData.data" size="large" v-loading="diyRouteTableData.loading">
<template #empty>
<span>{{ !diyRouteTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="title" :label="t('title')" min-width="70" />
<el-table-column prop="page" :label="t('wapUrl')" min-width="170">
<template #default="{ row }">
<span class="mr-[10px]">{{ wapDomain + row.page }}</span>
<el-button type="primary" link @click="copyEvent(wapDomain + row.page)">{{ t('copy') }}</el-button>
</template>
</el-table-column>
<el-table-column prop="page" :label="t('weappUrl')" min-width="120">
<template #default="{ row }">
<span class="mr-[10px]">{{ row.page }}</span>
<el-button type="primary" link @click="copyEvent(row.page)">{{ t('copy') }}</el-button>
</template>
</el-table-column>
<el-table-column :label="t('share')" fixed="right" min-width="80">
<template #default="{ row }">
<el-button v-if="row.is_share == 1" type="primary" link @click="openShare(row)">{{ t('shareSet') }}</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 分享设置-->
<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>
<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>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, computed } from 'vue'
import { t } from '@/lang'
import { getDiyTemplate, getDiyRouteList, getDiyRouteInfo, editDiyRouteShare } from '@/api/diy'
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { useClipboard } from '@vueuse/core'
import QRCode from "qrcode";
import { getUrl } from '@/api/sys'
const pageTemplate: any = reactive({})
const router = useRouter()
const route = useRoute()
const pageName = route.meta.title;
const formRef = ref<FormInstance>()
const dialogVisible = ref(false)
let diyRouteTableData = reactive({
loading: true,
data: [],
searchParam: {
"title": "",
"type":""
}
})
const wapDomain = ref('')
const getDomain = async () => {
wapDomain.value = (await getUrl()).data.wap_url;
};
getDomain();
/**
* 获取自定义路由列表
*/
const loadDiyRouteList = () => {
diyRouteTableData.loading = true
getDiyRouteList({
...diyRouteTableData.searchParam
}).then(res => {
diyRouteTableData.loading = false
diyRouteTableData.data = res.data
}).catch(() => {
diyRouteTableData.loading = false
})
}
loadDiyRouteList()
//
getDiyTemplate({}).then(res => {
for (let key in res.data) {
pageTemplate[key] = res.data[key]
}
})
const searchFormDiyRouteRef = ref<FormInstance>()
/**
* 复制
*/
const { copy, isSupported, copied } = useClipboard()
const copyEvent = (text: string) => {
if (!isSupported.value) {
ElMessage({
message: t('notSupportCopy'),
type: 'warning'
})
}
copy(text)
}
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t('copySuccess'),
type: 'success'
})
}
})
const tabShareType = ref('wechat')
const sharePage = ref('')
const shareFormId = ref(0)
const diyRouteData = reactive({
title: '',
name: '',
page: '',
is_share: 0,
sort: 0
})
const shareFormData = reactive({
wechat: {
title: '',
desc: '',
url: ''
},
weapp: {
title: '',
url: ''
}
})
const shareDialogVisible = ref(false)
const shareFormRules = computed(() => {
return {}
})
const shareFormRef = ref<FormInstance>()
const openShare = async (row: any) => {
//
let info = (await getDiyRouteInfo({
name: row.name
})).data;
if (info.title) {
row.id = info.id;
row.title = info.title
row.name = info.name
row.page = info.page
row.is_share = info.is_share
row.sort = info.sort
row.share = info.share
}
diyRouteData.title = row.title
diyRouteData.name = row.name
diyRouteData.page = row.page
diyRouteData.is_share = row.is_share
diyRouteData.sort = row.sort
shareFormId.value = row.id;
sharePage.value = row.title;
let share = row.share ? JSON.parse(row.share) : {
wechat: {title: '', desc: '', url: ''},
weapp: {title: '', url: ''}
};
if (share) {
shareFormData.wechat = share.wechat;
shareFormData.weapp = share.weapp;
}
shareDialogVisible.value = true;
}
const shareEvent = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
let save = editDiyRouteShare
save({
id: shareFormId.value,
share: JSON.stringify(shareFormData),
...diyRouteData
}).then(() => {
loadDiyRouteList()
shareDialogVisible.value = false;
}).catch(() => {
})
}
})
}
const resetForm = (formEl: FormInstance | undefined)=>{
if (!formEl) return
formEl.resetFields();
loadDiyRouteList();
}
</script>
<style lang="scss">
.copy {
background: var(--el-color-primary) !important;
color: var(--el-color-white) !important;
}
</style>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,283 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-[24px]">{{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>
<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]">
<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-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 :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-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>
<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>
<el-table-column :label="t('operation')" fixed="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-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-item>
<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>
<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>
<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>
<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>
</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>
<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>
<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>
<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>
<el-form-item :label="t('body')" >
<div class="input-width"> {{ formData.pay_info.body }} </div>
</el-form-item>
</div>
<!-- <el-form-item :label="t('headimg')" >
<div class="flex items-center">
<img class="w-[50px] h-[50px] mr-[10px]" v-if="formData.member.headimg" :src="img(formData.member.headimg)" alt="" >
<img class="w-[50px] h-[50px] mr-[10px]" v-else src="@/assets/images/default_headimg.png" alt="" >
</div>
</el-form-item>
<el-form-item :label="t('memberId')" >
<div class="input-width"> {{ formData.member.member_no }} </div>
</el-form-item>
<el-form-item :label="t('nickName')" >
<div class="input-width"> {{ formData.member.nickname }} </div>
</el-form-item>
<el-form-item :label="t('mobile')" >
<div class="input-width"> {{ formData.member.mobile }} </div>
</el-form-item>
<el-form-item :label="t('accountData')" >
<div class="input-width"> {{ formData.account_data }} </div>
</el-form-item>
<el-form-item :label="t('fromType')" >
<div class="input-width"> {{ formData.from_type_name }} </div>
</el-form-item>
<el-form-item :label="t('memo')" >
<div class="input-width"> {{ formData.memo }} </div>
</el-form-item>
<el-form-item :label="t('createTime')" >
<div class="input-width"> {{ formData.create_time }} </div>
</el-form-item> -->
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="showDialog = false">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { getAccountList, getAccountStat, getAccountType } from '@/api/site'
import { img } from '@/utils/common'
import { ElMessageBox } from 'element-plus'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title;
let siteAccountLogTable = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam:{
site_id: "",
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
})
}
loadSiteAccountLogList()
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadSiteAccountLogList()
}
const accountType = ref([])
const checkAccountType = () => {
getAccountType().then(res=>{
accountType.value = res.data
console.log(accountType.value)
})
}
checkAccountType()
const showDialog = ref(false)
const formData = ref([]);
const detailEvent = (info) => {
showDialog.value = true
formData.value = info
}
const accountStat = ref([])
const checkAccountStat = async () => {
accountStat.value = await (await getAccountStat()).data
}
checkAccountStat()
</script>
<style lang="scss" scoped></style>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -25,6 +25,7 @@ import { setMap, getMap } from '@/api/sys'
import { FormInstance, } from 'element-plus'
const loading = ref(false)
const formRef = ref<FormInstance>()
const formData = reactive<Record<string, string>>({
key : ''
})
@ -39,7 +40,6 @@ setFormData()
*/
const save = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
setMap(formData).then(() => {
loading.value = false
}).catch(() => {

View File

@ -101,10 +101,10 @@ const loadNoticeList = () => {
item.sms_type = item.support_type.indexOf('sms') !== -1 ? 1 : 0;
item.wechat_type = item.support_type.indexOf('wechat') !== -1 ? 1 : 0;
item.weapp_type = item.support_type.indexOf('weapp') !== -1 ? 1 : 0;
if(item.receiver_type == 1){
if(item.receiver_type == 0){
noticeTableData.buyer.push(item)
}
if(item.receiver_type == 2){
if(item.receiver_type == 1){
noticeTableData.seller.push(item)
}
})

View File

@ -110,7 +110,7 @@ const setTemplateList = async () => {
Object.keys(res.data).forEach(key => {
const item = res.data[key]
const value = { value: key, name: item.name }
item.receiver_type == 1 ? templateList.buyer.list.push(value) : templateList.seller.list.push(value)
item.receiver_type == 0 ? templateList.buyer.list.push(value) : templateList.seller.list.push(value)
})
}).catch(() => {

View File

@ -111,7 +111,7 @@ const setTemplateList = async () => {
Object.keys(res.data).forEach(key => {
const item = res.data[key]
const value = { value: key, name: item.name }
item.receiver_type == 1 ? templateList.buyer.list.push(value) : templateList.seller.list.push(value)
item.receiver_type == 0 ? templateList.buyer.list.push(value) : templateList.seller.list.push(value)
})
}).catch(() => {

View File

@ -126,20 +126,20 @@
</el-radio-group>
</div>
</div>
</div>
<el-divider content-position="left">{{ t('commentField') }}</el-divider>
<div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('isRequired') }}
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('viewType') }}
</div>
<div>
<el-radio-group class="ml-4"
v-model="formData.table_column[formData.editFiledIndex]['is_required']">
<el-radio :label="1" size="large">{{ t('yes') }}</el-radio>
<el-radio :label="0" size="large">{{ t('no') }}</el-radio>
</el-radio-group>
<el-select class="m-2" :placeholder="t('selectPlaceholder')" size="small"
v-model="formData.table_column[formData.editFiledIndex]['view_type']">
<el-option :label="item.label" :value="item.value" v-for="(item, index) in viewType"
:key="index" />
</el-select>
</div>
</div>
</div>
<el-divider content-position="left">{{t('addAndEdit')}}</el-divider>
<div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('isInsert') }}
</div>
@ -162,6 +162,21 @@
</el-radio-group>
</div>
</div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('isRequired') }}
</div>
<div>
<el-radio-group class="ml-4"
v-model="formData.table_column[formData.editFiledIndex]['is_required']">
<el-radio :label="1" size="large">{{ t('yes') }}</el-radio>
<el-radio :label="0" size="large">{{ t('no') }}</el-radio>
</el-radio-group>
</div>
</div>
</div>
<el-divider content-position="left">{{t('listSearch')}}</el-divider>
<div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('isLists') }}</div>
<div>
@ -204,17 +219,7 @@
</el-select>
</div>
</div>
<div class="flex flex-row ">
<div class="text-xs text-gray-500 w-20 items-center justify-end flex">{{ t('viewType') }}
</div>
<div>
<el-select class="m-2" :placeholder="t('selectPlaceholder')" size="small"
v-model="formData.table_column[formData.editFiledIndex]['view_type']">
<el-option :label="item.label" :value="item.value" v-for="(item, index) in viewType"
:key="index" />
</el-select>
</div>
</div>
</div>
</el-card>

View File

@ -1,160 +0,0 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-[24px]">{{pageName}}</span>
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="cronTableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('title')" prop="title">
<el-input v-model="cronTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
</el-form-item>
<el-form-item :label="t('type')" prop="type">
<el-select v-model="cronTableData.searchParam.type" clearable class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item" :value="key" v-for="(item, key) in typeList" />
</el-select>
</el-form-item>
<el-form-item :label="t('lastTime')" prop="last_time">
<el-date-picker
v-model="cronTableData.searchParam.last_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="loadCronList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div>
<el-table :data="cronTableData.data" size="large" v-loading="cronTableData.loading">
<template #empty>
<span>{{ !cronTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="title" :show-overflow-tooltip="true" :label="t('title')" min-width="150" />
<el-table-column prop="type_name" :label="t('typeName')" min-width="120" />
<el-table-column :label="t('crondType')" min-width="180" align="center">
<template #default="{ row }">
<span v-if="row.type == 'crond'">{{ row.crond_length }}{{ row.crond_type_name }}</span>
<span v-else>{{ t('cron') }}</span>
</template>
</el-table-column>
<el-table-column prop="count" :label="t('count')" min-width="120" />
<el-table-column :label="t('lastTime')" min-width="180" align="center">
<template #default="{ row }">
{{ row.last_time || '' }}
</template>
</el-table-column>
<el-table-column :label="t('nextTime')" min-width="180" align="center">
<template #default="{ row }">
{{ row.next_time || '' }}
</template>
</el-table-column>
<el-table-column :label="t('operation')" fixed="right" width="100">
<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="cronTableData.page" v-model:page-size="cronTableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="cronTableData.total"
@size-change="loadCronList()" @current-change="loadCronList" />
</div>
</div>
</el-card>
<cron-info ref="cronDialog" @complete="loadCronList" />
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { getCronList,getCronType } from '@/api/sys'
import { img } from '@/utils/common'
import { ElMessageBox, FormInstance } from 'element-plus'
import { useRouter } from 'vue-router'
import CronInfo from '@/views/setting/components/cron-info.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title;
const cronTableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
title: '',
type:'',
last_time:''
}
})
const typeList = ref([])
const searchFormRef = ref<FormInstance>()
const setTypeList = async () => {
typeList.value = await (await getCronType()).data
}
setTypeList()
const resetForm = (formEl: FormInstance | undefined)=>{
if (!formEl) return
formEl.resetFields();
loadCronList();
}
/**
* 获取任务列表
*/
const loadCronList = (page: number = 1) => {
cronTableData.loading = true
cronTableData.page = page
getCronList({
page: cronTableData.page,
limit: cronTableData.limit,
...cronTableData.searchParam
}).then(res => {
cronTableData.loading = false
cronTableData.data = res.data.data
cronTableData.total = res.data.total
}).catch(() => {
cronTableData.loading = false
})
}
loadCronList()
const router = useRouter()
const cronDialog: Record<string, any> | null = ref(null)
/**
* 查看任务
* @param data
*/
const infoEvent = (data: any) => {
cronDialog.value.setFormData(data)
cronDialog.value.showDialog = true
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,101 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-[24px]">{{pageName}}</span>
</div>
<div>
<el-table :data="cronTableData.data" size="large" v-loading="cronTableData.loading">
<template #empty>
<span>{{ !cronTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="name" :label="t('title')" min-width="150" />
<el-table-column prop="key" :label="t('key')" min-width="150" />
<el-table-column :label="t('crondType')" min-width="150">
<template #default="{ row }">
{{ row.crontab_content }}
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="cronTableData.page" v-model:page-size="cronTableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="cronTableData.total"
@size-change="loadCronList()" @current-change="loadCronList" />
</div>
</div>
</el-card>
<cron-info ref="cronDialog" @complete="loadCronList" />
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { getCronList,getCronType } from '@/api/sys'
import { ElMessageBox, FormInstance } from 'element-plus'
import { useRouter } from 'vue-router'
import CronInfo from '@/views/setting/components/cron-info.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title;
const cronTableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
title: '',
type:'',
last_time:''
}
})
const typeList = ref([])
const searchFormRef = ref<FormInstance>()
const setTypeList = async () => {
typeList.value = await (await getCronType()).data
}
setTypeList()
/**
* 获取任务列表
*/
const loadCronList = (page: number = 1) => {
cronTableData.loading = true
cronTableData.page = page
getCronList({
page: cronTableData.page,
limit: cronTableData.limit,
...cronTableData.searchParam
}).then(res => {
cronTableData.loading = false
cronTableData.data = res.data.data
cronTableData.total = res.data.total
}).catch(() => {
cronTableData.loading = false
})
}
loadCronList()
const router = useRouter()
const cronDialog: Record<string, any> | null = ref(null)
/**
* 查看任务
* @param data
*/
const infoEvent = (data: any) => {
cronDialog.value.setFormData(data)
cronDialog.value.showDialog = true
}
</script>
<style lang="scss" scoped></style>