This commit is contained in:
全栈小学生 2024-05-18 17:30:08 +08:00
parent 14585292f9
commit 71de55be4d
137 changed files with 6148 additions and 456 deletions

View File

@ -31,7 +31,7 @@ export function installAddon(params: Record<string, any>) {
* @returns
*/
export function cloudInstallAddon(params: Record<string, any>) {
return request.post(`addon/cloudinstall/${params.addon}`, params, { timeout: 60 * 1000 })
return request.post(`addon/cloudinstall/${params.addon}`, params)
}
/**
@ -49,7 +49,7 @@ export function uninstallAddon(params: Record<string, any>) {
* @returns
*/
export function preInstallCheck(addon: string) {
return request.get(`addon/install/check/${addon}`, { timeout: 30 * 1000 })
return request.get(`addon/install/check/${addon}`)
}
/**
@ -75,7 +75,7 @@ export function getAddonCloudInstallLog(addon: string) {
* @returns
*/
export function preUninstallCheck(addon: string) {
return request.get(`addon/uninstall/check/${addon}`, { timeout: 30 * 1000 })
return request.get(`addon/uninstall/check/${addon}`)
}
/**

View File

@ -5,7 +5,7 @@ import request from '@/utils/request'
* @param addon
*/
export function cloudBuild() {
return request.post('niucloud/build', {}, { timeout: 0 })
return request.post('niucloud/build', {})
}
/**

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 })
}
/**
@ -35,7 +35,7 @@ export function getMemberNo() {
* @returns
*/
export function addMember(params: Record<string, any>) {
return request.post(`member/member`, params, {showSuccessMessage: true})
return request.post(`member/member`, params, { showSuccessMessage: true })
}
/**
@ -61,10 +61,9 @@ export function getRegisterChannelType(params: Record<string, any>) {
* @param member_id
*/
export function deleteMember(member_id: number) {
return request.delete(`member/member/${member_id}`, {showSuccessMessage: true})
return request.delete(`member/member/${member_id}`, { showSuccessMessage: true })
}
/***************************************************** 会员标签 ****************************************************/
/**
@ -73,7 +72,7 @@ export function deleteMember(member_id: number) {
* @returns
*/
export function getMemberLabelList(params: Record<string, any>) {
return request.get(`member/label`, {params})
return request.get(`member/label`, { params })
}
/**
@ -91,7 +90,7 @@ export function getMemberLabelInfo(label_id: number) {
* @returns
*/
export function addMemberLabel(params: Record<string, any>) {
return request.post('member/label', params, {showSuccessMessage: true})
return request.post('member/label', params, { showSuccessMessage: true })
}
/**
@ -99,7 +98,7 @@ export function addMemberLabel(params: Record<string, any>) {
* @param params
*/
export function updateMemberLabel(params: Record<string, any>) {
return request.put(`member/label/${params.label_id}`, params, {showSuccessMessage: true})
return request.put(`member/label/${params.label_id}`, params, { showSuccessMessage: true })
}
/**
@ -108,7 +107,7 @@ export function updateMemberLabel(params: Record<string, any>) {
* @returns
*/
export function deleteMemberLabel(label_id: number) {
return request.delete(`member/label/${label_id}`, {showSuccessMessage: true})
return request.delete(`member/label/${label_id}`, { showSuccessMessage: true })
}
/**
@ -123,7 +122,7 @@ export function getMemberLabelAll() {
* @param params
*/
export function editMemberDetail(params: Record<string, any>) {
return request.put(`member/member/modify/${params.member_id}/${params.field}`, params, {showSuccessMessage: true})
return request.put(`member/member/modify/${params.member_id}/${params.field}`, params, { showSuccessMessage: true })
}
/***************************************************** 会员零钱 ****************************************************/
@ -145,7 +144,16 @@ export function getChangeTypeList(change_type: string) {
* @returns
*/
export function getPointList(params: Record<string, any>) {
return request.get(`member/account/point`, {params})
return request.get(`member/account/point`, { params })
}
/**
*
* @param params
* @returns
*/
export function getGrowthList(params: Record<string, any>) {
return request.get(`member/account/growth`, { params })
}
/**
@ -154,7 +162,7 @@ export function getPointList(params: Record<string, any>) {
* @returns
*/
export function getBalanceList(params: Record<string, any>) {
return request.get(`member/account/balance`, {params})
return request.get(`member/account/balance`, { params })
}
/**
@ -163,7 +171,7 @@ export function getBalanceList(params: Record<string, any>) {
* @returns
*/
export function getMoneyList(params: Record<string, any>) {
return request.get(`member/account/money`, {params})
return request.get(`member/account/money`, { params })
}
/**
@ -172,7 +180,7 @@ export function getMoneyList(params: Record<string, any>) {
* @returns
*/
export function getCommissionList(params: Record<string, any>) {
return request.get(`member/account/commission`, {params})
return request.get(`member/account/commission`, { params })
}
/**
@ -181,7 +189,7 @@ export function getCommissionList(params: Record<string, any>) {
* @returns
*/
export function adjustPoint(params: Record<string, any>) {
return request.post(`member/account/point`, params, {showSuccessMessage: true})
return request.post(`member/account/point`, params, { showSuccessMessage: true })
}
/**
@ -190,7 +198,7 @@ export function adjustPoint(params: Record<string, any>) {
* @returns
*/
export function adjustBalance(params: Record<string, any>) {
return request.post(`member/account/balance`, params, {showSuccessMessage: true})
return request.post(`member/account/balance`, params, { showSuccessMessage: true })
}
/***************************************************** 会员相关设置 ****************************************************/
@ -208,7 +216,7 @@ export function getLoginConfig() {
* @returns
*/
export function setLoginConfig(params: Record<string, any>) {
return request.post(`member/config/login`, params, {showSuccessMessage: true})
return request.post(`member/config/login`, params, { showSuccessMessage: true })
}
/**
@ -224,9 +232,40 @@ export function getMemberConfig() {
* @returns
*/
export function setMemberConfig(params: Record<string, any>) {
return request.post(`member/config/member`, params, {showSuccessMessage: true})
return request.post(`member/config/member`, params, { showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function setGrowthRuleConfig(params: Record<string, any>) {
return request.post(`member/config/growth_rule`, params, { showSuccessMessage: true })
}
/**
*
*/
export function getGrowthRuleConfig() {
return request.get(`member/config/growth_rule`)
}
/**
*
* @param params
* @returns
*/
export function setPointRuleConfig(params: Record<string, any>) {
return request.post(`member/config/point_rule`, params, { showSuccessMessage: true })
}
/**
*
*/
export function getPointRuleConfig() {
return request.get(`member/config/point_rule`)
}
/**
*
@ -242,7 +281,7 @@ export function getTransfertype() {
* @returns
*/
export function getCommissionSum(params: Record<string, any>) {
return request.get(`member/account/sum_commission`, {params})
return request.get(`member/account/sum_commission`, { params })
}
/**
@ -251,7 +290,7 @@ export function getCommissionSum(params: Record<string, any>) {
* @returns
*/
export function getPointSum(params: Record<string, any>) {
return request.get(`member/account/sum_point`, {params})
return request.get(`member/account/sum_point`, { params })
}
/**
@ -260,7 +299,7 @@ export function getPointSum(params: Record<string, any>) {
* @returns
*/
export function getBalanceSum(params: Record<string, any>) {
return request.get(`member/account/sum_balance`, {params})
return request.get(`member/account/sum_balance`, { params })
}
/**
@ -293,7 +332,7 @@ export function getCashOutConfig() {
* @returns
*/
export function setCashOutConfig(params: Record<string, any>) {
return request.post(`member/config/cash_out`, params, {showSuccessMessage: true})
return request.post(`member/config/cash_out`, params, { showSuccessMessage: true })
}
/**
@ -302,7 +341,7 @@ export function setCashOutConfig(params: Record<string, any>) {
* @returns
*/
export function getCashOutList(params: Record<string, any>) {
return request.get(`member/cash_out`, {params})
return request.get(`member/cash_out`, { params })
}
/**
@ -318,7 +357,7 @@ export function getCashOutDetail(id: number) {
* @param params
*/
export function memberAudit(params: Record<string, any>) {
return request.put(`member/cash_out/audit/${params.id}/${params.action}`, params, {showSuccessMessage: true})
return request.put(`member/cash_out/audit/${params.id}/${params.action}`, params, { showSuccessMessage: true })
}
/**
@ -326,7 +365,7 @@ export function memberAudit(params: Record<string, any>) {
* @param params
*/
export function memberTransfer(params: Record<string, any>) {
return request.put(`member/cash_out/transfer/${params.id}`, params, {showSuccessMessage: true})
return request.put(`member/cash_out/transfer/${params.id}`, params, { showSuccessMessage: true })
}
/**
@ -334,7 +373,7 @@ export function memberTransfer(params: Record<string, any>) {
* @param params
*/
export function editMemberStatus(params: Record<string, any>) {
return request.put(`member/setstatus/${params.status}`, params, {showSuccessMessage: true})
return request.put(`member/setstatus/${params.status}`, params, { showSuccessMessage: true })
}
/**
@ -352,3 +391,134 @@ export function getCashOutStat() {
return request.get(`member/cash_out/stat`)
}
/**
*
* @returns
*/
export function getBenefitsDict() {
return request.get(`member/dict/benefits`)
}
/**
*
* @returns
*/
export function getGiftDict() {
return request.get(`member/dict/gift`)
}
/**
*
* @returns
*/
export function getGrowthRuleDict() {
return request.get(`member/dict/growth_rule`)
}
/**
*
* @returns
*/
export function getPointRuleDict() {
return request.get(`member/dict/point_rule`)
}
/***************************************************** 会员等级 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getMemberLevelPageList(params: Record<string, any>) {
return request.get(`member/level`, { params })
}
/**
*
* @param params
* @returns
*/
export function getMemberLevelList(params: Record<string, any>) {
return request.get(`member/level/list`, { params })
}
/**
*
* @param level_id level_id
* @returns
*/
export function getMemberLevelInfo(level_id: number) {
return request.get(`member/level/${level_id}`);
}
/**
*
* @param params
* @returns
*/
export function addMemberLevel(params: Record<string, any>) {
return request.post('member/level', params, { showSuccessMessage: true })
}
/**
*
* @param params
*/
export function updateMemberLevel(params: Record<string, any>) {
return request.put(`member/level/${params.level_id}`, params, { showSuccessMessage: true })
}
/**
*
* @param level_id
* @returns
*/
export function deleteMemberLevel(level_id: number) {
return request.delete(`member/level/${level_id}`, { showSuccessMessage: true })
}
/**
*
*/
export function getMemberLevelAll() {
return request.get(`member/level/all`);
}
/***************************************************** 签到设置 ****************************************************/
/**
*
*/
export function getMemberBenefitsContent() {
return request.get(`member/benefits/content`);
}
/**
*
*/
export function getMemberGiftsContent(params: Record<string, any>) {
return request.get(`member/gifts/content`, { params });
}
/**
*
*/
export function getSignConfig() {
return request.get(`member/sign/config`)
}
/**
*
* @param params
* @returns
*/
export function setSignConfig(params: Record<string, any>) {
return request.put(`member/sign/config`, params, { showSuccessMessage: true })
}
/**
*
*/
export function getMemberSignList(params: Record<string, any>) {
return request.get(`member/sign`, { params });
}

View File

@ -46,7 +46,7 @@ export function getModuleVersion() {
* @returns
*/
export function downloadVersion(params: Record<string, any>) {
return request.post(`addon/download/${params.addon}`, params, { timeout: 0, showSuccessMessage: true })
return request.post(`addon/download/${params.addon}`, params, { showSuccessMessage: true })
}
/**

106
admin/src/app/api/poster.ts Normal file
View File

@ -0,0 +1,106 @@
import request from '@/utils/request'
/**
*
* @param params
* @returns
*/
export function getPosterPageList(params: Record<string, any>) {
return request.get(`sys/poster`, {params})
}
/**
*
* @param params
* @returns
*/
export function getPosterList(params: Record<string, any>) {
return request.get(`sys/poster/list`, {params})
}
/**
*
* @param id id
* @returns
*/
export function getPosterInfo(id: number) {
return request.get(`sys/poster/${id}`);
}
/**
*
* @param params
* @returns
*/
export function addPoster(params: Record<string, any>) {
return request.post('sys/poster', params, {showErrorMessage: true, showSuccessMessage: true})
}
/**
*
* @param params
* @returns
*/
export function editPoster(params: Record<string, any>) {
return request.put(`sys/poster/${params.id}`, params, {
showErrorMessage: true,
showSuccessMessage: true
})
}
/**
*
* @param id
* @returns
*/
export function deletePoster(id: number) {
return request.delete(`sys/poster/${id}`, {showErrorMessage: true, showSuccessMessage: true})
}
/**
*
* @param params
*/
export function modifyPosterStatus(params: Record<string, any>) {
return request.put(`sys/poster/status`, params, {showSuccessMessage: true})
}
/**
*
* @param params
*/
export function modifyPosterDefault(params: Record<string, any>) {
return request.put(`sys/poster/default`, params, {showSuccessMessage: true})
}
/**
*
* @param params
* @returns
*/
export function getPosterType(params: Record<string, any>) {
return request.get(`sys/poster/type`, {params})
}
/**
*
* @param params
* @returns
*/
export function getPosterTemplate(params: Record<string, any>) {
return request.get(`sys/poster/template`, {params})
}
/**
*
*/
export function initPoster(params: Record<string, any>) {
return request.get(`sys/poster/init`, {params})
}
/**
*
*/
export function getPreviewPoster(params: Record<string, any>) {
return request.get(`sys/poster/preview`, {params})
}

View File

@ -632,14 +632,6 @@ export function getLayouts() {
return request.get('sys/layout')
}
/**
*
* @returns
*/
export function setLayout(key: string) {
return request.put('sys/layout', { key }, { showSuccessMessage: true })
}
/**
*
*/
@ -700,3 +692,79 @@ export function getDeveloperToken() {
export function setDeveloperToken(params: Record<string, any>) {
return request.put(`sys/config/developer_token`, params, { showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getWebsiteLayout() {
return request.get('sys/web/layout')
}
/**
*
* @returns
*/
export function getLayout() {
return request.get('sys/config/layout')
}
/**
*
* @param params
* @returns
*/
export function setLayout(params: Record<string, any>) {
return request.put(`sys/config/layout`, params, { showSuccessMessage: true })
}
/***************************************************** 报表导出 ****************************************************/
/**
*
* @returns
*/
export function getExportList(params: Record<string, any>) {
return request.get(`sys/export`, { params })
}
/**
*
* @returns
*/
export function getExportStatusList() {
return request.get('sys/export/status')
}
/**
*
* @returns
*/
export function getExportKeyList() {
return request.get('sys/export/type')
}
/**
*
* @returns
*/
export function exportData(type: string, params: Record<string, any>) {
return request.get(`sys/export/${type}`, { params })
}
/**
*
* @returns
*/
export function exportDataCheck(type: string, params: Record<string, any>) {
return request.get(`sys/export/check/${type}`, { params })
}
/**
*
* @param id
*/
export function deleteExport(id: number) {
return request.delete(`sys/export/${id}`, { showSuccessMessage: true })
}

View File

@ -33,6 +33,14 @@ export function getAddonDevelopCheck(key: any) {
return request.get(`addon_develop/check/${key}`)
}
/**
* key黑名单
* @returns
*/
export function getAddonKeyBlackList(key: any) {
return request.get('addon_develop/key/blacklist')
}
/**
*
* @param key

View File

@ -29,7 +29,7 @@ export function upgradeAddon(addon: string = '') {
*
*/
export function executeUpgrade() {
return request.post('upgrade/execute', {}, { timeout: 0 })
return request.post('upgrade/execute', {})
}
/**

View File

@ -0,0 +1,60 @@
import request from '@/utils/request'
/***************************************************** 核销 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getVerifyRecord(params: Record<string, any>) {
return request.get(`verify/verify/record`, { params })
}
/**
*
* @param verifyCode
* @returns
*/
export function getVerifyDetail(verifyCode: string) {
return request.get(`verify/verify/${verifyCode}`)
}
/***************************************************** 核销员 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getVerifierList(params: Record<string, any>) {
return request.get(`verify/verifier`, { params })
}
/**
*
* @param params
* @returns
*/
export function getVerifyTypeList() {
return request.get(`verify/verifier/type`)
}
/**
*
* @param params
* @returns
*/
export function addVerifier(params: Record<string, any>) {
return request.post('verify/verifier', params, { showSuccessMessage: true })
}
/**
*
* @param id
* @returns
*/
export function deleteVerifier(id: number) {
return request.delete(`verify/verifier/${id}`, { showSuccessMessage: true })
}

View File

@ -122,4 +122,12 @@ export function editVersion(params: Record<string, any>) {
*/
export function deleteVersion(id: string) {
return request.delete(`applet/version/${id}`)
}
/**
*
* @returns
*/
export function getIsTradeManaged() {
return request.get('weapp/delivery/getIsTradeManaged')
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -103,18 +103,30 @@
</div>
</el-dialog>
<el-dialog v-model="upgradeTipsShowDialog" :title="t('warning')" width="500px" draggable>
<span v-html="t('upgrade.upgradeTips')"></span>
<template #footer>
<div class="flex justify-end">
<el-button @click="upgradeTipsConfirm(true)" type="primary">{{ t('upgrade.knownToKnow') }}</el-button>
<el-button @click="upgradeTipsConfirm()" type="primary" plain>{{ t('upgrade.upgradeButton') }}</el-button>
<el-button @click="upgradeTipsShowDialog = false">{{ t('cancel') }}</el-button>
</div>
</template>
</el-dialog>
<cloud-build ref="cloudBuildRef" />
</template>
<script lang="ts" setup>
import {ref, h, watch} from 'vue'
import { ref, h, watch } from 'vue'
import { t } from '@/lang'
import { getUpgradeContent, getUpgradeTask, upgradeAddon, executeUpgrade, preUpgradeCheck, clearUpgradeTask } from '@/app/api/upgrade'
import { Terminal, TerminalFlash } from 'vue-web-terminal'
import 'vue-web-terminal/lib/theme/dark.css'
import { AnyObject } from "@/types/global"
import { AnyObject } from '@/types/global'
import CloudBuild from '@/app/components/cloud-build/index.vue'
import { ElNotification, ElMessage, ElMessageBox } from "element-plus"
import { ElNotification, ElMessage, ElMessageBox } from 'element-plus'
import Storage from '@/utils/storage'
const showDialog = ref<boolean>(false)
const upgradeContent = ref<null | AnyObject>(null)
@ -125,6 +137,7 @@ const uploading = ref(false)
const terminalRef = ref(null)
const emits = defineEmits(['complete'])
const cloudBuildRef = ref(null)
const upgradeTipsShowDialog = ref<boolean>(false)
let upgradeLog = []
/**
@ -229,16 +242,20 @@ const open = (addonKey: string = '') => {
if (upgradeTask.value) {
ElMessage({ message: '已有正在执行中的升级任务', type: 'error' })
showDialog.value = true
return
} else {
getUpgradeContent(addonKey).then(({ data }) => {
upgradeContent.value = data
if (!data.version_list.length) {
ElMessage({ message: '已经是最新版本了', type: 'error' })
return
}
if (Storage.get('upgradeTipsLock')) {
showDialog.value = true
} else {
upgradeTipsShowDialog.value = true
}
}).catch()
}
getUpgradeContent(addonKey).then(({ data }) => {
upgradeContent.value = data
if (!data.version_list.length) {
ElMessage({ message: '已经是最新版本了', type: 'error' })
return
}
showDialog.value = true
}).catch()
}
/**
@ -309,6 +326,12 @@ const handleCloudBuild = () => {
cloudBuildRef.value?.open()
}
const upgradeTipsConfirm = (isLock: boolean = false) => {
isLock && Storage.set({ key: 'upgradeTipsLock', data: isLock })
upgradeTipsShowDialog.value = false
!isLock && (showDialog.value = true)
}
defineExpose({
open
})

View File

@ -8,7 +8,7 @@
"menuDeleteTips": "删除菜单会删除当前菜单以及该菜单下所有子菜单,是否确认删除?",
"initializeMenu":"重置菜单",
"initializeMenuTipsOne":"重置菜单会将应用或插件的dict目录下的菜单配置文件中菜单配置更新到数据库一般用做开发者修改了dict菜单配置文件后同步到数据库操作。",
"initializeMenuTipsTwo":"如果用户手动调整过以下菜单项,通常不允诛进行本项操作,操作会重置为原始菜单。 请谨慎使用!",
"initializeMenuTipsTwo":"如果用户手动调整过以下菜单项,通常允许进行本项操作,操作会重置为原始菜单。 请谨慎使用!",
"addMenu": "添加菜单",
"updateMenu": "编辑菜单",
"routePath": "路由路径",

View File

@ -9,7 +9,7 @@
"addMenu": "添加菜单",
"initializeMenu":"重置菜单",
"initializeMenuTipsOne":"重置菜单会将应用或插件的dict目录下的菜单配置文件中菜单配置更新到数据库一般用做开发者修改了dict菜单配置文件后同步到数据库操作。",
"initializeMenuTipsTwo":"如果用户手动调整过以下菜单项,通常不允诛进行本项操作,操作会重置为原始菜单。 请谨慎使用!",
"initializeMenuTipsTwo":"如果用户手动调整过以下菜单项,通常允许进行本项操作,操作会重置为原始菜单。 请谨慎使用!",
"updateMenu": "编辑菜单",
"routePath": "路由路径",
"viewPath": "组件路径",
@ -30,4 +30,4 @@
"addon":"选择应用",
"system":"系统菜单",
"application":"应用菜单"
}
}

View File

@ -38,5 +38,8 @@
"toSetting": "去配置",
"cloudRelease": "一键云端发布",
"localRelease": "本地发布",
"localInsertTips": "请先将uni-app编译成微信小程序然后使用微信开发者工具进行上传"
"localInsertTips": "请先将uni-app编译成微信小程序然后使用微信开发者工具进行上传",
"uploadSuccessTips": "小程序上传成功后还需到<a href='https://mp.weixin.qq.com/' target='_blank' class='text-primary'>微信公众平台</a>提交审核,审核通过后发布才算正式上线。",
"knownToKnow": "我已知晓,不需要再次提示",
"siteAuthTips": "上传代码需先绑定授权码,请联系平台管理员进行绑定"
}

View File

@ -17,6 +17,6 @@
"weappAccessFlow": "接入流程",
"subscribeMessage": "订阅消息",
"weappRelease": "版本管理",
"batchAcquisition": "一键获取"
}
"batchAcquisition": "一键获取",
"addon": "所属应用"
}

View File

@ -21,5 +21,6 @@
"wechatAccessFlow": "接入流程",
"customMenu": "自定义菜单",
"wechatTemplate": "模板消息",
"reply": "自动回复"
"reply": "自动回复",
"addon": "所属应用"
}

View File

@ -1,7 +1,7 @@
{
"templatePagePlaceholder": "选择模板",
"templatePageEmpty": "无",
"changeTemplatePageTips":"切换模板后,当前页面内容将被替换且不被保存,请谨慎操作",
"changeTemplatePageTips": "切换模板后,当前页面内容将被替换且不被保存,请谨慎操作",
"developTitle": "开发环境配置",
"wapDomain": "wap域名WAP_DOMAIN",
"wapDomainPlaceholder": "请输入wap域名",
@ -10,8 +10,15 @@
"tabEditStyle": "样式",
"pageStyle": "页面样式",
"pageContent": "页面内容",
"pageName": "页面名称",
"pageNamePlaceholder": "请输入页面名称",
"statusBarContent": "导航栏内容",
"statusBarStyle": "导航栏样式",
"bottomNavContent": "底部导航内容",
"diyPageTitle": "页面名称",
"diyPageTitlePlaceholder": "请输入页面名称",
"pageTitleTips": "页面名称用于后台显示",
"diyTitle": "页面标题",
"diyTitlePlaceholder": "请输入页面标题",
"titleTips": "页面标题用于前台显示",
"pageBgColor": "页面颜色",
"bgUrl": "背景图片",
"bgHeightScale": "高度比例",
@ -97,6 +104,8 @@
"titleStyle": "标题样式",
"selectStyle": "风格选择",
"styleLabel": "风格",
"styleShowTips": "风格 1 2 3 5 6仅在小程序中展示",
"topStatusBarBgColorTips": "当导航栏样式为风格5且页面滚动时背景颜色会跟随顶部颜色的设置而改变",
"titleContent": "标题内容",
"title": "标题名称",
"titlePlaceholder": "请输入标题",
@ -142,10 +151,14 @@
"addAddon": "添加应用",
"show": "显示",
"hidden": "隐藏",
"goodsCategoryTitle":"商品分类",
"customGoods":"手动选择",
"goodsNum":"商品数量",
"selectCategory":"选择分类",
"goodsCategoryTitle": "商品分类",
"customGoods": "手动选择",
"goodsNum": "商品数量",
"selectCategory": "选择分类",
"isBecomeFenxiao": "成为分销商",
"isBecomeFenxiaoDesc": "是否展示会员成为分销商的按钮",
"are": "是",
"no": "否",
"categoryName": "分类名称",
"categoryImage": "分类图片",
"selectSource": "选择数据源",
@ -205,7 +218,6 @@
"carouselSearchAddTabItem": "添加一个选项卡",
"selectSourcesDiyPage": "选择微页面",
"selectDiyPagePlaceholder": "请选择微页面",
"diyPageTitle": "页面名称",
"diyPageTypeName": "页面类型",
"diyPageForAddon": "所属应用",
"carouselSearchSwiperSet": "轮播图设置",
@ -236,5 +248,15 @@
"floatBtnImageSet": "图片设置",
"floatBtnImageSize": "图片大小",
"floatBtnAroundRadius": "图片圆角",
"floatBtnImageSuggest": "建议上传正方形图片"
}
"floatBtnImageSuggest": "建议上传正方形图片",
"topStatusBarImg": "图片",
"topStatusBarNav": "导航栏",
"topStatusBarNavTips": "此处控制当前页面导航栏是否显示",
"topStatusBarImgTips": "宽度自适应最大150px高度28px",
"topStatusBarIsTransparent": "顶部透明",
"topStatusBarTextColor": "标题颜色",
"topStatusBarBgColor": "顶部颜色",
"topStatusBarSearchName": "搜索内容",
"topStatusBarSearchNamePlaceholder": "请输入搜索关键词",
"settingTips": "点击查看如何配置"
}

View File

@ -0,0 +1,34 @@
{
"signSet": "签到设置",
"signList": "签到记录",
"isUse": "是否启用",
"signPeriod": "签到周期",
"signPeriodTip": "请输入签到周期",
"calendarSign": "日历签到",
"periodSign": "周期签到",
"daySignAward": "日签奖励",
"continueSignAward": "连签奖励",
"calendarSignTip": "用户根据日期进行打卡,连续签到一定天数可即可获得连签奖励。",
"periodSignTip": "用户在规定的周期内完成签到可以获得奖励;一个周期结束后将进入下一个循环周期。",
"daySignAwardTip": "用户每日签到可以获得的奖励",
"continueSignAwardTipTop": "超过固定周期天数的奖励将不向用户展示和发放",
"continueSignAwardTipBottom": "若用户已经达到新增的连签条件,则不补发连奖励(例:新增连签3天获得赠品若已有用户连签超过3天则不补发赠品)",
"set": "设置",
"modify": "修改",
"add": "+新增连签奖励",
"signRule": "签到规则",
"daySignTitle": "设置日签奖励",
"continueSignTitle": "设置连续奖励",
"ruleExplain": "规则说明",
"ruleExplainTip": "请输入规则说明",
"ruleExplainDefault": "1.每日签到可以获得日签奖励,连续签到可以获得连签奖励;\n2.每日最多可签到1次断签则会重新计算连签天数\n3.活动以及奖励最终解释权归商家所有。",
"useDefaultExplain": "使用默认说明",
"continueSign": "连续签到天数",
"receiveLimit": "领取限制",
"noLimit": "不限制",
"everyOneLimit": "每人限领",
"time": "次",
"day": "天",
"continueSignPlaceholder":"请输入连续签到天数",
"receiveNumPlaceholder":"请输入限领次数"
}

View File

@ -0,0 +1,15 @@
{
"memberId":"会员编号",
"memberInfoPlaceholder":"请输入会员信息",
"memberInfo":"会员信息",
"mobile":"手机号码",
"nickName":"会员昵称",
"headimg":"会员头像",
"createTime":"签到时间",
"days":"连续签到",
"day":"天",
"dayAward":"日签奖励",
"continueAward":"连签奖励",
"startDate":"开始时间",
"endDate":"结束时间"
}

View File

@ -0,0 +1,31 @@
{
"hotelVerify": "订单核销",
"verifyRecord": "核销记录",
"verifyTime": "核销时间",
"orderNo": "订单编号",
"orderNoPlaceholder": "请输入订单编号",
"OrderInfo": "订单信息",
"buyerInfo": "购买人信息",
"verifyCode": "核销码",
"verifyCodePlaceholder": "请输入核销码",
"verifyer": "核销人",
"startDate": "开始时间",
"endDate": "结束时间",
"mobile": "联系方式",
"searchValueEmptyTips": "请输入搜索内容",
"verify": "核销",
"buyInfo": "预订信息",
"orderRefunding": "该订单正在维权中不能进行核销",
"verifyTips": "是否要核销该订单?",
"toOrder": "查看订单",
"verifyType": "核销类型",
"verifyTypePlaceholder": "请选择核销类型",
"verifier": "核销员",
"createTime":"添加时间",
"addVerifier":"添加核销员",
"verifierDeleteTips":"确定要删除该核销员吗?",
"memberInfo": "会员信息",
"memberIdPlaceholder": "请选择会员",
"member": "会员",
"searchPlaceholder": "请输入会员昵称搜索"
}

View File

@ -0,0 +1,31 @@
{
"hotelVerify": "订单核销",
"verifyRecord": "核销记录",
"verifyTime": "核销时间",
"orderNo": "订单编号",
"orderNoPlaceholder": "请输入订单编号",
"verifyInfo": "核销信息",
"buyerInfo": "购买人信息",
"verifyCode": "核销码",
"verifyCodePlaceholder": "请输入核销码",
"verifyer": "核销人",
"startDate": "开始时间",
"endDate": "结束时间",
"mobile": "联系方式",
"searchValueEmptyTips": "请输入搜索内容",
"verify": "核销",
"buyInfo": "预订信息",
"orderRefunding": "该订单正在维权中不能进行核销",
"verifyTips": "是否要核销该订单?",
"toOrder": "查看订单",
"verifyType": "核销类型",
"verifyTypePlaceholder": "请选择核销类型",
"verifier": "核销员",
"createTime":"添加时间",
"addVerifier":"添加核销员",
"verifierDeleteTips":"确定要删除该核销员吗?",
"memberInfo": "会员信息",
"memberIdPlaceholder": "请选择会员",
"member": "会员",
"searchPlaceholder": "请输入会员昵称搜索"
}

View File

@ -0,0 +1,31 @@
{
"accountData":"变更数值",
"accountSum":"变更后成长值",
"fromType":"来源",
"memberId":"会员编号",
"pointInfo":"积分变动详情",
"memo":"备注",
"mobile":"手机号码",
"nickName":"会员信息",
"headimg":"会员头像",
"createTime":"发生时间",
"startDate":"开始时间",
"endDate":"结束时间",
"searchMember":"昵称/手机号",
"searchMemberPlaceholder":"请输入会员昵称/手机号码",
"memberIdPlaceholder":"请输入会员id",
"accountTypePlaceholder":"请输入账户类型",
"accountDataPlaceholder":"请输入账户数据",
"fromTypePlaceholder":"请输入来源类型",
"relatedIdPlaceholder":"请输入关联Id",
"createTimePlaceholder":"请输入创建时间",
"memoPlaceholder":"请输入备注信息",
"addMemberAccountLog":"添加会员账单表",
"updateMemberAccountLog":"编辑会员账单表",
"member_account_logDeleteTips":"确定要删除该会员账单表吗?",
"memberInfo":"会员信息",
"memberInfoPlaceholder":"请输入会员编号/昵称/手机号",
"memberLevel": "会员等级",
"growthRule": "成长值规则",
"growthDetail": "成长值明细"
}

View File

@ -0,0 +1,14 @@
{
"levelName":"等级名称",
"memberNumber":"会员数量",
"memberLabelDeleteTips":"确定要删除该会员登记吗?",
"addMemberLevel": "添加会员等级",
"levelNamePlaceholder": "请输入等级名称",
"memberLevelDeleteTips": "确定要删除该等级吗?",
"growth": "等级成长值",
"levelBenefits": "等级权益",
"levelGifts": "等级礼包",
"memberLevel": "会员等级",
"growthRule": "成长值规则",
"growthDetail": "成长值明细"
}

View File

@ -0,0 +1,20 @@
{
"basicInfo": "基础信息",
"levelName":"等级名称",
"memberNumber":"会员数量",
"memberLabelDeleteTips":"确定要删除该会员登记吗?",
"addMemberLevel": "添加会员等级",
"levelNamePlaceholder": "请输入等级名称",
"levelBenefits": "等级权益",
"levelGift": "等级礼包",
"benefits": "权益",
"gift": "礼包",
"growthPlaceholder": "请输入成长值",
"growth": "成长值",
"remark": "等级描述",
"remarkPlaceholder": "请输入等级描述",
"growthFormatError": "成长值只能为整数",
"growthNeedLt": "成长值需小于",
"growthNeedGt": "成长值需大于",
"growthTips": "升级到该等级需达到的最低成长值"
}

View File

@ -49,5 +49,6 @@
"memberInfo":"会员信息",
"memberInfoPlaceholder":"请输入会员编号/昵称/手机号",
"lock": "锁定",
"normal": "正常"
}
"normal": "正常",
"memberLevel": "会员等级"
}

View File

@ -21,8 +21,8 @@
"sexPlaceholder": "请选择性别",
"headimg": "会员头像",
"wxUnionid": "微信unionid",
"weappOpenid": "微信公众号openid",
"wxOpenid": "微信小程序openid",
"weappOpenid": "微信小程序openid",
"wxOpenid": "微信公众号openid",
"memberLabel": "会员标签",
"memberLabelPlaceholder": "请选择会员标签",
"nickNamePlaceholder": "请输入会员名称",
@ -47,5 +47,9 @@
"money":"可提现余额",
"adjustPoint":"调整积分",
"commission":"佣金",
"memberNull":"未读取到会员详情信息"
}
"memberNull":"未读取到会员详情信息",
"memberLevel": "会员等级",
"memberLevelUpdate": "修改等级至",
"memberLevelUpdateTips": "该操作只会修改会员等级不会发放等级礼包",
"memberLevelPlaceholder": "请选择会员等级"
}

View File

@ -0,0 +1,51 @@
{
"posterName": "海报名称",
"posterNamePlaceholder": "请输入海报名称",
"bgType": "背景类型",
"bgUrl": "背景图",
"bgColor": "背景色",
"bgUrlTips": "建议图片尺寸720*1280px",
"statusLabel": "启用状态",
"statusTips": "此处控制海报的启用状态",
"textSet": "文本设置",
"textLabel": "文本内容",
"textPlaceholder": "请输入文本内容",
"templatePosterPlaceholder": "选择模板",
"templatePosterEmpty": "无",
"changeTemplatePosterTips": "切换模板后,当前页面内容将被替换且不被保存,请谨慎操作",
"posterSet": "海报设置",
"diyPosterValueEmptyTips": "请编辑海报内容",
"componentStyleTitle": "组件样式",
"pageContent": "页面内容",
"zIndex": "层级",
"coordinate": "坐标(x,y)",
"coordinateTips": "x为横向坐标y为纵向坐标",
"leavePageTitleTips": "确定离开此页面?",
"leavePageContentTips": "系统可能不会保存您所做的更改。",
"decorating": "正在装修",
"preview": "预览",
"previewDialogTitle": "预览海报",
"moveUpComponentZIndex": "上移一层",
"moveDownComponentZIndex": "下移一层",
"copyComponent": "复制",
"delComponent": "删除",
"resetComponent": "重置",
"delComponentTips": "确认要删除当前组件吗?",
"notCopy": "无法复制",
"componentCanOnlyAdd": "组件只能添加",
"piece": "个",
"componentNotMoved": "该组件禁止移动",
"resetComponentTips": "确认要重置组件默认数据吗?",
"imageUrlTip": "请上传图片",
"textFontSize": "文字大小",
"textFontWeight": "文字粗细",
"fontWeightBold": "加粗",
"fontWeightNormal": "常规",
"textColor": "文字颜色",
"width": "宽度",
"drawType": "类型",
"polygon": "多边形",
"angle": "旋转角度",
"nickName": "昵称",
"needLoginTips": "前台需要登录才会展示"
}

View File

@ -0,0 +1,18 @@
{
"posterName": "海报名称",
"posterNamePlaceholder": "请输入海报名称",
"posterType": "海报类型",
"posterTypePlaceholder": "请选择海报类型",
"isDefault": "是否默认",
"defaultPoster": "默认海报",
"noDefault": "否",
"addPosterTitle": "添加海报",
"previewDialogTitle": "预览海报",
"title": "页面名称",
"status": "状态",
"updateTime": "更新时间",
"all": "全部",
"diyPosterDeleteTips": "确定要删除该自定义海报吗?",
"preview": "预览",
"modifyDefault": "设为默认"
}

View File

@ -0,0 +1,15 @@
{
"exportKey": "数据类型",
"exportKeyPlaceholder": "请输入主题关键字",
"exportStatus": "导出状态",
"exportStatusPlaceholder": "请选择导出状态",
"createTime": "导出时间",
"id": "编号",
"exportNum": "导出数据数量",
"filePath": "文件存储路径",
"fileSize": "文件大小",
"download": "下载",
"exportDeleteTips":"确定要删除该导出报表吗?",
"startDate": "开始时间",
"endDate": "结束时间"
}

View File

@ -0,0 +1,5 @@
{
"memberLevel": "会员等级",
"growthRule": "成长值规则",
"growthDetail": "成长值明细"
}

View File

@ -0,0 +1,7 @@
{
"app": "应用",
"setting": "设置",
"selectLayout": "选择布局",
"emptyData": "还没有安装应用",
"manyApp": "多应用"
}

View File

@ -1,4 +1,11 @@
{
"clickTutorial": "查看教程",
"clickSecretKey": "获取密钥"
"clickSecretKey": "获取密钥",
"isOpen": "定位开关",
"validTime": "定位有效期",
"minutes": "分钟",
"validTimeTips": "过期后将重新获取定位信息0为不过期",
"validTimePlaceholder": "请输入定位有效期",
"validTimeFormatTips": "格式输入错误",
"validTimeNotZeroTips": "定位有效期不能小于0"
}

View File

@ -14,7 +14,6 @@
"groupIdPlaceholder":"请输入套餐",
"addSite":"添加站点",
"updateSite":"编辑站点",
"expireTimePlaceholder":"请选择到期时间",
"desc": "网站简介",
"province": "省",

View File

@ -49,5 +49,7 @@
"siteDomainPlaceholder": "请输入站点域名",
"siteDomainTips": "站点域名的配置是针对站点的wap和web端",
"siteDomainTipsTwo": "需要将域名配置到您的服务器,同时域名需要解析您的服务器才可生效",
"toSite": "访问站点"
"siteDomainTipsThree": "站点域名不需要加http或者https末尾不需要加/",
"toSite": "访问站点",
"noPermission": "您没有该站点的管理权限"
}

View File

@ -61,12 +61,12 @@
<el-form :model="formData" label-width="0" ref="formRef" :rules="formRules" class="page-form">
<el-card class="box-card !border-none" shadow="never">
<el-form-item prop="auth_code">
<el-input v-model="formData.auth_code" :placeholder="t('authCodePlaceholder')" class="input-width" clearable size="large" />
<el-input v-model.trim="formData.auth_code" :placeholder="t('authCodePlaceholder')" class="input-width" clearable size="large" />
</el-form-item>
<div class="mt-[20px]">
<el-form-item prop="auth_secret">
<el-input v-model="formData.auth_secret" clearable :placeholder="t('authSecretPlaceholder')" class="input-width" size="large" />
<el-input v-model.trim="formData.auth_secret" clearable :placeholder="t('authSecretPlaceholder')" class="input-width" size="large" />
</el-form-item>
</div>

View File

@ -1,12 +1,12 @@
<template>
<div class="main-container w-full bg-white" v-loading="loading">
<div class="main-container w-full " v-loading="loading">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-page-title">应用管理</span>
</div>
<div class="flex flex-wrap plug-list pb-10 plug-large" v-if="appList.length">
<div v-for="(item, index) in appList" :key="index + 'b'">
<div class="relative app-item cursor-pointer px-4 mr-4 mt-[20px] bg-[#f7f7f7] border-[1px] hover:border-primary">
<div class="relative bg-page cursor-pointer px-4 mr-4 mt-[20px] border-br-light border-[1px] hover:border-primary">
<div @click="toLink(item.key)" class="flex py-5 items-center">
<div class="flex justify-center items-center">
<el-image class="w-[40px] h-[40px]" :src="img(item.icon)" fit="contain">
@ -18,7 +18,7 @@
</el-image>
</div>
<div class="flex flex-col justify-between text-left w-[190px]">
<p class="app-text w-[190px] text-[17px] text-[#222] pl-3">{{ item.title }}</p>
<p class="app-text w-[190px] text-[17px] pl-3">{{ item.title }}</p>
</div>
</div>
</div>

View File

@ -5,7 +5,7 @@
<div class="flex justify-between items-center h-[32px]">
<span class="text-[16px] text-[#222] font-600">{{ t('localAppText') }}</span>
<div class="w-[247px]">
<el-input :placeholder="t('search')" v-model="searchName" @keyup.enter="query">
<el-input :placeholder="t('search')" v-model.trim="searchName" @keyup.enter="query">
<template #suffix>
<el-icon class="el-input__icon cursor-pointer" size="14px" @click="query">
<search />
@ -139,12 +139,12 @@
<el-form :model="formData" label-width="0" ref="formRef" :rules="formRules" class="page-form">
<el-card class="box-card !border-none" shadow="never">
<el-form-item prop="auth_code">
<el-input v-model="formData.auth_code" :placeholder="t('authCodePlaceholder')" class="input-width" clearable size="large" />
<el-input v-model.trim="formData.auth_code" :placeholder="t('authCodePlaceholder')" class="input-width" clearable size="large" />
</el-form-item>
<div class="mt-[20px]">
<el-form-item prop="auth_secret">
<el-input v-model="formData.auth_secret" clearable :placeholder="t('authSecretPlaceholder')" class="input-width" size="large" />
<el-input v-model.trim="formData.auth_secret" clearable :placeholder="t('authSecretPlaceholder')" class="input-width" size="large" />
</el-form-item>
</div>

View File

@ -24,16 +24,15 @@
</el-form-item>
<el-form-item :label="t('userRealName')" prop="real_name">
<el-input v-model="formData.real_name" :placeholder="t('userRealNamePlaceholder')" clearable class="input-width" maxlength="10" show-word-limit />
<el-input v-model="formData.real_name" :placeholder="t('userRealNamePlaceholder')" :readonly="real_name_input" @click="real_name_input = false" @blur="real_name_input = true" clearable class="input-width" maxlength="10" show-word-limit />
</el-form-item>
<div v-if="!formData.uid">
<el-form-item :label="t('password')" prop="password">
<el-input v-model="formData.password" :placeholder="t('passwordPlaceholder')" type="password" :show-password="true" clearable class="input-width" />
<el-input v-model="formData.password" :placeholder="t('passwordPlaceholder')" :readonly="password_input" @click="password_input = false" @blur="password_input = true" type="password" :show-password="true" clearable class="input-width" />
</el-form-item>
<el-form-item :label="t('confirmPassword')" prop="confirm_password">
<el-input v-model="formData.confirm_password" :placeholder="t('confirmPasswordPlaceholder')" type="password" :show-password="true" clearable class="input-width" />
<el-input v-model="formData.confirm_password" :placeholder="t('confirmPasswordPlaceholder')" :readonly="confirm_password_input" @click="confirm_password_input = false" @blur="confirm_password_input = true" type="password" :show-password="true" clearable class="input-width" />
</el-form-item>
</div>
</div>
@ -85,7 +84,9 @@ const getUserList = () => {
}).catch()
}
getUserList()
const real_name_input = ref(true)
const password_input = ref(true)
const confirm_password_input = ref(true)
const needAddUserInfo = computed(() => {
if (formData.uid || !uid.value || typeof uid.value == 'string') {
return true

View File

@ -92,11 +92,9 @@ const refreshMenu = () => {
// type: 'warning'
}
).then(() => {
menusTableData.loading = true
menuRefresh({}).then(res => {
menusTableData.loading = false
location.reload()
}).catch(() => {
menusTableData.loading = false
})
}).catch(()=>{})

View File

@ -138,12 +138,9 @@ const refreshMenu = () => {
// type: 'warning'
}
).then(() => {
menusTableData.loading = true
menuRefresh({}).then(res => {
menusTableData.loading = false
}).catch(() => {
menusTableData.loading = false
})
location.reload()
}).catch(() => {})
}).catch(()=>{})
}

View File

@ -16,7 +16,6 @@
</el-form>
<el-button type="primary" class="w-[100px] self-start" @click="addEvent">{{ t('addUser') }}</el-button>
</div>
<div>
<el-table :data="userTableData.data" size="large" v-loading="userTableData.loading">
<template #empty>

View File

@ -1,5 +1,5 @@
<template>
<!-- <div class="w-full p-5 bg-white ">
<!-- <div class="w-full p-5 ">
<div class="flex justify-between items-center mb-[20px]">
<span class="text-[16px]">{{ t('aliappAccessFlow') }}</span>
</div>
@ -79,7 +79,7 @@
</div>
</div>
</div> -->
<div class="w-full p-5 bg-white">
<div class="w-full p-5 bg-body">
<div class="flex justify-between items-center mb-[20px]">
<span class="text-page-title">{{ t('title') }}</span>
</div>

View File

@ -5,9 +5,9 @@
</div>
<el-form :model="formData" label-width="150px" ref="formRef" class="page-form">
<el-card class="box-card !border-none" shadow="never">
<el-form-item :label="t('preview')" prop="weapp_name">
<img class="w-[500px]" src="@/app/assets/images/channel/preview.png" alt="">
</el-form-item>
<!-- <el-form-item :label="t('preview')" prop="weapp_name">-->
<!-- <img class="w-[500px]" src="@/app/assets/images/channel/preview.png" alt="">-->
<!-- </el-form-item>-->
<el-form-item :label="t('isOpen')">
<el-switch v-model="formData.is_open"/>

View File

@ -1,5 +1,5 @@
<template>
<div class="w-full p-5 bg-white">
<div class="w-full p-5 bg-body">
<div class="flex justify-between items-center mb-[20px]">
<span class="text-page-title">{{ t('title') }}</span>
</div>

View File

@ -8,7 +8,7 @@
<el-tab-pane :label="t('subscribeMessage')" name="/channel/weapp/message" />
<el-tab-pane :label="t('weappRelease')" name="/channel/weapp/code" />
</el-tabs>
<el-card class="box-card !border-none" shadow="never">
<el-card class="box-card !border-none" shadow="never" v-loading="loading">
<div class="mt-[50px]">
<el-button type="primary" @click="insert" :loading="uploading" :disabled="weappTableData.loading">{{ t('cloudRelease') }}</el-button>
<el-button @click="localInsert" :disabled="weappTableData.loading">{{ t('localRelease') }}</el-button>
@ -70,6 +70,15 @@
{{ failReason }}
</el-scrollbar>
</el-dialog>
<el-dialog v-model="uploadSuccessShowDialog" :title="t('warning')" width="500px" draggable>
<span v-html="t('uploadSuccessTips')"></span>
<template #footer>
<div class="flex justify-end">
<el-button @click="knownToKnow" type="primary">{{ t('knownToKnow') }}</el-button>
<el-button @click="uploadSuccessShowDialog = false" type="primary" plain>{{ t('confirm') }}</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@ -79,13 +88,16 @@ import { setWeappVersion, getWeappPreview, getWeappVersionList, getWeappUploadLo
import { t } from '@/lang'
import { useRoute, useRouter } from 'vue-router'
import { getAuthinfo } from '@/app/api/module'
import { getAppType } from '@/utils/common'
import { ElMessageBox } from 'element-plus'
import { AnyObject } from '@/types/global'
import Storage from '@/utils/storage'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const dialogVisible = ref(false)
const loading = ref(true)
const weappTableData:{
page: number,
limit: number,
@ -105,14 +117,17 @@ const form = ref({
path: '',
content: ''
})
const uploadSuccessShowDialog = ref(false)
const authCode = ref('')
getAuthinfo().then(res => {
if (res.data.data && res.data.data.auth_code) {
authCode.value = res.data.data.auth_code
getWeappPreviewImage()
}
loading.value = false
}).catch(() => {
loading.value = false
})
const weappConfig = ref<{
@ -211,6 +226,7 @@ const getWeappUploadLogFn = (key: string) => {
if (last.code == 1 && last.percent == 100) {
getWeappVersionListFn()
getWeappPreviewImage()
!Storage.get('weappUploadTipsLock') && (uploadSuccessShowDialog.value = true)
return
}
setTimeout(() => {
@ -221,21 +237,25 @@ const getWeappUploadLogFn = (key: string) => {
}
const authElMessageBox = () => {
ElMessageBox.confirm(
t('authTips'),
t('warning'),
{
distinguishCancelAndClose: true,
confirmButtonText: t('toBind'),
cancelButtonText: t('toNiucloud')
}
).then(() => {
router.push({ path: '/app/authorize' })
}).catch((action: string) => {
if (action === 'cancel') {
window.open('https://www.niucloud.com/app')
}
})
if (getAppType() == 'admin') {
ElMessageBox.confirm(
t('authTips'),
t('warning'),
{
distinguishCancelAndClose: true,
confirmButtonText: t('toBind'),
cancelButtonText: t('toNiucloud')
}
).then(() => {
router.push({ path: '/app/authorize' })
}).catch((action: string) => {
if (action === 'cancel') {
window.open('https://www.niucloud.com/app')
}
})
} else {
ElMessageBox.alert(t('siteAuthTips'), t('warning'))
}
}
const configElMessageBox = () => {
@ -258,6 +278,11 @@ const handleFailReason = (data: any) => {
failReason.value = data.fail_reason
failReasonDialogVisible.value = true
}
const knownToKnow = () => {
Storage.set({ key: 'weappUploadTipsLock', data: true })
uploadSuccessShowDialog.value = false
}
</script>
<style lang="scss" scoped>

View File

@ -16,21 +16,28 @@
<Warning />
</el-icon>
<div>
<p class="text-base">{{ t('operationTip') }} 1{{ t('operationTipOne') }}</p>
<p class="text-base">2{{ t('operationTipTwo') }}</p>
<p class="text-base">{{ t('operationTipTwo') }}</p>
</div>
</div>
</template>
</el-alert>
<div>
<el-table :data="cronTableData.data" size="large" v-loading="cronTableData.loading">
<el-table :data="cronTableData.data" :span-method="templateSpan" size="large" v-loading="cronTableData.loading">
<template #empty>
<span>{{ !cronTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="name" :show-overflow-tooltip="true" :label="t('name')" min-width="150" />
<el-table-column prop="addon_name" :label="t('addon')" min-width="120" />
<el-table-column prop="name" :show-overflow-tooltip="true" :label="t('name')" min-width="150" >
<template #default="{ row }">
<div class="flex items-center">
<span class="mr-[5px]">{{row.name }}</span>
<el-tooltip :content="row.weapp.tips" v-if="row.weapp.tips" placement="top">
<icon name="element-WarningFilled" />
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column :label="t('response')" min-width="180">
<template #default="{ row }">
@ -89,13 +96,44 @@ const loadCronList = (page: number = 1) => {
getTemplateList().then(res => {
cronTableData.loading = false
cronTableData.data = res.data
let data = []
res.data.forEach(item => {
if (item.notice.length) {
const addons = []
Object.keys(item.notice).forEach((key, index) => {
const notice = item.notice[key]
notice.addon_name = item.title
addons.push(notice)
})
if (addons.length) {
addons[0].rowspan = addons.length
data = data.concat(addons)
}
}
})
cronTableData.data = data
}).catch(() => {
cronTableData.loading = false
})
}
loadCronList()
const templateSpan = (row : any) => {
if (row.columnIndex === 0) {
if (row.row.rowspan) {
return {
rowspan: row.row.rowspan,
colspan: 1
}
} else {
return {
rowspan: 0,
colspan: 0
}
}
}
}
/**
* 批量获取
*/

View File

@ -1,5 +1,5 @@
<template>
<div class="w-full p-5 bg-white">
<div class="w-full p-5 bg-body">
<div class="flex justify-between items-center mb-[20px]">
<span class="text-page-title">{{ t('title') }}</span>
</div>

View File

@ -24,7 +24,7 @@
<!-- 素材管理 -->
<div v-if="attachment.data.length">
<div class="flex flex-wrap" v-if="prop.type != 'news'">
<div class="attachment-item mr-[10px] w-[120px]" v-for="(item, index) in attachment.data"
<div class="attachment-item mr-[10px] mb-[10px] w-[120px]" v-for="(item, index) in attachment.data"
:key="index" @click="selectedFile = item">
<div
class="attachment-wrap w-full rounded cursor-pointer overflow-hidden relative flex items-center justify-center h-[120px]">

View File

@ -12,32 +12,25 @@
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-page-title">{{ pageName }}</span>
<el-button type="primary" class="w-[100px]" @click="batchAcquisitionFn">{{ t('batchAcquisition')
<el-button type="primary" class="w-[100px]" @click="batchAcquisitionFn()">{{ t('batchAcquisition')
}}</el-button>
</div>
<el-alert class="warm-prompt !my-[20px]" type="info">
<template #default>
<div class="flex">
<el-icon class="mr-2 mt-[2px]" size="18">
<Warning />
</el-icon>
<div>
<p class="text-base">{{ t('operationTip') }}</p>
<p class="text-base">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>
</div>
</div>
</template>
</el-alert>
<div class="mt-[10px]">
<el-table :data="cronTableData.data" size="large" v-loading="cronTableData.loading">
<el-table :data="cronTableData.data" :span-method="templateSpan" size="large" v-loading="cronTableData.loading">
<template #empty>
<span>{{ !cronTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="name" :show-overflow-tooltip="true" :label="t('name')" min-width="150" />
<el-table-column prop="addon_name" :label="t('addon')" min-width="120" />
<el-table-column prop="name" :show-overflow-tooltip="true" :label="t('name')" min-width="150" >
<template #default="{ row }">
<div class="flex items-center">
<span class="mr-[5px]">{{row.name }}</span>
<el-tooltip :content="row.wechat.tips" v-if="row.wechat.tips" placement="top">
<icon name="element-WarningFilled" />
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column :label="t('messageType')" min-width="100" align="center">
<template #default="{ row }">
@ -102,13 +95,44 @@ const loadCronList = (page: number = 1) => {
getTemplateList().then(res => {
cronTableData.loading = false
cronTableData.data = res.data
}).catch(() => {
let data = []
res.data.forEach(item => {
if (item.notice.length) {
const addons = []
Object.keys(item.notice).forEach((key, index) => {
const notice = item.notice[key]
notice.addon_name = item.title
addons.push(notice)
})
if (addons.length) {
addons[0].rowspan = addons.length
data = data.concat(addons)
}
}
})
cronTableData.data = data
}).catch((e) => {
cronTableData.loading = false
})
}
loadCronList()
const templateSpan = (row : any) => {
if (row.columnIndex === 0) {
if (row.row.rowspan) {
return {
rowspan: row.row.rowspan,
colspan: 1
}
} else {
return {
rowspan: 0,
colspan: 0
}
}
}
}
/**
* 批量获取
*/

View File

@ -114,7 +114,7 @@
<template #empty>
<span>{{ !diyPageTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="title" :label="t('diyPageTitle')" min-width="120" />
<el-table-column prop="page_title" :label="t('diyPageTitle')" min-width="120" />
<el-table-column prop="addon_name" :label="t('diyPageTypeName')" min-width="80" />
<el-table-column prop="type_name" :label="t('diyPageForAddon')" min-width="80" />
</el-table>

View File

@ -0,0 +1,101 @@
<template>
<!-- 内容 -->
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('selectStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('selectStyle')" class="flex">
<span class="text-primary flex-1 cursor-pointer" @click="showStyle">{{ diyStore.editComponent.styleName }}</span>
<el-icon>
<ArrowRight />
</el-icon>
</el-form-item>
</el-form>
<el-dialog v-model="showDialog" :title="t('selectStyle')" width="500px">
<div class="flex flex-wrap">
<template v-for="(item,index) in styleList" :key="index">
<div :class="{ 'border-primary': selectStyle.value == item.value }" @click="changeStyle(item)" class="flex items-center justify-center overflow-hidden w-[200px] h-[100px] mr-[12px] cursor-pointer border bg-gray-50">
<img :src="img(item.url)" />
</div>
</template>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirmStyle">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<!-- 组件样式 -->
<slot name="style"></slot>
</div>
</template>
<script lang="ts" setup>
import { t } from '@/lang'
import useDiyStore from '@/stores/modules/diy'
import { img } from '@/utils/common'
import { ref, reactive } from 'vue'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = ['componentBgColor','componentBgUrl'] //
const selectStyle = reactive({
title: diyStore.editComponent.styleName,
value: diyStore.editComponent.style
})
//
const showDialog = ref(false)
const showStyle = () => {
showDialog.value = true
selectStyle.title = diyStore.editComponent.styleName;
selectStyle.value = diyStore.editComponent.style;
}
const styleList = reactive([
{
url: 'static/resource/images/diy/member/member_level_style1.jpg',
title: '风格1',
value: 'style-1'
},
{
url: 'static/resource/images/diy/member/member_level_style2.png',
title: '风格2',
value: 'style-2'
},
{
url: 'static/resource/images/diy/member/member_level_style3.jpg',
title: '风格3',
value: 'style-3'
}
])
const changeStyle = (item:any) => {
selectStyle.title = item.title;
selectStyle.value = item.value;
}
const confirmStyle = () => {
diyStore.editComponent.styleName = selectStyle.title;
diyStore.editComponent.style = selectStyle.value;
showDialog.value = false
}
defineExpose({})
</script>
<style lang="scss" scoped></style>

View File

@ -4,15 +4,87 @@
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('pageContent') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('pageName')">
<el-input v-model.trim="diyStore.global.title" :placeholder="t('pageNamePlaceholder')" clearable maxlength="12" show-word-limit/>
<el-form-item :label="t('diyPageTitle')">
<el-input v-model.trim="diyStore.pageTitle" :placeholder="t('diyPageTitlePlaceholder')" clearable maxlength="12" show-word-limit/>
<div class="text-sm text-gray-400">{{ t('pageTitleTips') }}</div>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('statusBarContent') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('diyTitle')">
<el-input v-model.trim="diyStore.global.title" :placeholder="t('diyTitlePlaceholder')" clearable maxlength="12" show-word-limit/>
<div class="text-sm text-gray-400">{{ t('titleTips') }}</div>
</el-form-item>
<el-form-item :label="t('selectStyle')" class="display-block">
<div class="flex">
<span class="text-primary flex-1 cursor-pointer" @click="showStyle">{{diyStore.global.topStatusBar.styleName}}</span>
<el-icon>
<ArrowRight />
</el-icon>
</div>
<div class="text-sm text-gray-400 leading-[1.5]">{{ t('styleShowTips') }}</div>
</el-form-item>
<el-form-item :label="t('topStatusBarImg')" v-if="['style-2','style-3'].indexOf(diyStore.global.topStatusBar.style) > -1">
<upload-image v-model="diyStore.global.topStatusBar.imgUrl" :limit="1" />
<div class="text-sm text-gray-400 mt-[10px]">{{ t('topStatusBarImgTips') }}</div>
</el-form-item>
<el-form-item :label="t('topStatusBarSearchName')" v-if="'style-3' == diyStore.global.topStatusBar.style">
<el-input v-model.trim="diyStore.global.topStatusBar.inputPlaceholder" :placeholder="t('topStatusBarSearchNamePlaceholder')" clearable maxlength="12" show-word-limit/>
</el-form-item>
<el-form-item :label="t('textAlign')" v-show="diyStore.global.topStatusBar.style == 'style-1'">
<el-radio-group v-model="diyStore.global.topStatusBar.textAlign">
<el-radio :label="'left'">{{ t('textAlignLeft') }}</el-radio>
<el-radio :label="'center'">{{ t('textAlignCenter') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('link')" v-if="['style-2','style-3'].indexOf(diyStore.global.topStatusBar.style) > -1">
<diy-link v-model="diyStore.global.topStatusBar.link" />
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('bottomNavContent') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('tabbar')" class="display-block">
<el-switch v-model="diyStore.global.bottomTabBarSwitch"/>
<div class="text-sm text-gray-400">{{ t('tabbarSwitchTips') }}</div>
</el-form-item>
</el-form>
</div>
<el-dialog v-model="showDialog" :title="t('selectStyle')" width="800px">
<div class="flex flex-wrap">
<div class="flex items-center justify-center overflow-hidden w-[32%] h-[100px] mr-[2%] mb-[15px] cursor-pointer border bg-gray-50" :class="{ 'border-primary': selectStyle == 'style-1' }" @click="selectStyle = 'style-1'">
<img class="max-w-[100%] max-h-[100%]" src="@/app/assets/images/diy/head/nav_style1.jpg" />
</div>
<div class="flex items-center justify-center overflow-hidden w-[32%] h-[100px] mr-[2%] mb-[15px] cursor-pointer border bg-gray-50" :class="{ 'border-primary': selectStyle == 'style-2' }" @click="selectStyle = 'style-2'">
<img class="max-w-[100%] max-h-[100%]" src="@/app/assets/images/diy/head/nav_style2.jpg" />
</div>
<div class="flex items-center justify-center overflow-hidden w-[32%] h-[100px] mb-[15px] cursor-pointer border bg-gray-50" :class="{ 'border-primary': selectStyle == 'style-3' }" @click="selectStyle = 'style-3'">
<img class="max-w-[100%] max-h-[100%]" src="@/app/assets/images/diy/head/nav_style3.jpg" />
</div>
<div class="flex items-center justify-center overflow-hidden w-[32%] h-[100px] mr-[2%] cursor-pointer border bg-gray-50" :class="{ 'border-primary': selectStyle == 'style-4' }" @click="selectStyle = 'style-4'">
<img class="max-w-[100%] max-h-[100%]" src="@/app/assets/images/diy/head/nav_style4.jpg" />
</div>
<div class="flex items-center justify-center overflow-hidden w-[32%] h-[100px] mr-[2%] cursor-pointer border bg-gray-50" :class="{ 'border-primary': selectStyle == 'style-5' }" @click="selectStyle = 'style-5'">
<img class="max-w-[100%] max-h-[100%]" src="@/app/assets/images/diy/head/nav_style5.png" />
</div>
<div class="flex items-center justify-center overflow-hidden w-[32%] h-[100px] cursor-pointer border bg-gray-50" :class="{ 'border-primary': selectStyle == 'style-6' }" @click="selectStyle = 'style-6'">
<img class="max-w-[100%] max-h-[100%]" src="@/app/assets/images/diy/head/nav_style6.png" />
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="changeStyle">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
<!-- 样式 -->
@ -40,6 +112,18 @@
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('statusBarStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('topStatusBarBgColor')" class="display-block" v-if="selectStyle == 'style-5'">
<el-color-picker v-model="diyStore.global.topStatusBar.bgColor" show-alpha />
<div class="text-sm text-gray-400 leading-[1.5]">{{ t('topStatusBarBgColorTips') }}</div>
</el-form-item>
<el-form-item :label="t('topStatusBarTextColor')" class="display-block">
<el-color-picker v-model="diyStore.global.topStatusBar.textColor" show-alpha />
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('marginSet') }}</h3>
<el-form label-width="80px" class="px-[10px]">
@ -53,7 +137,7 @@
<script lang="ts" setup>
import { t } from '@/lang'
import { watch } from 'vue'
import { watch, ref, onMounted, computed } from 'vue'
import useDiyStore from '@/stores/modules/diy'
import { img } from '@/utils/common'
@ -74,13 +158,54 @@ watch(
//
const inputBoth = (value:any)=>{
diyStore.value.forEach((item,index)=>{
item.margin.both = value;
})
}
watch(
() => diyStore.global,
(newValue, oldValue) => {
selectStyle.value = newValue.topStatusBar.style
}, { deep: true }
)
const showDialog = ref(false)
const showStyle = () => {
showDialog.value = true
}
const selectStyle = ref("style-1")
const changeStyle = () => {
diyStore.global.topStatusBar.isShow = true;
diyStore.global.topStatusBar.isTransparent = false;
switch (selectStyle.value) {
case 'style-1':
diyStore.global.topStatusBar.styleName = '风格1'
break
case 'style-2':
diyStore.global.topStatusBar.styleName = '风格2'
break
case 'style-3':
diyStore.global.topStatusBar.styleName = '风格3'
break
case 'style-4':
diyStore.global.topStatusBar.styleName = '风格4'
break
case 'style-5':
diyStore.global.topStatusBar.isTransparent = true;
diyStore.global.topStatusBar.styleName = '风格5'
break
case 'style-6':
diyStore.global.topStatusBar.isShow = false;
diyStore.global.topStatusBar.styleName = '风格6'
break
}
diyStore.global.topStatusBar.style = selectStyle.value
showDialog.value = false
}
defineExpose({})
</script>

View File

@ -3,7 +3,7 @@
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('richTextContentSet') }}</h3>
<editor v-model="diyStore.editComponent.html" height="600px" class="editor-width" />
<editor v-model="diyStore.editComponent.html" :height="600" class="editor-width" />
</div>
</div>

View File

@ -51,8 +51,34 @@
<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 class="preview-head bg-no-repeat bg-center bg-cover cursor-pointer h-[64px]" :class="[diyStore.global.topStatusBar.style]" :style="{backgroundColor :diyStore.global.topStatusBar.bgColor}" @click="diyStore.changeCurrentIndex(-99)">
<div v-if="diyStore.global.topStatusBar.style == 'style-1'" class="content-wrap">
<div class="title-wrap" :style="{ fontSize: '14px', color: diyStore.global.topStatusBar.textColor, textAlign: diyStore.global.topStatusBar.textAlign }">
{{ diyStore.global.title }}
</div>
</div>
<div v-if="diyStore.global.topStatusBar.style == 'style-2'" class="content-wrap">
<div class="title-wrap" :style="{ color: diyStore.global.topStatusBar.textColor }">
<div class="h-[28px] max-w-[150px] mr-[8px]" v-if="diyStore.global.topStatusBar.imgUrl">
<img class="max-w-[100%] max-h-[100%]" :src="img(diyStore.global.topStatusBar.imgUrl)" mode="heightFix" />
</div>
<div :style="{ color: diyStore.global.topStatusBar.textColor }">{{ diyStore.global.title }}</div>
</div>
</div>
<div v-if="diyStore.global.topStatusBar.style == 'style-3'" class="content-wrap">
<div class="title-wrap" v-if="diyStore.global.topStatusBar.imgUrl">
<img class="max-w-[100%] max-h-[100%]" :src="img(diyStore.global.topStatusBar.imgUrl)" />
</div>
<div class="search">
<span class="iconfont iconsousuo absolute left-[10px]"></span>
<span class="text-[14px]">{{diyStore.global.topStatusBar.inputPlaceholder}}</span>
</div>
</div>
<div v-if="diyStore.global.topStatusBar.style == 'style-4'" class="content-wrap">
<span class="iconfont iconxiazai19 !text-[14px]" :style="{ color: diyStore.global.topStatusBar.textColor }"></span>
<div class="title-wrap" :style="{ color: diyStore.global.topStatusBar.textColor }">我的位置</div>
<span class="iconfont iconxiangyoujiantou !text-[12px]" :style="{ color: diyStore.global.topStatusBar.textColor }"></span>
</div>
</div>
<div class="preview-block relative">
@ -84,6 +110,7 @@
<el-input v-model="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable />
</div>
<el-button type="primary" @click="saveWapDomain">{{ t('confirm') }}</el-button>
<el-button type="primary" @click="settingTips()" plain>{{ t('settingTips') }}</el-button>
</div>
</div>
@ -121,7 +148,7 @@
<icon name="iconfont-iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="diyStore.editComponent.pageEndBgColor" show-alpha :predefine="diyStore.predefineColors" />
</el-form-item>
<div class="text-sm text-gray-400 ml-[80px] mb-[10px]">{{ t('bottomBgTips') }}</div>
<div class="text-sm text-gray-400 ml-[90px] mb-[10px]">{{ t('bottomBgTips') }}</div>
</template>
<el-form-item :label="t('bgGradientAngle')" v-if="diyStore.editComponent.ignore.indexOf('pageBgColor') == -1">
@ -153,7 +180,7 @@
</el-form-item>
<el-form-item :label="t('marginTop')" v-if="diyStore.editComponent.ignore.indexOf('marginTop') == -1">
<el-slider v-model="diyStore.editComponent.margin.top" show-input size="small" :min="0" class="ml-[10px] horz-blank-slider" />
<el-slider v-model="diyStore.editComponent.margin.top" show-input size="small" :min="-100" class="ml-[10px] horz-blank-slider" />
</el-form-item>
<el-form-item :label="t('marginBottom')" v-if="diyStore.editComponent.ignore.indexOf('marginBottom') == -1">
<el-slider v-model="diyStore.editComponent.margin.bottom" show-input size="small" class="ml-[10px] horz-blank-slider" />
@ -186,6 +213,7 @@
<script lang="ts" setup>
import { ref, reactive, toRaw, watch, inject } from 'vue'
import { t } from '@/lang'
import { img } from '@/utils/common'
import { getDiyTemplatePages, addDiyPage, editDiyPage, initPage } from '@/app/api/diy'
import { useRoute, useRouter } from 'vue-router'
import { cloneDeep } from 'lodash-es'
@ -231,6 +259,7 @@ const handleChange = (val: string[]) => {
const originData = reactive({
id: diyStore.id,
name: diyStore.name,
pageTitle: diyStore.pageTitle,
title: diyStore.global.title,
value: JSON.stringify({
global: toRaw(diyStore.global),
@ -315,8 +344,8 @@ const changeTemplatePage = (value:any)=> {
} else {
//
diyStore.init();
if(route.query.title) diyStore.global.title = diyStore.typeName
}
if(route.query.title) diyStore.global.title = route.query.title
}).catch(() => {
//
template.value = oldTemplate.value;
@ -331,8 +360,8 @@ const changeTemplatePage = (value:any)=> {
} else {
//
diyStore.init();
if(route.query.title) diyStore.global.title = diyStore.typeName
}
if(route.query.title) diyStore.global.title = route.query.title
}
};
@ -343,6 +372,7 @@ watch(
const data = {
id: newValue.id,
name: newValue.name,
pageTitle: newValue.pageTitle,
title: newValue.global.title,
value: JSON.stringify({
global: toRaw(newValue.global),
@ -368,6 +398,7 @@ initPage({
diyStore.init() //
diyStore.id = data.id || 0
diyStore.name = data.name
diyStore.pageTitle = data.page_title
diyStore.type = data.type
diyStore.typeName = data.type_name
diyStore.templateName = data.template
@ -387,6 +418,7 @@ initPage({
//
originData.id = diyStore.id
originData.name = diyStore.name
originData.pageTitle = diyStore.pageTitle
originData.title = diyStore.global.title
originData.value = JSON.stringify({
global: toRaw(diyStore.global),
@ -583,6 +615,7 @@ const save = (callback: any) => {
let data = {
id: diyStore.id,
name: diyStore.name,
page_title: diyStore.pageTitle,
title: diyStore.global.title,
type: diyStore.type,
template: diyStore.templateName,
@ -623,6 +656,10 @@ const preview = () => {
window.open(url.href)
})
}
const settingTips = () => {
window.open('https://www.kancloud.cn/niucloud/niucloud-admin-develop/3213393')
}
</script>
<style lang="scss">
@ -699,4 +736,101 @@ const preview = () => {
.edit-attribute-wrap .box-card {
border: none;
}
.diy-view-wrap .preview-head{
padding: 28px 15px 0;
.content-wrap{
height: 30px;
}
&.style-1 {
.content-wrap {
.title-wrap {
height: 30px;
line-height: 30px;
}
}
}
&.style-2 {
.content-wrap {
.title-wrap {
display: flex;
align-items: center;
> div {
height: 30px;
line-height: 30px;
max-width: 150px;
font-size: 14px;
&:last-child {
overflow: hidden; //
text-overflow: ellipsis; //
white-space: nowrap; //
flex: 1;
max-width: 200px;
}
}
}
}
}
&.style-3 {
.content-wrap {
display: flex;
align-items: center;
.title-wrap {
height: 30px;
max-width: 85px;
margin-right: 5px;
display: flex;
align-items: center;
justify-content: center;
}
.search {
flex: 1;
padding-right: 10px;
padding-left: 31px;
position: relative;
background-color: #fff;
text-align: left;
border-radius: 30px;
height: 30px;
line-height: 30px;
border: 1px solid #eeeeee;
color: rgb(102, 102, 102);
display: flex;
align-items: center;
margin-right: 105px;
overflow: hidden;
span{
overflow: hidden; //
text-overflow: ellipsis; //
white-space: nowrap; //
}
.iconfont {
color: #909399;
font-size: 16px;
margin-right: 5px;
}
}
}
}
&.style-4 {
.content-wrap {
display: flex;
align-items: center;
.title-wrap {
flex: none;
margin: 0 5px;
max-width: 180px;
font-size: 14px;
}
}
}
}
</style>

View File

@ -36,7 +36,7 @@
<span>{{ !diyPageTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="title" :label="t('title')" min-width="120" />
<el-table-column prop="page_title" :label="t('title')" min-width="120" />
<el-table-column prop="addon_name" :label="t('forAddon')" min-width="80" />
<el-table-column prop="type_name" :label="t('typeName')" min-width="80" />
<el-table-column :label="t('status')" min-width="80">
@ -129,7 +129,6 @@ import { t } from '@/lang'
import { getApps,getDiyPageList, deleteDiyPage, getDiyTemplate, editDiyPageShare, setUseDiyPage } from '@/app/api/diy'
import { ElMessageBox, FormInstance } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { getUrl } from '@/app/api/sys'
const router = useRouter()
const route = useRoute()
@ -161,24 +160,19 @@ const addEvent = async (formEl: FormInstance | undefined) => {
await formEl.validate(async (valid) => {
if (valid) {
dialogVisible.value = false
const query = { type: formData.type, title: formData.title }
const url = router.resolve({
path: '/decorate/edit',
query
})
window.open(url.href)
dialogVisible.value = false
formData.title = ''
formData.type = ''
}
})
}
const wapDomain = ref('')
const getDomain = async () => {
wapDomain.value = (await getUrl()).data.wap_url
}
getDomain()
//
const loadDiyTemplate = (addon = '')=> {
getDiyTemplate({mode: '', addon}).then(res => {

View File

@ -3,7 +3,7 @@
<el-card class="box-card !border-none" shadow="never" v-loading="loading">
<div class="flex">
<div class="w-[360px] h-[400px] absolute mr-[30px] border-[1px] border-gray-300">
<div class="flex items-center justify-between absolute h-[60px] left-[0px] right-[0px] bottom-[0px] bg-white border-[1px] border-primary" :style="{ 'backgroundColor': diyBottomData.value.backgroundColor }">
<div class="flex items-center justify-between absolute h-[60px] left-[0px] right-[0px] bottom-[0px] border-[1px] border-primary" :style="{ 'backgroundColor': diyBottomData.value.backgroundColor }">
<div class="flex flex-1 flex-col items-center justify-center" v-for="(item, index) in diyBottomData.value.list" :key="'b' + index">
<el-image class="w-[22px] h-[22px] mb-[5px] leading-1" :src="img(item.iconPath)" :fit="contain" v-if="['1', '2'].includes(diyBottomData.value.type.toString())">
<template #error>
@ -44,7 +44,7 @@
<el-input class="w-[215px]" v-model="item.text" :placeholder="t('titleContent')" maxlength="5" show-word-limit />
</el-form-item>
<el-form-item :label="t('navLinkOne')">
<diy-link v-model="item.link"></diy-link>
<diy-link v-model="item.link"/>
</el-form-item>
<el-icon class="close-icon cursor-pointer -top-[11px] -right-[8px]" @click="deleteNav(index)">
<CircleCloseFilled />
@ -259,4 +259,4 @@ onMounted(() => {
.list-item:hover .close-icon {
display: block;
}
</style>
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="main-container w-full pt-[64px] bg-white" v-loading="loading">
<div class="main-container w-full pt-[64px] " v-loading="loading">
<div class="flex justify-between items-center h-[32px] mb-4">
<span class="text-page-title">{{ t('editPersonal') }}</span>
</div>

View File

@ -19,9 +19,11 @@
<el-button type="primary" class="w-[90px] !h-[34px]" @click="handleChick">创建站点</el-button>
</div>
<div class="flex justify-between items-center mt-[18px]">
<div class="flex items-center flex-wrap text-[14px] w-[800px]">
<span :class="['mr-[12px] cursor-pointer', {'text-[var(--el-color-primary)]': params.app == ''}]" @click="cutAppFn('')">所有应用</span>
<span :class="['mr-[12px] cursor-pointer', {'text-[var(--el-color-primary)]': params.app == item.key}]" @click="cutAppFn(item.key)" v-for="(item,index) in addonList" :key="index">{{item.title}}</span>
<div class="w-[800px] text-[14px] whitespace-nowrap">
<el-scrollbar :always="true">
<span :class="['mr-[12px] cursor-pointer', {'text-[var(--el-color-primary)]': params.app == ''}]" @click="cutAppFn('')">所有应用</span>
<span :class="['mr-[12px] cursor-pointer', {'text-[var(--el-color-primary)]': params.app == item.key}]" @click="cutAppFn(item.key)" v-for="(item,index) in addonList" :key="index">{{item.title}}</span>
</el-scrollbar>
</div>
<el-input v-model="params.keywords" class="!w-[300px] !h-[34px]" placeholder="请输入要搜索的站点名称" @keyup.enter.native="getHomeSiteFn()">
<template #suffix>
@ -37,7 +39,7 @@
<div v-for="(item, index) in tableData" :key="index" @click="selectSite(item)"
:class="['home-item w-[285px] box-border mb-[20px] cursor-pointer',{'mr-[20px]': index ==0 || (index+1)%4 != 0}]">
<div class="flex items-center px-[24px] pt-[22px] pb-[16px] bg-[#F0F2F4] home-item-head">
<img v-if="item.logo" class="w-[48px] h-[48px] mr-[15px] rounded-[50%] overflow-hidden" :src="img(item.logo)" />
<img v-if="item.icon" class="w-[48px] h-[48px] mr-[15px] rounded-[50%] overflow-hidden" :src="img(item.icon)" />
<img v-else class="w-[48px] h-[48px] mr-[15px] rounded-[50%] overflow-hidden" src="@/app/assets/images/member_head.png" />
<div class="flex flex-col flex-1 justify-center">
<div class="flex items-center flex-wrap">

View File

@ -1,5 +1,5 @@
<template>
<div class="main-container w-full pt-[64px] bg-white" v-loading="loading">
<div class="main-container w-full pt-[64px] " v-loading="loading">
<div class="flex justify-between items-center h-[32px] mb-4">
<span class="text-page-title">{{ t("personal") }}</span>
<span class="text-[14px] text-[#999] cursor-pointer" @click="toEditPersonal">{{ t("editPersonal") }}</span>

View File

@ -2,29 +2,8 @@
<template>
<div v-loading="loading">
<el-card class="box-card !border-none" shadow="never">
<div class="">
<div class="flex items-center">
<span class="text-[24px] font-600 text-[#242424] leading-[33px]">欢迎使用niucloud-admin</span>
<div class="ml-[12px] bg-[#333] flex items-center py-[3px] px-[6px] rounded">
<img class="w-[16px] h-[16px]" src="@/app/assets/images/SaaS.png" />
<span class="text-[12px] text-[#fff] font-600 leading-[16px] ml-[3px]">SAAS版</span>
</div>
</div>
<div class="flex items-center mt-[12px]">
<img class="w-[12px] h-[12px]" src="@/app/assets/images/versions.png" />
<span class="ml-[7px] text-[16px] font-600 text-[#424242] leading-[16px] font-[600]">{{ statInfo.version.version }}</span>
<div class="ml-[10px] cursor-pointer" v-if="newVersion" @click="toUpgrade">
<el-tag type="danger" size="small">{{ t('newVersion') }}{{ newVersion.last_version }}</el-tag>
</div>
<el-link class="text-color ml-[30px] text-[14px] leading-[20px]" href="https://www.niucloud.com/"
target="_blank" :underline="false">{{ t("officialWbsite") }}</el-link>
<el-link class="ml-[12px] text-color text-[14px] leading-[20px]"
href="https://gitee.com/niucloud-team/niucloud.git" target="_blank"
:underline="false">Gitee</el-link>
</div>
</div>
<div
class="px-[32px] pt-[24px] pb-[14px] bg-[#fff] border-[1px] border-[#E9EBF0] border-solid mt-[42px] box-border">
class="px-[32px] pt-[24px] pb-[14px] bg-[#fff] border-[1px] border-[#E9EBF0] border-solid box-border">
<el-card class="box-card !border-none profile-data" shadow="never"
:body-style="{ padding: '49px 32px 20px' }">
<template #header>

View File

@ -3,7 +3,7 @@
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center h-[32px] mb-4">
<span class="text-page-title text-[#222]">{{ t('localAppText') }}</span>
<el-input class="!w-[250px]" :placeholder="t('search')" v-model="search_name" @keyup.enter="query">
<el-input class="!w-[250px]" :placeholder="t('search')" v-model.trim="search_name" @keyup.enter="query">
<template #suffix>
<el-icon class="el-input__icon cursor-pointer" size="14px" @click="query">
<search />
@ -182,13 +182,13 @@
<el-form :model="formData" label-width="0" ref="formRef" :rules="formRules" class="page-form">
<el-card class="box-card !border-none" shadow="never">
<el-form-item prop="auth_code">
<el-input v-model="formData.auth_code" :placeholder="t('authCodePlaceholder')"
<el-input v-model.trim="formData.auth_code" :placeholder="t('authCodePlaceholder')"
class="input-width" clearable size="large" />
</el-form-item>
<div class="mt-[20px]">
<el-form-item prop="auth_secret">
<el-input v-model="formData.auth_secret" clearable :placeholder="t('authSecretPlaceholder')"
<el-input v-model.trim="formData.auth_secret" clearable :placeholder="t('authSecretPlaceholder')"
class="input-width" size="large" />
</el-form-item>
</div>
@ -377,7 +377,7 @@
</el-dialog>
<!-- 下载提示 -->
<el-dialog v-model="unloadHintDialog" title="下载提示" width="30%" :before-close="handleClose">
<el-dialog v-model="unloadHintDialog" title="下载提示" width="30%">
<span>本地已经存在该插件/应用再次下载会覆盖该插件/应用</span>
<template #footer>
<span class="dialog-footer">
@ -636,6 +636,8 @@ const getInstallTask = (first: boolean = true) => {
notificationEl.close()
}
}
}).catch((e) => {
terminalRef.value.pushMessage({ content: e.message, class: 'error' })
})
}

View File

@ -93,7 +93,7 @@
<img src="@/app/assets/images/tools/official_market.png" class="w-[256px] h-[128px]" />
</div>
</div>
<el-dialog v-model="developerDialogVisible" class="developer-dialog-wrap" title="开发人员模式说明" width="30%" :before-close="handleClose">
<el-dialog v-model="developerDialogVisible" class="developer-dialog-wrap" title="开发人员模式说明" width="30%">
<div>
<p class="text-[16px] mb-[4px] text-[#333]">开发模式</p>
<div class="text-[14px] indent-[2em]">

View File

@ -159,14 +159,6 @@ const webSite = computed(() => useSystemStore().website)
// []
const loginType = ref(getAppType())
watch(() => webSite.value, () => {
if (loginType.value == 'site') {
setWindowTitle(webSite.value.site_name+'-'+t('siteLogin'))
} else {
setWindowTitle(webSite.value.site_name+'-'+t('adminLogin'))
}
})
// - start
const verifyRef = ref(null)
const success = (params:any) => {

View File

@ -0,0 +1,148 @@
<template>
<el-form :model="formData" :rules="formRules" class="page-form" ref="formRef">
<el-form-item :label="t('continueSign')" prop="continue_sign">
<el-input class="input-width" v-model.trim="formData.continue_sign" clearable /><span class="ml-[10px]">{{ t('day')
}}</span>
</el-form-item>
<el-form-item :label="t('continueSign')" >
<div>
<div v-for="(item,index) in gifts" :key="index" class="mb-[15px]">
<component :is="item.component" v-model="formData[item.key]" ref="giftRefs" v-if="item.component" />
</div>
</div>
</el-form-item>
<el-form-item :label="t('receiveLimit')" prop="receive_num">
<div>
<el-radio class="mb-[15px]" v-model="formData.receive_limit" :label="1" @change="radioChange($event, 1)">{{
t('noLimit') }}</el-radio>
<div class="flex">
<el-radio class="!mr-[15px]" v-model="formData.receive_limit" :label="2" @change="radioChange($event, 2)">{{
t('everyOneLimit') }}</el-radio>
<el-input class="input-width" v-model="formData.receive_num" clearable /><span class="ml-[10px]">{{
t('time') }}</span>
</div>
</div>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { t } from '@/lang'
import { ref, reactive, defineAsyncComponent, computed, watch } from 'vue'
import { FormRules } from 'element-plus'
import { getGiftDict } from '@/app/api/member'
import { guid } from '@/utils/common'
import Test from '@/utils/test'
const gifts = ref({})
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {}
}
}
})
const emits = defineEmits(['update:modelValue'])
const formData = ref({
continue_sign: 0,
continue_tag: guid(),
receive_limit: 1,
receive_num: 0,
})
const value = computed({
get () {
return props.modelValue
},
set (value) {
emits('update:modelValue', value)
}
})
const giftRefs = ref([])
watch(() => value.value, (nval, oval) => {
if ((!oval || !Object.keys(oval).length) && Object.keys(nval).length) {
formData.value = value.value
}
}, { immediate: true })
watch(() => formData.value, () => {
value.value = formData.value
}, { deep: true })
const modules: any = import.meta.glob('@/**/*.vue')
getGiftDict().then(({ data }) => {
Object.keys(data).forEach((key: string) => {
data[key].component && (data[key].component = defineAsyncComponent(modules[data[key].component]))
})
gifts.value = data
})
const formRef = ref(null)
//
const formRules = reactive<FormRules>({
continue_sign: [
{ required: true, message: t('continueSignPlaceholder'), trigger: 'blur' },
{
validator: (rule: any, value: any, callback: Function) => {
console.log(formData.value.continue_sign)
if (!Test.digits(formData.value.continue_sign)) {
callback('连续签到格式错误')
} else if (formData.value.continue_sign <= 0) {
callback('连续签到不能小于等于0')
} else {
callback()
}
},
trigger: 'blur'
}
],
receive_num: [
{ required: true, message: t('receiveNumPlaceholder'), trigger: 'blur' },
{
validator: (rule: any, value: any, callback: Function) => {
if (formData.value.receive_limit == 2) {
if (Test.empty(formData.value.receive_num)) {
callback('请输入限领次数')
}
if (!Test.digits(formData.value.receive_num)) {
callback('限领次数格式错误')
}
if (formData.value.receive_num <= 0) {
callback('限领次数不能小于等于0')
}
callback()
} else {
callback()
}
}
}
]
})
const verify = async () => {
let verify = true
for (let i = 0; i < giftRefs.value.length; i++) {
const item = giftRefs.value[i]
!await item.verify() && (verify = false)
}
await formRef.value?.validate((valid) => {
verify = valid
})
return verify
}
const radioChange = (val, key) => {
formData[key] = val
}
defineExpose({
verify
})
</script>
<style lang="scss" scoped>
.input-width {
width: 100px;
}
</style>

View File

@ -0,0 +1,71 @@
<template>
<div v-for="(item,index) in gifts" :key="index" class="day-sign">
<component :is="item.component" v-model="formData[item.key]" ref="giftRefs" v-if="item.component" />
</div>
</template>
<script lang="ts" setup>
import { ref, defineAsyncComponent, computed, watch } from 'vue'
import { getGiftDict } from '@/app/api/member'
const gifts = ref({})
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {}
}
}
})
const emits = defineEmits(['update:modelValue'])
const formData = ref({})
const value = computed({
get () {
return props.modelValue
},
set (value) {
emits('update:modelValue', value)
}
})
const giftRefs = ref([])
watch(() => value.value, (nval, oval) => {
if ((!oval || !Object.keys(oval).length) && Object.keys(nval).length) {
formData.value = value.value
}
}, { immediate: true })
watch(() => formData.value, () => {
value.value = formData.value
}, { deep: true })
const modules: any = import.meta.glob('@/**/*.vue')
getGiftDict().then(({ data }) => {
Object.keys(data).forEach((key: string) => {
data[key].component && (data[key].component = defineAsyncComponent(modules[data[key].component]))
})
gifts.value = data
})
const verify = async () => {
let verify = true
for (let i = 0; i < giftRefs.value.length; i++) {
const item = giftRefs.value[i]
!await item.verify() && (verify = false)
}
return verify
}
defineExpose({
verify
})
</script>
<style lang="scss" scoped>
.day-sign:nth-child(1) :deep(.el-form-item__error) {
left:48px;
}
.day-sign:nth-child(2) :deep(.el-form-item__error) {
left:48px;
}
</style>

View File

@ -0,0 +1,417 @@
<template>
<div class="main-container bg-[#fff] rounded-[4px]">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center mb-[5px]">
<span class="text-page-title">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none my-[10px]" shadow="never">
<el-form :model="formData" label-width="150px" ref="ruleFormRef" :rules="formRules" class="page-form"
v-loading="loading">
<el-card class="box-card !border-none" shadow="never">
<h3 class="panel-title !text-sm">{{ t('signRule') }}</h3>
<el-form-item :label="t('isUse')">
<el-switch v-model="formData.is_use" />
</el-form-item>
<el-form-item :label="t('signPeriod')" v-if="formData.is_use">
<el-input-number v-model="formData.sign_period" clearable class="input-width"
controls-position="right" /><span class="ml-[10px]"></span>
</el-form-item>
<el-form-item :label="t('daySignAward')" prop="formData.day_award" v-if="formData.is_use">
<div v-for="(item, index) in daySignAwardText" :key="index">
<span v-if="item.is_use == '1'">{{ item.content }}&nbsp;&nbsp;</span>
</div>
<span class="cursor-pointer tutorial-btn ml-[5px]" @click="daySignAwardSet"
v-if="formData.day_award == ''">{{ t('set') }}</span>
<div class="flex ml-[5px]" v-else>
<span class="cursor-pointer tutorial-btn" @click="daySignAwardSet">{{ t('modify') }}</span>
<span class="ml-[5px] mr-[5px]">|</span>
<span class="cursor-pointer tutorial-btn" @click="daySignAwardDel">{{ t('delete') }}</span>
</div>
<div class="form-tip">{{ t('daySignAwardTip') }}</div>
</el-form-item>
<el-form-item :label="t('continueSignAward')" prop="formData.continue_award" v-if="formData.is_use">
<div>
<div class="form-tip">{{ t('continueSignAwardTipTop') }}</div>
<div class="mt-[10px]">
<el-table :data="continueSignAwardTableData.data" size="large"
v-loading="continueSignAwardTableData.loading">
<template #empty>
<span>{{ !continueSignAwardTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="continue_sign" :label="t('continueSign')" min-width="120" />
<el-table-column :label="t('continueSignAward')" min-width="300">
<template #default="{ row }">
<div v-for="(item, index) in row.continue_award" :key="index">
<span v-if="item.is_use == '1'">{{ item.content }}</span>
</div>
</template>
</el-table-column>
<el-table-column :label="t('receiveLimit')" min-width="120">
<template #default="{ row }">
<span v-if="row.receive_limit == 1">{{ t('noLimit') }}</span>
<span v-else>{{ t('everyOneLimit') }}{{ row.receive_num }}{{
t('time') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('operation')" align="right" fixed="right" width="130">
<template #default="scope">
<el-button type="primary" link
@click="continueSignAwardModify(true, scope.$index)">{{
t('modify') }}</el-button>
<el-button type="primary" link
@click="deleteContinueSignAwardEvent(scope.$index)">{{
t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="flex mt-[10px]">
<span class="cursor-pointer tutorial-btn" @click="continueSignAwardSet">{{
t('add') }}</span>
</div>
<div class="form-tip">{{ t('continueSignAwardTipBottom') }}</div>
</div>
</el-form-item>
<el-form-item :label="t('ruleExplain')" prop="formData.rule_explain" v-if="formData.is_use">
<div class="flex">
<el-input v-model="formData.rule_explain" :placeholder="t('ruleExplainTip')" type="textarea"
rows="5" class="textarea-width" clearable />
<el-button class="ml-[20px]" type="primary" @click="defaultExplainEvent()" plain>{{
t('useDefaultExplain') }}</el-button>
</div>
</el-form-item>
</el-card>
</el-form>
<!-- 日签奖励 -->
<el-dialog v-model="daySignDialog" :title="t('daySignTitle')" width="1200px" :destroy-on-close="true"
v-if="formData.is_use">
<sign-day ref="benefitsRef" v-model="formData.day_award" />
<template #footer>
<span class="dialog-footer">
<el-button @click="daySignDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="setDaySignAward()">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
<!-- 连签奖励 -->
<el-dialog v-model="continueSignDialog" :title="t('continueSignTitle')" width="1200px"
:destroy-on-close="true" v-if="formData.is_use">
<sign-continue ref="continueRef" v-model="continue_award" />
<template #footer>
<span class="dialog-footer">
<el-button @click="continueSignDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="setContinueSignAward()">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
<div class="fixed-footer-wrap">
<div class="fixed-footer">
<el-button type="primary" @click="onSave(ruleFormRef)">{{ t('save') }}</el-button>
</div>
</div>
</el-card>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getSignConfig, setSignConfig, getMemberGiftsContent } from '@/app/api/member'
import signDay from '@/app/views/marketing/components/sign-day.vue'
import signContinue from '@/app/views/marketing/components/sign-continue.vue'
import { FormInstance, FormRules } from 'element-plus'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title
const tab = ref('signSet')
const loading = ref(true)
const daySignDialog = ref(false)
const continueSignDialog = ref(false)
const ruleFormRef = ref<FormInstance>()
const continue_award = ref({})
let isEdit = false //
let editIndex = 0 //
//
const formRules = reactive<FormRules>({
sign_period: [
{ required: true, message: t('signPeriodTip'), trigger: 'blur' }
]
})
/**
* 签到奖励文本请求参数
*/
const contentData = reactive<Record<string, any>>({
gifts: [],
})
/**
* 连签奖励显示文本
*/
const continueSignAwardText = ref([])
const formData = reactive<Record<string, any>>({
is_use: 0,
sign_period: 30,
day_award: '',
continue_award: [],
rule_explain: '',
})
const newArr = reactive<Record<string, any>>({
receive_num: '',
continue_sign: '',
receive_limit: '',
continue_award: []
})
/**
* 连签奖励table数据
*/
const continueSignAwardTableData = reactive<Record<string, any>>({
loading: false,
data: []
})
/**
* 获取显示签到设置
*/
const setFormData = async () => {
const data = await (await getSignConfig()).data
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
if (formData.day_award) {
contentData.gifts = formData.day_award
setMemberBenefitsContent()
}
if (formData.continue_award) {
formData.continue_award.forEach((item: any, index: number) => {
continueSignAwardTableData.data.push(JSON.parse(JSON.stringify(item)))
contentData.gifts = [];
const val = JSON.parse(JSON.stringify(item))
delete val['continue_sign'];
delete val['continue_tag'];
delete val['receive_limit'];
delete val['receive_num'];
contentData.gifts = val
setMemberBenefitsContents(contentData, item, index)
})
}
loading.value = false
}
setFormData()
/**
* 获取日签奖励显示文本
*/
const daySignAwardText = ref([])
const setMemberBenefitsContent = async () => {
const data = await (await getMemberGiftsContent(contentData)).data
daySignAwardText.value = [];
Object.values(data).forEach((el: any) => {
daySignAwardText.value.push(el)
})
}
/**
* 获取连签奖励显示文本
* @param content 请求参数
* @param item 连签奖励数据
* @param index 数组索引
* @param tag 0默认数据加载
*/
const setMemberBenefitsContents = async (content: any, item: any, index: number = 0, tag = 0) => {
const data = await (await getMemberGiftsContent(content)).data
continueSignAwardText.value = [];
Object.values(data).forEach((el: any) => {
continueSignAwardText.value.push(el)
})
newArr.receive_num = item.receive_num
newArr.continue_sign = item.continue_sign
newArr.receive_limit = item.receive_limit
newArr.continue_award = continueSignAwardText.value
if (!isEdit) {
if (tag == 0) {
continueSignAwardTableData.data.splice(index, 1, JSON.parse(JSON.stringify(newArr)))
} else {
continueSignAwardTableData.data.push(JSON.parse(JSON.stringify(newArr)))
}
} else {
continueSignAwardTableData.data.splice(editIndex, 1, JSON.parse(JSON.stringify(newArr)))
}
isEdit = false
editIndex = 0
}
/**
* 设置签到设置
*/
const onSave = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
await formEl.validate((valid) => {
if (valid) {
const save = JSON.parse(JSON.stringify(formData))
setSignConfig(save).then(() => {
loading.value = false
}).catch(() => {
loading.value = false
})
}
})
}
/**
* 打开日签奖励设置页
*/
const daySignAwardSet = () => {
daySignDialog.value = true
}
/**
* 日签奖励设置
*/
const benefitsRef = ref(null)
const setDaySignAward = async () => {
if (!await benefitsRef.value?.verify()) return
console.log(formData.day_award)
daySignDialog.value = false
if (!formData.day_award.hasOwnProperty('balance') && !formData.day_award.hasOwnProperty('point') && formData.day_award.shop_coupon.is_use == 0) {
formData.day_award = ''
}
contentData.gifts = formData.day_award
setMemberBenefitsContent()
}
/**
* 日签奖励删除
*/
const daySignAwardDel = () => {
formData.day_award = ''
daySignAwardText.value = []
}
/**
* 打开连签奖励设置页
*/
const continueSignAwardSet = () => {
continue_award.value = ''
continueSignDialog.value = true
}
/**
* 修改连签奖励设置页
*/
const continueSignAwardModify = (flag: boolean, index: any) => {
isEdit = flag;
editIndex = index;
continue_award.value = formData.continue_award[index]
continueSignDialog.value = true
}
/**
* 连签奖励设置
*/
const continueRef = ref(null)
const setContinueSignAward = async () => {
if (!await continueRef.value?.verify()) return
continueSignDialog.value = false
if (!continue_award.value.hasOwnProperty('balance') && !continue_award.value.hasOwnProperty('point') && continue_award.value.shop_coupon.is_use == 0) {
continue_award.value = ''
}
if (Object.keys(continue_award.value).length > 0) {
const val = JSON.parse(JSON.stringify(continue_award.value))
delete val['continue_sign'];
delete val['continue_tag'];
delete val['receive_limit'];
delete val['receive_num'];
contentData.gifts = val
let index = 0;
if (formData.continue_award.length > 0) {
index = formData.continue_award.length - 1
}
setMemberBenefitsContents(contentData, continue_award.value, 0, 1)
if (!isEdit) {
formData.continue_award.push(JSON.parse(JSON.stringify(continue_award.value)))
} else {
formData.continue_award.splice(editIndex, 1, JSON.parse(JSON.stringify(continue_award.value)))
}
}
}
/**
* 连签奖励删除
*/
const deleteContinueSignAwardEvent = (index: number) => {
continueSignAwardTableData.data.splice(index, 1)
formData.continue_award.splice(index, 1)
}
/**
* 使用默认说明
*/
const defaultExplainEvent = () => {
formData.rule_explain = t('ruleExplainDefault');
}
</script>
<style lang="scss" scoped>
.input-width {
width: 100px;
}
.textarea-width {
width: 400px;
}
.el-form .form-tip {
line-height: 1.5;
margin-top: 5px;
}
.tutorial-btn {
color: var(--el-color-primary);
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center mb-[5px]">
<span class="text-page-title">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none mb-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="memberSignListTableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('memberInfo')" prop="keywords">
<el-input v-model="memberSignListTableData.searchParam.keywords" class="w-[240px]"
:placeholder="t('memberInfoPlaceholder')" />
</el-form-item>
<el-form-item :label="t('createTime')" prop="create_time">
<el-date-picker v-model="memberSignListTableData.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="loadMemberSignList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[10px]">
<el-table :data="memberSignListTableData.data" size="large" v-loading="memberSignListTableData.loading">
<template #empty>
<span>{{ !memberSignListTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="member_id" :label="t('memberId')" min-width="100" :show-overflow-tooltip="true">
<template #default="{ row }">
{{ row.member.member_no }}
</template>
</el-table-column>
<el-table-column :label="t('memberInfo')" min-width="140" :show-overflow-tooltip="true">
<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.member.headimg"
:src="img(row.member.headimg)" alt="">
<img class="w-[50px] h-[50px] mr-[10px]" v-else
src="@/app/assets/images/default_headimg.png" alt="">
<div class="flex flex flex-col">
<span>{{ row.member.nickname || '' }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="mobile" :label="t('mobile')" min-width="90">
<template #default="{ row }">
{{ row.member.mobile || '' }}
</template>
</el-table-column>
<el-table-column :label="t('days')" min-width="110">
<template #default="{ row }">
{{ row.days }}{{ t('day') }}
</template>
</el-table-column>
<el-table-column :label="t('dayAward')" min-width="100">
<template #default="{ row }">
<div v-if="row.day_award">
<div v-if="row.day_award.balance.is_use">{{ row.day_award.balance.content }}</div>
<div v-if="row.day_award.point.is_use">{{ row.day_award.point.content }}</div>
<div v-if="row.day_award.shop_coupon.is_use">{{ row.day_award.shop_coupon.content }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('continueAward')" min-width="100">
<template #default="{ row }">
<div v-if="row.continue_award">
<div v-if="row.continue_award.balance.is_use">{{ row.continue_award.balance.content }}</div>
<div v-if="row.continue_award.point.is_use">{{ row.continue_award.point.content }}</div>
<div v-if="row.continue_award.shop_coupon.is_use">{{
row.continue_award.shop_coupon.content }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="create_time" :show-overflow-tooltip="true" :label="t('createTime')"
min-width="150" />
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="memberSignListTableData.page"
v-model:page-size="memberSignListTableData.limit" layout="total, sizes, prev, pager, next, jumper"
:total="memberSignListTableData.total" @size-change="loadMemberSignList()"
@current-change="loadMemberSignList" />
</div>
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getMemberSignList } from '@/app/api/member'
import { FormInstance } from 'element-plus'
import { img } from '@/utils/common'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const memberSignListTableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
keywords: '',
create_time: '',
member_id: ''
}
})
const searchFormRef = ref<FormInstance>()
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadMemberSignList()
}
/**
* 获取会员账单表列表
*/
const loadMemberSignList = (page: number = 1) => {
memberSignListTableData.loading = true
memberSignListTableData.page = page
getMemberSignList({
page: memberSignListTableData.page,
limit: memberSignListTableData.limit,
...memberSignListTableData.searchParam
}).then(res => {
memberSignListTableData.loading = false
memberSignListTableData.data = res.data.data
memberSignListTableData.total = res.data.total
}).catch(() => {
memberSignListTableData.loading = false
})
}
loadMemberSignList()
/**
* 会员详情
*/
const toMember = (member_id: number) => {
router.push(`/member/detail?id=${member_id}`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,227 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-page-title">{{ pageName }}</span>
<el-button type="primary" @click="addEvent">{{ t('addVerifier') }}</el-button>
</div>
<div class="mt-[10px]">
<el-table :data="verifierTable.data" size="large" v-loading="verifierTable.loading">
<template #empty>
<span>{{ !verifierTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column :label="t('memberInfo')" min-width="120">
<template #default="{ row }">
<div class="flex items-center cursor-pointer " @click="toMember(row.member.member_id)"
v-if="row.member">
<img class="w-[50px] h-[50px] mr-[10px]" v-if="row.member.headimg"
:src="img(row.member.headimg)" alt="">
<img class="w-[50px] h-[50px] mr-[10px]" v-else
src="@/app/assets/images/default_headimg.png" alt="">
<div class="flex flex flex-col">
<span>{{ row.member.nickname || '' }}</span>
<span>{{ row.member.mobile || '' }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('verifyType')" min-width="120">
<template #default="{ row }">
<div class="flex flex-col">
<div v-for="(item, key) in row.verify_type_array" class="my-[3px]" :key="key">
{{ item.verify_type_name }}
</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('createTime')" prop="create_time" min-width="120" />
<el-table-column :label="t('operation')" fixed="right" align="right" width="100">
<template #default="{ row }">
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="verifierTable.page" v-model:page-size="verifierTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="verifierTable.total"
@size-change="loadVerifierList()" @current-change="loadVerifierList" />
</div>
</div>
</el-card>
<el-dialog v-model="showDialog" :title="t('addVerifier')" width="500px" :destroy-on-close="true">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form"
v-loading="addLoading">
<el-form-item :label="t('member')" prop="member_id">
<el-select v-model="formData.member_id" filterable remote reserve-keyword clearable
:placeholder="t('searchPlaceholder')" :remote-method="searchMember" :loading="searchLoading"
class="input-width">
<el-option v-for="item in memberList" :key="item.member_id" :label="item.nickname"
:value="item.member_id" />
</el-select>
</el-form-item>
<el-form-item :label="t('verifyType')" prop="verify_type">
<el-select v-model="formData.verify_type" multiple collapse-tags clearable
:placeholder="t('verifyTypePlaceholder')" class="input-width">
<el-option v-for="(item, index) in verifyTypeList" :key="index" :label="item.name" :value="index" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="addLoading" @click="addVerifiers(formRef)">{{ t('confirm')
}}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { useRouter, useRoute } from 'vue-router'
import { getVerifierList, deleteVerifier, addVerifier, getVerifyTypeList } from '@/app/api/verify'
import { getMemberList } from '@/app/api/member'
import { ElMessageBox, FormInstance } from 'element-plus'
import { img } from '@/utils/common'
const route = useRoute()
const pageName = route.meta.title
const showDialog = ref(false)
const addLoading = ref(false)
const formData: Record<string, any> = reactive({
member_id: '',
verify_type: '',
})
const formRules = reactive({
member_id: [
{ required: true, message: t('memberIdPlaceholder'), trigger: 'blur' }
]
})
const formRef = ref<FormInstance>()
const verifierTable = reactive<any>({
page: 1,
limit: 10,
total: 0,
loading: true,
data: []
})
/**
* 获取核销员表列表
*/
const loadVerifierList = (page: number = 1) => {
verifierTable.loading = true
verifierTable.page = page
getVerifierList({
page: verifierTable.page,
limit: verifierTable.limit,
...verifierTable.searchParam
}).then(res => {
verifierTable.loading = false
verifierTable.data = res.data.data
verifierTable.total = res.data.total
}).catch(() => {
verifierTable.loading = false
})
}
loadVerifierList()
/**
* 添加核销员表
*/
const addEvent = () => {
showDialog.value = true
}
/**
* 删除核销员表
*/
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('verifierDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
deleteVerifier(id).then(() => {
loadVerifierList()
}).catch(() => {
})
})
}
/**
* 添加核销员
* @param formEl
*/
const addVerifiers = async (formEl: FormInstance | undefined) => {
if (addLoading.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
addLoading.value = true
addVerifier(formData).then(res => {
addLoading.value = false
showDialog.value = false
formData.member_id = ''
formData.verify_type = ''
loadVerifierList()
}).catch(() => {
addLoading.value = false
})
}
})
}
/**
* 查询会员信息
*/
const memberList = ref<any>([])
const searchLoading = ref(false)
const searchMember = (query: string) => {
if (query) {
searchLoading.value = true
getMemberList({ keyword: query }).then(res => {
memberList.value = res.data.data
searchLoading.value = false
}).catch()
} else {
memberList.value = []
searchLoading.value = false
}
}
/**
* 获取核销类型
*/
const verifyTypeList = ref<any>([])
const setVerifyTypeList = () => {
getVerifyTypeList().then(res => {
verifyTypeList.value = res.data
}).catch()
}
setVerifyTypeList();
const router = useRouter()
/**
* 会员详情
*/
const toMember = (member_id: number) => {
router.push(`/member/detail?id=${member_id}`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,143 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-page-title">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none table-search-wrap" shadow="never">
<el-card class="box-card !border-none table-search-wrap" shadow="never">
<el-form :inline="true" :model="recordTable.searchParam" ref="searchFormRef">
<el-form-item :label="t('verifyCode')" prop="code">
<el-input v-model="recordTable.searchParam.code" :placeholder="t('verifyCodePlaceholder')" />
</el-form-item>
<el-form-item :label="t('verifyType')" prop="type">
<el-select v-model="recordTable.searchParam.type" clearable
:placeholder="t('verifyTypePlaceholder')" class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item.name" :value="key" v-for="(item, key) in verifyTypeList"
:key="key" />
</el-select>
</el-form-item>
<el-form-item :label="t('verifyTime')" prop="create_time">
<el-date-picker v-model="recordTable.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="loadRecordList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[10px]">
<el-table :data="recordTable.data" size="large" v-loading="recordTable.loading">
<template #empty>
<span>{{ !recordTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="code" :show-overflow-tooltip="true" :label="t('verifyCode')" align="left"
min-width="150" />
<el-table-column prop="type_name" :label="t('verifyType')" align="left" min-width="150" />
<el-table-column :label="t('verifyTime')" min-width="180" align="center"
:show-overflow-tooltip="true">
<template #default="{ row }">
{{ row.create_time || '' }}
</template>
</el-table-column>
<el-table-column prop="member.nickname" :label="t('verifyer')" min-width="180" align="center">
</el-table-column>
<el-table-column :label="t('operation')" align="right" fixed="right" width="100">
<template #default="{ row }">
<div class="flex justify-end">
<el-button type="primary" link @click="detailEvent(row)">{{ t('详情') }}</el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="recordTable.page" v-model:page-size="recordTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="recordTable.total"
@size-change="loadRecordList()" @current-change="loadRecordList" />
</div>
</div>
</el-card>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { useRoute, useRouter } from 'vue-router'
import { FormInstance } from 'element-plus'
import { getVerifyRecord, getVerifyTypeList } from '@/app/api/verify'
import { img } from '@/utils/common'
const route = useRoute()
const pageName = route.meta.title
const router = useRouter()
const recordTable = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
code: '',
type: '',
create_time: []
}
})
const searchFormRef = ref<FormInstance>()
/**
* 获取核销记录列表
*/
const loadRecordList = (page: number = 1) => {
recordTable.loading = true
recordTable.page = page
getVerifyRecord({
page: recordTable.page,
limit: recordTable.limit,
...recordTable.searchParam
}).then(res => {
recordTable.loading = false
recordTable.data = res.data.data
recordTable.total = res.data.total
}).catch(() => {
recordTable.loading = false
})
}
loadRecordList()
/**
* 获取核销类型
*/
const verifyTypeList = ref<any>([])
const setVerifyTypeList = () => {
getVerifyTypeList().then(res => {
verifyTypeList.value = res.data
}).catch()
}
setVerifyTypeList();
//
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadRecordList()
}
//
const detailEvent = (data: any)=>{
router.push(`/marketing/verify/detail?code=${data.code}`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,125 @@
<template>
<div class="main-container" v-loading="loading">
<div class="detail-head !ml-[20px] !mb-[5px]">
<div class="left" @click="router.push({ path: '/marketing/verify' })">
<span class="iconfont iconxiangzuojiantou !text-xs"></span>
<span class="ml-[1px]">{{t('returnToPreviousPage')}}</span>
</div>
<span class="adorn">|</span>
<span class="right">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none" shadow="never">
<h3 class="panel-title">{{ t('核销信息') }}</h3>
<div class="flex items-center mt-[15px]">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('核销类型') }}</span>
<span class="text-[14px] text-[#666666]">
{{ verifyData.type_name }}
</span>
</div>
<div class="flex items-center mt-[15px]">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('核销状态') }}</span>
<span class="text-[14px] text-[#666666]">
已核销
</span>
</div>
<div class="flex items-center mt-[15px]">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('核销人员') }}</span>
<span class="text-[14px] text-[#666666]">
{{ verifyData.member ? verifyData.member.nickname : '--' }}
</span>
</div>
<div class="flex items-center mt-[15px]">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ t('核销时间') }}</span>
<span class="text-[14px] text-[#666666]">
{{verifyData.create_time}}
</span>
</div>
<div class="flex items-center mt-[15px]" v-for="(item,index) in verifyContentData.fixed" :key="index">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ item.title }}</span>
<span class="text-[14px] text-[#666666]">
{{ item.value }}
</span>
</div>
</el-card>
<el-card class="box-card !border-none" shadow="never" v-for="(item,index) in verifyContentData.diy" :key="index">
<h3 class="panel-title">{{ item.title }}</h3>
<div class="flex items-center mt-[15px]" v-for="(subItem,subIndex) in item.list" :key="subIndex">
<span class="text-[14px] w-[130px] text-right mr-[20px]">{{ subItem.title }}</span>
<span class="text-[14px] text-[#666666]">
{{ subItem.value }}
</span>
</div>
</el-card>
<el-card class="box-card !border-none" shadow="never">
<h3 class="panel-title">{{ t('商品信息') }}</h3>
<el-table :data="verifyGoodsList" size="large">
<el-table-column :label="t('商品名称')" align="left" width="300">
<template #default="{ row }">
<div class="flex">
<div class="flex items-center shrink-0">
<img class="w-[50px] h-[50px] mr-[10px]" :src="img(row.cover)" />
</div>
<div class="flex flex-col">
<p class="multi-hidden text-[14px]">{{ row.name }}</p>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="num" :label="t('数量')" min-width="50" align="right" />
</el-table>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getVerifyDetail } from '@/app/api/verify'
import { ElMessage } from 'element-plus'
import { useRouter, useRoute } from 'vue-router'
import { img } from '@/utils/common'
import PointEdit from '@/app/views/member/components/member-point-edit.vue'
import BalanceEdit from '@/app/views/member/components/member-balance-edit.vue'
import EditMember from '@/app/views/member/components/edit-member.vue'
import useAppStore from '@/stores/modules/app'
const route = useRoute()
const pageName = route.meta.title
const appStore = useAppStore()
const router = useRouter()
const loading = ref(true)
//
const code: any = route.query.code
let verifyData: any = ref({})
let verifyContentData: any = ref({})
let verifyGoodsList: any = ref([])
const getVerifyDetailFn = async () => {
loading.value = true
if (code) {
const data = await (await getVerifyDetail(code)).data
if (!data || Object.keys(data).length == 0) {
ElMessage.error(t('memberNull'))
setTimeout(() => {
router.go(-1)
}, 2000)
return false
}
verifyData.value = data;
verifyContentData.value = data.value.content || {}
verifyGoodsList.value = data.value.list || []
loading.value = false
} else {
loading.value = false
}
}
getVerifyDetailFn()
</script>
<style lang="scss" scoped>
</style>

View File

@ -8,8 +8,7 @@
<el-row class="flex">
<el-col :span="8" class="min-w-[100px]">
<div class="statistic-card">
<el-statistic
:value="balanceStatistics.money && balanceStatistics.balance ? (Number.parseFloat(balanceStatistics.money) + Number.parseFloat(balanceStatistics.balance)).toFixed(2) : '0.00'"></el-statistic>
<el-statistic :value="balanceStatistics.money && balanceStatistics.balance ? (Number.parseFloat(balanceStatistics.money) + Number.parseFloat(balanceStatistics.balance)).toFixed(2) : '0.00'"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('totalAllBalance') }}</span>

View File

@ -8,8 +8,7 @@
<el-row class="flex">
<el-col :span="6" class="min-w-[100px]">
<div class="statistic-card">
<el-statistic
:value="commissionStatistics.total_commission ? Number.parseFloat(commissionStatistics.total_commission).toFixed(2) : '0.00'"></el-statistic>
<el-statistic :value="commissionStatistics.total_commission ? Number.parseFloat(commissionStatistics.total_commission).toFixed(2) : '0.00'"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('totalCommission') }}</span>
@ -19,8 +18,7 @@
</el-col>
<el-col :span="6" class="min-w-[100px]">
<div class="statistic-card">
<el-statistic
:value="commissionStatistics.commission ? Number.parseFloat(commissionStatistics.commission).toFixed(2) : '0.00'"></el-statistic>
<el-statistic :value="commissionStatistics.commission ? Number.parseFloat(commissionStatistics.commission).toFixed(2) : '0.00'"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('commission') }}</span>
@ -30,8 +28,7 @@
</el-col>
<el-col :span="6" class="min-w-[100px]">
<div class="statistic-card">
<el-statistic
:value="commissionStatistics.withdrawn_commission ? Number.parseFloat(commissionStatistics.withdrawn_commission).toFixed(2) : '0.00'"></el-statistic>
<el-statistic :value="commissionStatistics.withdrawn_commission ? Number.parseFloat(commissionStatistics.withdrawn_commission).toFixed(2) : '0.00'"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('withdrawnCommission') }}</span>
@ -41,8 +38,7 @@
</el-col>
<el-col :span="6" class="min-w-[100px]">
<div class="statistic-card">
<el-statistic
:value="commissionStatistics.commission_cash_outing ? Number.parseFloat(commissionStatistics.commission_cash_outing).toFixed(2) : '0.00'"></el-statistic>
<el-statistic :value="commissionStatistics.commission_cash_outing ? Number.parseFloat(commissionStatistics.commission_cash_outing).toFixed(2) : '0.00'"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('cashOutingCommission') }}</span>

View File

@ -41,6 +41,7 @@ import { filterNumber } from '@/utils/common'
const showDialog = ref(false)
const loading = ref(false)
const repeat = ref(false)
let popTitle: string = ''
let memberNo: string = ''
@ -67,7 +68,6 @@ const formRules = computed(() => {
{ required: true, message: t('memberNoPlaceholder'), trigger: 'blur' },
{ validator: memberNoVerify, trigger: 'blur' }
],
mobile: [
{ required: true, message: t('mobilePlaceholder'), trigger: 'blur' },
{ validator: mobileVerify, trigger: 'blur' }
@ -129,14 +129,19 @@ const confirm = async (formEl: FormInstance | undefined) => {
if (valid) {
loading.value = true
if (repeat.value) return
repeat.value = true
const data = formData
save(data).then(res => {
loading.value = false
repeat.value = false
showDialog.value = false
emit('complete')
}).catch(() => {
loading.value = false
repeat.value = false
})
}
})

View File

@ -0,0 +1,100 @@
<template>
<el-form ref="formRef" :model="formData" :rules="formRules">
<el-form-item label="" prop="discount" class="!mb-[10px]">
<div>
<div class="flex items-center">
<el-checkbox v-model="formData.is_use" :true-label="1" :false-label="0" label="" size="large" />
<span class="ml-[10px] el-form-item__label">消费折扣</span>
<div class="w-[120px]" v-show="formData.is_use">
<el-input v-model.trim="formData.discount" clearable >
<template #append></template>
</el-input>
</div>
</div>
<div class="text-sm text-gray-400 mb-[5px]">会员购买产品默认折扣需要商品设置参与会员折扣有效</div>
</div>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue'
import { FormRules } from 'element-plus'
import Test from '@/utils/test'
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {}
}
}
})
const emits = defineEmits(['update:modelValue'])
const formData = ref({
is_use: 0,
discount: ''
})
const formRef = ref(null)
const formRules = reactive<FormRules>({
discount: [
{
validator: (rule: any, value: any, callback: any) => {
if (formData.value.is_use) {
if (Test.empty(formData.value.discount)) {
callback('请输入折扣')
}
if (!Test.decimal(formData.value.discount, 1)) {
callback('折扣格式错误')
}
if (parseFloat(formData.value.discount) < 0.1 || parseFloat(formData.value.discount) > 9.9) {
callback('折扣只能输入0.1~9.9之间的值')
}
if (formData.value.discount <= 0) {
callback('折扣不能小于等于0')
}
callback()
} else {
callback()
}
}
}
]
})
const value = computed({
get () {
return props.modelValue
},
set (value) {
emits('update:modelValue', value)
}
})
watch(() => value.value, (nval, oval) => {
if ((!oval || !Object.keys(oval).length) && Object.keys(nval).length) {
formData.value = value.value
}
}, { immediate: true })
watch(() => formData.value, () => {
value.value = formData.value
}, { deep: true })
const verify = async () => {
let verify = true
await formRef.value?.validate((valid) => {
verify = valid
})
return verify
}
defineExpose({
verify
})
</script>
<style lang="scss" scoped>
</style>

View File

@ -31,6 +31,7 @@ import { filterNumber } from '@/utils/common'
const showDialog = ref(false)
const loading = ref(false)
const repeat = ref(false)
let popTitle:string = ''
/**
@ -41,7 +42,6 @@ const initialFormData = {
label_name: '',
memo: '',
sort: 0
}
const formData: Record<string, any> = reactive({ ...initialFormData })
@ -83,14 +83,19 @@ const confirm = async (formEl: FormInstance | undefined) => {
if (valid) {
loading.value = true
if (repeat.value) return
repeat.value = true
const data = formData
save(data).then(res => {
loading.value = false
repeat.value = false
showDialog.value = false
emit('complete')
}).catch(() => {
loading.value = false
repeat.value = false
// showDialog.value = false
})
}

View File

@ -1,7 +1,7 @@
<template>
<el-dialog v-model="showDialog" :title="title || t('updateMember')" width="500px" :destroy-on-close="true">
<el-form :model="saveData" label-width="90px" :rules="formRules" class="page-form" v-loading="loading">
<el-form :model="saveData" label-width="90px" :rules="formRules" ref="formRef" class="page-form" v-loading="loading">
<el-form-item :label="t('headimg')" v-if="type == 'headimg'">
<upload-image v-model="saveData.headimg" />
</el-form-item>
@ -21,6 +21,15 @@
<el-option :label="item['label_name']" :value="item['label_id']" v-for="(item,index) in labelSelectData" :key="index"/>
</el-select>
</el-form-item>
<div v-if="type == 'member_level'">
<el-form-item :label="t('memberLevelUpdate')" prop="member_level">
<el-select v-model="saveData.member_level" :placeholder="t('memberLevelPlaceholder')" class="input-width">
<el-option :label="t('memberLevelPlaceholder')" :value="0" />
<el-option :label="item['level_name']" :value="item['level_id']" v-for="(item,index) in levelSelectData" :key="index"/>
</el-select>
<div class="text-sm text-gray-400">{{ t('memberLevelUpdateTips') }}</div>
</el-form-item>
</div>
</el-form>
<template #footer>
@ -33,10 +42,11 @@
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { ref, reactive, computed } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { editMemberDetail, getMemberLabelAll } from '@/app/api/member'
import { editMemberDetail, getMemberLabelAll, getMemberLevelAll } from '@/app/api/member'
import Test from "@/utils/test";
//
const type = ref('')
@ -45,6 +55,9 @@ const title = ref('')
const memberId = ref('')
const showDialog = ref(false)
const loading = ref(false)
const repeat = ref(false)
const formRef = ref(null)
const sexSelectData = ref([
{
id: 0,
@ -66,6 +79,26 @@ const getMemberLabelAllFn = async () => {
}
getMemberLabelAllFn()
const levelSelectData = ref([])
getMemberLevelAll().then(({ data }) => {
levelSelectData.value = data
})
const formRules = computed(() => {
return {
member_level: [
{
validator: (rule: any, value: any, callback: Function) => {
if (Test.empty(saveData.member_level)) {
callback(t('memberLevelPlaceholder'))
}
callback()
}
}
]
}
})
/**
* 表单数据
*/
@ -73,6 +106,7 @@ const initialFormData = {
headimg: '',
nickname: '',
member_label: '',
member_level: '',
sex: '',
birthday: ''
}
@ -85,20 +119,29 @@ const emit = defineEmits(['complete'])
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
loading.value = true
const data = ref({
member_id: memberId.value,
field: type.value,
value: saveData[type.value]
})
await formRef.value?.validate((valid) => {
if (valid) {
loading.value = true
editMemberDetail(data.value).then(res => {
loading.value = false
showDialog.value = false
emit('complete')
}).catch(() => {
loading.value = false
// showDialog.value = false
if (repeat.value) return
repeat.value = true
const data = ref({
member_id: memberId.value,
field: type.value,
value: saveData[type.value]
})
editMemberDetail(data.value).then(res => {
loading.value = false
repeat.value = false
showDialog.value = false
emit('complete')
}).catch(() => {
loading.value = false
repeat.value = false
})
}
})
}

View File

@ -0,0 +1,93 @@
<template>
<el-form ref="formRef" :model="formData" :rules="formRules">
<el-form-item label="" prop="money">
<div class="flex items-center">
<el-checkbox v-model="formData.is_use" :true-label="1" :false-label="0" label="" size="large" />
<span class="ml-[10px] el-form-item__label"></span>
<div class="w-[70px]">
<el-input v-model.trim="formData.money" clearable />
</div>
<span class="ml-[15px] el-form-item__label">元红包</span>
</div>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue'
import { FormRules } from 'element-plus'
import Test from '@/utils/test'
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {}
}
}
})
const emits = defineEmits(['update:modelValue'])
const formData = ref({
is_use: 0,
money: ''
})
const formRef = ref(null)
const formRules = reactive<FormRules>({
money: [
{
validator: (rule: any, value: any, callback: any) => {
if (formData.value.is_use) {
if (Test.empty(formData.value.money)) {
callback('请输入红包金额')
}
if (!Test.amount(formData.value.money)) {
callback('红包金额格式错误')
}
if (formData.value.money <= 0) {
callback('红包金额不能小于等于0')
}
callback()
} else {
callback()
}
}
}
]
})
const value = computed({
get () {
return props.modelValue
},
set (value) {
emits('update:modelValue', value)
}
})
watch(() => value.value, (nval, oval) => {
if ((!oval || !Object.keys(oval).length) && Object.keys(nval).length) {
formData.value = value.value
}
}, { immediate: true })
watch(() => formData.value, () => {
value.value = formData.value
}, { deep: true })
const verify = async () => {
let verify = true
await formRef.value?.validate((valid) => {
verify = valid
})
return verify
}
defineExpose({
verify
})
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,92 @@
<template>
<el-form ref="formRef" :model="formData" :rules="formRules">
<el-form-item label="" prop="num">
<el-checkbox v-model="formData.is_use" :true-label="1" :false-label="0" label="" size="large" />
<span class="ml-[10px] el-form-item__label"></span>
<div class="w-[70px]">
<el-input v-model.trim="formData.num" clearable />
</div>
<span class="ml-[15px] el-form-item__label">积分</span>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue'
import { FormRules } from 'element-plus'
import Test from '@/utils/test'
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {}
}
}
})
const emits = defineEmits(['update:modelValue'])
const formData = ref({
is_use: 0,
num: ''
})
const formRef = ref(null)
const formRules = reactive<FormRules>({
num: [
{
validator: (rule: any, value: any, callback: Function) => {
if (formData.value.is_use) {
if (Test.empty(formData.value.num)) {
callback('请输入发放积分数量')
}
if (!Test.digits(formData.value.num)) {
callback('积分数量格式错误')
}
if (formData.value.num <= 0) {
callback('积分数量不能小于等于0')
}
callback()
} else {
callback()
}
}
}
]
})
const value = computed({
get () {
return props.modelValue
},
set (value) {
emits('update:modelValue', value)
}
})
watch(() => value.value, (nval, oval) => {
if ((!oval || !Object.keys(oval).length) && Object.keys(nval).length) {
formData.value = value.value
}
}, { immediate: true })
watch(() => formData.value, () => {
value.value = formData.value
}, { deep: true })
const verify = async () => {
let verify = true
await formRef.value?.validate((valid) => {
verify = valid
})
return verify
}
defineExpose({
verify
})
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,99 @@
<template>
<el-form ref="formRef" :model="formData" :rules="formRules">
<el-form-item label="" prop="growth">
<div>
<div>
<span class="el-form-item__label">会员注册</span>
<el-switch v-model="formData.is_use" :active-value="1" :inactive-value="0"/>
</div>
<div class="flex mt-[10px]" v-show="formData.is_use">
<span class="el-form-item__label">发放</span>
<div class="w-[70px]">
<el-input v-model.number.trim="formData.growth" clearable />
</div>
<span class="ml-[10px] el-form-item__label">成长值</span>
</div>
</div>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue'
import { FormRules } from 'element-plus'
import Test from '@/utils/test'
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {}
}
}
})
const emits = defineEmits(['update:modelValue'])
const formData = ref({
is_use: 0,
growth: ''
})
const formRef = ref(null)
const formRules = reactive<FormRules>({
growth: [
{
validator: (rule: any, value: any, callback: Function) => {
if (formData.value.is_use) {
if (Test.empty(formData.value.growth)) {
callback('请输入发放成长值数量')
}
if (!Test.digits(formData.value.growth)) {
callback('成长值数量格式错误')
}
if (formData.value.growth <= 0) {
callback('成长值数量不能小于等于0')
}
callback()
} else {
callback()
}
}
}
]
})
const value = computed({
get () {
return props.modelValue
},
set (value) {
emits('update:modelValue', value)
}
})
watch(() => value.value, (nval, oval) => {
if ((!oval || !Object.keys(oval).length) && Object.keys(nval).length) {
formData.value = value.value
}
}, { immediate: true })
watch(() => formData.value, () => {
value.value = formData.value
}, { deep: true })
const verify = async () => {
let verify = true
await formRef.value?.validate((valid) => {
verify = valid
})
return verify
}
defineExpose({
verify
})
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,6 +1,6 @@
<template>
<el-dialog v-model="showDialog" :title="t('adjustBalance')" width="550px" :destroy-on-close="true">
<el-form :model="formData" label-width="110px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form :model="formData" label-width="110px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading" @submit.enter.prevent>
<el-form-item :label="t('currBalance')" >
<div class="input-width"> {{ formData.balance }} </div>
@ -40,6 +40,7 @@ import { adjustBalance } from '@/app/api/member'
const showDialog = ref(false)
const loading = ref(true)
const repeat = ref(false)
/**
* 表单数据
@ -91,15 +92,21 @@ const confirm = async (formEl: FormInstance | undefined) => {
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
if (repeat.value) return
repeat.value = true
formData.account_data = Math.abs(parseFloat(formData.adjust)) * formData.adjust_type
const data = formData
adjustBalance(data).then(res => {
loading.value = false
repeat.value = false
showDialog.value = false
emit('complete')
}).catch(() => {
loading.value = false
repeat.value = false
// showDialog.value = false
})
}

View File

@ -0,0 +1,69 @@
<template>
<div v-for="item in benefits">
<component :is="item.component" v-model="formData[item.key]" ref="benefitsRefs" v-if="item.component"/>
</div>
</template>
<script lang="ts" setup>
import { ref, defineAsyncComponent, computed, watch } from 'vue'
import { t } from '@/lang'
import { getBenefitsDict } from '@/app/api/member'
const benefits = ref({})
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {}
}
}
})
const emits = defineEmits(['update:modelValue'])
const formData = ref({})
const value = computed({
get () {
return props.modelValue
},
set (value) {
emits('update:modelValue', value)
}
})
const benefitsRefs = ref([])
watch(() => value.value, (nval, oval) => {
if ((!oval || !Object.keys(oval).length) && Object.keys(nval).length) {
formData.value = value.value
}
}, { immediate: true })
watch(() => formData.value, () => {
value.value = formData.value
}, { deep: true })
const modules: any = import.meta.glob('@/**/*.vue')
getBenefitsDict().then(({ data }) => {
Object.keys(data).forEach((key: string) => {
data[key].component && (data[key].component = defineAsyncComponent(modules[data[key].component]))
})
benefits.value = data
})
/**
* 验证
*/
const verify = async () => {
let verify = true
for (let i = 0; i < benefitsRefs.value.length; i++) {
const item = benefitsRefs.value[i]
!await item.verify() && (verify = false)
}
return verify
}
defineExpose({
verify
})
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,12 +1,10 @@
<template>
<el-dialog v-model="showDialog" :title="t('moneyInfo')" width="550px" :destroy-on-close="true">
<el-form :model="formData" label-width="110px" ref="formRef" :rules="formRules" class="page-form"
v-loading="loading">
<el-form :model="formData" label-width="110px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form-item :label="t('headimg')">
<div class="flex items-center">
<img class="w-[50px] h-[50px] mr-[10px]" v-if="formData.member.headimg"
:src="img(formData.member.headimg)" alt="">
<img class="w-[50px] h-[50px] mr-[10px]" v-if="formData.member.headimg" :src="img(formData.member.headimg)" alt="">
<img class="w-[50px] h-[50px] mr-[10px]" v-else src="@/app/assets/images/default_headimg.png" alt="">
</div>
</el-form-item>

View File

@ -0,0 +1,65 @@
<template>
<div v-for="item in gifts">
<component :is="item.component" v-model="formData[item.key]" ref="giftRefs" v-if="item.component"/>
</div>
</template>
<script lang="ts" setup>
import { ref, defineAsyncComponent, computed, watch } from 'vue'
import { getGiftDict } from '@/app/api/member'
const gifts = ref({})
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {}
}
}
})
const emits = defineEmits(['update:modelValue'])
const formData = ref({})
const value = computed({
get () {
return props.modelValue
},
set (value) {
emits('update:modelValue', value)
}
})
const giftRefs = ref([])
watch(() => value.value, (nval, oval) => {
if ((!oval || !Object.keys(oval).length) && Object.keys(nval).length) {
formData.value = value.value
}
}, { immediate: true })
watch(() => formData.value, () => {
value.value = formData.value
}, { deep: true })
const modules: any = import.meta.glob('@/**/*.vue')
getGiftDict().then(({ data }) => {
Object.keys(data).forEach((key: string) => {
data[key].component && (data[key].component = defineAsyncComponent(modules[data[key].component]))
})
gifts.value = data
})
const verify = async () => {
let verify = true
for (let i = 0; i < giftRefs.value.length; i++) {
const item = giftRefs.value[i]
!await item.verify() && (verify = false)
}
return verify
}
defineExpose({
verify
})
</script>
<style lang="scss" scoped>
</style>

