This commit is contained in:
全栈小学生 2024-12-21 18:39:12 +08:00
parent 4b78c8b6dd
commit 0b5b0de1db
30 changed files with 579 additions and 41 deletions

View File

@ -189,4 +189,12 @@ export function changeTemplate(params: Record<string, any>) {
*/ */
export function getApps(params: Record<string, any>) { export function getApps(params: Record<string, any>) {
return request.get(`diy/apps`) return request.get(`diy/apps`)
}
/**
*
* @param params
*/
export function copyDiy(params: Record<string, any>) {
return request.post(`diy/copy`, params, { showSuccessMessage: true })
} }

View File

@ -513,3 +513,26 @@ export function setSignConfig(params: Record<string, any>) {
export function getMemberSignList(params: Record<string, any>) { export function getMemberSignList(params: Record<string, any>) {
return request.get(`member/sign`, { params }); return request.get(`member/sign`, { params });
} }
/***************************************************** 地址管理 ****************************************************/
/**
*
*/
export function getMemberAddress(params: Record<string, any>) {
return request.get(`member/address`, { params });
}
/**
*
*/
export function addMemberAddress(params: Record<string, any>) {
return request.post(`member/address`, params);
}
/**
*
*/
export function editMemberAddress(params: Record<string, any>) {
return request.put(`member/address`, params);
}

View File

@ -75,3 +75,26 @@ export function getRefundTransfer(params: Record<string, any>) {
export function getAllPayType() { export function getAllPayType() {
return request.get(`pay/type/all`) return request.get(`pay/type/all`)
} }
/**
*
*/
export function getPayList() {
return request.get(`pay/type/list`)
}
/**
*
*/
export function pay(params: Record<string, any>) {
return request.post(`pay`, params)
}
/**
*
* @param params
* @returns
*/
export function getFriendsPay(tradeType : string, tradeId : number, channel: string) {
return request.get(`pay/friendspay/info/${tradeType}/${tradeId}/${channel}`, { showErrorMessage: false })
}

View File

@ -103,4 +103,13 @@ export function initPoster(params: Record<string, any>) {
*/ */
export function getPreviewPoster(params: Record<string, any>) { export function getPreviewPoster(params: Record<string, any>) {
return request.get(`sys/poster/preview`, {params}) return request.get(`sys/poster/preview`, {params})
}
/**
*
* @param params
* @returns
*/
export function getPosterGenerate(params: Record<string, any>) {
return request.get(`sys/poster/generate`, {params, showErrorMessage: false})
} }

View File

@ -20,6 +20,7 @@
"titlePlaceholder": "请输入页面名称", "titlePlaceholder": "请输入页面名称",
"addDiyPage": "添加页面", "addDiyPage": "添加页面",
"diyPageDeleteTips": "确定要删除该自定义页面吗?", "diyPageDeleteTips": "确定要删除该自定义页面吗?",
"diyPageCopyTips": "确定要复制该自定义页面吗?",
"preview": "预览", "preview": "预览",
"share": "分享", "share": "分享",
"shareSet": "分享设置", "shareSet": "分享设置",

View File

@ -4,6 +4,8 @@
"isUse": "是否启用", "isUse": "是否启用",
"signPeriod": "签到周期", "signPeriod": "签到周期",
"signPeriodTip": "请输入签到周期", "signPeriodTip": "请输入签到周期",
"signPeriodLimitTips": "签到周期格式错误",
"signPeriodMustZeroTips": "签到周期必须大于0",
"calendarSign": "日历签到", "calendarSign": "日历签到",
"periodSign": "周期签到", "periodSign": "周期签到",
"daySignAward": "日签奖励", "daySignAward": "日签奖励",

View File

@ -13,9 +13,9 @@
"mchId": "商户号", "mchId": "商户号",
"mchIdPlaceholder": "请输入商户号", "mchIdPlaceholder": "请输入商户号",
"mchIdTips": "微信支付商户号MCHID", "mchIdTips": "微信支付商户号MCHID",
"mchSecretKey": "API密钥", "mchSecretKey": "APIv3密钥",
"mchSecretKeyPlaceholder": "请输入API密钥", "mchSecretKeyPlaceholder": "请输入APIv3密钥",
"mchSecretKeyTips": "微信支付商户API密钥paySignKey", "mchSecretKeyTips": "微信支付商户APIv3密钥paySignKey",
"mchSecretCert": "商户私钥", "mchSecretCert": "商户私钥",
"mchSecretCertPlaceholder": "请上传商户私钥", "mchSecretCertPlaceholder": "请上传商户私钥",
"mchSecretCertTips": "微信支付API证书apiclient_key.pem", "mchSecretCertTips": "微信支付API证书apiclient_key.pem",
@ -65,5 +65,21 @@
"nativeDomain": "Native支付回调链接", "nativeDomain": "Native支付回调链接",
"nativeDomainTips": "需在微信商户号>产品中心>开发配置>支付配置 Native支付回调链接中添加该链接", "nativeDomainTips": "需在微信商户号>产品中心>开发配置>支付配置 Native支付回调链接中添加该链接",
"wechatpayPublicCert": "微信支付公钥", "wechatpayPublicCert": "微信支付公钥",
"wechatpayPublicCertId": "微信支付公钥ID" "wechatpayPublicCertId": "微信支付公钥ID",
"updateFriendsPay":"找朋友帮忙付",
"friendsPaySwitch":"帮付说明",
"friendsPayTitle":"帮付说明标题",
"friendsPayTitlePlaceholder":"请输入帮付说明标题",
"desContent":"说明内容",
"friendsPayGoodsSwitch":"订单信息清单",
"friendsPayGoodsSwitchTips":"开启后,帮付人可以看到订单信息清单",
"friendsPayName":"支付方式名称",
"friendsPayNamePlaceholder":"请输入支付方式名称",
"desContentPlaceholder": "请输入说明内容",
"helpName":"帮付页面名称",
"helpNamePlaceholder":"请输入帮付页面名称",
"helpBtn":"帮付按钮名称",
"helpBtnPlaceholder":"请输入帮付按钮名称",
"remark":"发起帮付留言",
"remarkPlaceholder":"请输入留言备注"
} }

View File

@ -4,9 +4,9 @@
"mchId": "商户号", "mchId": "商户号",
"mchIdPlaceholder": "请输入商户号", "mchIdPlaceholder": "请输入商户号",
"mchIdTips": "微信支付商户号MCHID", "mchIdTips": "微信支付商户号MCHID",
"mchSecretKey": "API密钥", "mchSecretKey": "APIv3密钥",
"mchSecretKeyPlaceholder": "请输入API密钥", "mchSecretKeyPlaceholder": "请输入APIv3密钥",
"mchSecretKeyTips": "微信支付商户API密钥paySignKey", "mchSecretKeyTips": "微信支付商户APIv3密钥paySignKey",
"mchSecretCert": "商户私钥", "mchSecretCert": "商户私钥",
"mchSecretCertPlaceholder": "请上传商户私钥", "mchSecretCertPlaceholder": "请上传商户私钥",
"mchSecretCertTips": "微信支付API证书apiclient_key.pem", "mchSecretCertTips": "微信支付API证书apiclient_key.pem",
@ -28,4 +28,4 @@
"alipayPublicCertPathTips": "上传alipayCertPublicKey文件", "alipayPublicCertPathTips": "上传alipayCertPublicKey文件",
"alipayRootCertPathTips": "上传alipayRootCert文件", "alipayRootCertPathTips": "上传alipayRootCert文件",
"operationTip": "温馨提示:打款设置用于会员提现转账,发放红包等场景" "operationTip": "温馨提示:打款设置用于会员提现转账,发放红包等场景"
} }

