This commit is contained in:
全栈小学生 2023-05-20 18:44:16 +08:00
parent d0850818d2
commit 40ce047612
143 changed files with 6419 additions and 2405 deletions

View File

@ -10,6 +10,7 @@ declare module '@vue/runtime-core' {
Attachment: typeof import('./src/components/upload-attachment/attachment.vue')['default']
DiyLink: typeof import('./src/components/diy-link/index.vue')['default']
Editor: typeof import('./src/components/editor/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
@ -17,12 +18,14 @@ declare module '@vue/runtime-core' {
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
@ -39,10 +42,12 @@ declare module '@vue/runtime-core' {
ElImage: typeof import('element-plus/es')['ElImage']
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElOptionGroup: typeof import('element-plus/es')['ElOptionGroup']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
@ -58,7 +63,9 @@ declare module '@vue/runtime-core' {
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload']
Icon: typeof import('./src/components/icon/index.vue')['default']
PopoverInput: typeof import('./src/components/popover-input/index.vue')['default']

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image" href="/niucloud.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>管理端</title>
<title></title>
</head>
<body>
<div id="app"></div>

View File

@ -1,51 +1,53 @@
{
"name": "admin",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@vueuse/core": "^9.12.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.3.1",
"css-color-function": "^1.3.3",
"echarts": "^5.4.1",
"element-plus": "^2.2.29",
"nprogress": "^0.2.0",
"pinia": "^2.0.30",
"qrcode": "^1.5.1",
"sass": "^1.58.0",
"sortablejs": "^1.15.0",
"vue": "^3.2.45",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6",
"vue3-video-play": "^1.3.1-beta.6"
},
"devDependencies": {
"@tailwindcss/line-clamp": "^0.4.2",
"@types/qrcode": "^1.5.0",
"@types/sortablejs": "^1.15.0",
"@typescript-eslint/eslint-plugin": "^5.53.0",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.4.13",
"eslint": "^8.34.0",
"eslint-config-standard-with-typescript": "^34.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.9.0",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.5",
"unplugin-auto-import": "^0.13.0",
"unplugin-vue-components": "^0.23.0",
"vite": "^4.1.0",
"vue-tsc": "^1.0.24"
}
"name": "admin",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@vueuse/core": "^9.12.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.4.0",
"crypto-js": "^4.1.1",
"css-color-function": "^1.3.3",
"echarts": "^5.4.1",
"element-plus": "^2.2.29",
"nprogress": "^0.2.0",
"pinia": "^2.0.30",
"qrcode": "^1.5.1",
"sass": "^1.58.0",
"sortablejs": "^1.15.0",
"vue": "^3.2.45",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6",
"vue-web-terminal": "^3.1.7",
"vue3-video-play": "^1.3.1-beta.6"
},
"devDependencies": {
"@tailwindcss/line-clamp": "^0.4.2",
"@types/qrcode": "^1.5.0",
"@types/sortablejs": "^1.15.0",
"@typescript-eslint/eslint-plugin": "^5.53.0",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.4.13",
"eslint": "^8.34.0",
"eslint-config-standard-with-typescript": "^34.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.9.0",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.5",
"unplugin-auto-import": "^0.13.0",
"unplugin-vue-components": "^0.23.0",
"vite": "^4.1.0",
"vue-tsc": "^1.0.24"
}
}

35
admin/src/api/addon.ts Normal file
View File

@ -0,0 +1,35 @@
import request from '@/utils/request'
/**
*
* @returns
*/
export function getAddonLocal(params: Record<string, any>) {
return request.get('addon/local', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getAddonDetial(id: number) {
return request.get(`addon/${id}`)
}
/**
*
* @param params
* @returns
*/
export function installAddon(params: Record<string, any>) {
return request.post(`addon/install/${params.addon}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function uninstallAddon(params: Record<string, any>) {
return request.post(`addon/uninstall/${params.addon}`, params, { showErrorMessage: true, showSuccessMessage: true })
}

View File

@ -35,7 +35,7 @@ export function addArticle(params: Record<string, any>) {
* @param params
* @returns
*/
export function updateArticle(params: Record<string, any>) {
export function editArticle(params: Record<string, any>) {
return request.put(`article/article/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
@ -91,7 +91,7 @@ export function addArticleCategory(params: Record<string, any>) {
* @param params
* @returns
*/
export function updateArticleCategory(params: Record<string, any>) {
export function editArticleCategory(params: Record<string, any>) {
return request.put(`article/category/${params.category_id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}

View File

@ -5,8 +5,8 @@ import request from '@/utils/request'
* @param params
* @returns
*/
export function login(params: Record<string, any>) {
return request.get('login', { params, showErrorMessage: true })
export function login(params: Record<string, any>, app_type: string) {
return request.get(`login/${app_type}`, { params, showErrorMessage: true })
}
/**
@ -23,4 +23,12 @@ export function getAuthMenus() {
*/
export function getSiteInfo() {
return request.get('auth/site')
}
/**
*
* @returns
*/
export function getLoginConfig() {
return request.get('login/config')
}

View File

@ -8,7 +8,7 @@ import request from '@/utils/request'
* @returns
*/
export function getDiyPageList(params: Record<string, any>) {
return request.get(`diy/diy`, {params})
return request.get(`diy/diy`, { params })
}
/**
@ -26,15 +26,15 @@ export function getDiyPageInfo(id: number) {
* @returns
*/
export function addDiyPage(params: Record<string, any>) {
return request.post('diy/diy', params, {showErrorMessage: true, showSuccessMessage: true})
return request.post('diy/diy', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
*/
export function updateDiyPage(params: Record<string, any>) {
return request.put(`diy/diy/${params.id}`, params, {showErrorMessage: true, showSuccessMessage: true})
export function editDiyPage(params: Record<string, any>) {
return request.put(`diy/diy/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
@ -42,15 +42,15 @@ export function updateDiyPage(params: Record<string, any>) {
* @param params
*/
export function setUseDiyPage(params: Record<string, any>) {
return request.put(`diy/use`, params, {showErrorMessage: true, showSuccessMessage: true})
return request.put(`diy/use`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
*/
export function updateDiyPageShare(params: Record<string, any>) {
return request.put(`diy/diy/share`, params, {showErrorMessage: true, showSuccessMessage: true})
export function editDiyPageShare(params: Record<string, any>) {
return request.put(`diy/diy/share`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
@ -59,28 +59,28 @@ export function updateDiyPageShare(params: Record<string, any>) {
* @returns
*/
export function deleteDiyPage(id: number) {
return request.delete(`diy/diy/${id}`, {showErrorMessage: true, showSuccessMessage: true})
return request.delete(`diy/diy/${id}`, { showErrorMessage: true, 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 +89,14 @@ export function getDiyBottom(params: Record<string, any>) {
* @returns
*/
export function setDiyBottom(params: Record<string, any>) {
return request.post('diy/bottom', params, {showErrorMessage: true, showSuccessMessage: true})
return request.post('diy/bottom', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
*/
export function getDiyPageType(params: Record<string, any>) {
return request.get(`diy/type`, {params})
return request.get(`diy/type`, { params })
}
/**
@ -105,13 +105,21 @@ export function getDiyPageType(params: Record<string, any>) {
* @returns
*/
export function getDiyRouteList(params: Record<string, any>) {
return request.get(`diy/route`, {params})
return request.get(`diy/route`, { params })
}
/**
*
* @param params
*/
export function getDiyRouteInfo(params: Record<string, any>) {
return request.get(`diy/route/info`, { params });
}
/**
*
* @param params
*/
export function updateDiyRouteShare(params: Record<string, any>) {
return request.put(`diy/route/share`, params, {showErrorMessage: true, showSuccessMessage: true})
export function editDiyRouteShare(params: Record<string, any>) {
return request.put(`diy/route/share`, params, { showErrorMessage: true, showSuccessMessage: true })
}

View File

@ -9,7 +9,7 @@ import request from '@/utils/request'
* @returns
*/
export function getMemberList(params: Record<string, any>) {
return request.get(`member/member`, {params})
return request.get(`member/member`, { params })
}
/**
@ -39,6 +39,15 @@ export function getRegisterType(params: Record<string, any>) {
return request.get(`member/registertype`, params)
}
/**
*
* @param params
* @returns
*/
export function getRegisterChannelType(params: Record<string, any>) {
return request.get(`member/register/channel`, params)
}
/***************************************************** 会员标签 ****************************************************/
@ -49,7 +58,7 @@ export function getRegisterType(params: Record<string, any>) {
* @returns
*/
export function getMemberLabelList(params: Record<string, any>) {
return request.get(`member/label`, {params})
return request.get(`member/label`, { params })
}
/**
@ -104,20 +113,13 @@ export function getMemberLabelAll() {
* @param params
* @returns
*/
export function updateMemberDetail(params: Record<string, any>) {
export function editMemberDetail(params: Record<string, any>) {
return request.put(`member/member/modify/${params.member_id}/${params.field}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/***************************************************** 会员零钱 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getMoneyList(params: Record<string, any>) {
return request.get(`member/account/money`, {params})
}
/***************************************************** 会员账户 ****************************************************/
@ -146,7 +148,23 @@ export function getPointList(params: Record<string, any>) {
*/
export function getBalanceList(params: Record<string, any>) {
return request.get(`member/account/balance`, { params })
}
}
/**
*
* @param params
* @returns
*/
export function getMoneyList(params: Record<string, any>) {
return request.get(`member/account/money`, { params })
}
/**
*
* @param params
* @returns
*/
export function getCommissionList(params: Record<string, any>) {
return request.get(`member/account/commission`, { params })
}
/**
*
* @param params
@ -154,7 +172,7 @@ export function getBalanceList(params: Record<string, any>) {
*/
export function adjustPoint(params: Record<string, any>) {
return request.post(`member/account/point`, params, { showErrorMessage: true, showSuccessMessage: true })
}
}
/**
*
* @param params
@ -162,9 +180,9 @@ export function adjustPoint(params: Record<string, any>) {
*/
export function adjustBalance(params: Record<string, any>) {
return request.post(`member/account/balance`, params, { showErrorMessage: true, showSuccessMessage: true })
}
}
/***************************************************** 会员相关设置 ****************************************************/
/***************************************************** 会员相关设置 ****************************************************/
/**
*
@ -173,7 +191,7 @@ export function adjustBalance(params: Record<string, any>) {
*/
export function getLoginConfig(params: Record<string, any>) {
return request.get(`member/config/login`, params)
}
}
/**
*
* @param params
@ -183,21 +201,58 @@ export function setLoginConfig(params: Record<string, any>) {
return request.post(`member/config/login`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function getTransfertype() {
return request.get(`member/cash_out/transfertype`)
}
/**
*
* @param params
* @returns
*/
export function getBalanceSum(params: Record<string, any>) {
return request.get(`member/account/sum_balance`, { params })
}
/**
*
* @param params
* @returns
*/
export function getBalanceStatus() {
return request.get(`member/account/type`)
}
/**
*
*/
export function getAccountType(params: Record<string, any>) {
return request.get(`member/account/change_type/${params.account_type}`)
}
/***************************************************** 会员提现 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getWithdrawConfig() {
return request.get(`member/config/withdraw`)
}
return request.get(`member/config/cash_out`)
}
/**
*
* @param params
* @returns
*/
export function setWithdrawConfig(params: Record<string, any>) {
return request.post(`member/config/withdraw`, params, { showErrorMessage: true, showSuccessMessage: true })
return request.post(`member/config/cash_out`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
@ -206,14 +261,53 @@ export function setWithdrawConfig(params: Record<string, any>) {
* @returns
*/
export function getWithdrawList(params: Record<string, any>) {
return request.get(`member/withdraw`, {params})
return request.get(`member/cash_out`, { params })
}
/**
*
*
* @param params
* @returns id
*/
export function getWithdrawDetail(id: number) {
return request.get(`member/cash_out/${id}`, {})
}
/**
*
* @param id
* @param params
* @returns
*/
export function memberAudit(params: Record<string, any>) {
return request.put(`member/cash_out/audit/${params.id}/${params.action}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function memberTransfer(params: Record<string, any>) {
return request.put(`member/cash_out/transfer/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function getTransfertype() {
return request.get(`member/withdraw/transfertype`)
}
export function editMemberStatus(params: Record<string, any>) {
return request.put(`member/setstatus/${params.status}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function getWithdrawStatusList() {
return request.get(`member/cash_out/status`)
}

View File

@ -1,75 +0,0 @@
import request from '@/utils/request'
/***************************************************** 消息管理 ****************************************************/
/**
*
* @returns
*/
export function getMessageList() {
return request.get('message/message')
}
/**
*
* @param params
* @returns
*/
export function getMessageInfo(key: string) {
return request.get(`message/message/${key}`)
}
/**
*
* @param params
* @returns
*/
export function getMessageLog(params: any) {
return request.get(`message/log`, { params })
}
/**
*
* @param params
* @returns
*/
export function updateMessageStatus(params: Record<string, any>) {
return request.post(`message/message/updatestatus`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function updateMessage(params: Record<string, any>) {
return request.post(`message/message/update`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getSmsList() {
return request.get('message/message/sms')
}
/**
*
* @param sms_type
* @returns
*/
export function getSmsInfo(sms_type: string) {
return request.get(`message/message/sms/${sms_type}`,)
}
/**
*
* @param sms_type
* @param params
* @returns
*/
export function updateSms(params: Record<string, any>) {
return request.put(`message/message/sms/${params.sms_type}`, params, { showErrorMessage: true, showSuccessMessage: true })
}

85
admin/src/api/notice.ts Normal file
View File

@ -0,0 +1,85 @@
import request from '@/utils/request'
/***************************************************** 消息管理 ****************************************************/
/**
*
* @returns
*/
export function getNoticeList() {
return request.get('notice/notice')
}
/**
*
* @param params
* @returns
*/
export function getNoticeInfo(key: string) {
return request.get(`notice/notice/${key}`)
}
/**
*
* @param params
* @returns
*/
export function getNoticeLog(params: any) {
return request.get(`notice/log`, { params })
}
/**
*
* @param params
* @returns
*/
export function editNoticeStatus(params: Record<string, any>) {
return request.post(`notice/notice/editstatus`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function editNotice(params: Record<string, any>) {
return request.post(`notice/notice/edit`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getSmsList() {
return request.get('notice/notice/sms')
}
/**
*
* @param sms_type
* @returns
*/
export function getSmsInfo(sms_type: string) {
return request.get(`notice/notice/sms/${sms_type}`,)
}
/**
*
* @param sms_type
* @param params
* @returns
*/
export function editSms(params: Record<string, any>) {
return request.put(`notice/notice/sms/${params.sms_type}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param sms_type
* @param params
* @returns
*/
export function getSmsLog(params: Record<string, any>) {
return request.put(`notice/sms/log`, params)
}

View File

@ -27,3 +27,27 @@ export function getRechargeOrderInfo(order_id: number) {
export function getRechargeOrderStatusList() {
return request.get(`order/recharge/status`)
}
/**
* 退
* @returns
*/
export function getRefund(params: Record<string, any>) {
return request.get(`refund/refund`, { params })
}
/**
* 退
* @returns
*/
export function getRefundStatus() {
return request.get(`refund/status`)
}
/**
* 退
* @returns
*/
export function rechargeRefund(id) {
return request.get(`order/recharge/refund/${id}`);
}

View File

@ -4,7 +4,7 @@ import request from '@/utils/request'
*
* @returns
*/
export function getUserInfo(type:string) {
export function getUserInfo(type: string) {
return request.get(`auth/get`)
}
@ -13,6 +13,6 @@ export function getUserInfo(type:string) {
* @returns
*/
export function setUserInfo(params: Record<string, any>) {
return request.put(`auth/update`, params, { showErrorMessage: true, showSuccessMessage: true });
return request.put(`auth/edit`, params, { showErrorMessage: true, showSuccessMessage: true });
}

View File

@ -10,7 +10,7 @@ import request from '@/utils/request'
* @returns
*/
export function getSiteList(params: Record<string, any>) {
return request.get(`site/site`, {params})
return request.get(`site/site`, { params })
}
/**
@ -37,7 +37,7 @@ export function addSite(params: Record<string, any>) {
* @param params
* @returns
*/
export function updateSite(params: Record<string, any>) {
export function editSite(params: Record<string, any>) {
return request.put(`site/site/${params.site_id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
@ -59,7 +59,7 @@ export function getStatusList() {
* @returns
*/
export function getSiteGroupList(params: Record<string, any>) {
return request.get(`site/group`, {params})
return request.get(`site/group`, { params })
}
/**
@ -86,7 +86,7 @@ export function addSiteGroup(params: Record<string, any>) {
* @param params
* @returns
*/
export function updateSiteGroup(params: Record<string, any>) {
export function editSiteGroup(params: Record<string, any>) {
return request.put(`site/group/${params.group_id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
@ -144,7 +144,7 @@ export function addUser(params: Record<string, any>) {
* @param params
* @returns
*/
export function updateUser(params: Record<string, any>) {
export function editUser(params: Record<string, any>) {
return request.put(`site/user/${params.uid}`, params, { showErrorMessage: true, showSuccessMessage: true })
}

View File

@ -9,6 +9,14 @@ import request from '@/utils/request'
export function getInfo() {
return request.get('sys/role')
}
/**
*
* @returns
*/
export function getUrl() {
return request.get('sys/url')
}
/***************************************************** 用户组 ****************************************************/
/**
@ -43,7 +51,7 @@ export function addRole(params: Record<string, any>) {
* @param params
* @returns
*/
export function updateRole(params: Record<string, any>) {
export function editRole(params: Record<string, any>) {
return request.put(`sys/role/${params.role_id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
@ -70,7 +78,7 @@ export function allRole() {
*
* @returns
*/
export function getMenus(type:string) {
export function getMenus(type: string) {
return request.get(`sys/menu/${type}`)
}
@ -98,7 +106,7 @@ export function addMenu(params: Record<string, any>) {
* @param params
* @returns
*/
export function updateMenu(params: Record<string, any>) {
export function editMenu(params: Record<string, any>) {
return request.put(`sys/menu/${params.menu_key}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
@ -180,7 +188,7 @@ export function addAttachmentCategory(params: Record<string, any>) {
* @param params
* @returns
*/
export function updateAttachmentCategory(params: Record<string, any>) {
export function editAttachmentCategory(params: Record<string, any>) {
return request.put(`sys/attachment/category/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
@ -208,7 +216,7 @@ export function getAttachmentList(params: Record<string, any>) {
* @returns
*/
export function deleteAttachment(params: Record<string, any>) {
return request.delete(`sys/attachment/del`, { data: params, showErrorMessage: true, showSuccessMessage: true})
return request.delete(`sys/attachment/del`, { data: params, showErrorMessage: true, showSuccessMessage: true })
}
/**
@ -266,7 +274,7 @@ export function getStorageInfo(type: string) {
* @param params
* @returns
*/
export function updateStorage(params: Record<string, any>) {
export function editStorage(params: Record<string, any>) {
return request.put(`sys/storage/${params.storage_type}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
@ -276,7 +284,7 @@ export function updateStorage(params: Record<string, any>) {
*
* @returns
*/
export function getPayConfig(type:string) {
export function getPayConfig(type: string) {
return request.get(`pay/config/${type}`)
}
@ -296,13 +304,31 @@ export function getPayList() {
return request.get(`pay/lists`)
}
/***************************************************** 打款设置 ****************************************************/
/**
*
* @returns channel
* @returns
*/
export function getTransferInfo(channel) {
return request.get(`pay/channel/lists/${channel}`)
}
/**
*
* @param params
* @returns
*/
export function setTransferInfo(params: Record<string, any>) {
return request.post(`pay/channel/set/transfer`, params)
}
/***************************************************** 定时任务 ****************************************************/
/**
*
* @returns
*/
export function getCronList(params:any) {
export function getCronList(params: any) {
return request.get(`sys/cron`, { params })
}
@ -310,7 +336,7 @@ export function getCronList(params:any) {
*
* @returns
*/
export function getCronInfo(id:string) {
export function getCronInfo(id: string) {
return request.get(`sys/cron/${id}`);
}
@ -336,7 +362,7 @@ export function getAgreementList() {
*
* @returns
*/
export function getAgreementInfo(key:string) {
export function getAgreementInfo(key: string) {
return request.get(`sys/agreement/${key}`);
}
@ -344,7 +370,7 @@ export function getAgreementInfo(key:string) {
*
* @returns
*/
export function updateAgreement(params: Record<string, any>) {
export function editAgreement(params: Record<string, any>) {
return request.put(`sys/agreement/${params.key}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
@ -365,3 +391,54 @@ export function getSceneDomain() {
return request.get(`sys/scene_domain`);
}
/***************************************************** 登录注册配置 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getConfigLogin() {
return request.get(`sys/config/login`)
}
/**
*
* @param params
* @returns
*/
export function setConfigLogin(params: Record<string, any>) {
return request.put(`sys/config/login`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
*/
export function getPayConfigList() {
return request.get(`pay/channel/lists`)
}
/**
*
*/
export function setPatConfig(params: Record<string, any>) {
return request.post(`pay/channel/set/all`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/***************************************************** 刷新菜单 ****************************************************/
/**
*
*/
export function menuRefresh(params: Record<string, any>) {
return request.post(`sys/menu/refresh`,{},{ showErrorMessage: true, showSuccessMessage: true })
}
/***************************************************** 获取应用 ****************************************************/
/**
*
*/
export function getAppMange() {
return request.get(`sys/applist`)
}

View File

@ -8,7 +8,7 @@ import request from '@/utils/request'
* @returns
*/
export function getGenerateTableList(params: Record<string, any>) {
return request.get(`generator/generator`, {params})
return request.get(`generator/generator`, { params })
}
/**
@ -35,7 +35,7 @@ export function addGenerateTable(params: Record<string, any>) {
* @param params
* @returns
*/
export function updateGenerateTable(params: Record<string, any>) {
export function editGenerateTable(params: Record<string, any>) {
return request.put(`generator/generator/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
@ -66,3 +66,11 @@ export function generateTable() {
return request.get(`generator/table`)
}
/**
*
* @param file
* @returns
*/
export function getSystem() {
return request.get(`sys/system`)
}

View File

@ -37,7 +37,7 @@ export function addUser(params: Record<string, any>) {
* @param params
* @returns
*/
export function updateUser(params: Record<string, any>) {
export function editUser(params: Record<string, any>) {
return request.put(`user/user/${params.uid}`, params, { showErrorMessage: true, showSuccessMessage: true })
}

View File

@ -22,7 +22,7 @@ export function getWechatStatic() {
* @param params
* @returns
*/
export function updateWechatConfig(params: Record<string, any>) {
export function editWechatConfig(params: Record<string, any>) {
return request.put('wechat/config', params, { showErrorMessage: true, showSuccessMessage: true })
}
@ -39,7 +39,7 @@ export function getWechatMenu() {
* @param params
* @returns
*/
export function updateWechatMenu(params: Record<string, any>) {
export function editWechatMenu(params: Record<string, any>) {
return request.put('wechat/menu', params, { showErrorMessage: true, showSuccessMessage: true })
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -76,6 +76,7 @@
import {cloneDeep} from 'lodash-es'
import {getLink} from '@/api/diy';
import {ElMessage} from 'element-plus'
import { CollectionTag } from '@element-plus/icons-vue';
const prop = defineProps({
modelValue: {
@ -115,18 +116,17 @@
}
getLink({}).then((res: any) => {
if (res.code == 200) {
link.value = res.data;
childList.value = link.value[0].child_list;
if (value.value.name != '') {
selectLink.value = cloneDeep(value.value);
} else {
selectLink.value = {
parent: link.value[0].name
};
}
parentLinkName.value = selectLink.value.parent;
link.value = res.data;
childList.value = link.value[0].child_list;
if (value.value.name != '') {
selectLink.value = cloneDeep(value.value);
} else {
selectLink.value = {
parent: link.value[0].name
};
}
parentLinkName.value = selectLink.value.parent;
});
//

View File

@ -37,8 +37,8 @@
import { computed, ref } from 'vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
let type = ref('element')
let visible = ref('false')
const type = ref('element')
const visible = ref('false')
// element
const element = computed(() => {
@ -47,8 +47,8 @@ const element = computed(() => {
// iconfont
const iconfont = computed(() => {
let iconfile = import.meta.globEager('@/styles/iconfont.css')['/src/styles/iconfont.css'].default
let icons = Array.from(iconfile.matchAll(/(icon.*)\:before/g))
const iconfile = import.meta.globEager('@/styles/icon/iconfont.css')['/src/styles/iconfont.css'].default
const icons = Array.from(iconfile.matchAll(/(icon.*)\:before/g))
return icons.map(item => {
return item[1]
@ -72,4 +72,4 @@ const selectIcon = (name) => {
.active {
color: var(--el-color-primary);
}
</style>
</style>

View File

@ -38,9 +38,15 @@
<div class="attachment-list-wrap flex flex-col p-[15px] flex-1">
<el-row :gutter="15" class="h-[32px]">
<el-col :span="12">
<el-upload v-bind="upload" ref="uploadRef">
<el-button type="primary">{{ t('upload.upload' + type) }}</el-button>
</el-upload>
<div class="flex">
<el-upload v-bind="upload" ref="uploadRef">
<el-button type="primary">{{ t('upload.upload' + type) }}</el-button>
</el-upload>
<el-button v-if="operate === false" class="ml-[10px]" type="primary" @click="operate = true">{{
t('edit') }}</el-button>
<el-button v-else class="ml-[10px]" type="primary" @click="operate = false">{{ t('complete')
}}</el-button>
</div>
</el-col>
<el-col :span="12" class="text-right">
<el-input v-model="attachmentParam.real_name" class="m-0 w-[200px]"
@ -50,7 +56,7 @@
</el-row>
<div class="flex-1 my-[15px] h-0" v-loading="attachment.loading">
<el-scrollbar>
<div class="flex flex-wrap" v-if="attachment.data.length">
<div class="flex flex-wrap" v-if="attachment.data.length && operate === true">
<div class="attachment-item mr-[10px]" :class="scene == 'select' ? 'w-[100px]' : 'w-[120px]'"
v-for="(item, index) in attachment.data" :key="index">
<div class="attachment-wrap w-full rounded cursor-pointer overflow-hidden relative flex items-center justify-center"
@ -94,13 +100,32 @@
</div>
</div>
</div>
<div class="flex flex-wrap" v-else-if="attachment.data.length && operate === false">
<div class="attachment-item mr-[10px] w-[120px]" v-for="(item, index) in attachment.data"
:key="index">
<div
class="attachment-wrap w-full rounded cursor-pointer overflow-hidden relative flex items-center justify-center h-[120px]">
<el-image :src="img(item.url)" fit="contain" v-if="type == 'image'"
:preview-src-list="item.image_list"></el-image>
<video :src="img(item.url)" v-else></video>
</div>
<div class="flex items-center">
<el-tooltip placement="top">
<template #content>{{ item.real_name }}</template>
<div class="truncate my-[10px] cursor-pointer text-base flex-1 ">
{{ item.real_name }}
</div>
</el-tooltip>
</div>
</div>
</div>
<div class="flex items-center justify-center" v-else>
<el-empty :description="t('upload.attachmentEmpty')" :image-size="100" />
<el-empty v-if="!attachment.loading" :description="t('upload.attachmentEmpty')" :image-size="100" />
</div>
</el-scrollbar>
</div>
<el-row :gutter="20">
<el-col :span="8" v-if="scene == 'attachment'">
<el-col :span="8" v-if="scene == 'attachment' && operate === true">
<div class="flex items-center">
<el-checkbox v-model="selectAll" :label="t('selectAll')" size="large" />
<el-button class="ml-[15px]" :disabled="batchOperateDisabled" @click="deleteAttachmentEvent()">{{
@ -163,7 +188,7 @@ import {
getAttachmentCategoryList as attachmentCategoryList,
getAttachmentList as attachmentList,
addAttachmentCategory as addCategory,
updateAttachmentCategory as updateCategory,
editAttachmentCategory as updateCategory,
deleteAttachmentCategory as deleteCategory,
deleteAttachment,
moveAttachment
@ -172,6 +197,7 @@ import { debounce, img, getToken } from '@/utils/common'
import { ElMessage, UploadFile, UploadFiles, ElMessageBox } from 'element-plus'
import storage from '@/utils/storage'
const operate = ref(false)
const prop = defineProps({
//
limit: {
@ -196,7 +222,7 @@ const attachmentCategory: Record<string, any> = reactive({
data: []
})
const attachment: Record<string, any> = reactive({
loading: false,
loading: true,
page: 1,
total: 0,
limit: prop.scene == 'select' ? 10 : 20,
@ -241,6 +267,13 @@ const getAttachmentList = debounce((page: number = 1) => {
attachment.total = res.data.total
attachment.loading = false
prop.scene == 'attachment' && clearSelected()
for (let i = 0; i < attachment.data.length; i++) {
attachment.data[i]['image_list'] = []
attachment.data[i]['image_list'].push(img(res.data.data[i]['url']))
}
}).catch(() => {
attachment.loading = false
})
@ -508,5 +541,4 @@ defineExpose({
background: #fff !important;
box-shadow: var(--el-box-shadow-light);
}
}
</style>
}</style>

View File

@ -22,7 +22,7 @@ const prop = defineProps({
},
api: {
type: String,
default: 'sys/document'
default: 'sys/document/document'
}
})
@ -41,7 +41,7 @@ const upload: Record<string, any> = {
action: `${import.meta.env.VITE_APP_BASE_URL}/${prop.api}`,
showFileList: false,
headers: {},
accept: '.doc,.docx,.xml,.txt,.pem,.zip,.rar,.7z',
accept: '.doc,.docx,.xml,.txt,.pem,.zip,.rar,.7z,.crt',
onSuccess: (response: any, uploadFile: UploadFile) => {
value.value = response.data.url
ElMessage({

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,260 @@
<template>
<div style="position: relative"
>
<div class="verify-img-out">
<div class="verify-img-panel" :style="{'width': setSize.imgWidth,
'height': setSize.imgHeight,
'background-size' : setSize.imgWidth + ' '+ setSize.imgHeight,
'margin-bottom': vSpace + 'px'}"
>
<div class="verify-refresh" style="z-index:3" @click="refresh" v-show="showRefresh">
<i class="iconfont icon-refresh"></i>
</div>
<img :src="'data:image/png;base64,'+pointBackImgBase"
ref="canvas"
alt="" style="width:100%;height:100%;display:block"
@click="bindingClick?canvasClick($event):undefined">
<div v-for="(tempPoint, index) in tempPoints" :key="index" class="point-area"
:style="{
'background-color':'#1abd6c',
color:'#fff',
'z-index':9999,
width:'20px',
height:'20px',
'text-align':'center',
'line-height':'20px',
'border-radius': '50%',
position:'absolute',
top:parseInt(tempPoint.y-10) + 'px',
left:parseInt(tempPoint.x-10) + 'px'
}">
{{index + 1}}
</div>
</div>
</div>
<!-- 'height': this.barSize.height, -->
<div class="verify-bar-area"
:style="{'width': setSize.imgWidth,
'color': this.barAreaColor,
'border-color': this.barAreaBorderColor,
'line-height':this.barSize.height}">
<span class="verify-msg">{{text}}</span>
</div>
</div>
</template>
<script type="text/babel">
/**
* VerifyPoints
* @description 点选
* */
import {resetSize, _code_chars, _code_color1, _code_color2} from './../utils/util'
import {aesEncrypt} from "./../utils/ase"
import {reqGet,reqCheck} from "./../api/index"
import { computed, onMounted, reactive, ref,watch,nextTick,toRefs, watchEffect,getCurrentInstance} from 'vue';
export default {
name: 'VerifyPoints',
props: {
//popfixed
mode: {
type: String,
default: 'fixed'
},
captchaType:{
type:String,
},
//
vSpace: {
type: Number,
default: 5
},
imgSize: {
type: Object,
default() {
return {
width: '310px',
height: '155px'
}
}
},
barSize: {
type: Object,
default() {
return {
width: '310px',
height: '40px'
}
}
}
},
setup(props,context){
const {mode,captchaType,vSpace,imgSize,barSize} = toRefs(props)
const { proxy } = getCurrentInstance();
let secretKey = ref(''), //ase
checkNum = ref(3), //
fontPos = reactive([]), //
checkPosArr = reactive([]), //
num = ref(1), //
pointBackImgBase = ref(''), //
poinTextList = reactive([]), //
backToken = ref(''), //token
setSize = reactive({
imgHeight: 0,
imgWidth: 0,
barHeight: 0,
barWidth: 0
}),
tempPoints = reactive([]),
text = ref(''),
barAreaColor = ref(undefined),
barAreaBorderColor = ref(undefined),
showRefresh = ref(true),
bindingClick = ref(true)
const init = ()=>{
//
fontPos.splice(0, fontPos.length)
checkPosArr.splice(0, checkPosArr.length)
num.value = 1
getPictrue();
nextTick(() => {
let {imgHeight,imgWidth,barHeight,barWidth} = resetSize(proxy)
setSize.imgHeight = imgHeight
setSize.imgWidth = imgWidth
setSize.barHeight = barHeight
setSize.barWidth = barWidth
proxy.$parent.$emit('ready', proxy)
})
}
onMounted(()=>{
//
init()
proxy.$el.onselectstart = function () {
return false
}
})
const canvas = ref(null)
const canvasClick = (e)=>{
checkPosArr.push(getMousePos(canvas, e));
if (num.value == checkNum.value) {
num.value = createPoint(getMousePos(canvas, e));
//
let arr = pointTransfrom(checkPosArr,setSize)
checkPosArr.length = 0
checkPosArr.push(...arr);
//
setTimeout(() => {
// var flag = this.comparePos(this.fontPos, this.checkPosArr);
//
var captchaVerification = secretKey.value? aesEncrypt(backToken.value+'---'+JSON.stringify(checkPosArr),secretKey.value):backToken.value+'---'+JSON.stringify(checkPosArr)
let data = {
captchaType:captchaType.value,
"captcha_code":secretKey.value? aesEncrypt(JSON.stringify(checkPosArr),secretKey.value):JSON.stringify(checkPosArr),
"captcha_key":backToken.value
}
reqCheck(data).then(res=>{
if (res.code == "200") {
barAreaColor.value = '#4cae4c'
barAreaBorderColor.value = '#5cb85c'
text.value = '验证成功'
bindingClick.value = false
if (mode.value=='pop') {
setTimeout(()=>{
proxy.$parent.clickShow = false;
refresh();
},1500)
}
proxy.$parent.$emit('success', {captchaVerification})
}else{
proxy.$parent.$emit('error', proxy)
barAreaColor.value = '#d9534f'
barAreaBorderColor.value = '#d9534f'
text.value = '验证失败'
setTimeout(() => {
refresh();
}, 700);
}
})
}, 400);
}
if (num.value < checkNum.value) {
num.value = createPoint(getMousePos(canvas, e));
}
}
//
const getMousePos = function (obj, e) {
var x = e.offsetX
var y = e.offsetY
return {x, y}
}
//
const createPoint = function (pos) {
tempPoints.push(Object.assign({}, pos))
return num.value+1;
}
const refresh = function () {
tempPoints.splice(0, tempPoints.length)
barAreaColor.value = '#000'
barAreaBorderColor.value = '#ddd'
bindingClick.value = true
fontPos.splice(0, fontPos.length)
checkPosArr.splice(0, checkPosArr.length)
num.value = 1
getPictrue();
text.value = '验证失败'
showRefresh.value = true
}
//
function getPictrue() {
let data = {
captchaType:captchaType.value
}
reqGet(data).then(res=>{
if (res.code == "200") {
pointBackImgBase.value = res.data.originalImageBase64
backToken.value = res.data.token
secretKey.value = res.data.secretKey
poinTextList.value = res.data.wordList
text.value = '请依次点击【' + poinTextList.value.join(",") + '】'
}else{
text.value = res.msg;
}
})
}
//
const pointTransfrom = function(pointArr,imgSize){
var newPointArr = pointArr.map(p=>{
let x = Math.round(310 * p.x/parseInt(imgSize.imgWidth))
let y =Math.round(155 * p.y/parseInt(imgSize.imgHeight))
return {x,y}
})
return newPointArr
}
return {
secretKey,
checkNum,
fontPos,
checkPosArr,
num,
pointBackImgBase,
poinTextList,
backToken,
setSize,
tempPoints,
text,
barAreaColor,
barAreaBorderColor,
showRefresh,
bindingClick,
init,
canvas,
canvasClick,
getMousePos,createPoint,refresh,getPictrue,pointTransfrom
}
},
}
</script>

View File

@ -0,0 +1,366 @@
<template>
<div style="position: relative;">
<div v-if="type === '2'" class="verify-img-out"
:style="{height: (parseInt(setSize.imgHeight) + vSpace) + 'px'}"
>
<div class="verify-img-panel" :style="{width: setSize.imgWidth,
height: setSize.imgHeight,}">
<img :src="'data:image/png;base64,'+backImgBase" alt="" style="width:100%;height:100%;display:block">
<div class="verify-refresh" @click="refresh" v-show="showRefresh"><i class="iconfont icon-refresh"></i>
</div>
<transition name="tips">
<span class="verify-tips" v-if="tipWords" :class="passFlag ?'suc-bg':'err-bg'">{{tipWords}}</span>
</transition>
</div>
</div>
<!-- 公共部分 -->
<div class="verify-bar-area" :style="{width: setSize.imgWidth,
height: barSize.height,
'line-height':barSize.height}">
<span class="verify-msg" v-text="text"></span>
<div class="verify-left-bar"
:style="{width: (leftBarWidth!==undefined)?leftBarWidth: barSize.height, height: barSize.height, 'border-color': leftBarBorderColor, transaction: transitionWidth}">
<span class="verify-msg" v-text="finishText"></span>
<div class="verify-move-block"
@touchstart="start"
@mousedown="start"
:style="{width: barSize.height, height: barSize.height, 'background-color': moveBlockBackgroundColor, left: moveBlockLeft, transition: transitionLeft}">
<i :class="['verify-icon iconfont', iconClass]"
:style="{color: iconColor}"></i>
<div v-if="type === '2'" class="verify-sub-block"
:style="{'width':Math.floor(parseInt(setSize.imgWidth)*47/310)+ 'px',
'height': setSize.imgHeight,
'top':'-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',
'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
}">
<img :src="'data:image/png;base64,'+blockBackImgBase" alt="" style="width:100%;height:100%;display:block;-webkit-user-drag:none;">
</div>
</div>
</div>
</div>
</div>
</template>
<script type="text/babel">
/**
* VerifySlide
* @description 滑块
* */
import {aesEncrypt} from "./../utils/ase"
import {resetSize} from './../utils/util'
import {reqGet,reqCheck} from "./../api/index"
import { computed, onMounted, reactive, ref,watch,nextTick,toRefs, watchEffect,getCurrentInstance} from 'vue';
// "captchaType":"blockPuzzle",
export default {
name: 'VerifySlide',
props: {
captchaType:{
type:String,
},
type: {
type: String,
default: '1'
},
//popfixed
mode: {
type: String,
default: 'fixed'
},
vSpace: {
type: Number,
default: 5
},
explain: {
type: String,
default: '向右滑动完成验证'
},
imgSize: {
type: Object,
default() {
return {
width: '310px',
height: '155px'
}
}
},
blockSize: {
type: Object,
default() {
return {
width: '50px',
height: '50px'
}
}
},
barSize: {
type: Object,
default() {
return {
width: '310px',
height: '40px'
}
}
}
},
setup(props,context){
const {mode,captchaType,vSpace,imgSize,barSize,type,blockSize,explain} = toRefs(props)
const { proxy } = getCurrentInstance();
let secretKey = ref(''), //ase
passFlag = ref(''), //
backImgBase = ref(''), //
blockBackImgBase = ref(''), //
backToken = ref(''), //token
startMoveTime = ref(''), //
endMovetime = ref(''), //
tipsBackColor = ref(''), //
tipWords = ref(''),
text = ref(''),
finishText = ref(''),
setSize = reactive({
imgHeight: 0,
imgWidth: 0,
barHeight: 0,
barWidth: 0
}),
top = ref(0),
left = ref(0),
moveBlockLeft = ref(undefined),
leftBarWidth = ref(undefined),
//
moveBlockBackgroundColor = ref(undefined),
leftBarBorderColor = ref('#ddd'),
iconColor = ref(undefined),
iconClass = ref('icon-right'),
status = ref(false), //
isEnd = ref(false) , //
showRefresh = ref(true),
transitionLeft = ref(''),
transitionWidth = ref(''),
startLeft = ref(0)
const barArea = computed(()=>{
return proxy.$el.querySelector('.verify-bar-area')
})
function init() {
text.value = explain.value
getPictrue();
nextTick(() => {
let {imgHeight,imgWidth,barHeight,barWidth} = resetSize(proxy)
setSize.imgHeight = imgHeight
setSize.imgWidth = imgWidth
setSize.barHeight = barHeight
setSize.barWidth = barWidth
proxy.$parent.$emit('ready', proxy)
})
window.removeEventListener("touchmove", function (e) {
move(e);
});
window.removeEventListener("mousemove", function (e) {
move(e);
});
//
window.removeEventListener("touchend", function () {
end();
});
window.removeEventListener("mouseup", function () {
end();
});
window.addEventListener("touchmove", function (e) {
move(e);
});
window.addEventListener("mousemove", function (e) {
move(e);
});
//
window.addEventListener("touchend", function () {
end();
});
window.addEventListener("mouseup", function () {
end();
});
}
watch(type,()=>{
init()
})
onMounted(()=>{
//
init()
proxy.$el.onselectstart = function () {
return false
}
})
//
function start(e) {
e = e || window.event
if (!e.touches) { //PC
var x = e.clientX;
} else { //
var x = e.touches[0].pageX;
}
console.log(barArea);
startLeft.value =Math.floor(x - barArea.value.getBoundingClientRect().left);
startMoveTime.value = +new Date(); //
if (isEnd.value == false) {
text.value = ''
moveBlockBackgroundColor.value = '#337ab7'
leftBarBorderColor.value = '#337AB7'
iconColor.value = '#fff'
e.stopPropagation();
status.value = true;
}
}
//
function move(e) {
e = e || window.event
if (status.value && isEnd.value == false) {
if (!e.touches) { //PC
var x = e.clientX;
} else { //
var x = e.touches[0].pageX;
}
var bar_area_left = barArea.value.getBoundingClientRect().left;
var move_block_left = x - bar_area_left //left
if (move_block_left >= barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2) {
move_block_left = barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2;
}
if (move_block_left <= 0) {
move_block_left = parseInt(parseInt(blockSize.value.width) / 2);
}
//left
moveBlockLeft.value = (move_block_left - startLeft.value) + "px"
leftBarWidth.value = (move_block_left - startLeft.value) + "px"
}
}
//
function end() {
endMovetime.value = +new Date();
//
if (status.value && isEnd.value == false) {
var moveLeftDistance = parseInt((moveBlockLeft.value || '').replace('px', ''));
moveLeftDistance = moveLeftDistance * 310/ parseInt(setSize.imgWidth)
let data = {
captchaType:captchaType.value,
"captcha_code":secretKey.value ? aesEncrypt(JSON.stringify({x:moveLeftDistance,y:5.0}),secretKey.value):JSON.stringify({x:moveLeftDistance,y:5.0}),
"captcha_key":backToken.value
}
reqCheck(data).then(res=>{
if (res.code == "200") {
moveBlockBackgroundColor.value = '#5cb85c'
leftBarBorderColor.value = '#5cb85c'
iconColor.value = '#fff'
iconClass.value = 'icon-check'
showRefresh.value = false
isEnd.value = true;
if (mode.value=='pop') {
setTimeout(()=>{
proxy.$parent.clickShow = false;
refresh();
},1500)
}
passFlag.value = true
tipWords.value = `${((endMovetime.value-startMoveTime.value)/1000).toFixed(2)}s验证成功`
var captchaVerification = secretKey.value ? aesEncrypt(backToken.value+'---'+JSON.stringify({x:moveLeftDistance,y:5.0}),secretKey.value):backToken.value+'---'+JSON.stringify({x:moveLeftDistance,y:5.0})
setTimeout(()=>{
tipWords.value = ""
proxy.$parent.closeBox();
proxy.$parent.$emit('success', {captchaVerification})
},1000)
}else{
moveBlockBackgroundColor.value = '#d9534f'
leftBarBorderColor.value = '#d9534f'
iconColor.value = '#fff'
iconClass.value = 'icon-close'
passFlag.value = false
setTimeout(function () {
refresh();
}, 1000);
proxy.$parent.$emit('error',proxy)
tipWords.value = "验证失败"
setTimeout(()=>{
tipWords.value = ""
},1000)
}
})
status.value = false;
}
}
const refresh = ()=>{
showRefresh.value = true
finishText.value = ''
transitionLeft.value = 'left .3s'
moveBlockLeft.value = 0
leftBarWidth.value = undefined
transitionWidth.value = 'width .3s'
leftBarBorderColor.value = '#ddd'
moveBlockBackgroundColor.value = '#fff'
iconColor.value = '#000'
iconClass.value = 'icon-right'
isEnd.value = false
getPictrue()
setTimeout(() => {
transitionWidth.value = ''
transitionLeft.value = ''
text.value = explain.value
}, 300)
}
//
function getPictrue(){
let data = {
captchaType:captchaType.value
}
reqGet(data).then(res=>{
if (res.code == "200") {
backImgBase.value = res.data.originalImageBase64
blockBackImgBase.value = res.data.jigsawImageBase64
backToken.value = res.data.token
secretKey.value = res.data.secretKey
}else{
tipWords.value = res.msg;
}
})
}
return {
secretKey, //ase
passFlag, //
backImgBase, //
blockBackImgBase, //
backToken, //token
startMoveTime, //
endMovetime, //
tipsBackColor, //
tipWords,
text,
finishText,
setSize,
top,
left,
moveBlockLeft,
leftBarWidth,
//
moveBlockBackgroundColor,
leftBarBorderColor,
iconColor,
iconClass,
status, //
isEnd, //
showRefresh,
transitionLeft,
transitionWidth,
barArea,
refresh,
start
}
},
}
</script>

View File

@ -0,0 +1,31 @@
/**
* 此处可直接引用自己项目封装好的 axios 配合后端联调
*/
import request from "./../utils/axios" //组件内部封装的axios
// import request from "@/api/axios.js" //调用项目封装的axios
//获取验证图片 以及token
export function reqGet(data) {
return request.get('/captcha/create',{params:{...data}});
// return request({
// url: '/captcha/create',
// method: 'get',
// data
// })
}
//滑动或者点选验证
export function reqCheck(data) {
return request.get('/captcha/check',{params:{...data}});
// return request({
// url: '/captcha/check',
// method: 'post',
// data
// })
}

View File

@ -0,0 +1,11 @@
import CryptoJS from 'crypto-js'
/**
* @word 要加密的内容
* @keyWord String 服务器随机返回的关键字
* */
export function aesEncrypt(word,keyWord="XwKsGlMcdPMEhR1B"){
var key = CryptoJS.enc.Utf8.parse(keyWord);
var srcs = CryptoJS.enc.Utf8.parse(word);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
return encrypted.toString();
}

View File

@ -0,0 +1,30 @@
import axios from 'axios';
axios.defaults.baseURL = import.meta.env.VITE_APP_BASE_URL;
const service = axios.create({
timeout: 40000,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json; charset=UTF-8'
},
})
service.interceptors.request.use(
config => {
return config
},
error => {
Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
response => {
const res = response.data;
return res
},
error => {
}
)
export default service

View File

@ -0,0 +1,35 @@
export function resetSize(vm) {
var img_width, img_height, bar_width, bar_height; //图片的宽度、高度,移动条的宽度、高度
var parentWidth = vm.$el.parentNode.offsetWidth || window.offsetWidth
var parentHeight = vm.$el.parentNode.offsetHeight || window.offsetHeight
if (vm.imgSize.width.indexOf('%') != -1) {
img_width = parseInt(vm.imgSize.width) / 100 * parentWidth + 'px'
} else {
img_width = vm.imgSize.width;
}
if (vm.imgSize.height.indexOf('%') != -1) {
img_height = parseInt(vm.imgSize.height) / 100 * parentHeight + 'px'
} else {
img_height = vm.imgSize.height
}
if (vm.barSize.width.indexOf('%') != -1) {
bar_width = parseInt(vm.barSize.width) / 100 * parentWidth + 'px'
} else {
bar_width = vm.barSize.width
}
if (vm.barSize.height.indexOf('%') != -1) {
bar_height = parseInt(vm.barSize.height) / 100 * parentHeight + 'px'
} else {
bar_height = vm.barSize.height
}
return {imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height}
}
export const _code_chars = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']
export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']

View File

@ -9,6 +9,7 @@ let i18n = createI18n({
datetimeFormats: {},
numberFormats: {},
globalInjection: true, //是否全局注入
silentTranslationWarn: true,
messages: {
"zh-cn": zhCn,
en

View File

@ -3,7 +3,7 @@ import useAppStore from '@/stores/modules/app'
const t = (message: string) => {
const path = useAppStore().route
const file = path == '/' ? 'index' : path.replace('/', '').replaceAll('/', '.')
const file = path == '/' ? 'index' : path.replace(/^(\/admin\/|\/site\/|\/)/, '').replaceAll('/', '.')
const key = `${file}.${message}`
return i18n.global.t(key) != key ? i18n.global.t(key) : i18n.global.t(message)
}

View File

@ -30,12 +30,7 @@ class Language {
*/
public async loadLocaleMessages(path: string, locale: string) {
try {
const file = path == '/' ? 'index' : path.replace('/', '').replaceAll('/', '.')
if (this.loadLocale.includes(`${locale}/${file}`)) {
return nextTick()
}
this.loadLocale.push(`${locale}/${file}`)
const file = path == '/' ? 'index' : path.replace(/^(\/admin\/|\/site\/|\/)/, '').replaceAll('/', '.')
// 引入语言包文件
const messages = await import(`./${locale}/${file}.json`)
@ -48,7 +43,8 @@ class Language {
this.i18n.global.mergeLocaleMessage(locale, data)
this.setI18nLanguage(locale)
return nextTick()
} catch {
} catch (e) {
console.log(e)
this.setI18nLanguage(locale)
return nextTick()
}

View File

@ -31,5 +31,6 @@
"isShowNumber":"是否显示必须是数字",
"isShowBetween":"是否显示只能是0或者1",
"sortNumber":"排序号必须是数字",
"sortBetween":"排序号需要在0-10000之间"
"sortBetween":"排序号需要在0-10000之间",
"articleNull":"未读取到文章信息!"
}

View File

@ -0,0 +1,25 @@
{
"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

@ -15,6 +15,8 @@
"lock":"锁定",
"unlock":"解锁",
"status":"用户状态",
"statusUnlock":"正常",
"statusLock":"锁定",
"userUnlockTips":"确定要解锁该管理员吗?",
"userLockTips":"确定要锁定该管理员吗?"
}

View File

@ -1,6 +1,7 @@
{
"isOpen": "是否开启",
"h5DomainName": "h5域名",
"clickVisit": "点击访问",
"H5Info": "h5配置信息"
}

View File

@ -1,4 +1,7 @@
{
"pcInfo": "电脑端信息",
"preview": "预览图"
"preview": "预览图",
"copy": "复制",
"clickVisit": "点击访问",
"PCDomainName": "pc域名"
}

View File

@ -34,6 +34,7 @@
"yes": "是",
"no": "否",
"copy": "复制",
"complete": "完成",
"copySuccess": "复制成功",
"notSupportCopy": "浏览器不支持一键复制,请手动进行复制",
"selectPlaceholder": "全部",
@ -69,7 +70,10 @@
"layoutSetting": "主题设置",
"darkMode": "黑暗模式",
"sidebarMode": "主题风格",
"themeColor": "主题颜色"
"themeColor": "主题颜色",
"detectionLoginOperation": "确定",
"detectionLoginContent": "已检测到有其他账号登录,需要刷新后才能继续操作。",
"detectionLoginTip": "提示"
},
"axios": {
"unknownError": "未知错误",

View File

@ -0,0 +1,39 @@
{
"orderNo":"订单编号",
"orderStatus":"订单状态",
"orderNoPlaceholder":"请输入订单编号",
"createTime":"创建时间",
"rechargeMoney":"充值金额",
"orderMoney":"订单金额",
"member":"买家",
"orderFromName":"订单来源",
"payTypeName":"支付方式",
"startDate":"开始时间",
"endDate":"结束时间",
"namePlaceholder":"请选择",
"applyTime":"申请时间",
"cashOutStatus":"提现状态",
"actualTransferAmount":"实际转账金额",
"cashOutCommission":"提现手续费",
"applicationForWithdrawalAmount":"申请提现金额",
"cashOutMethod":"提现方式",
"cashOutAccountType":"会员账户",
"memberInfo":"会员信息",
"toBeReviewed":"待审核",
"toBeTransferred":"待转账",
"transferred":"已转账",
"turnDown":"拒绝",
"transfer": "转账",
"detail": "详情",
"auditFailure": "审核失败",
"successfulAudit": "审核成功",
"rejectionAudit": "拒绝审核",
"reasonsRefusal": "拒绝理由",
"reasonsRefusalPlaceholder": "请输入拒绝理由",
"isTransfer": "是否确认转账",
"nickname":"会员名称",
"headimg":"会员头像",
"cashOutDetail":"提现详情",
"cashOutMoney": "转账金额"
}

View File

@ -10,5 +10,9 @@
"payTypeName":"支付方式",
"startDate":"开始时间",
"endDate":"结束时间",
"namePlaceholder":"请选择"
"namePlaceholder":"请选择",
"refundBtn":"退款",
"refundContent":"是否确认退款"
}

View File

@ -0,0 +1,14 @@
{
"refundNumber":"退款单号",
"userInfo":"用户信息",
"sourceNumber":"来源单号",
"refundAmount":"退款金额",
"refundTime":"退款时间",
"detail": "详情",
"statusName": "状态",
"memberInfo": "会员",
"refundSource": "退款来源",
"startDate":"开始时间",
"endDate":"结束时间",
"refundStatus": "退款状态"
}

View File

@ -1,24 +0,0 @@
{
"orderNo":"订单编号",
"orderStatus":"订单状态",
"orderNoPlaceholder":"请输入订单编号",
"createTime":"创建时间",
"rechargeMoney":"充值金额",
"orderMoney":"订单金额",
"member":"买家",
"orderFromName":"订单来源",
"payTypeName":"支付方式",
"startDate":"开始时间",
"endDate":"结束时间",
"namePlaceholder":"请选择",
"applyTime":"申请时间",
"withdrawalStatus":"提现状态",
"actualTransferAmount":"实际转账金额",
"withdrawalCommission":"提现手续费",
"applicationForWithdrawalAmount":"申请提现金额",
"withdrawalMethod":"提现方式",
"memberInfo":"会员信息",
"toBeTransferred":"待转账",
"transferred":"已转账",
"turnDown":"拒绝"
}

View File

@ -1,9 +1,10 @@
{
"dataSummarize": "数据概括",
"todayData": "今日数据",
"memberNumb": "会员数",
"memberNumb": "新增会员数",
"orderMoney": "订单金额",
"numberOfSites": "站点数量",
"numberOfVisitors": "访客数",
"numberOfVisitors": "今日访客数",
"commonlyUsedFunction": "常用功能",
"articleList": "文章列表",
"memberManagement": "会员管理",
@ -20,7 +21,7 @@
"versions": "当前版本",
"frame": "基于框架",
"channel": "获取渠道",
"serviceSupport": "务支持",
"serviceSupport": "官方客服",
"officialWbsite": "官网",
"pageView": "访问量",
"siteInfo":"站点信息",
@ -28,5 +29,17 @@
"groupName":"站点套餐",
"expireTime":"过期时间",
"permanent":"永久",
"statusName":"站点状态"
"statusName":"站点状态",
"newSiteSum": "新增站点数",
"total": "总计",
"newMemberSum": "新增用户数",
"siteList": "站点列表",
"sitePackage": "站点套餐",
"newSite": "新增站点",
"appMarketplace": "应用市场",
"siteDistribution": "站点分布",
"siteSum": "站点数",
"memberSum": "用户数",
"appSum": "应用数",
"installAppSun": "安装应用数"
}

View File

@ -1,9 +1,12 @@
{
"siteTitle": "Niucloud Admin",
"siteTitle": "NIUCLOUD-ADMIN",
"siteDesc": "基于thinkphp6+elementplus+typescript等技术的多端saas通用管理框架采用restful的api接口设计前后端完全分离同时支持多语言开发。",
"logging": "登录中",
"platform": "管理端",
"login" : "登录",
"siteLogin": "站点登录",
"adminLogin": "平台登录",
"userPlaceholder": "请输入您的账号",
"passwordPlaceholder": "请输入您的密码"
"passwordPlaceholder": "请输入您的密码",
"manageAdminFramework": "管理系统后台框架"
}

View File

@ -1,6 +1,6 @@
{
"accountData":"金额",
"fromType":"变动类型",
"fromType":"来源/用途",
"balanceInfo":"余额变动详情",
"memo":"备注",
"mobile":"手机号码",
@ -20,5 +20,8 @@
"memoPlaceholder":"请输入备注信息",
"addMemberAccountLog":"添加会员账单表",
"updateMemberAccountLog":"编辑会员账单表",
"member_account_logDeleteTips":"确定要删除该会员账单表吗?"
"memberAccountLogDeleteTips":"确定要删除该会员账单表吗?",
"balance": "不可提现余额",
"money":"可提现余额",
"balanceType":"账户类型"
}

View File

@ -0,0 +1,24 @@
{
"accountData":"金额",
"fromType":"来源/用途",
"moneyInfo":"佣金变动详情",
"memo":"备注",
"mobile":"手机号码",
"nickName":"会员信息",
"headimg":"会员头像",
"createTime":"产生时间",
"startDate":"开始时间",
"endDate":"结束时间",
"searchMember":"昵称/手机号",
"searchMemberPlaceholder":"请输入会员昵称/手机号码",
"memberIdPlaceholder":"请输入会员id",
"accountTypePlaceholder":"请输入账户类型",
"accountDataPlaceholder":"请输入账户数据",
"fromTypePlaceholder":"请输入来源类型",
"relatedIdPlaceholder":"请输入关联Id",
"createTimePlaceholder":"请输入创建时间",
"memoPlaceholder":"请输入备注信息",
"addMemberAccountLog":"添加会员账单表",
"updateMemberAccountLog":"编辑会员账单表",
"member_account_logDeleteTips":"确定要删除该会员账单表吗?"
}

View File

@ -24,6 +24,7 @@
"wxOpenid": "微信小程openid",
"memberLabel": "会员标签",
"memberLabelPlaceholder": "请选择会员标签",
"nickNamePlaceholder": "请输入会员名称",
"updateMember": "编辑会员信息",
"notAvailable":"暂无",
"girlSex":"女",
@ -42,6 +43,8 @@
"mobile":"手机号",
"detail":"详情",
"accumulative":"累计",
"looseChange":"零钱",
"adjustPoint":"调整积分"
"money":"可提现余额",
"adjustPoint":"调整积分",
"commission":"佣金",
"memberNull":"未读取到会员详情信息"
}

View File

@ -12,6 +12,6 @@
"updateMemberLabel":"编辑会员标签",
"sortVerifyOne":"排序要大于零",
"sortVerifyTwo":"排序不能是小数",
"member_labelDeleteTips":"确定要删除该会员标签吗?"
"memberLabelDeleteTips":"确定要删除该会员标签吗?"
}

View File

@ -1,9 +1,9 @@
{
"registerType":"注册方式",
"registerChannel":"注册来源",
"nickname":"会员名称",
"mobile":"手机号",
"createTime":"注册时间",
"lastVisitTime":"最后登录时间",
"lastVisitTime":"最后访问时间",
"addMember":"添加会员",
"updateMember":"编辑会员",
"nickNamePlaceholder":"请输入会员名称",
@ -39,9 +39,11 @@
"doubleCipherHint": "输入的两次密码不一致",
"mobileHint": "请输入正确的手机号!",
"lable": "标签",
"setLable": "设置标签",
"setLable": "标签",
"notAvailable": "暂无",
"memberLabelPlaceholder": "请选择会员标签",
"memberInfo":"会员信息",
"memberInfoPlaceholder":"请输入会员名称/会员昵称/手机号"
"memberInfoPlaceholder":"请输入会员名称/会员昵称/手机号",
"lock": "锁定",
"scrubLock": "取消锁定"
}

View File

@ -0,0 +1,7 @@
{
"isCaptcha": "是否启用验证码",
"bgImg": "背景图片",
"admin": "平台端",
"isBgImgTip": "建议上传尺寸为450*400px",
"site": "站点端"
}

View File

@ -1,13 +1,13 @@
{
"siteId":"站点id",
"messageKey":"消息模板",
"messageType":"消息类型",
"messageInfo":"消息详情",
"noticeKey":"消息模板",
"noticeType":"消息类型",
"noticeInfo":"消息详情",
"uid":"通知的用户id",
"memberId":"消息的会员id",
"nickname":"接收会员",
"receiver":"手机/OPENID",
"messageData":"消息内容",
"noticeData":"消息内容",
"isClick":"点击次数",
"searchReceiver":"接收人",
"isVisit":"访问次数",
@ -17,16 +17,16 @@
"sms":"短信",
"weapp":"微信小程序",
"wechat":"微信公众号",
"messageTypePlaceholder":"请选择消息类型",
"noticeTypePlaceholder":"请选择消息类型",
"uidPlaceholder":"请输入通知的用户id",
"memberIdPlaceholder":"请输入消息的会员id",
"nicknamePlaceholder":"请输入接收人用户昵称或姓名",
"receiverPlaceholder":"请输入接收人手机号/openid",
"messageDataPlaceholder":"请输入消息数据",
"noticeDataPlaceholder":"请输入消息数据",
"isClickPlaceholder":"请输入点击次数",
"isVisitPlaceholder":"请输入访问次数",
"visitTimePlaceholder":"请输入访问时间",
"createTimePlaceholder":"请输入消息时间",
"buyerMessage": "会员消息",
"sellerMessage":"平台消息"
"buyerNotice": "会员消息",
"sellerNotice":"平台消息"
}

View File

@ -1,10 +1,10 @@
{
"buyerMessage": "会员消息",
"sellerMessage":"平台消息",
"buyerNotice": "会员消息",
"sellerNotice":"平台消息",
"sms":"短信",
"weapp":"微信小程序",
"wechat":"微信公众号",
"messageSetting":"消息设置",
"noticeSetting":"消息设置",
"name": "模板名称",
"title": "场景",
"smsContent": "短信内容",

View File

@ -34,5 +34,20 @@
"appIdTips": "支付宝分配给开发者的应用ID",
"appPublicCertPathTips": "上传appCertPublicKey文件",
"alipayPublicCertPathTips": "上传alipayCertPublicKey文件",
"alipayRootCertPathTips": "上传alipayRootCert文件"
"alipayRootCertPathTips": "上传alipayRootCert文件",
"payType": "支付方式",
"templateName": "配置信息",
"settingDefaultPay": "设置默认支付",
"settingDefault": "设置默认",
"defaultTamplate": "请选择模板",
"isEnable": "是否启用",
"onState": "开启状态",
"configurePaymentMethod": "请先配置值该支付方式",
"enablePaymentMode": "请先开启该支付方式",
"clickConfigure": "点击配置",
"setConfig": "设置支付配置",
"open": "开启",
"notOpen": "未开启",
"cancel": "取消"
}

View File

@ -0,0 +1,31 @@
{
"wechatpay": "微信打款设置",
"alipay": "支付宝打款设置",
"mchId": "商户号",
"mchIdPlaceholder": "请输入商户号",
"mchIdTips": "微信支付商户号MCHID",
"mchSecretKey": "API密钥",
"mchSecretKeyPlaceholder": "请输入API密钥",
"mchSecretKeyTips": "微信支付商户API密钥paySignKey",
"mchSecretCert": "商户私钥",
"mchSecretCertPlaceholder": "请上传商户私钥",
"mchSecretCertTips": "微信支付API证书apiclient_key.pem",
"mchPublicCertPath": "商户公钥",
"mchPublicCertPathPlaceholder": "请上传商户公钥",
"mchPublicCertPathTips": "微信支付API证书apiclient_cert.pem",
"appId": "app_id",
"appIdPlaceholder": "请输入app_id",
"appSecretCert": "应用私钥",
"appSecretCertPlaceholder": "请输入应用私钥",
"appPublicCertPath": "应用公钥",
"appPublicCertPathPlaceholder": "请上传应用公钥",
"alipayPublicCertPath": "支付宝公钥",
"alipayPublicCertPathPlaceholder": "请上传支付宝公钥",
"alipayRootCertPath": "支付宝根证书",
"alipayRootCertPathPlaceholder": "请上传支付宝根证书",
"appIdTips": "支付宝分配给开发者的应用ID",
"appPublicCertPathTips": "上传appCertPublicKey文件",
"alipayPublicCertPathTips": "上传alipayCertPublicKey文件",
"alipayRootCertPathTips": "上传alipayRootCert文件",
"operationTip": "温馨提示:打款设置用于会员提现转账,发放红包等场景"
}

View File

@ -0,0 +1,32 @@
{
"siteId":"站点id",
"noticeKey":"消息模板",
"noticeType":"消息类型",
"noticeInfo":"消息详情",
"uid":"通知的用户id",
"memberId":"消息的会员id",
"nickname":"接收会员",
"receiver":"手机号",
"noticeData":"消息内容",
"isClick":"点击次数",
"searchReceiver":"接收人",
"isVisit":"访问次数",
"createTime":"发送时间",
"startDate":"开始时间",
"endDate":"结束时间",
"sms":"短信",
"weapp":"微信小程序",
"wechat":"微信公众号",
"noticeTypePlaceholder":"请选择消息类型",
"uidPlaceholder":"请输入通知的用户id",
"memberIdPlaceholder":"请输入消息的会员id",
"nicknamePlaceholder":"请输入接收人用户昵称或姓名",
"receiverPlaceholder":"请输入接收人手机号",
"noticeDataPlaceholder":"请输入消息数据",
"isClickPlaceholder":"请输入点击次数",
"isVisitPlaceholder":"请输入访问次数",
"visitTimePlaceholder":"请输入访问时间",
"createTimePlaceholder":"请输入消息时间",
"buyerNotice": "会员消息",
"sellerNotice":"平台消息"
}

View File

@ -3,18 +3,18 @@
"updateTime": "修改时间",
"createTime": "创建时间",
"groupName": "套餐名称",
"groupDesc": "套餐介绍",
"groupDesc": "套餐说明",
"groupRoles": "套餐权限",
"groupDeleteTips": "确定要删除该套餐吗?",
"groupNamePlaceholder": "请输入套餐名称",
"groupDescPlaceholder": "请输入套餐介绍",
"groupDescPlaceholder": "请输入套餐说明",
"groupRolesPlaceholder": "请选择套餐权限",
"groupPlaceholder": "请选择权限",
"permission": "权限",
"updateGroup":"编辑套餐",
"addGroup":"添加套餐",
"checkStrictly": "父子级不关联",
"remark": "备注",
"remark": "套餐说明",
"reset": "重置",
"search": "搜索"
}

View File

@ -5,7 +5,7 @@
"status":"状态",
"businessHours":"营业时间",
"createTime":"创建时间",
"expireTime":"期时间",
"expireTime":"期时间",
"siteNamePlaceholder":"请输入站点名称",
"createTimePlaceholder":"请输入创建时间",
"addSite":"添加站点",
@ -24,5 +24,7 @@
"confirmPasswordPlaceholder": "请再次确认密码",
"userRealNamePlaceholder": "请输入名称",
"expireTimePlaceholder":"请选择到期时间",
"confirmPasswordError": "两次输入的密码不一致"
"confirmPasswordError": "两次输入的密码不一致",
"operationTip": "温馨提示:站点登录页面"
}

View File

@ -1,9 +1,9 @@
{
"todayData": "今日数据",
"memberNumb": "会员数",
"memberNumb": "新增会员数",
"orderMoney": "订单金额",
"numberOfSites": "站点数量",
"numberOfVisitors": "访客数",
"numberOfVisitors": "今日访客数",
"commonlyUsedFunction": "常用功能",
"articleList": "文章列表",
"memberManagement": "会员管理",

View File

@ -1,3 +1,12 @@
{
"content":"功能正在开发,请敬请期待..."
"noPlug":"暂无插件",
"install":"安装",
"unload":"卸载",
"installLabel":"已安装",
"uninstalledLabel":"未安装",
"version":"版本",
"title":"名称",
"desc":"描述",
"plugDetail": "插件信息",
"author": "作者"
}

View File

@ -0,0 +1,15 @@
{
"title": "名称",
"serverInformation": "服务器信息",
"systemDemand": "系统环境要求",
"authorityStatus": "权限状态",
"name": "选项",
"demand": "要求",
"status": "状态",
"environment": "环境",
"version": "版本",
"phpType": "PHP版本",
"phpTypeValue": "大于等于8.0.0",
"mysqlType": "mysql版本",
"mysqlTypeValue": "大于等于5.7"
}

View File

@ -0,0 +1,7 @@
{
"refresh":"刷新",
"refreshMenu":"刷新菜单",
"refreshMenuDesc":"新增/修改插件菜单后,需要刷新插件菜单",
"dataCache":"数据缓存",
"dataCacheDesc":"新增/修改数据表后,需要清除数据表缓存"
}

View File

@ -14,8 +14,10 @@
</div>
<!-- 返回上一页 -->
<div class="flex items-center cursor-pointer" v-if="appStore.pageReturn" @click="backFn">
<el-icon class="mr-1"><Back /></el-icon>
<span class="text-base mr-3">{{t('returnToPreviousPage')}}</span>
<el-icon class="mr-1">
<Back />
</el-icon>
<span class="text-base mr-3">{{ t('returnToPreviousPage') }}</span>
<span class=" text-gray-300">|</span>
</div>
<!-- 面包屑导航 -->
@ -30,8 +32,9 @@
</el-col>
<el-col :span="12">
<div class="right-panel h-full flex items-center justify-end">
<!-- 预览 -->
<img class="w-[16px] h-[16px] mr-1" src="@/assets/images/icon_preview.png" :alt="t('preview')" :title="t('preview')">
<!-- 预览 只有站点时展示-->
<img class="w-[16px] h-[16px] mr-1" v-if="appType == 'site'" src="@/assets/images/icon_preview.png"
:alt="t('preview')" :title="t('preview')">
<!-- 切换语言 -->
<div class="navbar-item flex items-center h-full cursor-pointer">
<switch-lang />
@ -52,6 +55,18 @@
</div>
</el-col>
</el-row>
<input type="hidden" v-model="comparisonToken">
<input type="hidden" v-model="comparisonSiteId">
<el-dialog v-model="detectionLoginDialog" :title="t('layout.detectionLoginTip')" width="30%"
:close-on-click-modal="false" :close-on-press-escape="false" :show-close="false">
<span>{{ t('layout.detectionLoginContent') }}</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="detectionLoginFn">{{ t('layout.detectionLoginOperation') }}</el-button>
</span>
</template>
</el-dialog>
</el-container>
</template>
@ -65,17 +80,43 @@ import useSystemStore from '@/stores/modules/system'
import useAppStore from '@/stores/modules/app'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
import useUserStore from '@/stores/modules/user'
const router = useRouter()
const appType = storage.get('app_type')
const { toggle: toggleFullscreen, isFullscreen } = useFullscreen()
const systemStore = useSystemStore()
const appStore = useAppStore()
const route = useRoute()
const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
// start
const detectionLoginDialog = ref(false)
const comparisonToken = ref('')
const comparisonSiteId = ref('')
if (storage.get('comparisonTokenStorage')) {
comparisonToken.value = storage.get('comparisonTokenStorage')
// storage.remove(['comparisonTokenStorage']);
}
if (storage.get('comparisonSiteIdStorage')) {
comparisonSiteId.value = storage.get('comparisonSiteIdStorage')
// storage.remove(['comparisonSiteIdStorage']);
}
//
document.addEventListener('visibilitychange', e => {
if (document.visibilityState === 'visible' && (comparisonSiteId.value != storage.get('siteId') || comparisonToken.value != storage.get('token'))) {
detectionLoginDialog.value = true
}
})
const detectionLoginFn = () => {
detectionLoginDialog.value = false
location.reload();
}
// end
//
onMounted(() => {
//
window.onresize = () => {
return (() => {
screenWidth.value = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
@ -111,15 +152,15 @@ const refreshRouter = () => {
//
const breadcrumb = computed(() => {
const matched = route.matched
const matched = route.matched.filter(item => { return item.meta.title })
if (matched[0] && matched[0].path == '/') matched.splice(0, 1)
return matched
})
//
const backFn = ()=>{
router.go(-1);
};
const backFn = () => {
router.go(-1)
}
</script>
<style lang="scss" scoped>
@ -129,5 +170,4 @@ const backFn = ()=>{
&:hover {
background-color: var(--el-bg-color-page);
}
}
</style>
}</style>

View File

@ -1,18 +1,29 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createRouter, createWebHistory, RouteLocationRaw } from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { STATIC_ROUTES, NO_LOGIN_ROUTES, INDEX_ROUTE, findFirstValidRoute, DECORATE_ROUTER } from './routers'
import { STATIC_ROUTES, NO_LOGIN_ROUTES, ROOT_ROUTER, ADMIN_ROUTE, SITE_ROUTE, DECORATE_ROUTER, findFirstValidRoute } from './routers'
import { language } from '@/lang'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import { setWindowTitle } from '@/utils/common'
import { setWindowTitle, getAppType, urlToRouteRaw } from '@/utils/common'
import storage from '@/utils/storage'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: STATIC_ROUTES,
routes: [...STATIC_ROUTES, ADMIN_ROUTE, SITE_ROUTE]
})
/**
* push方法
*/
const originPush = router.push
router.push = (to: RouteLocationRaw) => {
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 originPush(route)
}
// 全局前置守卫
router.beforeEach(async (to, from, next) => {
NProgress.configure({ showSpinner: false })
@ -23,19 +34,22 @@ router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
const siteInfo = storage.get('siteInfo') || false
let title = (to.meta?.title || '') + (siteInfo.site_name ? ('-' + siteInfo.site_name) : '')
// 设置网站标题
setWindowTitle(title)
// 加载语言包
await language.loadLocaleMessages(to.path, useSystemStore().lang);
const loginPath = to.path == '/' ? '/admin/login' : `${to.matched[0].path}/login`
// 判断是否需登录
if (NO_LOGIN_ROUTES.includes(to.path)) {
next()
} else if (userStore.token) {
// 如果已加载路由
if (userStore.routers.length) {
if (to.path === '/login') {
next('/')
if (to.path === loginPath) {
next(`/${getAppType()}`)
} else {
next()
}
@ -45,8 +59,17 @@ router.beforeEach(async (to, from, next) => {
// 设置首页路由
const firstRoute = findFirstValidRoute(userStore.routers)
INDEX_ROUTE.redirect = { name: firstRoute }
router.addRoute(INDEX_ROUTE)
ROOT_ROUTER.redirect = { name: firstRoute }
router.addRoute(ROOT_ROUTER)
// 设置应用首页路由
if (getAppType() == 'admin') {
ADMIN_ROUTE.children[0].redirect = { name: firstRoute }
router.addRoute(ADMIN_ROUTE)
} else {
SITE_ROUTE.children[0].redirect = { name: firstRoute }
router.addRoute(SITE_ROUTE)
}
// 添加动态路由
userStore.routers.forEach(route => {
@ -56,25 +79,35 @@ router.beforeEach(async (to, from, next) => {
router.addRoute(DECORATE_ROUTER)
return
}
if (!route.children) {
router.addRoute('root', route)
if (route.meta.app == 'admin') {
router.addRoute(ADMIN_ROUTE.children[0].name, route)
} else {
router.addRoute(SITE_ROUTE.children[0].name, route)
}
return
}
// 动态添加可访问路由表
router.addRoute(route)
if (route.meta.app == 'admin') {
router.addRoute(ADMIN_ROUTE.name, route)
} else {
router.addRoute(SITE_ROUTE.name, route)
}
})
next({ path: to.path, query: to.query, replace: true })
} catch (err) {
userStore.logout()
next({ path: '/login', query: { redirect: to.fullPath } })
next({ path: loginPath, query: { redirect: to.fullPath } })
}
}
} else {
if (to.path === '/login') {
if (to.path === loginPath) {
next()
} else {
next({ path: '/login', query: { redirect: to.fullPath } })
next({ path: loginPath, query: { redirect: to.fullPath } })
}
}
})

View File

@ -4,52 +4,86 @@ import Decorate from '@/layout/decorate/index.vue'
// 静态路由
export const STATIC_ROUTES: Array<RouteRecordRaw> = [
{
path: `${import.meta.env.BASE_URL}`,
component: Default,
name: 'root'
},
{
path: '/:pathMatch(.*)*',
component: () => import('@/views/error/404.vue')
},
{
path: '/403',
component: () => import('@/views/error/403.vue')
},
{
path: '/login',
component: () => import('@/views/login/index.vue')
},
{
path: '/user',
component: Default,
children: [
{
path: 'center',
meta: {
type: 1,
title: '个人中心'
},
component: () => import('@/views/index/personal.vue')
}
]
}
]
// 免登录路由
export const NO_LOGIN_ROUTES: string[] = [
'/403',
'/404'
]
// 首页路由
export const INDEX_ROUTE: RouteRecordRaw = {
// 根路由
export const ROOT_ROUTER: RouteRecordRaw = {
path: '/',
component: Default,
name: Symbol()
}
// 平台端根路由
export const ADMIN_ROUTE: RouteRecordRaw = {
path: '/admin',
name: Symbol('admin'),
children: [
{
path: '',
name: Symbol('adminRoot'),
component: Default
},
{
path: 'login',
component: () => import('@/views/login/index.vue')
},
{
path: 'user',
component: Default,
children: [
{
path: 'center',
meta: {
type: 1,
title: '个人中心'
},
component: () => import('@/views/index/personal.vue')
}
]
}
]
}
// 站点端路由
export const SITE_ROUTE: RouteRecordRaw = {
path: '/site',
name: Symbol('site'),
children: [
{
path: '',
name: Symbol('siteRoot'),
component: Default
},
{
path: 'login',
component: () => import('@/views/login/index.vue')
},
{
path: 'user',
component: Default,
children: [
{
path: 'center',
meta: {
type: 1,
title: '个人中心'
},
component: () => import('@/views/index/personal.vue')
}
]
}
]
}
// 装修路由
export const DECORATE_ROUTER: RouteRecordRaw = {
path: '/decorate',
@ -70,7 +104,8 @@ interface Route {
type: string
},
children?: [],
is_show: boolean
is_show: boolean,
app_type: string
}
/**
@ -80,13 +115,14 @@ interface Route {
*/
const createRoute = function (route: Route, parentRoute: RouteRecordRaw | null = null): RouteRecordRaw {
const record: RouteRecordRaw = {
path: parentRoute ? route.router_path : '/' + route.router_path,
path: parentRoute ? route.router_path : route.router_path != 'decorate' ? `/${route.app_type}/${route.router_path}` : `/${route.router_path}`,
name: parentRoute ? Symbol(`${parentRoute.path}/${route.router_path}`) : Symbol(`/${route.router_path}`),
meta: {
title: route.menu_name,
icon: route.icon,
type: route.menu_type,
show: route.is_show
show: route.is_show,
app: route.app_type
}
}
if (route.menu_type == 0) {

View File

@ -50,20 +50,19 @@ const useDiyStore = defineStore('diy', {
},
actions: {
// 添加组件
addComponent(data: any) {
addComponent(key: string, data: any) {
// 加载完才能添加组件
if(!this.load) return;
if (!this.load) return;
// 删除不用的字段
let component = cloneDeep(data);
component.id = this.generateRandom();
component.componentName = component.name;
component.componentName = key;
component.componentTitle = component.title;
component.maxCount = component.max_count;
Object.assign(component, component.value);
delete component.name;
delete component.title;
delete component.value;
delete component.type;

View File

@ -1,9 +1,9 @@
import { defineStore } from 'pinia'
import { getToken, setToken, removeToken } from '@/utils/common'
import { getToken, setToken, removeToken, getAppType } from '@/utils/common'
import { login, getAuthMenus } from '@/api/auth'
import storage from '@/utils/storage'
import router from '@/router'
import { formatRouters, INDEX_ROUTE } from '@/router/routers'
import { formatRouters } from '@/router/routers'
import useTabbarStore from './tabbar'
interface User {
@ -25,9 +25,9 @@ const useSystemStore = defineStore('user', {
}
},
actions: {
login(form: object) {
login(form: object, app_type: any) {
return new Promise((resolve, reject) => {
login(form)
login(form, app_type)
.then((res) => {
this.token = res.data.token
this.userInfo = res.data.userinfo
@ -35,6 +35,8 @@ const useSystemStore = defineStore('user', {
storage.set({ key: 'userinfo', data: res.data.userinfo })
storage.set({ key: 'siteId', data: res.data.site_id })
storage.set({ key: 'siteInfo', data: res.data.site_info })
storage.set({ key: 'comparisonSiteIdStorage', data: res.data.site_id })
storage.set({ key: 'comparisonTokenStorage', data: res.data.token })
resolve(res)
})
.catch((error) => {
@ -48,15 +50,10 @@ const useSystemStore = defineStore('user', {
this.siteInfo = {}
removeToken()
storage.remove(['userinfo', 'siteId', 'siteInfo'])
// 清除路由
this.routers.forEach(item => {
router.removeRoute(item.name)
})
router.removeRoute(INDEX_ROUTE.name)
this.routers = []
// 清除tabbar
useTabbarStore().clearTab()
router.push('/login')
router.push(`/${getAppType()}/login`)
},
getAuthMenus() {
return new Promise((resolve, reject) => {

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3883393 */
src: url('//at.alicdn.com/t/c/font_3883393_c2tb4v0fz24.woff2?t=1681374762000') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_c2tb4v0fz24.woff?t=1681374762000') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_c2tb4v0fz24.ttf?t=1681374762000') format('truetype');
src: url('//at.alicdn.com/t/c/font_3883393_4jlgm3tby7.woff2?t=1683947082967') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_4jlgm3tby7.woff?t=1683947082967') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_4jlgm3tby7.ttf?t=1683947082967') format('truetype');
}
.iconfont {
@ -13,6 +13,46 @@
-moz-osx-font-smoothing: grayscale;
}
.icontuikuanjilu:before {
content: "\e8cf";
}
.icongengxinhuancun:before {
content: "\e686";
}
.iconsixingjiance:before {
content: "\e645";
}
.iconzhuceshezhi:before {
content: "\e6ad";
}
.iconmanage-apply:before {
content: "\e619";
}
.iconyingyongguanli:before {
content: "\e61f";
}
.iconkaifazheguanli:before {
content: "\e624";
}
.iconlianmengguanli:before {
content: "\e65f";
}
.icondianzan:before {
content: "\ec7f";
}
.iconh5e:before {
content: "\e654";
}
.iconyingyongshichang:before {
content: "\e618";
}

View File

@ -1,8 +1,9 @@
import type {App} from 'vue'
import type { App } from 'vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import {useCssVar, useTitle} from '@vueuse/core'
import { useCssVar, useTitle } from '@vueuse/core'
import colorFunction from 'css-color-function'
import storage from './storage'
import { useRoute } from 'vue-router'
/**
* element-icon
@ -45,6 +46,20 @@ export function setThemeColor(color: string, mode: string = 'light'): void {
})
}
/**
* 访
*/
export function getAppType() {
const path = location.pathname.split('/').filter((val) => { return val })
if (!path.length) {
return 'admin'
} else {
const app = path[0]
return app == 'decorate' ? 'site' : app
}
}
/**
* title
* @param value
@ -68,7 +83,7 @@ export function getToken(): null | string {
* @returns
*/
export function setToken(token: string): void {
storage.set({key: 'token', data: token})
storage.set({ key: 'token', data: token })
}
/**
@ -142,4 +157,20 @@ export function getWapDomain(): string {
} else {
return (import.meta.env.VITE_WAP_DOMAIN || location.origin + '/wap') + (storage.get('siteId') == 1 ? '' : '/s' + storage.get('siteId'));
}
}
/**
* url route
* @param url
*/
export function urlToRouteRaw(url: string) {
const query = {}
const [path, param] = url.split('?')
param && param.split('&').forEach((str : string) => {
let [name, value] = str.split('=')
query[name] = value
})
return { path, query }
}

View File

@ -52,7 +52,7 @@ class Request {
this.instance.interceptors.response.use(
(response: requestResponse) => {
const res = response.data
if (res.code != 200) {
if (res.code != 1) {
this.handleAuthError(res.code)
if (response.config.showErrorMessage) ElMessage({ message: res.msg, type: 'error' })
return Promise.reject(new Error(res.msg || 'Error'))

View File

@ -1,3 +1,5 @@
import { getAppType } from './common'
interface setParam {
key: string,
data: any,
@ -6,6 +8,11 @@ interface setParam {
}
class Storage {
private prefix = ''
public constructor() {
this.prefix = getAppType()
}
/**
*
@ -13,7 +20,7 @@ class Storage {
*/
public set(param: setParam) {
try {
window.localStorage.setItem(param.key, JSON.stringify(param.data))
window.localStorage.setItem(`${this.prefix}.${param.key}`, JSON.stringify(param.data))
typeof param.success == 'function' && param.success()
} catch (error) {
typeof param.fail == 'function' && param.fail(error)
@ -27,7 +34,7 @@ class Storage {
*/
public get(key: string) {
try {
const json: any = window.localStorage.getItem(key)
const json: any = window.localStorage.getItem(`${this.prefix}.${key}`)
return JSON.parse(json)
} catch (error) {
return null
@ -39,8 +46,8 @@ class Storage {
* @param key
*/
public remove(key: string | string[]) {
if (typeof key == 'string') window.localStorage.removeItem(key)
else key.forEach(item => { window.localStorage.removeItem(item) })
if (typeof key == 'string') window.localStorage.removeItem(`${this.prefix}.${key}`)
else key.forEach(item => { window.localStorage.removeItem(`${this.prefix}.${item}`) })
}
/**

View File

@ -0,0 +1,63 @@
<template>
<div class="main-container bg-[#fff] min-w-[1000px] min-h-[650px]">
<div class="pt-[1px]" v-for="(listItems, listIndex) in appManageList" :key="listIndex">
<p class="ml-4 mt-[20px] border-l-[2px] border-[#273de3] pl-4 leading-[1]">{{ listItems.name }}</p>
<div class="flex flex-wrap" v-if="isTrue == 0">
<div @click="toLink(appItems.url)" class="flex cursor-pointer ml-4 mt-[20px] w-[240px] p-[10px] border-[1px] border-[#eee] border-[solid]" v-for="(appItems, appIndex) in listItems.app_list" :key="appIndex">
<div class="flex justify-center items-center mr-[10px] min-w-[50px]">
<img v-if="appItems.icon" class="w-[50px] h-[50px]" :src="img(appItems.icon)"/>
</div>
<div class="w-[150px]">
<p class="app-text text-[14px] text-[#666]">{{ appItems.title }}</p>
<p class="app-text text-[12px] text-[#999]">{{ appItems.desc }}</p>
</div>
</div>
</div>
<div class="flex flex-wrap" v-if="isTrue == 1">
<div @click="toLink(appItems.url)" class="cursor-pointer ml-4 mt-[20px] w-[140px] p-[10px] border-[1px] border-[#eee] border-[solid]" v-for="(appItems, appIndex) in listItems.app_list" :key="appIndex">
<div class="flex justify-center items-center">
<img v-if="appItems.cover" class="w-[110px] h-[110px]" :src="img(appItems.cover)"/>
</div>
<div class="text-center mt-2">
<p class="app-text text-[14px] text-[#666]">{{ appItems.title }}</p>
<p class="app-text text-[12px] text-[#999]">{{ appItems.desc }}</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getAppMange } from '@/api/sys'
import { img } from '@/utils/common'
import { useRouter } from 'vue-router'
const appManageList = ref([])
const isTrue = ref(1)
const checkAppMange = () => {
getAppMange().then(res=>{
appManageList.value = res.data;
})
}
checkAppMange()
const router = useRouter()
/**
* 链接跳转
*/
const toLink = (link) => {
router.push(link)
}
</script>
<style lang="scss" scoped>
.app-text {
overflow:hidden;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow:ellipsis;
}
</style>

View File

@ -1,14 +1,14 @@
<template>
<el-dialog v-model="showDialog" :title="popTitle" width="500px"
:destroy-on-close="true">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-dialog v-model="showDialog" :title="popTitle" width="500px" :destroy-on-close="true">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form"
v-loading="loading">
<el-form-item :label="t('name')" prop="name">
<el-input v-model="formData.name" clearable :placeholder="t('namePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('sort')" prop="sort">
<el-input-number v-model="formData.sort" :min="0" />
</el-form-item>
<el-form-item :label="t('isShow')">
<el-radio-group v-model="formData.is_show" :placeholder="t('isShowPlaceholder')">
<el-radio :label="1">{{ t('show') }}</el-radio>
@ -32,9 +32,9 @@
import { ref, reactive, computed } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { addArticleCategory, updateArticleCategory, getArticleCategoryInfo } from '@/api/article'
import { addArticleCategory, editArticleCategory, getArticleCategoryInfo } from '@/api/article'
let popTitle:string = '';
let popTitle: string = '';
let showDialog = ref(false)
const loading = ref(true)
@ -42,7 +42,7 @@ const loading = ref(true)
/**
* 表单数据
*/
const initialFormData = {
const initialFormData = {
category_id: '',
name: '',
sort: '',
@ -55,34 +55,34 @@ const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
name: [
{ required: true, message: t('namePlaceholder'), trigger: 'blur' },
{
validator: (rule: any, value: string, callback: any) => {
if (value.length > 20) {
callback(new Error(t('nameMax')))
}
callback()
},
trigger: 'blur'
}
],
sort: [
{
validator: (rule: any, value: string, callback: any) => {
if(value === "" || isNaN(value)){
callback(new Error(t('sortNumber')))
}
if (parseInt(value) > 10000) {
callback(new Error(t('sortBetween')))
}
callback()
},
trigger: 'blur'
}
],
name: [
{ required: true, message: t('namePlaceholder'), trigger: 'blur' },
{
validator: (rule: any, value: string, callback: any) => {
if (value.length > 20) {
callback(new Error(t('nameMax')))
}
callback()
},
trigger: 'blur'
}
],
sort: [
{
validator: (rule: any, value: string, callback: any) => {
if (value === "" || isNaN(value)) {
callback(new Error(t('sortNumber')))
}
if (parseInt(value) > 10000) {
callback(new Error(t('sortBetween')))
}
callback()
},
trigger: 'blur'
}
],
}
})
@ -94,7 +94,7 @@ const emit = defineEmits(['complete'])
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
let save = formData.category_id ? updateArticleCategory : addArticleCategory
let save = formData.category_id ? editArticleCategory : addArticleCategory
await formEl.validate(async (valid) => {
if (valid) {

View File

@ -1,7 +1,8 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form"
v-loading="loading">
<el-form-item :label="t('title')" prop="title">
<el-input v-model="formData.title" clearable :placeholder="t('titlePlaceholder')" class="input-width"
maxlength="20" />
@ -62,10 +63,11 @@
import { ref, reactive, computed, watch } from 'vue'
import { t } from '@/lang'
import type { FormInstance, ElNotification } from 'element-plus'
import { getArticleInfo, getArticleCategoryAll, addArticle, updateArticle } from '@/api/article'
import { getArticleInfo, getArticleCategoryAll, addArticle, editArticle } from '@/api/article'
import { useRoute, useRouter } from 'vue-router'
import useTabbarStore from '@/stores/modules/tabbar'
import useAppStore from '@/stores/modules/app'
import { ElMessage } from 'element-plus'
const route = useRoute()
const router = useRouter()
@ -77,7 +79,7 @@ const appStore = useAppStore()
//
appStore.pageReturn = true;
watch(route, (newX,oldX) => {
watch(route, (newX, oldX) => {
appStore.pageReturn = false;
});
@ -104,16 +106,23 @@ const formData: Record<string, any> = reactive({ ...initialFormData })
const setFormData = async (id: number = 0) => {
loading.value = true;
Object.assign(formData, initialFormData)
if(id){
if (id) {
const data = await (await getArticleInfo(id)).data
if (!data || Object.keys(data).length == 0) {
ElMessage.error(t('articleNull'))
setTimeout(() => {
router.go(-1);
}, 2000)
return false;
}
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
loading.value = false;
}else{
} else {
loading.value = false;
}
}
if (id) setFormData(id)
@ -139,9 +148,9 @@ const formRules = computed(() => {
{
validator: (rule: any, value: string, callback: any) => {
let content = value.replace(/<[^<>]+>/g, "").replace(/&nbsp;/gi, "")
if(!content && value.indexOf('img') === -1){
if (!content && value.indexOf('img') === -1) {
callback(new Error(t('contentPlaceholder')))
}else callback()
} else callback()
},
trigger: ['blur', 'change']
}
@ -155,7 +164,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
if (valid) {
loading.value = true
const data = formData
const save = id ? updateArticle : addArticle
const save = id ? editArticle : addArticle
save(data).then(res => {
loading.value = false
back();

View File

@ -1,7 +1,7 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between">
<el-button type="primary" @click="addEvent">
{{ t('addArticle') }}
@ -14,8 +14,8 @@
<el-input v-model="articleTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
</el-form-item>
<el-form-item :label="t('categoryName')" prop="category_id">
<el-select v-model="articleTableData.searchParam.category_id" clearable :placeholder="t('categoryIdPlaceholder')"
class="input-width">
<el-select v-model="articleTableData.searchParam.category_id" clearable
:placeholder="t('categoryIdPlaceholder')" class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item['name']" :value="item['category_id']" v-for="item in categoryList" />
</el-select>
@ -34,18 +34,18 @@
</template>
<el-table-column prop="title" :show-overflow-tooltip="true" :label="t('title')" width="140" />
<el-table-column :label="t('image')" min-width="120" align="center">
<template #default="{ row }">
<el-image class="w-12 h-12" v-if="row.image" :src="img(row.image)" fit="contain" />
<el-image class="w-12 h-12" v-if="row.image" :src="img(row.image)" fit="contain" />
</template>
</el-table-column>
<el-table-column prop="category_name" :label="t('categoryName')" align="center" min-width="140" />
<el-table-column prop="category_name" :label="t('categoryName')" align="center" min-width="140" />
<el-table-column prop="summary" :label="t('summary')" width="180" :show-overflow-tooltip="true" />
<el-table-column prop="summary" :label="t('summary')" width="180" :show-overflow-tooltip="true"/>
<el-table-column :label="t('isShow')" min-width="120" align="center">
<template #default="{ row }">
<span v-if="row.is_show == 1">{{ t('show') }}</span>
@ -83,8 +83,8 @@
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { getArticleList, deleteArticle,getArticleCategoryAll } from '@/api/article'
import { img } from '@/utils/common'
import { getArticleList, deleteArticle, getArticleCategoryAll } from '@/api/article'
import { img, getAppType } from '@/utils/common'
import { ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
@ -96,7 +96,7 @@ const articleTableData = reactive({
data: [],
searchParam: {
title: '',
category_id:''
category_id: ''
}
})
const categoryList = ref([])

View File

@ -1,7 +1,7 @@
<template>
<el-dialog v-model="showDialog" :title="popTitle" width="500px"
:destroy-on-close="true">
<el-form :model="formData" label-width="90px" class="page-form" ref="formRef" :rules="formRules" v-loading="loading">
<el-dialog v-model="showDialog" :title="popTitle" width="500px" :destroy-on-close="true">
<el-form :model="formData" label-width="90px" class="page-form" ref="formRef" :rules="formRules"
v-loading="loading">
<el-form-item :label="t('menuName')" prop="menu_name">
<el-input v-model="formData.menu_name" :placeholder="t('menuNamePlaceholder')" class="input-width" />
</el-form-item>
@ -98,12 +98,12 @@ import { ref, reactive, computed } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import selectMenuItem from './select-menu-item.vue'
import { addMenu, updateMenu, getMenuInfo } from '@/api/sys'
import { addMenu, editMenu, getMenuInfo } from '@/api/sys'
const showDialog = ref(false)
const method = ref('post')
const loading = ref(false)
let popTitle:string = '';
let popTitle: string = '';
/**
* 表单数据
@ -121,8 +121,8 @@ const initialFormData = {
sort: '',
status: 1,
is_show: 1,
menu_key:'',
app_type:''
menu_key: '',
app_type: ''
}
const formData: Record<string, any> = reactive({ ...initialFormData })
@ -147,12 +147,12 @@ const formRules = computed(() => {
],
menu_key: [
{ required: true, message: t('menuKeyPlaceholder'), trigger: 'blur' },
{
{
validator: (rule: any, value: string, callback: any) => {
if(!validataMenuKey(value)){
if (!validataMenuKey(value)) {
callback(new Error(t('menuKeyValidata')))
}
callback()
},
trigger: 'blur'
@ -188,7 +188,7 @@ const emit = defineEmits(['complete'])
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
const save = formData.id ? updateMenu : addMenu
const save = formData.id ? editMenu : addMenu
await formEl.validate(async (valid, fields) => {
if (valid) {
@ -219,7 +219,7 @@ const setFormData = async (row: any = null) => {
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
}else{
} else {
Object.keys(formData).forEach((key: string) => {
if (row[key] != undefined) formData[key] = row[key]
})

View File

@ -1,7 +1,7 @@
<template>
<el-dialog v-model="showDialog" :title="popTitle" width="500px"
:destroy-on-close="true">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-dialog v-model="showDialog" :title="popTitle" width="500px" :destroy-on-close="true">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form"
v-loading="loading">
<el-form-item :label="t('roleName')" prop="role_name">
<el-input v-model="formData.role_name" :placeholder="t('roleNamePlaceholder')" clearable
:disabled="formData.uid" class="input-width" maxlength="10" :show-word-limit="true" />
@ -42,12 +42,12 @@
import { ref, reactive, computed, watch, toRaw } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { addRole, updateRole, getRoleInfo, getSiteMenus } from '@/api/sys'
import { addRole, editRole, getRoleInfo, getSiteMenus } from '@/api/sys'
import { debounce } from '@/utils/common'
const showDialog = ref(false)
const loading = ref(false)
let popTitle:string = '';
let popTitle: string = '';
//
const menus = ref<Record<string, any>[]>([])
@ -110,7 +110,7 @@ const emit = defineEmits(['complete'])
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
const save = formData.role_id ? updateRole : addRole
const save = formData.role_id ? editRole : addRole
await formEl.validate(async (valid) => {
if (valid) {
@ -134,7 +134,7 @@ const setFormData = async (row: any = null) => {
loading.value = true
selectAll.value = false
Object.assign(formData, initialFormData)
popTitle = t('addRole')
popTitle = t('addRole')
if (row) {
popTitle = t('updateRole')
const data = await (await getRoleInfo(row.role_id)).data

View File

@ -1,7 +1,7 @@
<template>
<el-dialog v-model="showDialog" :title="popTitle" width="500px"
:destroy-on-close="true">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-dialog v-model="showDialog" :title="popTitle" width="500px" :destroy-on-close="true">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form"
v-loading="loading">
<el-form-item :label="t('accountNumber')" prop="username">
<el-input v-model="formData.username" :placeholder="t('accountNumberPlaceholder')" clearable
:disabled="formData.uid" class="input-width" maxlength="10" show-word-limit />
@ -35,8 +35,8 @@
<el-form-item :label="t('status')">
<el-radio-group v-model="formData.status">
<el-radio :label="1">{{ t('statusNormal') }}</el-radio>
<el-radio :label="0">{{ t('statusDeactivate') }}</el-radio>
<el-radio :label="1">{{ t('statusUnlock') }}</el-radio>
<el-radio :label="0">{{ t('lock') }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
@ -56,12 +56,12 @@
import { ref, reactive, computed } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { addUser, updateUser, getUserInfo } from '@/api/site'
import { addUser, editUser, getUserInfo } from '@/api/site'
import { allRole } from '@/api/sys'
const showDialog = ref(false)
const loading = ref(false)
let popTitle:string = '';
let popTitle: string = '';
/**
* 表单数据
@ -75,7 +75,7 @@ const initialFormData = {
confirm_password: '',
status: 1,
role_ids: [],
userrole:{}
userrole: {}
}
const formData: Record<string, any> = reactive({ ...initialFormData })
@ -115,6 +115,9 @@ const emit = defineEmits(['complete'])
const roles = ref<Record<string, any>>([])
allRole().then(res => {
roles.value = res.data
roles.value.forEach(element => {
element.role_id = element.role_id.toString()
});
})
/**
@ -123,7 +126,7 @@ allRole().then(res => {
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
const save = formData.uid ? updateUser : addUser
const save = formData.uid ? editUser : addUser
await formEl.validate(async (valid) => {
if (valid) {
@ -147,7 +150,7 @@ const setFormData = async (row: any = null) => {
loading.value = true
Object.assign(formData, initialFormData)
popTitle = t('addUser')
if (row) {
popTitle = t('updateUser')
const data = await (await getUserInfo(row.uid)).data

View File

@ -31,9 +31,9 @@
</el-table-column>
<el-table-column :label="t('status')" min-width="120" align="center">
<template #default="{ row }">
<el-tag class="ml-2" type="success" v-if="row.status == 1">{{ t('statusNormal') }}</el-tag>
<el-tag class="ml-2" type="success" v-if="row.status == 1">{{ t('statusUnlock') }}</el-tag>
<el-tag class="ml-2" type="error" v-if="row.status == 0">{{
t('statusDeactivate')
t('statusLock')
}}</el-tag>
</template>
</el-table-column>

View File

@ -36,21 +36,21 @@
<el-form-item :label="t('publicKey')">
<div class="input-width">
<upload-file v-model="formData.public_key_crt" api="upload/cert/aliapp" />
<upload-file v-model="formData.public_key_crt" api="sys/document/aliyun" />
</div>
<div class="form-tip">{{ t('publicKeyTips') }}</div>
</el-form-item>
<el-form-item :label="t('alipayPublicKey')">
<div class="input-width">
<upload-file v-model="formData.alipay_public_key_crt" api="upload/cert/aliapp" />
<upload-file v-model="formData.alipay_public_key_crt" api="sys/document/aliyun" />
</div>
<div class="form-tip">{{ t('alipayPublicKeyTips') }}</div>
</el-form-item>
<el-form-item :label="t('alipayWithCrt')">
<div class="input-width">
<upload-file v-model="formData.alipay_with_crt" api="upload/cert/aliapp" />
<upload-file v-model="formData.alipay_with_crt" api="sys/document/aliyun" />
</div>
<div class="form-tip">{{ t('alipayWithCrtTips') }}</div>
</el-form-item>

View File

@ -18,6 +18,7 @@
<div class="cursor-pointer" @click="copyEvent(formData.request_url)">{{ t('copy') }}</div>
</template>
</el-input>
<span class="ml-2 cursor-pointer visit-btn" @click="visitFn">{{t('clickVisit')}}</span>
</el-form-item>
</el-card>
@ -35,7 +36,7 @@
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { setH5Config, getH5Config } from '@/api/h5'
import { getSceneDomain } from '@/api/sys'
import { getUrl } from '@/api/sys'
import { useClipboard } from '@vueuse/core'
import { ElMessage, FormInstance } from 'element-plus'
@ -60,8 +61,8 @@ getH5Config().then(res => {
/**
* 获取h5域名
*/
getSceneDomain().then(res => {
formData.request_url = res.data.wap_domain
getUrl().then(res => {
formData.request_url = res.data.wap_url
})
@ -89,6 +90,11 @@ watch(copied, () => {
}
})
// 访
const visitFn = ()=>{
window.open(formData.request_url);
}
/**
* 保存
*/
@ -111,4 +117,8 @@ const save = async (formEl: FormInstance | undefined) => {
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.visit-btn{
color:var(--el-color-primary);
}
</style>

View File

@ -1,12 +1,21 @@
<template>
<div class="main-container">
<el-form label-width="120px" ref="formRef" class="page-form">
<div class="main-container" v-loading="loading">
<el-form :model="formData" label-width="120px" ref="formRef" class="page-form">
<el-card class="box-card !border-none" shadow="never">
<h3 class="panel-title">{{ t('pcInfo') }}</h3>
<el-form-item :label="t('preview')" prop="weapp_name">
<img class="w-[1010px]" src="@/assets/images/channel/preview.png" alt="">
<img class="w-[500px]" src="@/assets/images/channel/preview.png" alt="">
</el-form-item>
<el-form-item :label="t('PCDomainName')">
<el-input :model-value="formData.request_url" class="input-width" :readonly="true">
<template #append>
<div class="cursor-pointer" @click="copyEvent(formData.request_url)">{{ t('copy') }}</div>
</template>
</el-input>
<span class="ml-2 cursor-pointer visit-btn" @click="visitFn">{{t('clickVisit')}}</span>
</el-form-item>
</el-card>
@ -15,9 +24,65 @@
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { getUrl } from '@/api/sys'
import { useClipboard } from '@vueuse/core'
import { ElMessage, FormInstance, FormRules } from 'element-plus'
import { useRouter } from 'vue-router'
const loading = ref(true)
const formData = reactive<Record<string, string | boolean>>({
is_open: false,
request_url: ''
})
const formRef = ref<FormInstance>()
const router = useRouter()
/**
* 获取pc域名
*/
getUrl().then(res => {
formData.request_url = res.data.web_url;
loading.value = false;
})
/**
* 复制
*/
const { copy, isSupported, copied } = useClipboard()
const copyEvent = (text: string) => {
if (!isSupported.value) {
ElMessage({
message: t('notSupportCopy'),
type: 'warning'
})
return
}
copy(text)
}
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t('copySuccess'),
type: 'success'
})
}
})
// 访
const visitFn = ()=>{
window.open(formData.request_url);
}
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.visit-btn{
color:var(--el-color-primary);
}
</style>

View File

@ -3,7 +3,7 @@
<el-card class="box-card !border-none" shadow="never">
<el-alert class="warm-prompt" type="info">
<template #default>
<p class="text-base">{{t('operationTip')}} 1{{ t('operationTipOne') }}</p>
<p class="text-base">{{ t('operationTip') }} 1{{ t('operationTipOne') }}</p>
<p class="text-base">2{{ t('operationTipTwo') }}</p>
</template>
</el-alert>
@ -51,7 +51,7 @@
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getTemplateList, getBatchAcquisition } from '@/api/weapp'
import { updateMessageStatus } from '@/api/message'
import { editMessageStatus } from '@/api/notice'
import { ElLoading } from 'element-plus'
const cronTableData = reactive({
@ -105,7 +105,7 @@ const infoSwitch = (res) => {
data.value.key = res.key
data.value.type = 'weapp'
cronTableData.loading = true
updateMessageStatus(data.value).then(res => {
editMessageStatus(data.value).then(res => {
loadCronList()
}).catch(() => {
cronTableData.loading = false
@ -115,13 +115,15 @@ const infoSwitch = (res) => {
</script>
<style lang="scss" scoped>
::v-deep .warm-prompt{
background-color: var(--el-color-primary-light-9);
}
::v-deep .warm-prompt .el-icon{
color: var(--el-color-primary);
}
::v-deep .warm-prompt p{
color: var(--el-color-primary);
}
::v-deep .warm-prompt {
background-color: var(--el-color-primary-light-9);
}
::v-deep .warm-prompt .el-icon {
color: var(--el-color-primary);
}
::v-deep .warm-prompt p {
color: var(--el-color-primary);
}
</style>

View File

@ -129,7 +129,7 @@
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { getWechatConfig, getWechatStatic, updateWechatConfig } from '@/api/wechat'
import { getWechatConfig, getWechatStatic, editWechatConfig } from '@/api/wechat'
import { useClipboard } from '@vueuse/core'
import { ElMessage, FormInstance, FormRules } from 'element-plus'
@ -223,7 +223,7 @@ const save = async (formEl: FormInstance | undefined) => {
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
updateWechatConfig(formData).then(() => {
editWechatConfig(formData).then(() => {
loading.value = false
}).catch(() => {
loading.value = false

View File

@ -82,7 +82,7 @@
import { ref } from 'vue'
import { t } from '@/lang'
import { ElMessageBox, ElMessage } from 'element-plus'
import { getWechatMenu, updateWechatMenu } from '@/api/wechat'
import { getWechatMenu, editWechatMenu } from '@/api/wechat'
import menuForm from './components/menu-form.vue'
const loading = ref(true)
@ -199,7 +199,7 @@ const save = async () => {
}
if (loading.value) return
loading.value = true
updateWechatMenu({ button: button.value }).then(() => {
editWechatMenu({ button: button.value }).then(() => {
loading.value = false
}).catch(() => {
loading.value = false

View File

@ -3,7 +3,7 @@
<el-card class="box-card !border-none" shadow="never">
<el-alert class="warm-prompt" type="info">
<template #default>
<p class="text-base">{{t('operationTip')}} 1{{ t('operationTipOne') }}</p>
<p class="text-base">{{ t('operationTip') }} 1{{ t('operationTipOne') }}</p>
<p class="text-base">2{{ t('operationTipTwo') }}</p>
<p class="text-base">3{{ t('operationTipThree') }}</p>
<p class="text-base">4{{ t('operationTipFour') }}</p>
@ -58,7 +58,7 @@
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getTemplateList, getBatchAcquisition } from '@/api/wechat'
import { updateMessageStatus } from '@/api/message'
import { editMessageStatus } from '@/api/notice'
import { AnyObject } from '@/types/global'
import { ElLoading } from 'element-plus'
@ -114,7 +114,7 @@ const infoSwitch = (res: AnyObject) => {
data.value.key = res.key
data.value.type = 'wechat'
cronTableData.loading = true
updateMessageStatus(data.value).then(res => {
editMessageStatus(data.value).then(res => {
loadCronList()
}).catch(() => {
cronTableData.loading = false
@ -123,13 +123,15 @@ const infoSwitch = (res: AnyObject) => {
</script>
<style lang="scss" scoped>
::v-deep .warm-prompt{
background-color: var(--el-color-primary-light-9);
}
::v-deep .warm-prompt .el-icon{
color: var(--el-color-primary);
}
::v-deep .warm-prompt p{
color: var(--el-color-primary);
}
::v-deep .warm-prompt {
background-color: var(--el-color-primary-light-9);
}
::v-deep .warm-prompt .el-icon {
color: var(--el-color-primary);
}
::v-deep .warm-prompt p {
color: var(--el-color-primary);
}
</style>

View File

@ -1,365 +1,374 @@
<template>
<div class="main-container flex-1">
<el-header class="flex items-center h-[60px] bg-primary px-[20px]">
<div class="text-white cursor-pointer flex items-center" @click="goBack">
<el-icon size="14">
<ArrowLeft />
</el-icon>
<span class="pl-[5px]">{{ t('back') }}</span>
</div>
<div class="text-white ml-[10px] flex items-center">
<span class="mr-[5px]"> {{ t('decorating') }}{{ diyStore.typeName }}</span>
<!-- <el-icon class="font-bold"><EditPen /></el-icon>-->
</div>
<div class="flex-1"></div>
<el-button @click="save()">{{ t('save') }}</el-button>
</el-header>
<div class="main-container flex-1">
<el-header class="flex items-center h-[60px] bg-primary px-[20px]">
<div class="text-white cursor-pointer flex items-center" @click="goBack">
<el-icon size="14"><ArrowLeft /></el-icon>
<span class="pl-[5px]">{{ t('back') }}</span>
</div>
<div class="text-white ml-[10px] flex items-center">
<span class="mr-[5px]"> {{ t('decorating') }}{{ diyStore.typeName }}</span>
<!-- <el-icon class="font-bold"><EditPen /></el-icon>-->
</div>
<div class="flex-1"></div>
<el-button @click="save()">{{ t('save') }}</el-button>
</el-header>
<div class="full-container flex flex-row flex-1 bg-page">
<div class="full-container flex flex-row flex-1 bg-page">
<div class="component-list w-[290px]">
<div class="component-list w-[290px]">
<!-- 组件列表区域 -->
<el-scrollbar class="px-[10px]">
<el-collapse v-model="activeNames" @change="handleChange">
<el-collapse-item v-for="(item, key) in component" :key="key" :title="item.title" :name="key">
<ul class="flex flex-row flex-wrap">
<li v-for="(compItem, compKey) in item.list" :key="compKey"
class="w-2/6 text-center cursor-pointer h-[75px]" :title="compItem.title"
@click="diyStore.addComponent(compKey, compItem)">
<icon :name="compItem.icon" size="23px" />
<span class="block text-base truncate">{{ compItem.title }}</span>
</li>
</ul>
</el-collapse-item>
</el-collapse>
</el-scrollbar>
<!-- 组件列表区域 -->
<el-scrollbar class="px-[10px]">
<el-collapse v-model="activeNames" @change="handleChange">
<el-collapse-item v-for="(item,index) in component" :key="index" :title="item.type_name" :name="item.type">
<ul class="flex flex-row flex-wrap">
<li v-for="(compItem,compIndex) in item.list" :key="compIndex" class="w-2/6 text-center cursor-pointer h-[75px]" :title="compItem.title" @click="diyStore.addComponent(compItem)">
<icon :name="compItem.icon" size="23px"/>
<span class="block text-base truncate">{{compItem.title}}</span>
</li>
</ul>
</el-collapse-item>
</el-collapse>
</el-scrollbar>
</div>
</div>
<div class="preview-wrap flex-1 relative mt-[20px]">
<div class="preview-wrap flex-1 relative mt-[20px]">
<el-scrollbar>
<el-button class="page-btn absolute right-[20px]" @click="diyStore.changeCurrentIndex(-99)">{{
t('pageSet') }}</el-button>
<div class="diy-view-wrap w-[375px] shadow-lg mx-auto">
<div class="preview-head bg-no-repeat bg-center bg-cover" @click="diyStore.changeCurrentIndex(-99)">
<span class="text-base block text-center truncate cursor-pointer h-[64px] leading-[84px]">{{
diyStore.global.title }}</span>
</div>
<div class="preview-block relative">
<el-scrollbar>
<el-button class="page-btn absolute right-[20px]" @click="diyStore.changeCurrentIndex(-99)">{{ t('pageSet')}}</el-button>
<div class="diy-view-wrap w-[375px] shadow-lg mx-auto">
<div class="preview-head bg-no-repeat bg-center bg-cover" @click="diyStore.changeCurrentIndex(-99)">
<span class="text-base block text-center truncate cursor-pointer h-[64px] leading-[84px]">{{ diyStore.global.title }}</span>
</div>
<div class="preview-block relative">
<ul
class="quick-action absolute text-center -right-[70px] top-[20px] w-[42px] rounded shadow-md">
<el-tooltip effect="light" :content="t('moveUpComponent')" placement="right">
<icon name="iconfont-iconjiantoushang" size="20px"
class="block cursor-pointer leading-[40px]" @click="diyStore.moveUpComponent" />
</el-tooltip>
<el-tooltip effect="light" :content="t('moveDownComponent')" placement="right">
<icon name="iconfont-iconjiantouxia" size="20px"
class="block cursor-pointer leading-[40px]" @click="diyStore.moveDownComponent" />
</el-tooltip>
<el-tooltip effect="light" :content="t('copyComponent')" placement="right">
<icon name="iconfont-iconcopy-line" size="20px"
class="block cursor-pointer leading-[40px]" @click="diyStore.copyComponent" />
</el-tooltip>
<el-tooltip effect="light" :content="t('delComponent')" placement="right">
<icon name="iconfont-icondelete-line" size="20px"
class="block cursor-pointer leading-[40px]" @click="diyStore.delComponent" />
</el-tooltip>
<el-tooltip effect="light" :content="t('resetComponent')" placement="right">
<icon name="iconfont-iconloader-line" size="20px"
class="block cursor-pointer leading-[40px]" @click="diyStore.resetComponent" />
</el-tooltip>
</ul>
<ul class="quick-action absolute text-center -right-[70px] top-[20px] w-[42px] rounded shadow-md">
<el-tooltip effect="light" :content="t('moveUpComponent')" placement="right">
<icon name="iconfont-iconjiantoushang" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.moveUpComponent" />
</el-tooltip>
<el-tooltip effect="light" :content="t('moveDownComponent')" placement="right">
<icon name="iconfont-iconjiantouxia" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.moveDownComponent" />
</el-tooltip>
<el-tooltip effect="light" :content="t('copyComponent')" placement="right">
<icon name="iconfont-iconcopy-line" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.copyComponent" />
</el-tooltip>
<el-tooltip effect="light" :content="t('delComponent')" placement="right">
<icon name="iconfont-icondelete-line" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.delComponent"/>
</el-tooltip>
<el-tooltip effect="light" :content="t('resetComponent')" placement="right">
<icon name="iconfont-iconloader-line" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.resetComponent" />
</el-tooltip>
</ul>
<!-- 组件预览渲染区域 -->
<iframe id="previewIframe" v-show="wapDomain" :src="wapDomain" frameborder="0"
class="preview-iframe w-[375px]"></iframe>
<!-- 组件预览渲染区域 -->
<iframe id="previewIframe" v-show="wapDomain" :src="wapDomain" frameborder="0" class="preview-iframe w-[375px]"></iframe>
</div>
</div>
</el-scrollbar>
</div>
</div>
</el-scrollbar>
</div>
</div>
<div class="edit-attribute-wrap w-[400px]">
<div class="edit-attribute-wrap w-[400px]">
<!-- 编辑组件属性区域 -->
<el-scrollbar>
<el-card class="box-card" shadow="never">
<template #header>
<div class="card-header flex justify-between items-center">
<span class="title flex-1">{{ diyStore.currentIndex == -99 ? t('pageSet') :
diyStore.editComponent.componentTitle }}</span>
</div>
</template>
<component :is="modules[diyStore.currentComponent]"
:value="diyStore.value[diyStore.currentIndex]" />
</el-card>
</el-scrollbar>
<!-- 编辑组件属性区域 -->
<el-scrollbar>
<el-card class="box-card" shadow="never">
<template #header>
<div class="card-header flex justify-between items-center">
<span class="title flex-1">{{ diyStore.currentIndex == -99 ? t('pageSet') : diyStore.editComponent.componentTitle }}</span>
</div>
</template>
<component :is="modules[diyStore.currentComponent]" :value="diyStore.value[diyStore.currentIndex]" />
</el-card>
</el-scrollbar>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref, reactive, toRaw, onMounted, watch} from 'vue'
import {t} from '@/lang'
import { addDiyPage, updateDiyPage,initPage} from '@/api/diy';
import {useRoute,useRouter} from 'vue-router'
import { cloneDeep, range, isEmpty } from 'lodash-es'
import { ElMessage, ElMessageBox } from 'element-plus'
import useDiyStore from '@/stores/modules/diy'
import { getWapDomain } from '@/utils/common'
import { ref, reactive, toRaw, onMounted, watch } from 'vue'
import { t } from '@/lang'
import { addDiyPage, editDiyPage, initPage } from '@/api/diy';
import { useRoute, useRouter } from 'vue-router'
import { cloneDeep, range, isEmpty } from 'lodash-es'
import { ElMessage, ElMessageBox } from 'element-plus'
import useDiyStore from '@/stores/modules/diy'
const diyStore = useDiyStore()
const route = useRoute();
const router = useRouter()
const diyStore = useDiyStore()
const route = useRoute();
const router = useRouter()
const wapDomain = ref('')
const wapDomain = ref('')
const component = ref([])
const componentType:string[] = reactive([])
const component = ref([])
const componentType: string[] = reactive([])
const activeNames = ref(componentType)
const handleChange = (val: string[]) => {}
const activeNames = ref(componentType)
const handleChange = (val: string[]) => { }
route.query.id = route.query.id || 0;
route.query.name = route.query.name || '';
route.query.type = route.query.type || ''; //
route.query.title = route.query.title || '';
route.query.id = route.query.id || 0;
route.query.name = route.query.name || '';
route.query.type = route.query.type || ''; //
route.query.title = route.query.title || '';
//
const originData = reactive({
id: diyStore.id,
name: diyStore.name,
title: diyStore.global.title,
value: JSON.stringify({
global: toRaw(diyStore.global),
value: toRaw(diyStore.value)
})
})
//
const isChange = ref(true) // truefalse
const goBack = () => {
if (isChange.value) {
router.push('/diy/list');
} else {
//
ElMessageBox.confirm(
t('leavePageTitleTips'),
t('leavePageContentTips'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
autofocus: false
}
).then(() => {
router.push('/diy/list');
}).catch(() => {
})
}
}
//
const modulesFiles = import.meta.glob('./components/*.vue', { eager: true })
const modules = {}
for (const [key, value] of Object.entries(modulesFiles)) {
const moduleName = key.replace(/^\.\/(.*)\.\w+$/, '$1')
const name = moduleName.split('/')[1]
modules[name] = value.default
}
//
watch(
() => diyStore,
(newValue, oldValue) => {
let data = {
id: newValue.id,
name: newValue.name,
title: newValue.global.title,
value: JSON.stringify({
global: toRaw(newValue.global),
value: toRaw(newValue.value)
})
};
diyStore.postMessage();
isChange.value = JSON.stringify(data) == JSON.stringify(originData);
},
{ deep: true }
)
//
initPage({
id: route.query.id,
name: route.query.name,
type: route.query.type,
title: route.query.title
}).then(res => {
let data = res.data;
diyStore.id = data.id || 0;
diyStore.name = data.name;
diyStore.type = data.type;
diyStore.typeName = data.type_name;
if (data.value) {
let sources = JSON.parse(data.value);
diyStore.global = sources.global;
if (sources.value.length) {
diyStore.value = sources.value;
// diyStore.changeCurrentIndex(0,diyStore.value[0]);
}
} else {
diyStore.global.title = data.title;
}
//
const originData = reactive({
originData.id = diyStore.id;
originData.name = diyStore.name;
originData.title = diyStore.global.title;
originData.value = JSON.stringify({
global: toRaw(diyStore.global),
value: toRaw(diyStore.value)
});
component.value = data.component
for (let type in component.value) {
componentType.push(type)
for (let key in component.value[type].list) {
let com = cloneDeep(component.value[type].list[key]);
com.id = diyStore.generateRandom();
com.componentName = key;
com.componentTitle = com.title;
com.maxCount = com.max_count;
Object.assign(com, com.value);
delete com.name;
delete com.title;
delete com.value;
delete com.type;
delete com.icon;
delete com.max_count;
diyStore.components.push(com)
}
}
wapDomain.value = `${data.domain_url.wap_url}/${data.page}?mode=decorate`; // decorate 访
})
onMounted(() => {
// uniapp iframe
window.previewIframe = document.getElementById('previewIframe')
})
// uni-app
window.addEventListener('message', (event) => {
try {
let data = JSON.parse(event.data);
if (!data.type) return;
switch (data.type) {
case 'init':
// uniapp
diyStore.load = true;
diyStore.postMessage();
break;
case 'change':
//
diyStore.changeCurrentIndex(data.index, data.component);
break;
case 'data':
//
diyStore.changeCurrentIndex(data.index, data.component);
diyStore.global = data.global;
diyStore.value = data.value;
break;
}
} catch (e) {
console.log('后台接受数据错误', e)
}
}, false);
const loading = ref(false)
const save = () => {
if (!diyStore.verify()) {
return;
}
if (loading.value) return
loading.value = true
let data = {
id: diyStore.id,
name: diyStore.name,
title: diyStore.global.title,
type: diyStore.type,
value: JSON.stringify({
global: toRaw(diyStore.global),
value: toRaw(diyStore.value)
})
})
};
//
const isChange = ref(true) // truefalse
const goBack = () => {
if(isChange.value){
router.push('/diy/list');
}else{
//
ElMessageBox.confirm(
t('leavePageTitleTips'),
t('leavePageContentTips'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
autofocus: false
}
).then(() => {
router.push('/diy/list');
}).catch(() => {
})
}
}
//
const modulesFiles = import.meta.glob('./components/*.vue', {eager: true})
const modules = {}
for (const [key, value] of Object.entries(modulesFiles)) {
const moduleName = key.replace(/^\.\/(.*)\.\w+$/, '$1')
const name = moduleName.split('/')[1]
modules[name] = value.default
}
//
watch(
() => diyStore,
(newValue, oldValue) => {
let data = {
id: newValue.id,
name: newValue.name,
title: newValue.global.title,
value: JSON.stringify({
global: toRaw(newValue.global),
value: toRaw(newValue.value)
})
};
diyStore.postMessage();
isChange.value = JSON.stringify(data) == JSON.stringify(originData);
},
{ deep: true }
)
//
initPage({
id:route.query.id,
name:route.query.name,
type:route.query.type,
title:route.query.title
}).then(res=> {
const save = diyStore.id ? editDiyPage : addDiyPage
save(data).then((res: any) => {
loading.value = false
if (res.code == 200) {
let data = res.data;
diyStore.id = data.id || 0;
diyStore.name = data.name;
diyStore.type = data.type;
diyStore.typeName = data.type_name;
if (data.value) {
let sources = JSON.parse(data.value);
diyStore.global = sources.global;
if(sources.value.length) {
diyStore.value = sources.value;
// diyStore.changeCurrentIndex(0,diyStore.value[0]);
}
if (diyStore.id) {
loading.value = false //
} else {
diyStore.global.title = data.title;
router.push('/diy/list');
}
//
originData.id = diyStore.id;
originData.name = diyStore.name;
originData.title = diyStore.global.title;
originData.value = JSON.stringify({
global: toRaw(diyStore.global),
value: toRaw(diyStore.value)
});
component.value = data.component
component.value.forEach((item: any) => {
componentType.push(item.type)
item.list.forEach((comp: any) => {
let com = cloneDeep(comp);
com.id = diyStore.generateRandom();
com.componentName = com.name;
com.componentTitle = com.title;
com.maxCount = com.max_count;
Object.assign(com, com.value);
delete com.name;
delete com.title;
delete com.value;
delete com.type;
delete com.icon;
delete com.max_count;
diyStore.components.push(com)
})
})
wapDomain.value = `${getWapDomain()}/${data.page}?mode=decorate`; // decorate 访
}
}).catch(err => {
loading.value = false
})
onMounted(() => {
// uniapp iframe
window.previewIframe = document.getElementById('previewIframe')
})
// uni-app
window.addEventListener('message', (event) => {
try {
let data = JSON.parse(event.data);
if (!data.type) return;
switch (data.type) {
case 'init':
// uniapp
diyStore.load = true;
diyStore.postMessage();
break;
case 'change':
//
diyStore.changeCurrentIndex(data.index, data.component);
break;
case 'data':
//
diyStore.changeCurrentIndex(data.index, data.component);
diyStore.global = data.global;
diyStore.value = data.value;
break;
}
} catch (e) {
console.log('后台接受数据错误',e)
}
}, false);
const loading = ref(false)
const save = ()=> {
if (!diyStore.verify()) {
return;
}
if (loading.value) return
loading.value = true
let data = {
id: diyStore.id,
name: diyStore.name,
title: diyStore.global.title,
type: diyStore.type,
value: JSON.stringify({
global: toRaw(diyStore.global),
value: toRaw(diyStore.value)
})
};
const save = diyStore.id ? updateDiyPage : addDiyPage
save(data).then((res: any) => {
loading.value = false
if (res.code == 200) {
if (diyStore.id) {
loading.value = false //
} else {
router.push('/diy/list');
}
}
}).catch(err => {
loading.value = false
})
}
}
</script>
<style lang="scss">
.el-collapse-item__wrap {
border-bottom: none;
}
.el-collapse-item__wrap {
border-bottom: none;
}
.el-collapse-item__content {
padding-bottom: 0;
}
.el-collapse-item__content {
padding-bottom: 0;
}
.el-collapse-item__header {
font-size: var(--el-font-size-base);
}
.el-collapse-item__header {
font-size: var(--el-font-size-base);
}
</style>
<style lang="scss" scoped>
.full-container {
height: calc(100vh - 60px);
}
.full-container {
height: calc(100vh - 60px);
}
.preview-iframe{
height: calc(100vh - 160px);
}
.preview-iframe {
height: calc(100vh - 160px);
}
.component-list {
background: var(--el-bg-color);
}
.component-list {
background: var(--el-bg-color);
}
.component-list ul li {
&:not(.disabled):hover {
color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
}
}
.component-list ul li {
&:not(.disabled):hover {
color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
}
}
.diy-view-wrap {
background: var(--el-bg-color-page);
}
.diy-view-wrap {
background: var(--el-bg-color-page);
}
.diy-view-wrap .preview-head {
background-image: url(assets/images/diy_preview_head.png);
background-color: var(--el-bg-color);
}
.diy-view-wrap .preview-head {
background-image: url(assets/images/diy_preview_head.png);
background-color: var(--el-bg-color);
}
.quick-action {
background: var(--el-bg-color);
}
.quick-action {
background: var(--el-bg-color);
}
.edit-attribute-wrap {
background: var(--el-bg-color);
}
.edit-attribute-wrap {
background: var(--el-bg-color);
}
.edit-attribute-wrap .box-card {
border: none;
}
</style>
.edit-attribute-wrap .box-card {
border: none;
}</style>

View File

@ -49,18 +49,21 @@
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 {getWapDomain} from '@/utils/common'
import QRCode from "qrcode";
const wapDomain = ref(getWapDomain() + '/pages/index/index')
const wapDomain = ref('')
const wapImage = ref('')
QRCode.toDataURL(wapDomain.value, {errorCorrectionLevel: 'L', margin: 0, width: 100}).then(url => {
wapImage.value = url
})
getUrl().then((res:any)=>{
wapDomain.value = res.data.wap_url + '/pages/index/index'
QRCode.toDataURL(wapDomain.value, {errorCorrectionLevel: 'L', margin: 0, width: 100}).then(url => {
wapImage.value = url
})
});
const router = useRouter()
@ -84,6 +87,7 @@
path: '/decorate/edit',
query: {name: 'DIY_INDEX'}
});
const toDecorate = () => {
router.push('/decorate/edit?name=DIY_INDEX')
// window.open(url.href);

View File

@ -1,465 +1,507 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex">
<el-button type="primary" @click="dialogVisible=true">
{{ t('addDiyPage') }}
</el-button>
</div>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex">
<el-button type="primary" @click="dialogVisible = true">
{{ t('addDiyPage') }}
</el-button>
</div>
<el-card class="box-card !border-none my-[16px] table-search-wrap" shadow="never">
<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="searchFormDiyRouteRef?.resetFields()">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
<el-form :inline="true" :model="diyPageTableData.searchParam" ref="searchFormDiyPageRef" v-if="tabValue == 'diy'">
<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 in pageType" :label="item.type_name" :value="item.type" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadDiyPageList()">{{ t('search') }}</el-button>
<el-button @click="searchFormDiyPageRef?.resetFields()">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="box-card !border-none my-[16px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="diyPageTableData.searchParam" ref="searchFormDiyPageRef"
v-if="tabValue == 'diy'">
<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="searchFormDiyPageRef?.resetFields()">{{ 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="searchFormDiyRouteRef?.resetFields()">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[16px]">
<div class="mt-[16px]">
<el-tabs v-model="tabValue" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane :label="t('diyPage')" name="diy">
<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="120">
<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" min-width="120">
<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-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="120"/>
<el-table-column prop="page" :label="t('wapUrl')" min-width="120">
<template #default="{ row }">
<span class="mr-[10px]">{{ getWapDomain() + row.page }}</span>
<el-button type="primary" link @click="copyEvent(getWapDomain() + 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>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="diyRouteTableData.page" v-model:page-size="diyRouteTableData.limit" layout="total, sizes, prev, pager, next, jumper" :total="diyRouteTableData.total" @size-change="loadDiyPageList()" @current-change="loadDiyPageList"/>
</div>
</el-tab-pane>
</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="120" />
<el-table-column prop="page" :label="t('wapUrl')" min-width="120">
<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-tabs>
</div>
</div>
</el-card>
</el-card>
<!--添加页面-->
<el-dialog v-model="dialogVisible" :title="t('addPageTips')" width="20%">
<!--添加页面-->
<el-dialog v-model="dialogVisible" :title="t('addPageTips')" width="20%">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules">
<el-form-item :label="t('typeName')" prop="type">
<el-select v-model="formData.type" :placeholder="t('pageTypePlaceholder')">
<el-option v-for="item in pageType" :label="item.type_name" :value="item.type" />
</el-select>
</el-form-item>
<el-form-item :label="t('title')" prop="title">
<el-input v-model="formData.title" :placeholder="t('titlePlaceholder')" clearable maxlength="12" show-word-limit/>
</el-form-item>
</el-form>
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules">
<el-form-item :label="t('typeName')" prop="type">
<el-select v-model="formData.type" :placeholder="t('pageTypePlaceholder')">
<el-option v-for="(item, key) in pageType" :label="item.title" :value="key" />
</el-select>
</el-form-item>
<el-form-item :label="t('title')" prop="title">
<el-input v-model="formData.title" :placeholder="t('titlePlaceholder')" clearable maxlength="12"
show-word-limit />
</el-form-item>
</el-form>
<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 #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>
<!-- 分享设置-->
<el-dialog v-model="shareDialogVisible" :title="t('shareSet')" width="30%">
<!-- 分享设置-->
<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-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>
<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>
<!-- 推广-->
<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="wapDomain">
<template #append>
<el-button @click="copyEvent(wapDomain)" 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-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-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>
</el-dialog>
</div>
</div>
</template>
<script lang="ts" setup>
import {reactive, ref,watch,computed} from 'vue'
import {t} from '@/lang'
import {getDiyPageList, deleteDiyPage, setUseDiyPage,getDiyPageType,getDiyRouteList,updateDiyPageShare,updateDiyRouteShare} from '@/api/diy'
import {TabsPaneContext, ElMessage,ElMessageBox,FormInstance} from 'element-plus'
import {useRouter} from 'vue-router'
import {useClipboard} from '@vueuse/core'
import { getWapDomain } from '@/utils/common'
import QRCode from "qrcode";
import { reactive, ref, watch, computed } from 'vue'
import { t } from '@/lang'
import { getDiyPageList, deleteDiyPage, setUseDiyPage, getDiyPageType, getDiyRouteList, getDiyRouteInfo, editDiyPageShare, editDiyRouteShare } from '@/api/diy'
import { TabsPaneContext, ElMessage, ElMessageBox, FormInstance } from 'element-plus'
import { useRouter } from 'vue-router'
import { useClipboard } from '@vueuse/core'
import QRCode from "qrcode";
import { getUrl } from '@/api/sys'
const pageType:any = reactive([])
const pageType: any = reactive({})
const router = useRouter()
const router = useRouter()
//
const formData = reactive({
type: '',
title: ''
})
//
const formRules = computed(() => {
return {
type: [
{ required: true, message: t('pageTypePlaceholder'), trigger: 'blur' },
],
title: [
{ required: true, message: t('titlePlaceholder'), trigger: 'blur' },
]
//
const formData = reactive({
type: '',
title: ''
})
//
const formRules = computed(() => {
return {
type: [
{ required: true, message: t('pageTypePlaceholder'), trigger: 'blur' },
],
title: [
{ required: true, message: t('titlePlaceholder'), trigger: 'blur' },
]
}
})
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?type=' + formData.type + '&title=' + formData.title);
}
})
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?type=' + formData.type + '&title=' + formData.title);
}
})
let diyRouteTableData = reactive({
loading: true,
data: [],
searchParam: {
"title": "",
}
})
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()
//
getDiyPageType({}).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": "",
"type": '',
}
})
const tabValue = ref('diy')
const handleClick = (tab: TabsPaneContext, event: Event) => {
tabValue.value = tab.props.name;
if (tabValue.value == 'diy') {
loadDiyPageList()
} else {
loadDiyRouteList()
}
let diyRouteTableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam:{
"title":"",
}
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',
}
})
/**
* 获取自定义路由列表
*/
const loadDiyRouteList = (page: number = 1) => {
diyRouteTableData.loading = true
diyRouteTableData.page = page
getDiyRouteList({
page: diyRouteTableData.page,
limit: diyRouteTableData.limit,
...diyRouteTableData.searchParam
}).then(res => {
diyRouteTableData.loading = false
diyRouteTableData.data = res.data.data
diyRouteTableData.total = res.data.total
}).catch(() => {
diyRouteTableData.loading = false
})
}
loadDiyRouteList()
//
getDiyPageType({}).then(res=>{
for (let item in res.data){
pageType.push(res.data[item])
}
})
let diyPageTableData:any = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
"title": "",
"type": '',
}
})
const tabValue = ref('diy')
const handleClick = (tab: TabsPaneContext, event: Event) => {
tabValue.value = tab.props.name;
if(tabValue.value == 'diy'){
).then(() => {
deleteDiyPage(id).then(() => {
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
})
})
}
// 使
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)
}
loadDiyPageList()
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t('copySuccess'),
type: 'success'
})
}
})
//
const editEvent = (data: any) => {
let url = router.resolve({
path: '/decorate/edit',
query: {id: data.id}
});
window.open(url.href);
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 {
// title: [
// { required: true, message: t('shareTitlePlaceholder'), trigger: 'blur' },
// // {
// // validator: (rule: any, value: string, callback: any) => {
// // console.log('validator',value,tabShareType)
// // // let content = value.replace(/<[^<>]+>/g, "").replace(/&nbsp;/gi, "")
// // // if(!content && value.indexOf('img') === -1){
// // // callback(new Error(t('shareTitlePlaceholder')))
// // // }else callback()
// // },
// // trigger: ['blur', 'change']
// // }
// ],
}
})
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
}
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;
}
//
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('diyPageDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}
).then(() => {
deleteDiyPage(id).then(() => {
loadDiyPageList()
shareDialogVisible.value = true;
}
const shareEvent = async (formEl: FormInstance | undefined) => {
if (!formEl) return
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()
}
shareDialogVisible.value = false;
}).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 shareFormData = reactive({
wechat:{
title: '',
desc: '',
url: ''
},
weapp:{
title: '',
url: ''
}
})
const shareDialogVisible = ref(false)
const shareFormRules = computed(() => {
return {
// title: [
// { required: true, message: t('shareTitlePlaceholder'), trigger: 'blur' },
// // {
// // validator: (rule: any, value: string, callback: any) => {
// // console.log('validator',value,tabShareType)
// // // let content = value.replace(/<[^<>]+>/g, "").replace(/&nbsp;/gi, "")
// // // if(!content && value.indexOf('img') === -1){
// // // callback(new Error(t('shareTitlePlaceholder')))
// // // }else callback()
// // },
// // trigger: ['blur', 'change']
// // }
// ],
}
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 shareFormRef = ref<FormInstance>()
const openShare = (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 shareEvent = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
let save = tabValue.value == 'diy' ? updateDiyPageShare : updateDiyRouteShare
save({
id:shareFormId.value,
share:JSON.stringify(shareFormData)
}).then(() => {
if(tabValue.value == 'diy'){
loadDiyPageList()
}else{
loadDiyRouteList()
}
shareDialogVisible.value = false;
}).catch(() => {
})
}
})
}
const promoteDialogVisible = ref(false)
const tabPromote = ref('wechat')
const wapDomain = ref('')
const wapImage = ref('')
const promoteEvent = (data:any)=>{
wapDomain.value = getWapDomain() + '/pages/index/diy?id=' + data.id;
QRCode.toDataURL(wapDomain.value, {errorCorrectionLevel: 'L', margin: 0, width: 100}).then(url => {
wapImage.value = url
})
promoteDialogVisible.value = true;
console.log('promoteEvent',data)
}
promoteDialogVisible.value = true;
console.log('promoteEvent', data)
}
</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

@ -5,9 +5,6 @@
<script lang="ts" setup>
import {ref} from 'vue'
import {useRouter} from 'vue-router'
import {getWapDomain} from '@/utils/common'
const wapDomain = ref(getWapDomain() + '/pages/member/index')
const router = useRouter()
const url = router.resolve({

View File

@ -1,16 +0,0 @@
<template>
<div class="error404">
<error code="403" :title="t('tips')" :show-btn="false">
<template #content>
<div class="flex justify-center">
<img class="w-[150px] h-[150px]" src="@/assets/images/no_perms.png" alt="" />
</div>
</template>
</error>
</div>
</template>
<script lang="ts" setup>
import error from './components/error.vue'
import { t } from '@/lang'
</script>

View File

@ -1,9 +1,56 @@
<template>
<div class="error404">
<error code="404" title="哎呀,出错了!您访问的页面不存在…"></error>
<div class="error">
<div>
<slot name="content">
<div class="error-code">404</div>
</slot>
<div class="text-lg text-tx-secondary mt-7 mb-7">页面不存在</div>
<div>
<el-button type="primary" @click="router.go(-1)">
{{ second }} 秒后返回上一页
</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import Error from './components/error.vue'
import { onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
let timer: any = null
const second = ref(5)
const router = useRouter()
timer = setInterval(() => {
if (second.value === 0) {
clearInterval(timer)
router.go(-1)
} else {
second.value--
}
}, 1000)
onUnmounted(() => {
timer && clearInterval(timer)
})
</script>
<style lang="scss" scoped>
.error {
text-align: center;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
.error-code {
@apply text-primary;
font-size: 150px;
}
.el-button {
width: 176px;
}
}
</style>

View File

@ -1,58 +0,0 @@
<template>
<div class="error">
<div>
<slot name="content">
<div class="error-code">{{ code }}</div>
</slot>
<div class="text-lg text-tx-secondary mt-7 mb-7">{{ title }}</div>
<el-button v-if="showBtn" type="primary" @click="router.go(-1)">
{{ second }} 秒后返回上一页
</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({
code: String,
title: String,
showBtn: {
type: Boolean,
default: true
}
})
let timer: any = null
const second = ref(5)
const router = useRouter()
props.showBtn &&
(timer = setInterval(() => {
if (second.value === 0) {
clearInterval(timer)
router.go(-1)
} else {
second.value--
}
}, 1000))
onUnmounted(() => {
timer && clearInterval(timer)
})
</script>
<style lang="scss" scoped>
.error {
text-align: center;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
.error-code {
@apply text-primary;
font-size: 150px;
}
.el-button {
width: 176px;
}
}
</style>

View File

@ -0,0 +1,332 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<el-card class="box-card !border-none my-[16px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="orderTableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('cashOutStatus')" prop="order_from">
<el-select v-model="orderTableData.searchParam.status" clearable class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item" :value="key" v-for="(item, key) in cashOutStatusList" />
</el-select>
</el-form-item>
<el-form-item :label="t('createTime')" prop="create_time">
<el-date-picker v-model="orderTableData.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="loadOrderList()">{{ t('search') }}</el-button>
<el-button @click="searchFormRef?.resetFields()">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[16px]">
<el-table :data="orderTableData.data" size="large" v-loading="orderTableData.loading">
<template #empty>
<span>{{ !orderTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="order_no" :show-overflow-tooltip="true" :label="t('memberInfo')" align="center" min-width="140">
<template #default="{ row }">
<div class="flex items-center cursor-pointer " @click="toMember(row.member_id)">
<img class="w-[50px] h-[50px] mr-[10px]" v-if="row.headimg" :src="img(row.headimg)" alt="" >
<img class="w-[50px] h-[50px] mr-[10px]" v-else src="@/assets/images/default_headimg.png" alt="" >
<div class="flex flex flex-col">
<span class="text-blue-700">{{ row.nickname || '' }}</span>
<span class="text-blue-700">{{ row.mobile || '' }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="account_type_name" :label="t('cashOutAccountType')" align="center" min-width="140" />
<el-table-column :label="t('cashOutMethod')" align="center" min-width="140" >
<template #default="{ row }">
{{ Transfertype[row.transfer_type].name }}
</template>
</el-table-column>
<el-table-column prop="apply_money" :label="t('applicationForWithdrawalAmount')" min-width="140" align="center" />
<!--
<el-table-column prop="service_money" :label="t('cashOutCommission')" align="center" min-width="140" />
-->
<el-table-column prop="money" :label="t('cashOutMoney')" min-width="200" align="center">
<template #default="{ row }">
<div>{{ t('actualTransferAmount') }} {{ row.money }}</div>
<div>{{ t('cashOutCommission') }} {{ row.service_money }}</div>
</template>
</el-table-column>
<el-table-column prop="status_name" :label="t('cashOutStatus')" align="center" min-width="100" />
<el-table-column :label="t('applyTime')" min-width="180" align="center">
<template #default="{ row }">
{{ row.create_time || '' }}
</template>
</el-table-column>
<el-table-column :label="t('operation')" fixed="right" width="230">
<template #default="{ row }">
<el-button v-for="(item,index) in operationBtn[row.status.toString()].value" :key="index+'a'" @click="fnProcessing(operationBtn[row.status.toString()].clickArr[index],row)" type="primary" link>{{ item }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="orderTableData.page" v-model:page-size="orderTableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="orderTableData.total"
@size-change="loadOrderList()" @current-change="loadOrderList" />
</div>
</div>
</el-card>
<!-- 详情 -->
<el-dialog v-model="cashOutShowDialog" :title="t('cashOutDetail')" width="500px" :destroy-on-close="true">
<el-form :model="cashOutInfo" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="cashOutLoading">
<el-form-item :label="t('nickname')">
<div class="input-width"> {{ cashOutInfo.nickname }} </div>
</el-form-item>
<el-form-item :label="t('cashOutAccountType')">
<div class="input-width"> {{ cashOutInfo.account_type_name }} </div>
</el-form-item>
<el-form-item :label="t('cashOutMethod')">
<div class="input-width"> {{ Transfertype[cashOutInfo.transfer_type].name }} </div>
</el-form-item>
<el-form-item :label="t('applicationForWithdrawalAmount')">
<div class="input-width"> {{ cashOutInfo.apply_money }} </div>
</el-form-item>
<el-form-item :label="t('cashOutCommission')">
<div class="input-width"> {{ cashOutInfo.service_money }} </div>
</el-form-item>
<el-form-item :label="t('actualTransferAmount')">
<div class="input-width"> {{ cashOutInfo.money }} </div>
</el-form-item>
<el-form-item :label="t('cashOutStatus')">
<div class="input-width"> {{ cashOutInfo.status_name }} </div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="cashOutShowDialog = false">{{t('confirm')}}</el-button>
</span>
</template>
</el-dialog>
<!-- 是否审核 -->
<el-dialog v-model="auditShowDialog" :title="t('rejectionAudit')" width="500px" :destroy-on-close="true">
<el-form :model="auditFailure" label-width="90px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form-item :label="t('reasonsRefusal')" prop="label_name">
<el-input v-model="auditFailure.refuse_reason" clearable :placeholder="t('reasonsRefusalPlaceholder')" class="input-width" type="textarea" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="auditShowDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{
t('confirm')
}}</el-button>
</span>
</template>
</el-dialog>
<!-- 是否转账 -->
<el-dialog v-model="transferShowDialog" :title="t('rejectionAudit')" width="500px" :destroy-on-close="true">
<p>{{t('isTransfer')}}</p>
<template #footer>
<span class="dialog-footer">
<el-button @click="transferShowDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirm(formRef)">{{
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 { getWithdrawList, getTransfertype, memberTransfer, memberAudit, getWithdrawDetail, getWithdrawStatusList } from '@/api/member'
import { img } from '@/utils/common'
import { ElMessageBox } from 'element-plus'
import { data } from 'dom7'
import { useRouter, useRoute } from 'vue-router'
const cashOutStatusList = ref([])
const checkStatusList = async () => {
cashOutStatusList.value = await (await getWithdrawStatusList({})).data
}
checkStatusList()
const route = useRoute()
const router = useRouter()
const member_id: number = parseInt(route.query.id || 0)
const operationBtn = ref({
"1": {
value: [t('successfulAudit'),t('auditFailure'),t('detail')],
clickArr: ['successfulAuditFn','auditFailureFn','detailFn']
},
"2": {
value: [t('transfer'),t('detail')],
clickArr: ['transferFn','detailFn']
},
"3": {
value: [t('detail')],
clickArr: ['detailFn']
},
"-1": {
value: [t('detail')],
clickArr: ['detailFn']
},
"-2": {
value: [t('detail')],
clickArr: ['detailFn']
}
});
const orderTableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
order_no: '',
member_id,
create_time: [],
status: ''
}
})
//
const Transfertype = ref<Array<Object>>([])
const getTransfertypeFn = async()=>{
Transfertype.value = await (await getTransfertype()).data
}
getTransfertypeFn()
/**
* 获取提现列表
*/
const loadOrderList = (page: number = 1) => {
orderTableData.loading = true
orderTableData.page = page
getWithdrawList({
page: orderTableData.page,
limit: orderTableData.limit,
...orderTableData.searchParam
}).then(res => {
orderTableData.loading = false
orderTableData.data = res.data.data
orderTableData.total = res.data.total
}).catch(() => {
orderTableData.loading = false
})
}
loadOrderList()
//
let auditFailure = ref({refuse_reason:'',id:0,action: 0})
let auditShowDialog = ref(false);
const fnProcessing = (type:string, data: any)=>{
let obj = {}
if(['successfulAuditFn','auditFailureFn'].includes(type)){
obj.id = data.id;
if(type == 'successfulAuditFn'){
obj.action = 'agree';
cashOutAuditFn(obj)
}else{
obj.action = 'refuse';
auditFailure.value = Object.assign(auditFailure.value,obj);
auditShowDialog.value = true;
}
}else if(type == 'transferFn'){
obj.id = data.id;
ElMessageBox.confirm(`${t('isTransfer')}`,`${t('transfer')}`)
.then(() => {
transferFn(obj);
})
}else{
detailFn(data.id);
}
}
/**
* 转账
* @param data
*/
const transferFn = (data)=>{
memberTransfer({...data}).then(res => {
loadOrderList()
}).catch(() => {
loadOrderList()
})
}
/**
* 详情
* @param data
*/
let cashOutShowDialog = ref(false);
let cashOutInfo = ref({});
let cashOutLoading = ref(true);
const detailFn = (id)=>{
getWithdrawDetail(id).then(res => {
cashOutInfo.value = res.data;
cashOutShowDialog.value = true;
cashOutLoading.value = false;
}).catch(() => {
loadOrderList()
})
}
/**
* 提现审核
* @param data
*/
const cashOutAuditFn = (data)=>{
memberAudit({
...data
}).then(res => {
loadOrderList()
}).catch(() => {
loadOrderList()
})
}
/**
* 拒绝审核
* @param data
*/
const confirm = ()=>{
auditShowDialog.value = false;
cashOutAuditFn(auditFailure.value);
}
/**
* 订单详情
* @param data
*/
const infoEvent = (data: any) => {
router.push(`/finance/recharge/detail?order_id=${data.order_id}`)
}
/**
* 会员详情
*/
const toMember = (member_id: number) => {
router.push(`/member/detail?id=${member_id}`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -2,7 +2,7 @@
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<el-card class="box-card !border-none my-[16px] table-search-wrap" shadow="never">
<el-card class="box-card !border-none mb-[16px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="orderTableData.searchParam" ref="searchFormRef">
<!-- <el-form-item :label="t('orderNo')" prop="order_no">
<el-input v-model="orderTableData.searchParam.order_no" :placeholder="t('orderNoPlaceholder')" />
@ -73,9 +73,10 @@
<template #default="{ row }">
<el-button type="primary" link @click="infoEvent(row)">{{ t('info') }}</el-button>
<el-button v-if="[1, 10].includes(row.order_status_info.status)" type="primary" link @click="refundBtnFn(row)">{{ t('refundBtn') }}</el-button>
<template v-for="(item, index) in row.order_status_info.action">
<el-button type="danger" link @click="orderEvent(row, item.class)">{{ item.name
}}</el-button>
<el-button type="danger" link @click="orderEvent(row, item.class)">{{ item.name }}</el-button>
</template>
</template>
</el-table-column>
@ -87,20 +88,28 @@
@size-change="loadOrderList()" @current-change="loadOrderList" />
</div>
</div>
</el-card>
<!-- 是否退款 -->
<el-dialog v-model="refundShowDialog" :title="t('refundBtn')" width="500px" :destroy-on-close="true">
<p>{{t('refundContent')}}</p>
<template #footer>
<span class="dialog-footer">
<el-button @click="refundShowDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirm(formRef)">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getRechargeOrderStatusList, getRechargeOrderList } from '@/api/order'
import { getRechargeOrderStatusList, getRechargeOrderList, rechargeRefund } from '@/api/order'
import { getChannelType } from '@/api/sys'
import { img } from '@/utils/common'
import { ElMessageBox } from 'element-plus'
import { data } from 'dom7'
import { useRouter, useRoute } from 'vue-router'
const route = useRoute()
const router = useRouter()
const member_id: number = parseInt(route.query.id || 0)
@ -169,6 +178,32 @@ const infoEvent = (data: any) => {
const orderEvent = (data: any, type: string) => {
}
/**
* 退款操作
*/
let refundShowDialog = ref(false);
const refundFn = (data) => {
console.log("退款操作",data);
refundShowDialog.value = true;
rechargeRefund(data.order_id).then(res => {
refundShowDialog.value = false;
}).catch(() => {
})
// getRechargeOrderList({
// page: orderTableData.page,
// limit: orderTableData.limit,
// ...orderTableData.searchParam
// }).then(res => {
// orderTableData.loading = false
// orderTableData.data = res.data.data
// orderTableData.total = res.data.total
// }).catch(() => {
// orderTableData.loading = false
// })
}
/**
* 会员详情

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