View File

@ -68,7 +68,6 @@ const initialFormData = {
nickname: '',
related_id: '',
username: ''
}
const formData: Record<string, any> = reactive({ ...initialFormData })

View File

@ -1,6 +1,6 @@
<template>
<el-dialog v-model="showDialog" :title="t('adjustPoint')" width="550px" :destroy-on-close="true">
<el-form :model="formData" label-width="110px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form :model="formData" label-width="110px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading" @submit.enter.prevent>
<el-form-item :label="t('currPoint')" >
<div class="input-width"> {{ formData.point }} </div>
@ -40,6 +40,7 @@ import { adjustPoint } from '@/app/api/member'
const showDialog = ref(false)
const loading = ref(true)
const repeat = ref(false)
/**
* 表单数据
@ -93,15 +94,21 @@ const confirm = async (formEl: FormInstance | undefined) => {
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
if (repeat.value) return
repeat.value = true
formData.account_data = Math.abs(parseFloat(formData.adjust)) * formData.adjust_type;
const data = formData
adjustPoint(data).then(res => {
loading.value = false
repeat.value = false
showDialog.value = false
emit('complete')
}).catch(() => {
loading.value = false
repeat.value = false
// showDialog.value = false
})
}

View File

@ -0,0 +1,99 @@
<template>
<el-form ref="formRef" :model="formData" :rules="formRules">
<el-form-item label="" prop="point">
<div>
<div>
<span class="el-form-item__label">会员注册</span>
<el-switch v-model="formData.is_use" :active-value="1" :inactive-value="0"/>
</div>
<div class="flex mt-[10px]" v-show="formData.is_use">
<span class="el-form-item__label">发放</span>
<div class="w-[70px]">
<el-input v-model.number.trim="formData.point" clearable />
</div>
<span class="ml-[10px] el-form-item__label">积分</span>
</div>
</div>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue'
import { FormRules } from 'element-plus'
import Test from '@/utils/test'
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {}
}
}
})
const emits = defineEmits(['update:modelValue'])
const formData = ref({
is_use: 0,
point: ''
})
const formRef = ref(null)
const formRules = reactive<FormRules>({
point: [
{
validator: (rule: any, value: any, callback: Function) => {
if (formData.value.is_use) {
if (Test.empty(formData.value.point)) {
callback('请输入发放积分数量')
}
if (!Test.digits(formData.value.point)) {
callback('积分数量格式错误')
}
if (formData.value.point <= 0) {
callback('积分数量不能小于等于0')
}
callback()
} else {
callback()
}
}
}
]
})
const value = computed({
get () {
return props.modelValue
},
set (value) {
emits('update:modelValue', value)
}
})
watch(() => value.value, (nval, oval) => {
if ((!oval || !Object.keys(oval).length) && Object.keys(nval).length) {
formData.value = value.value
}
}, { immediate: true })
watch(() => formData.value, () => {
value.value = formData.value
}, { deep: true })
const verify = async () => {
let verify = true
await formRef.value?.validate((valid) => {
verify = valid
})
return verify
}
defineExpose({
verify
})
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,168 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center mb-[5px]">
<span class="text-page-title">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none mb-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="memberAccountLogTableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('fromType')" prop="from_type">
<el-select v-model="memberAccountLogTableData.searchParam.from_type" clearable :placeholder="t('fromTypePlaceholder')" class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item.name" :value="key" v-for="(item, key) in fromTypeList" />
</el-select>
</el-form-item>
<el-form-item :label="t('createTime')" prop="create_time">
<el-date-picker v-model="memberAccountLogTableData.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="loadMemberAccountLogList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[16px]">
<el-table :data="memberAccountLogTableData.data" size="large" v-loading="memberAccountLogTableData.loading">
<template #empty>
<span>{{ !memberAccountLogTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="member_id" :label="t('memberId')" min-width="110" :show-overflow-tooltip="true">
<template #default="{ row }">
{{ row.member.member_no }}
</template>
</el-table-column>
<el-table-column :label="t('memberInfo')" min-width="150" :show-overflow-tooltip="true">
<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.member.headimg" :src="img(row.member.headimg)" alt="">
<img class="w-[50px] h-[50px] mr-[10px]" v-else src="@/app/assets/images/default_headimg.png" alt="">
<div class="flex flex flex-col">
<span>{{ row.member.nickname || '' }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="mobile" :label="t('mobile')" min-width="100">
<template #default="{ row }">
{{ row.member.mobile || '' }}
</template>
</el-table-column>
<el-table-column prop="account_data" :label="t('accountData')" min-width="110" align="right">
<template #default="{ row }">
<span v-if="row.account_data >= 0">+{{ row.account_data }}</span>
<span v-else>{{ row.account_data }}</span>
</template>
</el-table-column>
<el-table-column prop="account_sum" :label="t('accountSum')" min-width="120" align="right" />
<el-table-column prop="from_type_name" :label="t('fromType')" min-width="180" align="center" />
<el-table-column prop="memo" :label="t('memo')" min-width="300" align="left" :show-overflow-tooltip="true"/>
<el-table-column prop="create_time" :show-overflow-tooltip="true" :label="t('createTime')" min-width="150" />
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="memberAccountLogTableData.page"
v-model:page-size="memberAccountLogTableData.limit" layout="total, sizes, prev, pager, next, jumper"
:total="memberAccountLogTableData.total" @size-change="loadMemberAccountLogList()"
@current-change="loadMemberAccountLogList" />
</div>
</div>
</el-card>
<point-info ref="pointDialog" @complete="loadMemberAccountLogList" />
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getChangeTypeList, getGrowthList } from '@/app/api/member'
import { FormInstance } from 'element-plus'
import { img } from '@/utils/common'
import pointInfo from '@/app/views/member/components/member-point-info.vue'
import { useRouter, useRoute } from 'vue-router'
const route = useRoute()
const memberId: number = parseInt(route.query.id || 0)
const pageName = route.meta.title
const memberAccountLogTableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
keywords: '',
from_type: '',
create_time: '',
mobile: '',
member_id: memberId
}
})
const fromTypeList = ref([])
const setFromTypeList = async () => {
fromTypeList.value = await (await getChangeTypeList('growth')).data
}
setFromTypeList()
const searchFormRef = ref<FormInstance>()
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadMemberAccountLogList()
}
/**
*
* 获取会员账单表列表
*/
const loadMemberAccountLogList = (page: number = 1) => {
memberAccountLogTableData.loading = true
memberAccountLogTableData.page = page
getGrowthList({
page: memberAccountLogTableData.page,
limit: memberAccountLogTableData.limit,
...memberAccountLogTableData.searchParam
}).then(res => {
memberAccountLogTableData.loading = false
memberAccountLogTableData.data = res.data.data
memberAccountLogTableData.total = res.data.total
}).catch(() => {
memberAccountLogTableData.loading = false
})
}
loadMemberAccountLogList()
const pointDialog: Record<string, any> | null = ref(null)
/**
* 查看详情
* @param data
*/
const infoEvent = (data: any) => {
pointDialog.value.setFormData(data)
pointDialog.value.showDialog = true
}
const router = useRouter()
/**
* 会员详情
*/
const toMember = (memberId: number) => {
router.push(`/member/detail?id=${memberId}`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,153 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-page-title">{{pageName}}</span>
<el-button type="primary" @click="addEvent">{{ t('addMemberLevel') }}</el-button>
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="memberLevelTableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('levelName')" prop="level_name">
<el-input v-model.trim="memberLevelTableData.searchParam.level_name" :placeholder="t('levelNamePlaceholder')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadMemberLevelList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[10px]">
<el-table :data="memberLevelTableData.data" size="large" v-loading="memberLevelTableData.loading">
<template #empty>
<span>{{ !memberLevelTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="level_name" :label="t('levelName')" min-width="120" />
<el-table-column prop="growth" :label="t('growth')" min-width="120" />
<el-table-column :label="t('levelBenefits')" min-width="120" :show-overflow-tooltip="true">
<template #default="{ row }">
<div>
<template v-for="item in row.level_benefits">
<div v-if="item.content">{{ item.content }}</div>
</template>
</div>
</template>
</el-table-column>
<el-table-column :label="t('levelGifts')" min-width="120" >
<template #default="{ row }">
<div>
<template v-for="item in row.level_gifts">
<div v-if="item.content">{{ item.content }}</div>
</template>
</div>
</template>
</el-table-column>
<el-table-column prop="member_num" :label="t('memberNumber')" min-width="120" />
<el-table-column :label="t('operation')" align="right" fixed="right" width="130">
<template #default="{ row }">
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.level_id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="memberLevelTableData.page" v-model:page-size="memberLevelTableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="memberLevelTableData.total"
@size-change="loadMemberLevelList()" @current-change="loadMemberLevelList" />
</div>
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getMemberLevelPageList, deleteMemberLevel } from '@/app/api/member'
import { ElMessageBox, FormInstance } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const memberLevelTableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
level_name: ''
}
})
const searchFormRef = ref<FormInstance>()
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadMemberLevelList()
}
/**
* 获取会员标签列表
*/
const loadMemberLevelList = (page: number = 1) => {
memberLevelTableData.loading = true
memberLevelTableData.page = page
getMemberLevelPageList({
page: memberLevelTableData.page,
limit: memberLevelTableData.limit,
...memberLevelTableData.searchParam
}).then(res => {
memberLevelTableData.loading = false
memberLevelTableData.data = res.data.data
memberLevelTableData.total = res.data.total
}).catch(() => {
memberLevelTableData.loading = false
})
}
loadMemberLevelList()
/**
* 添加会员标签
*/
const addEvent = () => {
router.push({ path: '/member/level_edit' })
}
/**
* 编辑会员标签
* @param data
*/
const editEvent = (data: any) => {
router.push({ path: '/member/level_edit', query: { id: data.level_id } })
}
/**
* 删除会员标签
*/
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('memberLevelDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
deleteMemberLevel(id).then(() => {
loadMemberLevelList()
}).catch(() => {
})
})
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,153 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never" v-loading="loading">
<div class="detail-head !ml-[20px] !mb-[5px]">
<div class="left" @click="router.push({ path: '/member/level' })">
<span class="iconfont iconxiangzuojiantou !text-xs"></span>
<span class="ml-[1px]">{{t('returnToPreviousPage')}}</span>
</div>
<span class="adorn">|</span>
<span class="right">{{ pageName }}</span>
</div>
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" >
<el-card class="box-card !border-none" shadow="never">
<h3 class="panel-title !text-sm">{{ t('basicInfo') }}</h3>
</el-card>
<el-form-item :label="t('levelName')" prop="level_name">
<el-input v-model.trim="formData.level_name" :placeholder="t('levelNamePlaceholder')" class="input-width" maxlength="20" show-word-limit clearable />
</el-form-item>
<el-form-item :label="t('remark')" prop="remark">
<el-input v-model.trim="formData.remark" type="textarea" :placeholder="t('remarkPlaceholder')" class="input-width" clearable rows="4" maxlength="200" show-word-limit />
</el-form-item>
<el-form-item :label="t('growth')" prop="growth">
<div>
<div class="w-[150px]">
<el-input v-model.number.trim="formData.growth" :placeholder="t('growthPlaceholder')" clearable />
</div>
<div class="text-sm text-gray-400 mb-[5px]">{{ t('growthTips') }}</div>
</div>
</el-form-item>
</el-form>
<el-card class="box-card !border-none" shadow="never">
<h3 class="panel-title !text-sm">{{ t('levelBenefits') }}</h3>
<div class="pl-[100px]">
<member-benefits ref="benefitsRef" v-model="formData.level_benefits"/>
</div>
</el-card>
<el-card class="box-card !border-none" shadow="never">
<h3 class="panel-title !text-sm">{{ t('levelGift') }}</h3>
<div class="pl-[100px]">
<member-gift ref="giftRef" v-model="formData.level_gifts"/>
</div>
</el-card>
</el-card>
<div class="fixed-footer-wrap">
<div class="fixed-footer">
<el-button type="primary" :loading="saveLoading" @click="save(formRef)">{{ t('save') }}</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { FormInstance, FormRules } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import memberBenefits from '@/app/views/member/components/member-benefits.vue'
import memberGift from '@/app/views/member/components/member-gift.vue'
import { getMemberLevelInfo, addMemberLevel, updateMemberLevel, getMemberLevelAll } from '@/app/api/member'
import Test from '@/utils/test'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const benefitsRef = ref(null)
const giftRef = ref(null)
const loading = ref(true)
const growthInterval = ref({ min: 0, max: 0 })
const formData = reactive<Record<string, any>>({
level_id: 0,
level_name: '',
remark: '',
growth: '',
level_benefits: {},
level_gifts: {}
})
const formRef = ref<FormInstance>()
//
const formRules = reactive<FormRules>({
level_name: [
{ required: true, message: t('levelNamePlaceholder'), trigger: 'blur' }
],
growth: [
{ required: true, message: t('growthPlaceholder'), trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
if (!Test.digits(formData.growth)) {
callback(t('growthFormatError'))
}
if (formData.growth <= 0) {
callback(t('growthNeedGt') + 0)
}
if (growthInterval.value.min && formData.growth <= growthInterval.value.min) {
callback(t('growthNeedGt') + growthInterval.value.min)
}
if (growthInterval.value.max && formData.growth >= growthInterval.value.max) {
callback(t('growthNeedLt') + growthInterval.value.max)
}
callback()
}
}
]
})
if (route.query.id) {
getMemberLevelInfo(route.query.id).then(({ data }) => {
Object.assign(formData, data)
getMemberLevelAll().then(({ data }) => {
let index = 0
data.forEach((item, i) => {
item.level_id == formData.level_id && (index = i)
})
data[ index - 1 ] && (growthInterval.value.min = data[ index - 1 ].growth)
data[ index + 1 ] && (growthInterval.value.max = data[ index + 1 ].growth)
})
loading.value = false
})
} else {
getMemberLevelAll().then(({ data }) => {
data[ data.length - 1 ] && (growthInterval.value.min = data[ data.length - 1 ].growth)
})
loading.value = false
}
const saveLoading = ref(false)
/**
* 保存
*/
const save = async (formEl: FormInstance | undefined) => {
if (saveLoading.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
if (!await benefitsRef.value?.verify()) return
if (!await giftRef.value?.verify()) return
saveLoading.value = true
const save = formData.level_id ? updateMemberLevel : addMemberLevel
save(formData).then(() => {
router.push({ path: '/member/level' })
}).catch(() => {
saveLoading.value = false
})
}
})
}
</script>
<style lang="scss" scoped></style>

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