View File

@ -22,7 +22,7 @@
<div class="cursor-pointer" @click="copyEvent(formData.request_url)">{{ t('copy') }}</div> <div class="cursor-pointer" @click="copyEvent(formData.request_url)">{{ t('copy') }}</div>
</template> </template>
</el-input> </el-input>
<span class="ml-2 cursor-pointer visit-btn">{{t('clickVisit')}}</span> <span class="ml-2 cursor-pointer visit-btn" @click="visitFn">{{t('clickVisit')}}</span>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card> </el-card>

View File

@ -237,6 +237,8 @@ route.query.type = route.query.type || '' // 页面模板,新页面传入
route.query.title = route.query.title || '' route.query.title = route.query.title || ''
route.query.back = route.query.back || '/site/diy/list' route.query.back = route.query.back || '/site/diy/list'
console.log('route',route.path)
const backPath = route.query.back const backPath = route.query.back
const template = ref(''); const template = ref('');
const oldTemplate = ref(''); const oldTemplate = ref('');
@ -298,6 +300,9 @@ const modulesFiles = import.meta.glob('./components/*.vue', { eager: true })
const addonModulesFiles = import.meta.glob('@/addon/**/views/diy/components/*.vue', { eager: true }) const addonModulesFiles = import.meta.glob('@/addon/**/views/diy/components/*.vue', { eager: true })
addonModulesFiles && Object.assign(modulesFiles, addonModulesFiles) addonModulesFiles && Object.assign(modulesFiles, addonModulesFiles)
// todo 便
// todo 使
const modules = {} const modules = {}
for (const [key, value] of Object.entries(modulesFiles)) { for (const [key, value] of Object.entries(modulesFiles)) {
const moduleName = key.split('/').pop() const moduleName = key.split('/').pop()

View File

@ -122,7 +122,7 @@ const refreshData = () => {
if (import.meta.env.MODE == 'development') { if (import.meta.env.MODE == 'development') {
// wap // wap
if (wapDomain.value) { if (wapDomain.value && wapDomain.value.indexOf('localhost') != -1) {
page.wapUrl = wapDomain.value + '/wap' page.wapUrl = wapDomain.value + '/wap'
repeat = false repeat = false
setDomain() setDomain()

View File

@ -57,6 +57,7 @@
<el-button v-if="row.type == 'DIY_PAGE'" type="primary" link @click="openShare(row)">{{ t('shareSet') }}</el-button> <el-button v-if="row.type == 'DIY_PAGE'" type="primary" link @click="openShare(row)">{{ t('shareSet') }}</el-button>
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button> <el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button v-if="row.is_default == 0 || row.type == 'DIY_PAGE'" type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button> <el-button v-if="row.is_default == 0 || row.type == 'DIY_PAGE'" type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
<el-button type="primary" link @click="copyEvent(row.id)">{{ t('copy') }}</el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -126,7 +127,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, computed } from 'vue' import { reactive, ref, computed } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { getApps,getDiyPageList, deleteDiyPage, getDiyTemplate, editDiyPageShare, setUseDiyPage } from '@/app/api/diy' import { getApps,getDiyPageList, deleteDiyPage, getDiyTemplate, editDiyPageShare, setUseDiyPage,copyDiy } from '@/app/api/diy'
import { ElMessageBox, FormInstance } from 'element-plus' import { ElMessageBox, FormInstance } from 'element-plus'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { setTablePageStorage,getTablePageStorage } from "@/utils/common"; import { setTablePageStorage,getTablePageStorage } from "@/utils/common";
@ -258,6 +259,30 @@ const setUse = (id: any) => {
loadDiyPageList() loadDiyPageList()
}) })
} }
const repeat = ref(false)
//
const copyEvent = (id: any) => {
ElMessageBox.confirm(t('diyPageCopyTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
if (repeat.value) return
repeat.value = true
copyDiy({id: id}).then((res: any) => {
if (res.code == 1) {
loadDiyPageList()
}
repeat.value = false
}).catch(err => {
repeat.value = false
})
})
}
// //
const deleteEvent = (id: number) => { const deleteEvent = (id: number) => {

View File

@ -155,7 +155,7 @@ const refreshData = () => {
if (import.meta.env.MODE == 'development') { if (import.meta.env.MODE == 'development') {
// wap // wap
if (wapDomain.value) { if (wapDomain.value && wapDomain.value.indexOf('localhost') != -1) {
page.wapUrl = wapDomain.value + '/wap' page.wapUrl = wapDomain.value + '/wap'
repeat = false repeat = false
setDomain() setDomain()

View File

@ -77,7 +77,7 @@
<div class="flex flex-col mx-[25px] h-[430px] mt-[15px]"> <div class="flex flex-col mx-[25px] h-[430px] mt-[15px]">
<div class="flex items-center"> <div class="flex items-center">
<div class="text-[18px] text-[#333333]">站点名称</div> <div class="text-[18px] text-[#333333]">站点名称</div>
<div class="w-[420px] h-[34px] ml-[10px]"> <div class="w-[350px] h-[34px] ml-[10px]">
<el-form :model="createSiteData.formData" ref="formRef" :rules="formRules"> <el-form :model="createSiteData.formData" ref="formRef" :rules="formRules">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input class="create-site-name" v-model.trim="createSiteData.formData.site_name" maxlength="20" placeholder="请输入站点名称" autocomplete="off"></el-input> <el-input class="create-site-name" v-model.trim="createSiteData.formData.site_name" maxlength="20" placeholder="请输入站点名称" autocomplete="off"></el-input>
@ -85,7 +85,7 @@
</el-form> </el-form>
</div> </div>
</div> </div>
<div class="flex-1 mt-[20px] h-[160px]" v-show="createSiteData.step == 1"> <div class="flex-1 mt-[20px] h-[160px]" v-show="createSiteData.step == 1">
<div class="text-[18px] text-[#333333]">店铺套餐</div> <div class="text-[18px] text-[#333333]">店铺套餐</div>
<el-scrollbar class="w-full mt-[10px] meal-site -ml-[10px]" height="350px"> <el-scrollbar class="w-full mt-[10px] meal-site -ml-[10px]" height="350px">
@ -93,8 +93,7 @@
<div v-for="(item, index) in siteGroup" :key="index" <div v-for="(item, index) in siteGroup" :key="index"
class="inline-flex flex-col w-[300px] h-[330px] box-border rounded-[17px] border-transparent border-[2px] border-solid create-site-item my-[10px]" class="inline-flex flex-col w-[300px] h-[330px] box-border rounded-[17px] border-transparent border-[2px] border-solid create-site-item my-[10px]"
:class="{'bg-[#F6F7FF] border-[#466CEA]': createSiteData.formData.group_id == item.group_id ,'ml-[20px]': index > 0, ' ml-[10px]': index == 0, 'mr-[10px]': (siteGroup.length-1) == index }" :class="{'bg-[#F6F7FF] border-[#466CEA]': createSiteData.formData.group_id == item.group_id ,'ml-[20px]': index > 0, ' ml-[10px]': index == 0, 'mr-[10px]': (siteGroup.length-1) == index }"
@click="createSiteData.formData.group_id = item.group_id" @click="createSiteData.formData.group_id = item.group_id">
>
<div class="w-[140px] h-[40px] px-[15px] truncate text-white text-[16px] text-center leading-[40px] creatBg relative -left-[1px] -top-[2px]"> <div class="w-[140px] h-[40px] px-[15px] truncate text-white text-[16px] text-center leading-[40px] creatBg relative -left-[1px] -top-[2px]">
{{ item.site_group.group_name }} {{ item.site_group.group_name }}
</div> </div>
@ -264,7 +263,7 @@ const createSiteFn = () => {
getHomeSiteFn() getHomeSiteFn()
}).catch(() => { }).catch(() => {
createSiteData.value.loading = false createSiteData.value.loading = false
}) })
} }
watch(() => createSiteDialog.value, () => { watch(() => createSiteDialog.value, () => {
@ -329,4 +328,4 @@ watch(() => createSiteDialog.value, () => {
.meal-site{ .meal-site{
height: calc(100% - 30px) !important; height: calc(100% - 30px) !important;
} }
</style> </style>

View File

@ -10,7 +10,8 @@
</template> </template>
</div> </div>
<div class="login flex flex-col w-[400px] h-[400px] p-[40px]"> <div class="login flex flex-col w-[400px] h-[400px] p-[40px]">
<h3 class="text-center text-2xl mb-[26px]">{{ webSite.site_name || t('siteTitle') }}{{ t('platform') }}</h3> <h3 class="text-center text-lg font-bold mb-[10px]">{{ webSite.site_name || t('siteTitle') }}</h3>
<h3 class="text-center text-2xl font-bold mb-[26px]">{{ t('platform') }}</h3>
<el-form :model="form" ref="formRef" :rules="formRules" class="mt-[30px]"> <el-form :model="form" ref="formRef" :rules="formRules" class="mt-[30px]">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model.trim="form.username" :placeholder="t('userPlaceholder')" autocomplete="off" @keyup.enter="handleLogin(formRef)" class="h-[40px]"></el-input> <el-input v-model.trim="form.username" :placeholder="t('userPlaceholder')" autocomplete="off" @keyup.enter="handleLogin(formRef)" class="h-[40px]"></el-input>

View File

@ -10,8 +10,8 @@
<el-switch v-model="formData.is_use" /> <el-switch v-model="formData.is_use" />
</el-form-item> </el-form-item>
<el-form-item :label="t('signPeriod')" v-if="formData.is_use"> <el-form-item :label="t('signPeriod')" prop="sign_period" v-if="formData.is_use">
<el-input-number v-model="formData.sign_period" :min="1" :precision="0" clearable class="input-width" controls-position="right" /><span class="ml-[10px]"></span> <el-input v-model="formData.sign_period" placeholder="0" maxlength="8" clearable class="input-width" /><span class="ml-[10px]"></span>
</el-form-item> </el-form-item>
<el-form-item :label="t('daySignAward')" prop="day_award" v-if="formData.is_use"> <el-form-item :label="t('daySignAward')" prop="day_award" v-if="formData.is_use">
@ -130,14 +130,33 @@ const continue_award = ref({})
let isEdit = false // let isEdit = false //
let editIndex = 0 // let editIndex = 0 //
//
const regExp: any = {
required: /[\S]+/,
number: /^\d{0,10}$/,
digit: /^\d{0,10}(.?\d{0,2})$/,
special: /^\d{0,10}(.?\d{0,3})$/
}
// //
const formRules = reactive<FormRules>({ const formRules = reactive<FormRules>({
sign_period: [
{ required: true, message: t('signPeriodTip'), trigger: 'blur' }
],
day_award: [ day_award: [
{ required: true, message: t('daySignAwardPlaceholder'), trigger: 'change' } { required: true, message: t('daySignAwardPlaceholder'), trigger: 'change' }
], ],
sign_period:[{
required: true,
trigger: 'blur',
validator: (rule: any, value: any, callback: any) => {
if (value === null || value === '') {
callback(t('signPeriodTip'))
}else if (isNaN(value) || !regExp.number.test(value)) {
callback(t('signPeriodLimitTips'))
}else if (value <= 0) {
callback(t('signPeriodMustZeroTips'))
} else {
callback();
}
}
}],
}) })
/** /**

View File

@ -0,0 +1,22 @@
<template>
<!-- 属性内容 -->
<div class="content-wrap">
<!-- 组件公共属性 -->
<slot name="common"></slot>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { t } from '@/lang'
import usePosterStore from '@/stores/modules/poster'
const posterStore = usePosterStore()
defineExpose({})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,22 @@
<template>
<!-- 属性内容 -->
<div class="content-wrap">
<!-- 组件公共属性 -->
<slot name="common"></slot>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { t } from '@/lang'
import usePosterStore from '@/stores/modules/poster'
const posterStore = usePosterStore()
defineExpose({})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,47 @@
<template>
<div class="overflow-hidden" :style="componentStyle">{{ data.value }}</div>
</template>
<script lang="ts" setup>
import { ref,computed } from 'vue'
import usePosterStore from '@/stores/modules/poster'
const posterStore = usePosterStore()
const prop = defineProps({
value: {
type: Object,
default: {}
}
})
const data = computed(()=> {
return prop.value;
})
const componentStyle = computed(()=> {
var style = '';
style += `font-size: ${prop.value.fontSize}px;color: ${prop.value.fontColor};line-height: ${prop.value.lineHeight + prop.value.fontSize}px;`;
if(prop.value.x == 'left' || prop.value.x == 'center' || prop.value.x == 'right'){
style += `text-align: ${prop.value.x};`;
}
if(prop.value.weight){
style += `font-weight: bold;`;
}
if(!prop.value.fontFamily || prop.value.fontFamily == 'static/font/SourceHanSansCN-Regular.ttf'){
style += `font-family: poster_default_font;`;
}
let box: any = document.getElementById(prop.value.id)
if (box) {
style += `width:${box.offsetWidth}px;height:${box.offsetHeight}px;`;
}else{
style += `width:${prop.value.width}px;height:${prop.value.height}px;`;
}
return style;
})
defineExpose({})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,47 @@
<template>
<div class="overflow-hidden" :style="componentStyle">{{ data.value }}</div>
</template>
<script lang="ts" setup>
import { ref,computed } from 'vue'
import usePosterStore from '@/stores/modules/poster'
const posterStore = usePosterStore()
const prop = defineProps({
value: {
type: Object,
default: {}
}
})
const data = computed(()=> {
return prop.value;
})
const componentStyle = computed(()=> {
var style = '';
style += `font-size: ${prop.value.fontSize}px;color: ${prop.value.fontColor};line-height: ${prop.value.lineHeight + prop.value.fontSize}px;`;
if(prop.value.x == 'left' || prop.value.x == 'center' || prop.value.x == 'right'){
style += `text-align: ${prop.value.x};`;
}
if(prop.value.weight){
style += `font-weight: bold;`;
}
if(!prop.value.fontFamily || prop.value.fontFamily == 'static/font/SourceHanSansCN-Regular.ttf'){
style += `font-family: poster_default_font;`;
}
let box: any = document.getElementById(prop.value.id)
if (box) {
style += `width:${box.offsetWidth}px;height:${box.offsetHeight}px;`;
}else{
style += `width:${prop.value.width}px;height:${prop.value.height}px;`;
}
return style;
})
defineExpose({})
</script>
<style lang="scss" scoped></style>

View File

@ -134,7 +134,7 @@
<!-- 文本 --> <!-- 文本 -->
<template v-if="posterStore.editComponent.type == 'text'"> <template v-if="posterStore.editComponent.type == 'text'">
<el-form-item :label="t('textFontSize')"> <el-form-item :label="t('textFontSize')">
<el-slider v-model="posterStore.editComponent.fontSize" show-input size="small" class="ml-[10px]" :min="14" :max="50" /> <el-slider v-model="posterStore.editComponent.fontSize" show-input size="small" class="ml-[10px]" :min="14" :max="100" />
</el-form-item> </el-form-item>
<el-form-item :label="t('textColor')"> <el-form-item :label="t('textColor')">
<el-color-picker v-model="posterStore.editComponent.fontColor" /> <el-color-picker v-model="posterStore.editComponent.fontColor" />

View File

@ -71,7 +71,7 @@ const formData = reactive({
is_auto_transfer: '0', is_auto_transfer: '0',
is_auto_verify: '0', is_auto_verify: '0',
is_open: '0', is_open: '0',
min: '0.01', min: '0',
rate: '0', rate: '0',
transfer_type: [] transfer_type: []
}) })

View File

@ -32,7 +32,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button> <el-button @click="cancel">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{t('confirm')}}</el-button> <el-button type="primary" :loading="loading" @click="confirm(formRef)">{{t('confirm')}}</el-button>
</span> </span>
</template> </template>
@ -44,10 +44,11 @@ import { ref, reactive, computed } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import type { FormInstance } from 'element-plus' import type { FormInstance } from 'element-plus'
import Test from '@/utils/test' import Test from '@/utils/test'
import { cloneDeep } from 'lodash-es'
const showDialog = ref(false) const showDialog = ref(false)
const loading = ref(true) const loading = ref(true)
const initData = ref<any>(null)
/** /**
* 表单数据 * 表单数据
*/ */
@ -105,15 +106,28 @@ const confirm = async (formEl: FormInstance | undefined) => {
}) })
} }
const cancel = () => {
Object.assign(formData, initialFormData)
if (initData.value) {
Object.keys(formData).forEach((key: string) => {
if (initData.value[key] != undefined) formData[key] = initData.value[key]
})
formData.channel = initData.value.redio_key.split('_')[0]
formData.status = Number(formData.status)
}
emit('complete', formData)
showDialog.value = false
}
const setFormData = async (data: any = null) => { const setFormData = async (data: any = null) => {
initData.value = cloneDeep(data)
loading.value = true loading.value = true
Object.assign(formData, initialFormData) Object.assign(formData, initialFormData)
if (data) { if (data) {
Object.keys(formData).forEach((key: string) => { Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key] if (data[key] != undefined) formData[key] = data[key]
}) })
formData['channel'] = data['redio_key'].split('_')[0] formData.channel = data.redio_key.split('_')[0]
formData['status'] = Number(formData['status']) formData.status = Number(formData.status)
} }
loading.value = false loading.value = false
} }

View File

@ -0,0 +1,174 @@
<template>
<el-dialog v-model="showDialog" :title="formData.config.pay_type_name ? formData.config.pay_type_name : t('updateFriendsPay')" 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-item :label="t('friendsPaySwitch')">
<el-switch v-model="formData.config.pay_explain_switch" :active-value="1" :inactive-value="0"/>
</el-form-item>
<template v-if="formData.config.pay_explain_switch == 1">
<el-form-item :label="t('friendsPayTitle')" prop="config.pay_explain_title">
<el-input v-model.trim="formData.config.pay_explain_title" :placeholder="t('friendsPayTitlePlaceholder')" class="input-width" maxlength="10" show-word-limit clearable />
</el-form-item>
<el-form-item :label="t('desContent')" prop="config.pay_explain_content">
<el-input v-model.trim="formData.config.pay_explain_content" :placeholder="t('desContentPlaceholder')" class="input-width" type="textarea" rows="4" maxlength="120" show-word-limit clearable />
</el-form-item>
</template>
<el-form-item :label="t('friendsPayGoodsSwitch')">
<div>
<el-switch v-model="formData.config.pay_info_switch" :active-value="1" :inactive-value="0"/>
<div class="text-[12px] text-[#999] leading-[20px]">{{ t('friendsPayGoodsSwitchTips') }}</div>
</div>
</el-form-item>
<el-form-item :label="t('friendsPayName')" prop="config.pay_type_name">
<el-input v-model.trim="formData.config.pay_type_name" :placeholder="t('friendsPayNamePlaceholder')" class="input-width" maxlength="10" show-word-limit clearable />
</el-form-item>
<el-form-item :label="t('helpName')" prop="config.pay_page_name">
<el-input v-model.trim="formData.config.pay_page_name" :placeholder="t('helpNamePlaceholder')" class="input-width" maxlength="10" show-word-limit clearable />
</el-form-item>
<el-form-item :label="t('helpBtn')" prop="config.pay_button_name">
<el-input v-model.trim="formData.config.pay_button_name" :placeholder="t('helpBtnPlaceholder')" class="input-width" maxlength="10" show-word-limit clearable />
</el-form-item>
<el-form-item :label="t('remark')" prop="config.pay_leave_message">
<el-input v-model.trim="formData.config.pay_leave_message" :placeholder="t('remarkPlaceholder')" class="input-width" type="textarea" rows="4" maxlength="20" show-word-limit clearable />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="cancel()">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{t('confirm')}}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import Test from '@/utils/test'
import { cloneDeep } from 'lodash-es'
const showDialog = ref(false)
const loading = ref(true)
const initData = ref<any>(null)
/**
* 表单数据
*/
const initialFormData = {
type: 'friendspay',
app_id: '',
config: {
pay_explain_switch: 0,
pay_info_switch: 1,
pay_explain_title: '',
pay_explain_content: '',
pay_type_name: '',
pay_page_name: '',
pay_button_name: '',
pay_leave_message: ''
},
channel: '',
status: 0,
is_default: 0
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
'config.pay_explain_title': [
{ required: true, message: t('friendsPayTitlePlaceholder'), trigger: 'blur' },
{
validator: (rule: any, value: string, callback: any) => {
if (formData.config.pay_explain_switch == 1 && value === '') {
callback(new Error(t('friendsPayTitlePlaceholder')))
}
callback()
},
trigger: 'blur'
}
],
'config.pay_explain_content': [
{ required: true, message: t('desContentPlaceholder'), trigger: 'blur' },
{
validator: (rule: any, value: string, callback: any) => {
if (formData.config.pay_explain_switch == 1 && value === '') {
callback(new Error(t('desContentPlaceholder')))
}
callback()
},
trigger: 'blur'
}
],
'config.pay_type_name': [
{ required: true, message: t('friendsPayNamePlaceholder'), trigger: 'blur' }
],
'config.pay_page_name': [
{ required: true, message: t('helpNamePlaceholder'), trigger: 'blur' }
],
'config.pay_button_name': [
{ required: true, message: t('helpBtnPlaceholder'), trigger: 'blur' }
],
'config.pay_leave_message': [
{ required: true, message: t('remarkPlaceholder'), trigger: 'blur' }
]
}
})
const emit = defineEmits(['complete'])
/**
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
emit('complete', formData)
showDialog.value = false
}
})
}
const cancel = () => {
Object.assign(formData, initialFormData)
if (initData.value) {
Object.keys(formData).forEach((key: string) => {
if (initData.value[key] != undefined) formData[key] = initData.value[key]
})
formData.channel = initData.value.redio_key.split('_')[0]
formData.status = Number(formData.status)
}
emit('complete', formData)
showDialog.value = false
}
const setFormData = async (data: any = null) => {
initData.value = cloneDeep(data)
loading.value = true
Object.assign(formData, initialFormData)
if (data) {
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
formData.channel = data.redio_key.split('_')[0]
formData.status = Number(formData.status)
}
loading.value = false
}
const enableVerify = () => {
let verify = true
if ((formData.config.pay_explain_switch == 1 && Test.empty(formData.config.pay_explain_title)) || (formData.config.pay_explain_switch == 1 && Test.empty(formData.config.pay_explain_content)) || Test.empty(formData.config.pay_type_name) || Test.empty(formData.config.pay_page_name) || Test.empty(formData.config.pay_button_name) || Test.empty(formData.config.pay_leave_message)) verify = false
return verify
}
defineExpose({
showDialog,
setFormData,
enableVerify
})
</script>
<style lang="scss" scoped></style>

View File

@ -71,7 +71,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button> <el-button @click="cancel">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{ t('confirm') }}</el-button> <el-button type="primary" :loading="loading" @click="confirm(formRef)">{{ t('confirm') }}</el-button>
</span> </span>
</template> </template>
@ -85,11 +85,13 @@ import { FormInstance, ElMessage } from 'element-plus'
import Test from '@/utils/test' import Test from '@/utils/test'
import { getUrl } from '@/app/api/sys' import { getUrl } from '@/app/api/sys'
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
import { cloneDeep } from 'lodash-es'
const showDialog = ref(false) const showDialog = ref(false)
const loading = ref(true) const loading = ref(true)
const wapDomain = ref('') const wapDomain = ref('')
const serviceDomain = ref('') const serviceDomain = ref('')
const initData = ref<any>(null)
getUrl().then((res: any) => { getUrl().then((res: any) => {
wapDomain.value = res.data.wap_domain wapDomain.value = res.data.wap_domain
@ -151,7 +153,20 @@ const confirm = async (formEl: FormInstance | undefined) => {
}) })
} }
const cancel = () => {
Object.assign(formData, initialFormData)
if (initData.value) {
Object.keys(formData).forEach((key: string) => {
if (initData.value[key] != undefined) formData[key] = initData.value[key]
})
formData.channel = initData.value.redio_key.split('_')[0]
formData.status = Number(formData.status)
}
emit('complete', formData)
showDialog.value = false
}
const setFormData = async (data: any = null) => { const setFormData = async (data: any = null) => {
initData.value = cloneDeep(data)
loading.value = true loading.value = true
Object.assign(formData, initialFormData) Object.assign(formData, initialFormData)
if (data) { if (data) {

View File

@ -14,17 +14,17 @@
<div> <div>
<div class="flex items-center justify-between p-[10px] table-item-border bg"> <div class="flex items-center justify-between p-[10px] table-item-border bg">
<span class="text-base w-[150px]">{{ t('payType') }}</span> <span class="text-base w-[230px]">{{ t('payType') }}</span>
<span class="text-base w-[110px] text-center">{{ t('onState') }}</span> <span class="text-base w-[110px] text-center">{{ t('onState') }}</span>
<span class="text-base w-[80px] text-center" v-if="isEdit">{{ t('templateName') }}</span> <span class="text-base w-[80px] text-center" v-if="isEdit">{{ t('templateName') }}</span>
</div> </div>
<div ref="fieldBoxRefs" :data-key="payKey"> <div ref="fieldBoxRefs" :data-key="payKey">
<div class="flex items-center justify-between p-[10px] table-item-border" v-for="(childrenItem, childrenIndex) in payItems.pay_type" :key="childrenItem.redio_key" :id="payKey + '_' + childrenIndex"> <div class="flex items-center justify-between p-[10px] table-item-border" v-for="(childrenItem, childrenIndex) in payItems.pay_type" :key="childrenItem.redio_key" :id="payKey + '_' + childrenIndex">
<div class="flex w-[150px]"> <div class="flex w-[230px] flex-shrink-0">
<span v-if="isEdit" class="iconfont icontuodong mr-2 handle cursor-pointer"></span> <span v-if="isEdit" class="iconfont icontuodong mr-2 handle cursor-pointer"></span>
<div class="flex items-center select-none"> <div class="flex items-center select-none">
<div class="mr-[15px] w-[30px] h-[30px]"> <div class="mr-[15px] w-[30px] h-[30px] flex-shrink-0">
<img class="w-[30px]" :src="img(childrenItem.icon)" /> <img class="w-[30px]" :src="img(childrenItem.icon)" />
</div> </div>
<span class="text-base text-[#666]">{{ childrenItem.name }}</span> <span class="text-base text-[#666]">{{ childrenItem.name }}</span>
@ -134,6 +134,7 @@ const setConfigInfo = (data:any) => {
element.config = data.config element.config = data.config
} }
}) })
console.log(payConfigData.value)
} }
// //

View File

@ -16,20 +16,20 @@
<div v-for="(item, key) in rules.grant" :key="key" class="flex items-center"> <div v-for="(item, key) in rules.grant" :key="key" class="flex items-center">
<span class="p-[15px] w-[25%] text-[14px]">{{ item.name }}</span> <span class="p-[15px] w-[25%] text-[14px]">{{ item.name }}</span>
<span class="p-[15px] w-[50%] text-[14px] text-[#666]">{{ formData.grant[item.key] && formData.grant[item.key].content ? formData.grant[item.key].content : '--' }}</span> <span class="p-[15px] w-[50%] text-[14px] text-[#666]">{{ formData.grant[item.key] && formData.grant[item.key].content ? formData.grant[item.key].content : '--' }}</span>
<span class="p-[15px] w-[10%] text-[14px] text-[#666]"> <span class="p-[15px] w-[15%] text-[14px] text-[#666]">
<el-tag type="success" v-if="formData.grant[item.key] && formData.grant[item.key].is_use">已启用</el-tag> <el-tag type="success" v-if="formData.grant[item.key] && formData.grant[item.key].is_use">已启用</el-tag>
<el-tag type="danger" v-else>未启用</el-tag> <el-tag type="danger" v-else>未启用</el-tag>
</span> </span>
<span class="p-[15px] w-[15%] text-[14px] text-[#666] text-[var(--el-color-primary)] cursor-pointer" @click="examineFn(key)">配置</span> <span class="p-[15px] w-[10%] text-[14px] text-[#666] text-[var(--el-color-primary)] cursor-pointer" @click="examineFn(key)">配置</span>
</div> </div>
<div v-for="(item, key) in rules.consume" :key="key" class="flex items-center"> <div v-for="(item, key) in rules.consume" :key="key" class="flex items-center">
<span class="p-[15px] w-[25%] text-[14px]">{{ item.name }}</span> <span class="p-[15px] w-[25%] text-[14px]">{{ item.name }}</span>
<span class="p-[15px] w-[50%] text-[14px] text-[#666]">{{ formData.consume[item.key] && formData.consume[item.key].content ? formData.consume[item.key].content : '--' }}</span> <span class="p-[15px] w-[50%] text-[14px] text-[#666]">{{ formData.consume[item.key] && formData.consume[item.key].content ? formData.consume[item.key].content : '--' }}</span>
<span class="p-[15px] w-[10%] text-[14px] text-[#666]"> <span class="p-[15px] w-[15%] text-[14px] text-[#666]">
<el-tag type="success" v-if="formData.consume[item.key] && formData.consume[item.key].is_use">已启用</el-tag> <el-tag type="success" v-if="formData.consume[item.key] && formData.consume[item.key].is_use">已启用</el-tag>
<el-tag type="danger" v-else>未启用</el-tag> <el-tag type="danger" v-else>未启用</el-tag>
</span> </span>
<span class="p-[15px] w-[15%] text-[14px] text-[#666] text-[var(--el-color-primary)] cursor-pointer" @click="examineFn(key)">配置</span> <span class="p-[15px] w-[10%] text-[14px] text-[#666] text-[var(--el-color-primary)] cursor-pointer" @click="examineFn(key)">配置</span>
</div> </div>
</div> </div>

View File

@ -60,6 +60,42 @@
<el-option :label="item['group_name']" :value="item['group_id']" v-for="(item,index) in groupList" :key="index"/> <el-option :label="item['group_name']" :value="item['group_id']" v-for="(item,index) in groupList" :key="index"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('manager')" prop="uid">
<el-select v-model="formData.uid" :placeholder="t('managerPlaceholder')" class="input-width" filterable>
<el-option :label="t('newAddManager')" :value="0">
<template #default>
<div class="flex items-center">
<el-icon class="mr-[10px]">
<Plus />
</el-icon>
{{ t('newAddManager') }}
</div>
</template>
</el-option>
<el-option v-for="item in siteUser" :key="item.uid" :label="item['username']" :value="item['uid']">
<div class="flex items-center">
<el-avatar :src="img(item.head_img)" size="small" class="mr-[10px]" v-if="item.head_img" />
<img src="@/app/assets/images/member_head.png" alt="" class="mr-[10px] w-[24px]" v-else>
{{ item.username }}
</div>
</el-option>
</el-select>
</el-form-item>
<div v-show="formData.uid === 0">
<el-form-item :label="t('username')" prop="username">
<el-input v-model.trim="formData.username" clearable :placeholder="t('usernamePlaceholder')" class="input-width" :readonly="user_name_input" @click="inputClick('user_name_input')" @blur="user_name_input = true" />
</el-form-item>
<el-form-item :label="t('password')" prop="password">
<el-input v-model.trim="formData.password" clearable :placeholder="t('passwordPlaceholder')" class="input-width" :show-password="true" type="password" :readonly="password_input" @click="inputClick('password_input')" @blur="password_input = true" />
</el-form-item>
<el-form-item :label="t('confirmPassword')" prop="confirm_password">
<el-input v-model.trim="formData.confirm_password" :placeholder="t('confirmPasswordPlaceholder')" type="password" :show-password="true" clearable class="input-width" :readonly="confirm_password_input" @click="inputClick('confirm_password_input')" @blur="confirm_password_input = true" />
</el-form-item>
</div>
</div> </div>
<el-form-item :label="t('siteDomain')" prop="site_domain"> <el-form-item :label="t('siteDomain')" prop="site_domain">
@ -231,6 +267,9 @@ const confirm = async (formEl: FormInstance | undefined) => {
save(data).then(res => { save(data).then(res => {
loading.value = false loading.value = false
showDialog.value = false showDialog.value = false
getUserListSelect({}).then(({ data }) => {
siteUser.value = data
})
emit('complete') emit('complete')
}).catch(() => { }).catch(() => {
loading.value = false loading.value = false

View File

@ -105,6 +105,16 @@ html, body {
transition:var(--el-transition-duration) width ease-in-out,var(--el-transition-duration) padding-left ease-in-out,var(--el-transition-duration) padding-right ease-in-out; transition:var(--el-transition-duration) width ease-in-out,var(--el-transition-duration) padding-left ease-in-out,var(--el-transition-duration) padding-right ease-in-out;
} }
} }
/* 单行超出隐藏 */
.using-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
white-space: break-spaces;
}
/** 多行超出隐藏 **/ /** 多行超出隐藏 **/
.multi-hidden { .multi-hidden {

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "nc-iconfont"; /* Project id 4567203 */ font-family: "nc-iconfont"; /* Project id 4567203 */
src: url('//at.alicdn.com/t/c/font_4567203_unxj5bqsj6o.woff2?t=1732704100572') format('woff2'), src: url('//at.alicdn.com/t/c/font_4567203_vnozyu9086.woff2?t=1734084780804') format('woff2'),
url('//at.alicdn.com/t/c/font_4567203_unxj5bqsj6o.woff?t=1732704100572') format('woff'), url('//at.alicdn.com/t/c/font_4567203_vnozyu9086.woff?t=1734084780804') format('woff'),
url('//at.alicdn.com/t/c/font_4567203_unxj5bqsj6o.ttf?t=1732704100572') format('truetype'); url('//at.alicdn.com/t/c/font_4567203_vnozyu9086.ttf?t=1734084780804') format('truetype');
} }
.nc-iconfont { .nc-iconfont {
@ -13,6 +13,22 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.nc-icon-daifujineV6xx:before {
content: "\e83b";
}
.nc-icon-daifuliuyanV6xx:before {
content: "\e83a";
}
.nc-icon-jichuxinxiV6xx:before {
content: "\e838";
}
.nc-icon-dianpuzhandianV6xx:before {
content: "\e836";
}
.nc-icon-gouwucheV6xx6:before { .nc-icon-gouwucheV6xx6:before {
content: "\e835"; content: "\e835";
} }