This commit is contained in:
全栈小学生 2025-03-10 14:07:44 +08:00
parent 6648951a28
commit b021bc6660
89 changed files with 6878 additions and 5150 deletions

View File

@ -90,3 +90,4 @@ export function cancelInstall(addon: string) {
export function getInstalledAddonList() {
return request.get('addon/list/install')
}

View File

@ -219,6 +219,29 @@ export function getDiyTheme(params: Record<string, any>) {
return request.get(`diy/theme`, {params})
}
/**
*
* @param params
*/
export function addTheme(params: Record<string, any>) {
return request.post(`diy/theme/add`, params)
}
/**
*
* @param params
*/
export function editTheme(params: Record<string, any>) {
return request.put(`diy/theme/edit/${params.id}`, params, {showSuccessMessage: true})
}
/**
*
* @param params
*/
export function deleteTheme(id: number) {
return request.delete(`diy/theme/delete/${id}`, {showSuccessMessage: true})
}
/**
*

View File

@ -19,6 +19,14 @@ export function getDiyFormPageList(params: Record<string, any>) {
export function getDiyFormList(params: Record<string, any>) {
return request.get(`diy/form/list`, { params })
}
/**
*
* @param params
* @returns
*/
export function getDiyFormSelectPageList(params: Record<string, any>) {
return request.get(`diy/form/select`, { params })
}
/**
*
@ -225,4 +233,4 @@ export function getFormRecordsMember(params: Record<string, any>) {
*/
export function copyForm(params: Record<string, any>) {
return request.post(`diy/form/copy`, params, { showSuccessMessage: true })
}
}

View File

@ -359,6 +359,14 @@ export function getCashOutDetail(id: number) {
export function memberAudit(params: Record<string, any>) {
return request.put(`member/cash_out/audit/${params.id}/${params.action}`, params, { showSuccessMessage: true })
}
/**
*
* @param params
*/
export function memberCancel(params: Record<string, any>) {
return request.put(`member/cash_out/cancel/${params.id}`, params, { showSuccessMessage: true,showErrorMessage: true })
}
/**
*

View File

@ -99,3 +99,25 @@ export function pay(params: Record<string, any>) {
export function getFriendsPay(tradeType : string, tradeId : number, channel: string) {
return request.get(`pay/friendspay/info/${tradeType}/${tradeId}/${channel}`, { showErrorMessage: false })
}
/**
*
*/
export function getTransferScene() {
return request.get(`pay/transfer_scene`)
}
/**
* id
*/
export function setSceneId(params: Record<string, any>) {
return request.post(`pay/transfer_scene/set_scene_id/${params.scene}`, params, { showSuccessMessage: true })
}
/**
*
*/
export function setTradeScene(params: Record<string, any>) {
return request.post(`pay/transfer_scene/set_trade_scene/${params.type}`, params)
}

View File

@ -604,6 +604,13 @@ export function clearSchemaCache(params: Record<string, any>) {
return request.post(`sys/schema/clear`, {}, { showSuccessMessage: true })
}
/**
*
*/
export function clearCache(params: Record<string, any>) {
return request.post(`sys/cache/clear`, {}, { showSuccessMessage: true })
}
/***************************************************** 获取应用 ****************************************************/
/**
*

View File

@ -61,8 +61,8 @@ export function getWeappVersionList(params: Record<string, any>) {
/**
*
* @param key
* @returns
* @param key
* @returns
*/
export function getWeappUploadLog(key: string) {
return request.get(`weapp/upload/${key}`)
@ -130,4 +130,28 @@ export function deleteVersion(id: string) {
*/
export function getIsTradeManaged() {
return request.get('weapp/delivery/getIsTradeManaged')
}
}
/**
*
* @param params
*/
export function setWeappDomain(params: Record<string, any>) {
return request.put('weapp/domain', params, { showSuccessMessage: true })
}
/**
*
* @param params
*/
export function setWeappPrivacySetting(params: Record<string, any>) {
return request.put('weapp/privacysetting', params, { showSuccessMessage: true })
}
/**
*
* @param params
*/
export function getWeappPrivacySetting() {
return request.get('weapp/privacysetting')
}

View File

@ -194,6 +194,7 @@ const open = async () => {
} else {
loading.value = false
cloudBuildCheck.value = data
showDialog.value = true
}
}).catch(() => {
showDialog.value = false

View File

@ -134,6 +134,7 @@ const emits = defineEmits(['complete', 'cloudbuild'])
const upgradeTipsShowDialog = ref<boolean>(false)
let upgradeLog: any = []
let errorLog: any = []
/**
* 查询升级任务
*/
@ -158,12 +159,18 @@ const getUpgradeTaskFn = () => {
})
//
if (data.error) {
upgradeTask.value = data
ElMessage({ message: '升级失败', type: 'error' })
terminalRef.value.pushMessage({ content: data.error, class: 'error' })
data.error.forEach(item => {
if (!errorLog.includes(item)) {
terminalRef.value.pushMessage({ content: item, class: 'error' })
errorLog.push(item)
}
})
}
//
if (data.step == 'restoreComplete') {
return
}
//
//
if (data.step == 'upgradeComplete') {
active.value = 'complete'
notificationEl && notificationEl.close()
@ -316,6 +323,7 @@ const clearUpgradeTaskFn = () => {
uploading.value = false
upgradeTask.value = null
upgradeLog = []
errorLog = []
flashInterval && clearInterval(flashInterval)
clearUpgradeTask().then(() => {}).catch()
}

View File

@ -42,5 +42,6 @@
"uploadSuccessTips": "小程序上传成功后还需到<a href='https://mp.weixin.qq.com/' target='_blank' class='text-primary'>微信公众平台</a>提交审核,审核通过后发布才算正式上线。",
"knownToKnow": "我已知晓,不需要再次提示",
"siteAuthTips": "上传代码需先绑定授权码,请联系平台管理员进行绑定",
"againUpload": "重新上传"
"againUpload": "重新上传",
"uploadWeapp": "上传小程序"
}

View File

@ -34,5 +34,26 @@
"weappUpload": "小程序代码上传",
"uploadKey": "上传密钥",
"uploadKeyTips": "配置之后可实现在线上传小程序版本",
"uploadIpTips": "如果小程序代码上传开启了ip白名单设置在ip白名单中添加ip"
}
"uploadIpTips": "如果小程序代码上传开启了ip白名单设置在ip白名单中添加ip",
"update": "修改",
"udpUrl": "udp合法域名",
"tcpUrl": "tcp合法域名",
"requestdomainPlaceholder": "以 https:// 开头。域名间请用 ; 分割",
"wsrequestdomainPlaceholder": "以 wss:// 开头。域名间请用 ; 分割",
"uploaddomainPlaceholder": "以 https:// 开头。域名间请用 ; 分割",
"downloaddomainPlaceholder": "以 https:// 开头。域名间请用 ; 分割",
"udpdomainPlaceholder": "以 udp:// 开头。域名间请用 ; 分割",
"tcpdomainPlaceholder": "以 tcp:// 开头。域名间请用 ; 分割",
"domainError": "该域名协议头非法",
"serviceContentStatement": "服务内容声明",
"privacyAgreement": "用户隐私保护指引",
"privacyAgreementTips": "基于微信提供的 标准化用户隐私保护指引,根据小程序实际情况更新并展示给用户。",
"setting": "设置",
"privacyAgreementTitle": "用户隐私保护指引设置",
"settingPlaceholder": "请填写用途",
"addSettingType": "增加信息类型",
"settingTypeTitle": "使用用户信息类型",
"addContact": "增加联系方式",
"addSdkInfo": "增加第三方SDK信息",
"addSdkSettingList": "增加SDK提供方的隐私信息"
}

View File

@ -257,8 +257,9 @@
"horzLineStyleDashed": "虚线",
"horzLineBorderColor": "线条颜色",
"horzLineBorderWidth": "线条宽度",
"floatBtnBtton": "按钮位置",
"floatBtnButton": "按钮位置",
"floatBtnOffset": "上下偏移",
"lateralBtnOffset": "左右偏移",
"floatBtnImageSet": "图片设置",
"floatBtnImageSize": "图片大小",
"floatBtnAroundRadius": "图片圆角",

View File

@ -258,8 +258,9 @@
"horzLineStyleDashed": "虚线",
"horzLineBorderColor": "线条颜色",
"horzLineBorderWidth": "线条宽度",
"floatBtnBtton": "按钮位置",
"floatBtnButton": "按钮位置",
"floatBtnOffset": "上下偏移",
"lateralBtnOffset": "左右偏移",
"floatBtnImageSet": "图片设置",
"floatBtnImageSize": "图片大小",
"floatBtnAroundRadius": "图片圆角",
@ -291,5 +292,104 @@
"formPlaceholder": "提示语",
"formPlaceholderTips": "请输入提示语",
"isRequired": "是否必填",
"optionPlaceholder": "请输入选项内容"
"optionPlaceholder": "请输入选项内容",
"formLayout": "表单布局",
"layoutStyle": "排版风格",
"singleTiling": "单列平铺‌",
"singleTilingTipsOne": "将所有需要填写的表单内容项直接罗列在页面上。",
"singleTilingTipsTwo": "适用于表单内容项较少且项目之间无逻辑关系的情况。",
"singleTilingTipsThree": "其优势在于相对简洁、便于操作",
"singleTilingTipsFour": "但当表单项数量较大时,一次性展示全部信息会增加用户的操作负担,填写效率较低",
"arrangeSideBySide": "左右排列‌",
"arrangeSideBySideTipsOne": "将表单分为左右两部分,左侧为标题和描述,右侧为输入区域。",
"arrangeSideBySideTipsTwo": "这种布局适用于标题和描述内容较少的情况,能够提高表单的紧凑性和用户体验。",
"layoutStyleTips": "切换后将同步所有表单组件的展示形式",
"borderControl": "边框开关",
"fieleContent": "字段内容",
"fieldName": "字段名称",
"filedRemark": "字段说明",
"otherSetting": "其他设置",
"hideControl": "隐藏该组件",
"hideControlTipsOne": "勾选后填表人填表时看不到该字段。",
"hideControlTipsTwo": "适用于你不再收集该字段又不希望删除已收集的数据。",
"textStyle": "文字样式",
"filedRemarkStyle": "字段说明样式",
"style": "样式",
"listStyle": "列表",
"dropDownStyle": "下拉",
"option": "选项",
"addSingleOption": "添加单个选项",
"addMultipleOption": "批量添加选项",
"addOptionTips": "每个选项之间用英文“,” 隔开,自动过滤重复内容",
"errorTipsOne": "存在重复选项,请检查内容",
"errorTipsTwo": "选项已存在,请重新输入",
"dataFormat": "日期格式",
"startDate": "开始日期",
"startTime": "开始时间",
"startDataTips": "开始日期不能为空",
"startTimeTips": "开始时间不能为空",
"startDataPlaceholder": "请选择开始日期",
"dataPlaceholder": "请选择日期",
"timePlaceholder": "请选择时间",
"startTimePlaceholder": "请选择开始时间",
"endDate": "结束日期",
"endTime": "结束时间",
"endDataPlaceholder": "请选择结束日期",
"endTimePlaceholder": "请选择结束时间",
"endDataTips": "结束日期不能为空",
"endTimeTips": "结束时间不能为空",
"startEndDataTips": "开始日期不能大于结束日期",
"startEndTimeTips": "开始时间不能大于结束时间",
"currentDate": "当天日期",
"diyDate": "指定日期",
"currentTime": "当天时间",
"diyTime": "指定时间",
"preventDuplication":"内容防重复",
"preventDuplicationTipsOne":"该组件填写的内容不能与已提交的数据重复。",
"preventDuplicationTipsTwo":"极端情况下可能存在延时导致限制失效。",
"privacyProtection":"隐私保护",
"privacyProtectionTipsOne":"会自动将提交的个人信息做加密展示。",
"privacyProtectionTipsTwo":"适用于公开展示收集的数据且不暴露用户隐私。",
"privacyProtectionTipsThree":"提交后自动隐藏中间11位数字仅管理员可查看",
"privacyProtectionTipsFour":"提交后自动隐藏文本,仅管理员可查看",
"privacyProtectionTipsFive":"提交后自动隐藏中间5位数字仅管理员可查看",
"imageLimit":"限制数量",
"imageLimitPlaceholder":"请输入限制数量",
"imageLimitErrorTips":"限制数量格式输入错误",
"imageLimitErrorTipsTwo":"限制数量不能小于0",
"imageLimitErrorTipsThree":"限制数量必须大于0",
"imafeLimitErrorTipsFour":"限制数量最大不能超过9",
"defaultValueTips":"设置后,默认值会自动填充到输入框,填表人可在此基础上进行修改。",
"defaultErrorTips":"默认值格式输入错误",
"defaultMustZeroTips":"默认值不能小于0",
"access":"获取方式",
"authorizeWeChatLocation":"授权微信定位",
"manuallySelectPositioning":"手动选择定位",
"unit":"单位",
"unitPlaceholder":"请输入单位",
"followContent":"跟随内容",
"hoverScreenBottom":"悬浮屏幕底部",
"btnTips":"当表单内容多时,只有滚动页面至最底部才会显示提交按钮",
"btnTipsTwo":"当表单内容多时,滚动页面至最底部时,提交按钮会自动按钮悬浮在屏幕底部,方便填表人快速提交显示在屏幕底部",
"btnTipsThree":"若前端以嵌入形式调用表单,提交按钮组件将不显示,相关业务由该页面负责处理",
"submitBtn":"提交按钮",
"submitBtnName":"按钮名称",
"btnNamePlaceholder":"请输入按钮名称",
"submitBtnNamePlaceholder":"请输入提交按钮名称",
"resetBtn":"重置按钮",
"btnStyle":"按钮样式",
"resetBtnNamePlaceholder":"请输入重置按钮名称",
"rowCount":"显示行数",
"rowCountPlaceholder":"请输入显示行数"
}

View File

@ -45,5 +45,82 @@
"batchDeletion": "批量删除",
"batchEmptySelectedFormsTips": "请选择要删除的表单",
"batchFormsDeleteTips": "确定要删除选中的表单吗?"
"batchFormsDeleteTips": "确定要删除选中的表单吗?",
"promotion":"推广",
"submitSuccess":"提交成功页",
"writeSet":"填写设置",
"export":"导出",
"detail":"详情",
"more":"更多",
"formPromotion":"表单推广",
"promoteUrl":"推广链接",
"downLoadQRCode":"下载二维码",
"configureFailed":"配置失败",
"writeSuccess":"填写成功",
"viewFillingDetails":"查看填写详情",
"finish":"完成",
"finishTips":"点击后进入首页",
"back":"返回",
"backTips":"点击后返回表单",
"afterSubmission":"填表人提交后",
"displayTextMessages":"显示文字消息",
"displayTextMessagesTips":"提交后页面显示文字信息。",
"promptText":"提示文字",
"defaultPrompt":"默认提示",
"defaultPromptTips":"将显示: 填写成功",
"diyPrompt":"自定义提示",
"tipsTextPlaceholder":"感谢你的填写",
"subsequentPperationButtons":"后续操作按钮",
"validityPeriodOfVoucher":"凭证有效期",
"noLimit":"不限制",
"specifyTime":"设置固定有效期",
"specifyTimeTips":"每条记录的凭证有效期都是一样的,例如:会议凭证可设置有效期为会议举行时间。",
"submissionTime":"按提交时间设置有效期",
"submissionTimeTips":"每条记录的凭证有效期按照提交时间来计算例如优惠凭证的有效期可以设置为领取后3天内有效。",
"afterSubmissionRecords":"提交记录后",
"effective":"有效",
"voucherStyle":"凭证样式",
"titleAboveTheCode":"码上方标题",
"titleAboveTheCodePlaceholder":"请妥善保存你的核销凭证",
"contentAboveTheCode":"码上方内容",
"contentAboveTheCodePlaceholder":"请输入码上方内容",
"addLinefeeds":"添加换行符",
"addFields":"添加字段",
"contentBelowTheCode":"码下方内容",
"submissionRecordTime":"展示提交记录时间",
"currentTime":"展示当前时间",
"currentTimeTips":"会以秒进行跳动,可起到防作假的功能",
"voucherDeadline":"展示凭证截止时间",
"saveVoucher":"支持填表人保存凭证",
"dispalyPromptText":"展示提示文字",
"diy":"自定义",
"apieceFillQuantity":"每人可填写次数",
"fillQuantityTotal":"表单可填写总数",
"writeTips":"填写限制的校验在极端情况下可能存在延时,从而导致限制失效,不建议商品限时抢购等场景使用该功能",
"fillInTheTimePeriod":"可填写时间段",
"setSpecifyTime":"设置开始/停止时间",
"openDayTime":"设置每日开启时间",
"timeLimitRuleOne":"开始/停止时间不能为空",
"timeLimitRuleTwo":"开启时间不能为空",
"timeLimitRuleThree":"开始时间不能等于结束时间",
"numCannotNull":"次数不能为空",
"dataAndStatistics":"数据与统计",
"detailData":"明细数据",
"fillInFormPerson":"填表人",
"fillInFormPersonplaceholder":"请输入填表人",
"fillInFormDate":"填表时间",
"fillInFormPersonInfo":"填表人信息",
"fillInFormPersonStatics":"填表人统计",
"fillInFormTotal":"总计(表单填写数)",
"fieldStatistics":"字段统计",
"viewInformation":"查看信息",
"deleteTips":"确定删除该条数据吗"
}

View File

@ -41,7 +41,7 @@
"cashOutMoney": "转账金额",
"auditTime": "审核时间",
"transferTime": "转账时间",
"memberInfoPlaceholder": "请输入会员名称/会员昵称/手机号",
"memberInfoPlaceholder": "请输入会员编号/昵称/手机号搜索",
"cashOutNumber": "提现单号",
"cashOutNumberPlaceholder": "请输入提现单号",
"alipayAccount": "支付宝账号",
@ -60,5 +60,7 @@
"transferRemark":"转账补充说明",
"transferRemarkPlaceholder":"请输入转账补充说明",
"notes":"备注",
"check":"检查打款进度"
"check":"检查打款进度",
"cancelWithdrawal":"取消",
"cancelTips":"确定要取消提现吗?"
}

View File

@ -1,6 +1,6 @@
{
"memberId":"会员编号",
"memberInfoPlaceholder":"请输入会员信息",
"memberInfoPlaceholder":"请输入会员编码/昵称/手机号搜索",
"memberInfo":"会员信息",
"mobile":"手机号码",
"nickName":"会员昵称",

View File

@ -6,7 +6,7 @@
"isMobile": "手机验证码登录",
"isMobileTip": "开启之后可以使用手机+验证码进行注册和登录或者快捷登录/注册",
"isBindMobile": "强制绑定手机",
"isBindMobileTip": "开启之后,会员注册时会强制绑定手机号,并且在相关页面也会引导会员强制绑定手机账号,否则将影响功能正常使用,方便会员在不同端口统一账号,也方便商家进行管理",
"isBindMobileTip": "开启之后,会员注册时会强制绑定手机号,并且在相关页面也会引导会员强制绑定手机账号,否则将影响功能正常使用,方便会员在不同端口统一账号,也方便商家进行管理,已注册会员不受影响",
"agreement": "政策协议",
"agreementTips": "注册时服务协议和隐私协议是否进行展示",
"tripartiteSetting": "第三方设置",
@ -20,4 +20,4 @@
"bgUrlPlaceholder": "建议图片尺寸750*669像素图片格式jpg、png、jpeg",
"desc": "描述",
"descPlaceholder": "请输入描述"
}
}

View File

@ -0,0 +1,10 @@
{
"transferSceneId":"转账场景ID",
"transferType":"业务类型",
"recvPerception":"收款感知",
"recvPerceptionTips":"请选择收款感知",
"reportInfos":"报备背景",
"operation":"操作",
"deploy":"配置",
"noData":"暂无数据"
}

View File

@ -1,7 +1,6 @@
{
"refresh":"刷新",
"refreshMenu":"刷新菜单",
"refreshMenuDesc":"新增/修改插件菜单后,需要刷新插件菜单",
"dataCache":"数据缓存",
"dataCacheDesc":"新增/修改数据表后,需要清除数据表缓存"
}
"dataCache":"清除缓存",
"dataCacheDesc":"清除系统的所有缓存",
"refresh": "立即清除",
"clearCacheTips": "确定要清除缓存吗?"
}

View File

@ -62,10 +62,8 @@ const getAppList = async () => {
// loading.value = false
const res = await getShowApp();
console.log('app',res)
appList.value = res.data
loading.value = false
console.log('appList.value',appList.value,appList.value.length)
}
getAppList()

View File

@ -17,6 +17,9 @@
<el-button type="primary" @click="insert" :loading="uploading" :disabled="loading">{{ t('cloudRelease') }}</el-button>
<el-button @click="localInsert" :disabled="loading">{{ t('localRelease') }}</el-button>
</div>
<div class="mt-[20px]" v-else>
<el-button type="primary" @click="againUpload" :loading="uploading" :disabled="loading">{{ t('uploadWeapp') }}</el-button>
</div>
<el-table class="mt-[15px]" :data="weappTableData.data" v-loading="weappTableData.loading" size="default">
<template #empty>

View File

@ -0,0 +1,164 @@
<template>
<el-dialog v-model="showDialog" :title="t('functionSetting')" width="700px" :destroy-on-close="true">
<el-form :model="formData" label-width="180px" ref="formRef" :rules="formRules" class="page-form pr-[100px]" v-loading="loading">
<el-form-item :label="t('requestUrl')" prop="requestdomain">
<el-input v-model="formData.requestdomain" :placeholder="t('requestdomainPlaceholder')" type="textarea">
</el-input>
</el-form-item>
<el-form-item :label="t('socketUrl')" prop="wsrequestdomain">
<el-input v-model="formData.wsrequestdomain" :placeholder="t('wsrequestdomainPlaceholder')" type="textarea">
</el-input>
</el-form-item>
<el-form-item :label="t('uploadUrl')" prop="uploaddomain">
<el-input v-model="formData.uploaddomain" :placeholder="t('uploaddomainPlaceholder')" type="textarea">
</el-input>
</el-form-item>
<el-form-item :label="t('downloadUrl')" prop="downloaddomain">
<el-input v-model="formData.downloaddomain" :placeholder="t('downloaddomainPlaceholder')" type="textarea">
</el-input>
</el-form-item>
<el-form-item :label="t('udpUrl')" prop="udpdomain">
<el-input v-model="formData.udpdomain" :placeholder="t('udpdomainPlaceholder')" type="textarea">
</el-input>
</el-form-item>
<el-form-item :label="t('tcpUrl')" prop="tcpdomain">
<el-input v-model="formData.tcpdomain" :placeholder="t('tcpdomainPlaceholder')" type="textarea">
</el-input>
</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="loading" @click="confirm(formRef)">{{t('confirm')}}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import {computed, reactive, ref} from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { setWeappDomain } from '@/app/api/weapp'
import Test from '@/utils/test'
const showDialog = ref(false)
const loading = ref(false)
/**
* 表单数据
*/
const initialFormData = {
requestdomain: '',
wsrequestdomain: '',
uploaddomain: '',
downloaddomain: '',
tcpdomain: '',
udpdomain: ''
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
const emit = defineEmits(['complete'])
const formRules = computed(() => {
return {
requestdomain: [
{
validator: (rule: any, value: any, callback: any) => validatorProtocol(rule, value, callback, 'https://'),
trigger: 'blur'
}
],
uploaddomain: [
{
validator: (rule: any, value: any, callback: any) => validatorProtocol(rule, value, callback, 'https://'),
trigger: 'blur'
}
],
downloaddomain: [
{
validator: (rule: any, value: any, callback: any) => validatorProtocol(rule, value, callback, 'https://'),
trigger: 'blur'
}
],
wsrequestdomain: [
{
validator: (rule: any, value: any, callback: any) => validatorProtocol(rule, value, callback, 'wss://'),
trigger: 'blur'
}
],
tcpdomain: [
{
validator: (rule: any, value: any, callback: any) => validatorProtocol(rule, value, callback, 'tcp://'),
trigger: 'blur'
}
],
udpdomain: [
{
validator: (rule: any, value: any, callback: any) => validatorProtocol(rule, value, callback, 'udp://'),
trigger: 'blur'
}
]
}
})
const validatorProtocol = (rule: any, value: any, callback: any, protocol: string) => {
if (!Test.empty(value)) {
let flag = true
value.split(';').forEach((item: string) => {
if (!item.startsWith(protocol)) {
flag = false
callback(new Error(t('domainError')))
}
})
if (flag) callback()
} else {
callback()
}
}
/**
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
if (loading.value) return
loading.value = true
const data = formData
setWeappDomain(data).then(res => {
loading.value = false
showDialog.value = false
emit('complete', data)
}).catch(() => {
loading.value = false
})
}
})
}
const setFormData = async (data: any = null) => {
loading.value = false
Object.assign(formData, initialFormData)
if (data) {
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
}
}
defineExpose({
showDialog,
setFormData
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,223 @@
<template>
<el-dialog v-model="showDialog" :title="t('privacyAgreementTitle')" width="900px" :destroy-on-close="true">
<div class="h-[60vh]">
<el-scrollbar>
<el-form :model="formData" label-width="auto" label-position="left" ref="formRef" :rules="formRules"
class="page-form w-[700px] mx-auto" v-loading="loading">
<h3 class="text-center text-xl font-bold my-[20px]">{{ config.weapp_name }} 小程序隐私保护指引</h3>
<h4 class="text-lg my-[10px]">1. 开发者处理的信息</h4>
<div class="mb-[8px]">根据法律规定开发者仅处理实现小程序功能所必要的信息</div>
<div class="setting-list">
<setting-list v-model="formData.setting_list" ref="settingListRef"/>
</div>
<div>
<el-button type="primary" link @click="settingListRef.addSettingList()">{{ t('addSettingType') }}</el-button>
</div>
<h4 class="text-lg my-[10px]">2. 第三方插件信息/SDK信息</h4>
<div class="mb-[8px]">
为实现特定功能开发者可能会接入由第三方提供的插件/SDK第三方插件/SDK的个人信息处理规则请以其公示的官方说明为准{{ config.weapp_name }}小程序接入的第三方插件信息/SDK信息如下
</div>
<div>
<div v-for="(item, index) in formData.sdk_privacy_info_list" class="mb-[15px]">
<el-form-item label="SDK名称" class="!mb-[8px]">
<el-input v-model="formData.sdk_privacy_info_list[index].sdk_name" class="input-width" placeholder="请输入SDK名称" />
<el-button type="primary" class="ml-[10px]" link @click="delSdk(index)">{{ t('delete') }}</el-button>
</el-form-item>
<el-form-item label="SDK提供方名称" class="!mb-[8px]">
<el-input v-model="formData.sdk_privacy_info_list[index].sdk_biz_name" class="input-width" placeholder="请输入SDK提供方名称" />
</el-form-item>
<setting-list v-model="formData.sdk_privacy_info_list[index].sdk_list" ref="sdkSettingListRef"/>
<el-form-item label="" class="!mb-[8px]">
<el-button type="primary" link @click="sdkSettingListRef[index].addSettingList()">{{ t('addSdkSettingList') }}</el-button>
</el-form-item>
</div>
</div>
<div>
<el-button type="primary" link @click="addSdk">{{ t('addSdkInfo') }}</el-button>
</div>
<h4 class="text-lg my-[10px]">3. 你的权益</h4>
<div class="mb-[8px]">3.1
关于收集你的位置信息你可以通过以下路径小程序主页右上角设置点击特定信息点击不允许撤回对开发者的授权</div>
<div class="mb-[8px]">3.2 关于收集你的微信昵称头像收集你的手机号你可以通过以下路径小程序主页右上角... 设置
小程序已获取的信息 点击特定信息
点击通知开发者删除开发者承诺收到通知后将删除信息法律法规另有规定的开发者承诺将停止除存储和采取必要的安全保护措施之外的处理</div>
<div class="mb-[8px]">3.3 关于你的个人信息你可以通过以下方式与开发者联系行使查阅复制更正删除等法定权利</div>
<div class="mb-[8px]">3.4
若你在小程序中注册了账号你可以通过以下方式与开发者联系申请注销你在小程序中使用的账号在受理你的申请后开发者承诺在十五个工作日内完成核查和处理并按照法律法规要求处理你的相关信息</div>
<div>
<el-form-item label="电话" class="!mb-[8px]">
<el-input v-model="formData.owner_setting.contact_phone" class="input-width" placeholder="请输入开发者的手机号" />
</el-form-item>
<el-form-item label="邮箱" class="!mb-[8px]">
<el-input v-model="formData.owner_setting.contact_email" class="input-width" placeholder="请输入开发者的邮箱" />
</el-form-item>
<el-form-item label="微信号" class="!mb-[8px]">
<el-input v-model="formData.owner_setting.contact_weixin" class="input-width" placeholder="请输入开发者的微信号" />
</el-form-item>
<el-form-item label="qq号" class="!mb-[8px]">
<el-input v-model="formData.owner_setting.contact_qq" class="input-width" placeholder="请输入开发者的qq号" />
</el-form-item>
<div class="form-tip">信息收集方开发者的联系方式4种联系方式至少要填一种</div>
</div>
<h4 class="text-lg my-[10px]">4. 开发者对信息的存储</h4>
<div>
<el-radio-group v-model="formData.store_expire_type">
<div>
<el-radio :label="1">
固定存储期限
<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="formData.store_expire_timestamp"/></div>
</el-radio>
</div>
<div>
<el-radio :label="0">开发者承诺除法律法规另有规定外开发者对你的信息的保存期限应当为实现处理目的所必要的最短时间</el-radio>
</div>
</el-radio-group>
</div>
<h4 class="text-lg my-[10px]">5. 信息的使用规则</h4>
<div class="mb-[8px]">5.1 开发者将会在本指引所明示的用途内使用收集的信息</div>
<div class="mb-[8px]">
5.2 如开发者使用你的信息超出本指引目的或合理范围开发者必须在变更使用目的或范围前再次以
<div class="!w-[180px] inline-block">
<el-input v-model="formData.owner_setting.notice_method"/>
</div>
方式告知并征得你的明示同意</div>
<h4 class="text-lg my-[10px]">6. 信息对外提供</h4>
<div class="mb-[8px]">6.1
开发者承诺不会主动共享或转让你的信息至任何第三方如存在确需共享或转让时开发者应当直接征得或确认第三方征得你的单独同意</div>
<div class="mb-[8px]">6.2
开发者承诺不会对外公开披露你的信息如必须公开披露时开发者应当向你告知公开披露的目的披露信息的类型及可能涉及的信息并征得你的单独同意</div>
<h4 class="text-lg my-[10px]">7.
你认为开发者未遵守上述约定或有其他的投诉建议或未成年人个人信息保护相关问题可通过以下方式与开发者联系或者向微信进行投诉</h4>
<!-- <h4 class="text-lg my-[10px]">8. 补充文档</h4>-->
</el-form>
</el-scrollbar>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ 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 { computed, reactive, ref } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import Test from '@/utils/test'
import SettingList from './setting-list.vue'
import { getWeappPrivacySetting, setWeappPrivacySetting } from '@/app/api/weapp'
const showDialog = ref(false)
const loading = ref(false)
const settingListRef = ref(null)
const sdkSettingListRef = ref(null)
const props = defineProps({
config: {
type: Object,
default: () => {}
}
})
/**
* 表单数据
*/
const initialFormData = {
setting_list: [
{
privacy_key: 'UserInfo',
privacy_text: ''
},
{
privacy_key: 'Location',
privacy_text: ''
},
{
privacy_key: 'PhoneNumber',
privacy_text: ''
}
],
owner_setting: {
notice_method: ''
},
sdk_privacy_info_list: [],
store_expire_type: 0,
store_expire_timestamp: ''
}
const formData: Record<string, any> = reactive({...initialFormData})
const formRef = ref<FormInstance>()
const emit = defineEmits(['complete'])
const formRules = computed(() => {
return {}
})
const addSdk = () => {
formData.sdk_privacy_info_list.push({
sdk_name: '',
sdk_biz_name: '',
sdk_list: []
})
}
const delSdk = (index: number) => {
formData.sdk_privacy_info_list.splice(index, 1)
}
/**
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
if (loading.value) return
loading.value = true
const data = formData
if (!data.store_expire_type) data.owner_setting.store_expire_timestamp = data.store_expire_timestamp
setWeappPrivacySetting(data).then(res => {
loading.value = false
showDialog.value = false
emit('complete', data)
}).catch(() => {
loading.value = false
})
}
})
}
const setFormData = async () => {
getWeappPrivacySetting().then(({ data }) => {
loading.value = false
Object.assign(formData, initialFormData)
if (data) {
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
if (data.owner_setting.store_expire_timestamp) {
formData.store_expire_type = 1
formData.store_expire_timestamp = data.owner_setting.store_expire_timestamp
}
}
showDialog.value = true
})
}
defineExpose({
showDialog,
setFormData
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,221 @@
<template>
<div v-for="(item, index) in value">
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'UserInfo'">
为了<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>开发者将在获取你的明示同意后收集你的微信昵称头像
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Location'">
为了<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>开发者将在获取你的明示同意后收集你的位置信息
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'PhoneNumber'">
为了<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>开发者将在获取你的明示同意后收集你的手机号
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Address'">
开发者收集你的地址用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Record'">
为了<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>开发者将在获取你的明示同意后访问你的麦克风
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Contact'">
开发者使用你的通讯录仅写入权限用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'EXOrderInfo'">
开发者收集你的订单信息用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'EXUserOpLog'">
开发者收集你的操作日志用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'BlueTooth'">
开发者访问你的蓝牙用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'MessageFile'">
开发者收集你选中的文件用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Compass'">
开发者调用你的磁场传感器用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Clipboard'">
开发者读取你的剪切板用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'DeviceMotion'">
开发者调用你的方向传感器用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'ChooseLocation'">
开发者获取你选择的位置信息用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'CalendarWriteOnly'">
开发者使用你的日历仅写入权限用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'AlbumWriteOnly'">
为了<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div> 开发者将在获取你的明示同意后使用你的相册仅写入权限
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'EXUserPublishContent'">
开发者收集你的发布内容用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'DeviceInfo'">
开发者收集你的设备信息用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Album'">
开发者收集你选中的照片或视频信息用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Invoice'">
开发者收集你的发票信息用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'RunData'">
为了<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>开发者将在获取你的明示同意后收集你的微信运动步数
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Camera'">
为了<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>开发者将在获取你的明示同意后访问你的摄像头
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'EXUserFollowAcct'">
开发者收集你的所关注账号用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'EXIDNumber'">
开发者收集你的身份证号码用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'LicensePlate'">
为了<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>开发者将在获取你的明示同意后收集你的车牌号
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Email'">
开发者收集你的邮箱用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Accelerometer'">
开发者调用你的加速传感器用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
<div class="flex items-center mb-[8px]" v-if="item.privacy_key == 'Gyroscope'">
开发者调用你的陀螺仪传感器用于<div class="!w-[200px] inline-block mx-[5px]"><el-input v-model="value[index].privacy_text" :placeholder="t('settingPlaceholder')"/></div>
<icon name="element Remove" @click="removeSettingList(index)" color="red" class="cursor-pointer"></icon>
</div>
</div>
<el-dialog v-model="settingTypeDialog" :title="t('settingTypeTitle')" width="500px" :destroy-on-close="true">
<el-checkbox-group v-model="checkList">
<template v-for="(item, index) in privacyList">
<el-checkbox :label="item.privacy_key" v-if="!checkIsSelected(item.privacy_key)">{{ item.privacy_text }}</el-checkbox>
</template>
</el-checkbox-group>
<template #footer>
<span class="dialog-footer">
<el-button @click="settingTypeDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="selectSettingType()">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { t } from '@/lang'
const props = defineProps({
modelValue: {
type: Array,
default: () => {
return []
}
}
})
const settingTypeDialog = ref(false)
const privacyList = ref([
{ privacy_key: 'UserInfo', privacy_text: '用户信息(微信昵称、头像)' },
{ privacy_key: 'Location', privacy_text: '位置信息' },
{ privacy_key: 'Address', privacy_text: '地址' },
{ privacy_key: 'Invoice', privacy_text: '发票信息' },
{ privacy_key: 'RunData', privacy_text: '微信运动数据' },
{ privacy_key: 'Record', privacy_text: '麦克风' },
{ privacy_key: 'Album', privacy_text: '选中的照片或视频信息' },
{ privacy_key: 'Camera', privacy_text: '摄像头' },
{ privacy_key: 'PhoneNumber', privacy_text: '手机号码' },
{ privacy_key: 'Contact', privacy_text: '通讯录(仅写入)权限' },
{ privacy_key: 'DeviceInfo', privacy_text: '设备信息' },
{ privacy_key: 'EXIDNumber', privacy_text: '身份证号码' },
{ privacy_key: 'EXOrderInfo', privacy_text: '订单信息' },
{ privacy_key: 'EXUserPublishContent', privacy_text: '发布内容' },
{ privacy_key: 'EXUserFollowAcct', privacy_text: '所关注账号' },
{ privacy_key: 'EXUserOpLog', privacy_text: '操作日志' },
{ privacy_key: 'AlbumWriteOnly', privacy_text: '相册(仅写入)权限' },
{ privacy_key: 'LicensePlate', privacy_text: '车牌号' },
{ privacy_key: 'BlueTooth', privacy_text: '蓝牙' },
{ privacy_key: 'CalendarWriteOnly', privacy_text: '日历(仅写入)权限' },
{ privacy_key: 'Email', privacy_text: '邮箱' },
{ privacy_key: 'MessageFile', privacy_text: '选中的文件' },
{ privacy_key: 'ChooseLocation', privacy_text: '选择的位置信息' },
{ privacy_key: 'Accelerometer', privacy_text: '加速传感器' },
{ privacy_key: 'Compass', privacy_text: '磁场传感器' },
{ privacy_key: 'DeviceMotion', privacy_text: '方向传感器' },
{ privacy_key: 'Gyroscope', privacy_text: '陀螺仪传感器' },
{ privacy_key: 'Clipboard', privacy_text: '剪切板' }
])
const emits = defineEmits(['update:modelValue', 'change'])
const value = computed({
get () {
return props.modelValue
},
set (value) {
emits('update:modelValue', value)
}
})
const removeSettingList = (index: number) => {
value.value.splice(index, 1)
}
const checkList = ref([])
const selectSettingType = () => {
checkList.value.forEach((item: string) => {
value.value.push({
privacy_key: item,
privacy_text: ''
})
})
settingTypeDialog.value = false
checkList.value = []
}
const addSettingList = () => {
settingTypeDialog.value = true
}
const selectedSettingType = computed(() => {
return value.value.map((item: any) => item.privacy_key)
})
const checkIsSelected = (key: string) => {
return selectedSettingType.value.includes(key)
}
defineExpose({
addSettingList
})
</script>
<style lang="scss" scoped></style>

View File

@ -84,37 +84,78 @@
</el-card>
<el-card class="box-card !border-none mt-[15px]" shadow="never">
<div class="flex">
<div class="flex items-start justify-between">
<h3 class="panel-title !text-sm">{{ t('functionSetting') }}</h3>
<el-button type="primary" link @click="modifyDomainFn" v-if="formData.is_authorization">{{ t('update') }}</el-button>
</div>
<el-form-item :label="t('requestUrl')">
<el-input :model-value="formData.request_url" placeholder="Please input" class="input-width" :readonly="true">
<template #append>
<div class="cursor-pointer" @click="copyEvent(formData.request_url)">{{ t('copy') }}</div>
</template>
</el-input>
</el-form-item>
<el-form-item :label="t('socketUrl')">
<el-input :model-value="formData.socket_url" placeholder="Please input" class="input-width" :readonly="true">
<template #append>
<div class="cursor-pointer" @click="copyEvent(formData.socket_url)">{{ t('copy') }}</div>
</template>
</el-input>
</el-form-item>
<el-form-item :label="t('uploadUrl')">
<el-input :model-value="formData.upload_url" placeholder="Please input" class="input-width" :readonly="true">
<template #append>
<div class="cursor-pointer" @click="copyEvent(formData.upload_url)">{{ t('copy') }}</div>
</template>
</el-input>
</el-form-item>
<el-form-item :label="t('downloadUrl')">
<el-input :model-value="formData.download_url" placeholder="Please input" class="input-width" :readonly="true">
<template #append>
<div class="cursor-pointer" @click="copyEvent(formData.download_url)">{{ t('copy') }}</div>
</template>
</el-input>
<div v-if="!formData.is_authorization">
<el-form-item :label="t('requestUrl')">
<el-input :model-value="formData.request_url" placeholder="Please input" class="input-width" :readonly="true">
<template #append>
<div class="cursor-pointer" @click="copyEvent(formData.request_url)">{{ t('copy') }}</div>
</template>
</el-input>
</el-form-item>
<el-form-item :label="t('socketUrl')">
<el-input :model-value="formData.socket_url" placeholder="Please input" class="input-width" :readonly="true">
<template #append>
<div class="cursor-pointer" @click="copyEvent(formData.socket_url)">{{ t('copy') }}</div>
</template>
</el-input>
</el-form-item>
<el-form-item :label="t('uploadUrl')">
<el-input :model-value="formData.upload_url" placeholder="Please input" class="input-width" :readonly="true">
<template #append>
<div class="cursor-pointer" @click="copyEvent(formData.upload_url)">{{ t('copy') }}</div>
</template>
</el-input>
</el-form-item>
<el-form-item :label="t('downloadUrl')">
<el-input :model-value="formData.download_url" placeholder="Please input" class="input-width" :readonly="true">
<template #append>
<div class="cursor-pointer" @click="copyEvent(formData.download_url)">{{ t('copy') }}</div>
</template>
</el-input>
</el-form-item>
</div>
<div v-else>
<el-form-item :label="t('requestUrl')">
<div v-if="formData.domain.requestdomain">{{ formData.domain.requestdomain }}</div>
<div v-else>-</div>
</el-form-item>
<el-form-item :label="t('socketUrl')">
<div v-if="formData.domain.wsrequestdomain">{{ formData.domain.wsrequestdomain }}</div>
<div v-else>-</div>
</el-form-item>
<el-form-item :label="t('uploadUrl')">
<div v-if="formData.domain.uploaddomain">{{ formData.domain.uploaddomain }}</div>
<div v-else>-</div>
</el-form-item>
<el-form-item :label="t('downloadUrl')">
<div v-if="formData.domain.downloaddomain">{{ formData.domain.downloaddomain }}</div>
<div v-else>-</div>
</el-form-item>
<el-form-item :label="t('udpUrl')">
<div v-if="formData.domain.udpdomain">{{ formData.domain.udpdomain }}</div>
<div v-else>-</div>
</el-form-item>
<el-form-item :label="t('tcpUrl')">
<div v-if="formData.domain.tcpdomain">{{ formData.domain.tcpdomain }}</div>
<div v-else>-</div>
</el-form-item>
</div>
</el-card>
<el-card class="box-card !border-none mt-[15px]" shadow="never" v-if="formData.is_authorization">
<div class="flex items-start justify-between">
<h3 class="panel-title !text-sm">{{ t('serviceContentStatement') }}</h3>
</div>
<el-form-item :label="t('privacyAgreement')">
<div class="flex items-center">
<div class="form-tip !mt-0">{{ t('privacyAgreementTips') }}</div>
<el-button type="primary" link @click="modifyPrivacyAgreementFn">{{ t('setting') }}</el-button>
</div>
</el-form-item>
</el-card>
</el-form>
@ -125,6 +166,9 @@
</div>
</div>
</div>
<modify-domain ref="modifyDomainRef" @complete="modifyDomainComplete"/>
<modify-privacy-agreement :config="formData" ref="modifyPrivacyAgreementRef"/>
</template>
<script lang="ts" setup>
@ -135,6 +179,8 @@ import { useClipboard } from '@vueuse/core'
import { ElMessage, FormInstance } from 'element-plus'
import { ArrowLeft } from '@element-plus/icons-vue'
import { useRoute, useRouter } from 'vue-router'
import ModifyDomain from '@/app/views/channel/weapp/components/modify-domain.vue'
import ModifyPrivacyAgreement from '@/app/views/channel/weapp/components/modify-privacy-agreement.vue'
const route = useRoute()
const router = useRouter()
@ -159,7 +205,15 @@ const formData = reactive<Record<string, any>>({
upload_url: '',
download_url: '',
upload_private_key: '',
is_authorization: 0
is_authorization: 0,
domain: {
requestdomain: '',
wsrequestdomain: '',
uploaddomain: '',
downloaddomain: '',
tcpdomain: '',
udpdomain: ''
}
})
const formRef = ref<FormInstance>()
@ -242,6 +296,20 @@ const save = async (formEl: FormInstance | undefined) => {
})
}
const modifyDomainRef = ref(null)
const modifyDomainFn = () => {
modifyDomainRef.value.setFormData(formData.domain)
modifyDomainRef.value.showDialog = true
}
const modifyDomainComplete = (data) => {
formData.domain = data
}
const modifyPrivacyAgreementRef = ref(null)
const modifyPrivacyAgreementFn = () => {
modifyPrivacyAgreementRef.value.setFormData()
}
</script>
<style lang="scss" scoped></style>

View File

@ -10,6 +10,7 @@
<el-radio label="static">{{ t('carouselSearchShowWayStatic') }}</el-radio>
<el-radio label="fixed">{{ t('carouselSearchShowWayFixed') }}</el-radio>
</el-radio-group>
<div v-if="diyStore.editComponent.positionWay == 'fixed'" class="text-sm text-gray-400 mb-[10px]">滑动页面查看效果</div>
</el-form-item>
<el-form-item :label="t('carouselSearchFixedBgColor')" v-show="diyStore.editComponent.positionWay == 'fixed'">
<el-color-picker v-model="diyStore.editComponent.fixedBgColor" show-alpha :predefine="diyStore.predefineColors"/>

View File

@ -28,9 +28,9 @@
</template>
</el-dialog> -->
<h3 class="mb-[10px]">{{ t('floatBtnBtton') }}</h3>
<h3 class="mb-[10px]">{{ t('floatBtnButton') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('floatBtnBtton')">
<el-form-item :label="t('floatBtnButton')">
<span>{{ selectTemplate.name }}</span>
<ul class="ml-[10px] flex items-center">
<template v-for="(item,i) in templateList" :key="i">
@ -43,6 +43,9 @@
<el-form-item :label="t('floatBtnOffset')">
<el-slider v-model="diyStore.editComponent.offset" show-input size="small" class="ml-[10px] diy-nav-slider" :max="100"/>
</el-form-item>
<el-form-item :label="t('lateralBtnOffset')">
<el-slider v-model="diyStore.editComponent.lateralOffset" show-input size="small" class="ml-[10px] diy-nav-slider" :max="15" :min="-10"/>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
@ -91,7 +94,7 @@ import { t } from '@/lang'
import Sortable from 'sortablejs'
import useDiyStore from '@/stores/modules/diy'
import { img } from '@/utils/common'
import { range } from 'lodash-es'
import { min, range } from 'lodash-es'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = ['pageBgColor','marginTop','marginBottom','marginBoth','componentBgUrl'] //

View File

@ -1,23 +1,28 @@
<template>
<el-dialog v-model="dialogThemeVisible" title="编辑色调" width="850px" align-center>
<el-form :model="openData" label-width="150px" :rules="formRules" class="h-[640px] overflow-auto" ref="formRef" @submit.prevent>
<el-dialog v-model="dialogThemeVisible" title="编辑色调" width="850px" align-center destroy-on-close="true">
<el-form :model="openData" label-width="150px" :rules="formRules">
<el-form-item label="色调名称" prop="title">
<el-input v-model="openData.title" placeholder="请输入色调名称" maxlength="15" class="!w-[250px]"
:disabled="openData.id != ''" />
</el-form-item>
</el-form>
<el-form-item label="色调名称" prop="title" >
<el-input v-model="openData.title" placeholder="请输入色调名称" maxlength="15" class="!w-[250px]" :disabled="openData.mark != 'diy'" />
<el-form :model="formData" label-width="150px" class="h-[640px] overflow-auto" ref="formRef" @submit.prevent>
<el-form-item :label="item.title" v-for="(item, index) in formData" :key="index" :prop="`${index}.value`"
:rules="[{ required: true, message: `请选择${item.title}色调`, trigger: 'blur' }]">
<el-color-picker v-model="item.value" show-alpha :predefine="diyStore.predefineColors" @change="colorPickerChange($event, item)" />
<div class="form-tip">{{ item.tip }}</div>
</el-form-item>
<el-form-item :label="item.title" v-for="(item,index) in formData" :key="index">
<el-color-picker v-model="item.value" show-alpha :predefine="diyStore.predefineColors"/>
<div class="form-tip">{{item.tip}}</div>
</el-form-item>
<el-form-item :label="item.title" v-for="(item,index) in openData.diy_value" :key="index">
<el-form-item :label="item.title" v-for="(item, index) in openData.new_theme" :key="index">
<div class="flex items-center">
<el-color-picker v-model="item.value" show-alpha :predefine="diyStore.predefineColors"/>
<span class="text-primary cursor-pointer text-[14px] ml-[20px]" @click="editThemeFn(item)">编辑</span>
<span class="text-primary cursor-pointer text-[14px] ml-[8px]" @click="deleteThemeFn(item)">删除</span>
<el-color-picker v-model="item.value" show-alpha :predefine="diyStore.predefineColors" />
<span class="text-primary cursor-pointer text-[14px] ml-[20px]" @click="editThemeFn(item)">编辑</span>
<span class="text-primary cursor-pointer text-[14px] ml-[8px]"
@click="deleteThemeFn(item)">删除</span>
</div>
<div class="form-tip">{{item.tip}}</div>
<div class="form-tip">{{ item.tip }}</div>
</el-form-item>
<el-form-item>
@ -28,7 +33,7 @@
<div class="form-tip">新增颜色key值不能与当前的存在的key值重复</div>
</el-form-item>
</el-form>
<add-theme ref="addThemeRef" @confirm="addThemeConfirm" />
<add-theme-component ref="addThemeRef" @confirm="addThemeConfirm" />
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogThemeVisible = false">取消</el-button>
@ -42,23 +47,26 @@
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { t } from '@/lang'
import { filterNumber } from '@/utils/common'
import { deepClone, filterNumber } from '@/utils/common'
import { ElMessage } from 'element-plus'
import { cloneDeep } from 'lodash-es'
import addTheme from './add-theme.vue'
import addThemeComponent from './add-theme.vue'
import useDiyStore from '@/stores/modules/diy'
import { deleteTheme, addTheme, editTheme } from '@/app/api/diy'
import type { FormInstance } from 'element-plus'
const diyStore = useDiyStore()
const dialogThemeVisible = ref(false)
const addThemeRef = ref(null)
const addThemeRef = ref()
const openData: Record<string, any> = reactive({ //
title: '',
mark: '',
diy_value: [],
default: {},
data: {}
})
id: '',
theme: {},
default_theme: {},
new_theme: [],
key: '',
theme_field: [] //
})
const emit = defineEmits(['confirm'])
@ -68,191 +76,194 @@ const formRef = ref<FormInstance>()
const formRules = computed(() => {
return {
title: [
{ required: true, message: "请输入色调名称", trigger: 'blur' }
{ required: true, message: '请输入色调名称', trigger: 'blur' }
]
}
})
/**
* 表单数据
*/
const initialFormData = [
{
title: '主色调',
label: '--primary-color',
value: '#333333',
tip: '主色调在uiapp中使用var(--primary-color)'
},
{
title: '辅色调',
label: '--primary-help-color',
value: '#333333',
tip: '辅色调在uiapp中使用var(--primary-help-color)'
},
{
title: '页面背景色',
label: '--page-bg-color',
value: '#ffffff',
tip: '页面背景色在uiapp中使用var(--page-bg-color)'
},
{
title: '主色调浅色(淡)',
label: '--primary-color-light',
value: '',
tip: '主色调浅色在uiapp中使用var(--primary-color-light)'
},
{
title: '主色调浅色(深)',
label: '--primary-color-light2',
value: '',
tip: '主色调浅色在uiapp中使用var(--primary-color-light2)'
},
{
title: '灰色调',
label: '--primary-color-dark',
value: '#cccccc',
tip: '灰色调在uiapp中使用var(--primary-color-dark)'
},
{
title: '禁用色',
label: '--primary-color-disabled',
value: '#eeeeee',
tip: '禁用色在uiapp中使用var(--primary-color-disabled)'
},
{
title: '价格颜色',
label: '--price-text-color',
value: '#333333',
tip: '价格颜色在uiapp中使用var(--price-text-color)'
}
]
const formData = ref([...cloneDeep(initialFormData)])
const formData = ref()
const open = (res:any) => { // name=>key=>default=>data=>
const open = (res: any) => { // title=>key=>default_theme=>theme=>
Object.keys(openData).forEach((key: string) => {
openData[key] = res[key] != undefined ? cloneDeep(res[key]) : '';
});
openData[key] = res[key] != undefined ? cloneDeep(res[key]) : ''
})
//
formData.value.forEach((item,index) => {
initialFormData.forEach((subItem, subIndex)=>{
if(item.label == subItem.label){
item.value = subItem.value;
}
})
});
formData.value = [...cloneDeep(openData.theme_field)]
//
formData.value.forEach((item,index) => {
item.value = res.data[item.label] ? res.data[item.label] : item.value
});
formData.value.forEach((item, index) => {
item.value = res.theme[item.label] ? res.theme[item.label] : item.value
})
dialogThemeVisible.value = true
}
//
const addThemeFn = ()=>{
let keyArr = []
formData.value.forEach((item,index) => {
keyArr.push(item.label);
});
let obj = {
const addThemeFn = () => {
// keyArr, key
const keyArr: string[] = []
formData.value.forEach((item, index) => {
keyArr.push(item.label)
})
const obj = {
key: keyArr
}
addThemeRef.value.open(obj);
addThemeRef.value.open(obj)
}
//
const editThemeFn = (res:any)=>{
let keyArr = []
formData.value.forEach((item,index) => {
keyArr.push(item.label);
});
let obj = {
const editThemeFn = (res: any) => {
// keyArr, key
const keyArr: string[] = []
formData.value.forEach((item, index) => {
keyArr.push(item.label)
})
const obj = {
key: keyArr,
data: res
}
addThemeRef.value.open(obj);
addThemeRef.value.open(obj)
}
//
const deleteThemeFn = (res:any)=>{
let indent = -1;
for(let i = 0; i < openData.diy_value.length; i++){
if(openData.diy_value[i].label == res.label){
indent = i;
//
const deleteThemeFn = (res: any) => {
let indent = -1
for (let i = 0; i < openData.new_theme.length; i++) {
if (openData.new_theme[i].label == res.label) {
indent = i
}
}
if(indent > -1){
openData.diy_value.splice(indent,1);
if (indent > -1) {
openData.new_theme.splice(indent, 1)
}
}
//
const addThemeConfirm = (res:any) =>{
for(let i = 0; i < openData.diy_value.length; i++){
if(openData.diy_value[i].label == res.label){
openData.diy_value[i] = res;
return;
const addThemeConfirm = (res: any) => {
for (let i = 0; i < openData.new_theme.length; i++) {
if (openData.new_theme[i].label == res.label) {
openData.new_theme[i] = res
return
}
}
openData.diy_value.push(res);
openData.new_theme.push(res)
}
//
const resetConfirmFn = ()=>{
if(openData.default && Object.keys(openData.default).length){
formData.value.forEach((item,index)=>{
item.value = cloneDeep(openData.default[item.label]);
const resetConfirmFn = () => {
if (openData.default_theme && Object.keys(openData.default_theme).length) {
formData.value.forEach((item, index) => {
item.value = openData.default_theme[item.label]
})
}else{
formData.value = cloneDeep(initialFormData);
} else {
formData.value = cloneDeep(openData.theme_field)
// title
if (!openData.id) {
openData.title = ''
}
}
openData.diy_value = [];
if(openData.mark == 'diy'){
openData.title = '';
}
openData.new_theme = []
ElMessage({
message: '重置成功',
type: 'success',
type: 'success'
})
}
let confirmRepeat = false
const confirmFn = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
let params = {
const params = {
title: '',
id: '',
theme: {},
diy_value: [],
title: ''
default_theme: {},
new_theme: [],
addon: ''
}
params.title = openData.title;
formData.value.forEach((item,index) => {
params.title = openData.title
formData.value.forEach((item, index) => {
params.theme[item.label] = item.value
});
openData.diy_value.forEach((item,index) => {
})
openData.new_theme.forEach((item, index) => {
params.theme[item.label] = item.value
});
})
params.diy_value = openData.diy_value || [];
emit('confirm', params);
params.new_theme = openData.new_theme || []
let api = null
if (openData.id) {
api = editTheme
params.id = openData.id
} else {
api = addTheme
}
params.addon = openData.key
dialogThemeVisible.value = false;
//
if (openData.id == '') {
const defaultTheme = {}
openData.theme_field.forEach((item, index) => {
defaultTheme[item.label] = item.value
})
params.default_theme = cloneDeep(defaultTheme)
} else {
params.default_theme = cloneDeep(openData.default_theme)
}
if (confirmRepeat) return false
confirmRepeat = true
api(params).then((res: any) => {
confirmRepeat = false
dialogThemeVisible.value = false
emit('confirm', params)
}).catch(() => {
confirmRepeat = false
})
}
})
}
const applyOpacity = (color, opacity) => {
// RGBA
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
const rgbaRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(,\s*\d*\.?\d+)?\)$/
if (hexRegex.test(color)) {
// #ffffff
const hex = color.replace('#', '')
const r = parseInt(hex.substring(0, 2), 16)
const g = parseInt(hex.substring(2, 4), 16)
const b = parseInt(hex.substring(4, 6), 16)
return `rgba(${r},${g},${b},${opacity})`
} else if (rgbaRegex.test(color)) {
// RGBA rgba(255,255,255,0.5)
return color.replace(/[\d\.]+\)$/, `${opacity})`)
}
return color
}
const colorPickerChange = (e: any, data: any) => {
if (data.label == '--primary-color') {
formData.value.forEach((item, index) => {
if (item.label == '--primary-color-light') {
item.value = applyOpacity(data.value, 0.1)
}
if (item.label == '--primary-color-light2') {
item.value = applyOpacity(data.value, 0.8)
}
})
}
}
defineExpose({
dialogThemeVisible,
open
})
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

View File

@ -1,26 +1,41 @@
<template>
<el-dialog v-model="dialogThemeVisible" :title="data.addon_title" width="550px" align-center>
<el-form class="page-form mt-[15px]" :model="formData" label-width="90px" v-loading="loading">
<el-form-item label="选择配色">
<div class="flex items-center flex-wrap">
<template v-for="(tempItem,tempIndex) in theme_temp">
<div :key="tempIndex" v-if="tempItem.name != 'diy'" class="flex items-center border-[1px] border-solid border-[#dcdee2] rounded-[5px] h-[40px] px-[15px] mr-[10px] cursor-pointer my-[5px]" :class="{'!border-[var(--el-color-primary)]': curr_theme_mark == tempItem.name}" @click="themeTempChange(tempItem)">
<span v-if="data.theme" class="w-[20px] h-[20px] mr-[5px] rounded-[3px]" :style="{backgroundColor: data.theme['--primary-color']}"></span>
<span class="text-[14px]" :class="{'!text-[var(--el-color-primary)]': curr_theme_mark == tempItem.name}">{{tempItem.title}}</span>
<el-dialog v-model="dialogThemeVisible" :title="dialogTitle" width="535px" align-center class="custom-theme-dialog" @close="cancelFn">
<div class="flex flex-col items-baseline">
<div class="flex items-center flex-wrap max-h-[365px] overflow-auto [&>:nth-child(3n)]:mr-0" >
<div :key="tempIndex" v-for="(tempItem, tempIndex) in themeTemp"
class="flex flex-col border-[1px] border-solid border-[#dcdee2] rounded-[4px] px-[10px] pt-[10px] pb-[15px] mr-[10px] cursor-pointer my-[5px]"
:class="{ '!border-[var(--el-color-primary)]': currTheme.id == tempItem.id }"
@click="themeTempChange(tempItem)">
<div class="flex justify-between pb-[5px]">
<div class="text-[14px] text-[#666] max-w-[85px] whitespace-nowrap overflow-hidden text-ellipsis" :class="{ '!text-[#333]': currTheme.id == tempItem.id }">{{ tempItem.title }}</div>
<div>
<span class="iconfont iconshanchu-fanggaiV6xx !text-[14px] text-[#999]" v-if="currTheme.id != tempItem.id && tempItem.theme_type != 'default' && currTableTheme != tempItem.id" @click.stop="deleteThemeFn(tempItem)"></span>
<span class="nc-iconfont nc-icon-bianjiV6xx1 !text-[14px] text-[#999] ml-[5px]" @click.stop="editThemeFn('edit', tempItem)"></span>
</div>
</div>
<div class="flex">
<div class="w-[70px] h-[54px] pl-[7px] pt-[9px] flex flex-col mr-[4px] rounded-[4px] text-[10px] leading-[1] text-[#fff]" :style="{ backgroundColor: tempItem.theme['--primary-color'] }">
<span>主色调</span>
</div>
<div class="flex flex-col">
<div class="secod-color-item mb-[4px]" :style="{ backgroundColor: tempItem.theme['--primary-help-color2'] }">
<span>辅色</span>
</div>
<div class="secod-color-item" :style="{ backgroundColor: tempItem.theme['--primary-color-dark'] }">
<span>配色</span>
</div>
</div>
</template>
<div class="flex items-center border-[1px] border-solid border-[#dcdee2] rounded-[5px] h-[40px] px-[15px] cursor-pointer" :class="{'!border-[var(--el-color-primary)]': curr_theme_mark == 'diy'}" @click="themeTempChange('diy')">
<span class="nc-iconfont nc-icon-tianjiaV6xx mr-[5px]" :class="{'!text-[var(--el-color-primary)]': curr_theme_mark == 'diy'}"></span>
<span class="text-[14px]" :class="{'!text-[var(--el-color-primary)]': curr_theme_mark == 'diy'}">自定义</span>
</div>
</div>
</el-form-item>
</el-form>
<edit-theme ref="editThemeRef" @confirm="editThemeConfirm"/>
</div>
<div class="flex items-center border-[1px] border-solid border-[var(--el-color-primary)] rounded-[2px] h-[32px] px-[15px] cursor-pointer mt-[15px]" @click="editThemeFn()">
<span class="text-[14px] text-[var(--el-color-primary)]">新增配色</span>
</div>
</div>
<edit-theme ref="editThemeRef" @confirm="editThemeConfirm" />
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogThemeVisible = false">取消</el-button>
<el-button type="primary" plain @click="editThemeFn()">编辑</el-button>
<el-button @click="cancelFn()">取消</el-button>
<el-button type="primary" @click="confirmFn()">确定</el-button>
</div>
</template>
@ -30,137 +45,142 @@
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { t } from '@/lang'
import { setDiyTheme, getDefaultTheme } from '@/app/api/diy'
import { setDiyTheme, getDefaultTheme, deleteTheme } from '@/app/api/diy'
import { cloneDeep } from 'lodash-es'
import editTheme from './edit-theme.vue'
import useDiyStore from '@/stores/modules/diy'
import { time } from 'echarts'
const diyStore = useDiyStore()
const editThemeRef = ref(null)
const editThemeRef = ref()
const dialogThemeVisible = ref(false)
let confirmRepeat = false
const curr_theme_title = ref('') //title
const curr_theme_mark = ref('') //
const curr_theme_value = ref('') //theme
const theme_temp = ref([]);
const mode = ref('default'); //
const currTheme = reactive({
title: '',
id: '',
theme: {},
default_theme: {},
new_theme: [],
addon_title: '',
key: ''
})
const themeTemp = ref([])
const emit = defineEmits(['confirm'])
const data = ref({})
const open = (res:any) => {
confirmRepeat = false;
data.value = cloneDeep(res);
curr_theme_value.value = res.value;
curr_theme_mark.value = res.color_mark;
curr_theme_title.value = res.color_name;
//
theme_temp.value.forEach((item,index)=>{
if(item.name == data.value.color_mark){
item.diy_value = data.value.diy_value;
item.title = res.color_name;
const initData = (params: any, callback: any = '') => {
getDefaultTheme({ addon: params.key }).then((res) => {
themeTemp.value = res.data || []
if (callback) {
callback(res.data[res.data.length - 1])
}
})
}
mode.value = res.mode;
const currTableTheme = ref('')
const open = (res: any) => {
currTableTheme.value = res.id
initData(res)
confirmRepeat = false
currTheme.title = res.title
currTheme.id = res.id
currTheme.theme = res.theme
currTheme.addon_title = res.addon_title
currTheme.key = res.key
dialogThemeVisible.value = true
}
const emit = defineEmits(['confirm'])
const initData = () => {
getDefaultTheme().then((res) => {
theme_temp.value = res.data || [];
//
let diy_theme_temp = {
name: 'diy',
theme: '',
title: ''
}
theme_temp.value.push(diy_theme_temp);
})
}
initData()
const dialogTitle = computed(() => {
const name = `选择${currTheme.addon_title}配色`
return name
})
//
const themeTempChange = (item)=>{
if(item.name == data.value.color_mark){ //
curr_theme_title.value = data.value.color_name;
curr_theme_mark.value = data.value.color_mark;
curr_theme_value.value = data.value.value;
}else if(typeof item == 'object'){ //
curr_theme_title.value = item.title;
curr_theme_mark.value = item.name;
curr_theme_value.value = item.theme;
}else{ //
curr_theme_title.value = '自定义';
curr_theme_mark.value = item;
curr_theme_value.value = '';
}
const themeTempChange = (item: any = {}) => {
currTheme.title = item.title
currTheme.id = item.id
currTheme.theme = item.theme
currTheme.default_theme = item.default_theme
currTheme.new_theme = item.new_theme
}
//
const editThemeFn = ()=>{
let theme = {
default: {}, //
data: {}, //
title:'',
mark: '', // ,
diy_value: [] //
const editThemeFn = (type = 'add', item = {}) => {
const theme = {
default_theme: {}, //
theme: {}, //
title: '',
id: '', // ,
new_theme: [], //
key: '', //
theme_field: ''
}
theme.data = cloneDeep(curr_theme_value.value) || {};
theme.mark = curr_theme_mark.value;
theme_temp.value.forEach((item,index)=>{
if(item.name == curr_theme_mark.value){
theme.default = item.theme ? cloneDeep(item.theme) : '';
theme.diy_value= item.diy_value || [];
theme.title = item.title;
if (type == 'edit') {
theme.title = item.title
theme.theme = cloneDeep(item.theme) || {}
theme.id = item.id
theme.default_theme = cloneDeep(item.default_theme) || ''
theme.new_theme = cloneDeep(item.new_theme) || []
theme.new_theme = cloneDeep(item.new_theme) || []
}
theme.key = currTheme.key
//
themeTemp.value.forEach((item, index) => {
if (item.id == currTheme.id) {
theme.theme_field = item.theme_field
}
})
editThemeRef.value.open(theme)
}
//
const editThemeConfirm = (res)=>{
if(curr_theme_mark.value == data.value.color_mark){
data.value.value = res.theme;
}
theme_temp.value.forEach((item,index)=>{
if(item.name == curr_theme_mark.value){
item.diy_value= res.diy_value || [];
}
const editThemeConfirm = (res: any) => {
initData(currTheme, (params: any) => {
currTheme.new_theme = res.new_theme
currTheme.theme = res.theme
currTheme.title = res.title
currTheme.id = res.id || params.id // id, id
})
}
//
let deleteRepeat = false
const deleteThemeFn = (res: any) => {
if (deleteRepeat) return false
deleteRepeat = true
const id = res.id
deleteTheme(id).then((res) => {
initData(currTheme)
deleteRepeat = false
}).catch(() => {
deleteRepeat = false
})
data.value.title = res.title;
curr_theme_value.value = res.theme;
}
//
const confirmFn = () => {
if (confirmRepeat) return
confirmRepeat = true
let params = {}
params.id = data.value.id;
params.mode = mode.value;
params.color_mark = curr_theme_mark.value;
params.value = curr_theme_value.value;
params.key = data.value.key;
params.color_name = curr_theme_mark.value == 'diy' ? (data.value.title || '自定义') : curr_theme_title.value;
theme_temp.value.forEach((item,index)=>{
if(item.name == curr_theme_mark.value){
params.diy_value = cloneDeep(item.diy_value);
}
})
const params = {}
params.addon = currTheme.key
params.id = currTheme.id
params.title = currTheme.title
params.theme = currTheme.theme
params.new_theme = currTheme.new_theme
setDiyTheme(params).then((res) => {
emit('confirm', data);
confirmRepeat = false;
dialogThemeVisible.value = false;
}).catch(()=>{
confirmRepeat = false;
confirmRepeat = false
dialogThemeVisible.value = false
emit('confirm')
}).catch(() => {
confirmRepeat = false
})
}
//
const cancelFn = () => {
dialogThemeVisible.value = false
emit('confirm')
}
defineExpose({
@ -170,5 +190,7 @@ defineExpose({
</script>
<style lang="scss" scoped>
.secod-color-item {
@apply w-[60px] h-[25px] flex flex-col rounded-[4px] text-[10px] text-[#fff] leading-[1] items-end pt-[8px] pr-[7px];
}
</style>

View File

@ -237,8 +237,6 @@ route.query.type = route.query.type || '' // 页面模板,新页面传入
route.query.title = route.query.title || ''
route.query.back = route.query.back || '/site/diy/list'
console.log('route',route.path)
const backPath = route.query.back
const template = ref('');
const oldTemplate = ref('');

View File

@ -31,7 +31,7 @@
<div class="w-[700px]">
<div class="flex flex-wrap">
<diy-link v-model="link" :ignore="['DIY_LINK','DIY_JUMP_OTHER_APPLET','DIY_MAKE_PHONE_CALL']" @success="changePage">
<diy-link v-model="link" :ignore="['OTHER_LINK']" @success="changePage">
<el-button type="primary">{{ t('changePage') }}</el-button>
</diy-link>
<el-button type="primary" @click="toDecorate()" v-show="page.use_template.action == 'decorate'" class="ml-[12px]">{{ t('decorate') }}</el-button>

View File

@ -27,16 +27,16 @@
<el-table-column label="配色名称" min-width="120" >
<template #default="{ row }">
<div>{{ row.color_name }}</div>
<div>{{ row.title }}</div>
</template>
</el-table-column>
<el-table-column label="配色方案" min-width="120" >
<template #default="{ row }">
<div class="rounded-[3px] inline-flex items-center justify-center border-[1px] border-solid border-[#f2f2f2] overflow-hidden" v-if="row.value">
<span class="w-[18px] h-[18px]" :style="{backgroundColor: row.value['--primary-color']}"></span>
<span class="w-[18px] h-[18px]" :style="{backgroundColor: row.value['--primary-help-color']}"></span>
<span class="w-[18px] h-[18px]" :style="{backgroundColor: '#fff'}"></span>
<div class="rounded-[3px] inline-flex items-center justify-center border-[1px] border-solid border-[#f2f2f2] overflow-hidden" v-if="row.theme">
<span class="w-[18px] h-[18px]" :style="{backgroundColor: row.theme['--primary-color']}"></span>
<span class="w-[18px] h-[18px]" :style="{backgroundColor: row.theme['--primary-help-color2']}"></span>
<span class="w-[18px] h-[18px]" :style="{backgroundColor: row.theme['--primary-color-dark']}"></span>
</div>
</template>
</el-table-column>
@ -85,11 +85,17 @@ const initData = () => {
}
initData()
//
const editEvent = (data)=>{
themeListRef.value.open(data);
const editEvent = (data)=> {
themeListRef.value.open(data)
}
</script>
<style lang="scss" scoped></style>
<!-- 设置弹窗标题 -->
<style scoped>
/* 使用深度选择器 */
::v-deep .custom-theme-dialog .el-dialog__title {
font-size: 16px;
}
</style>

View File

@ -5,31 +5,31 @@
<!-- 表单组件 字段内容设置 -->
<slot name="field"></slot>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('样式')">
<el-form-item :label="t('style')">
<el-radio-group v-model="diyStore.editComponent.style">
<el-radio label="style-1">{{ t('默认') }}</el-radio>
<el-radio label="style-2">{{ t('列表') }}</el-radio>
<el-radio label="style-3">{{ t('下拉') }}</el-radio>
<el-radio label="style-1">{{ t('defaultSources') }}</el-radio>
<el-radio label="style-2">{{ t('listStyle') }}</el-radio>
<el-radio label="style-3">{{ t('dropDownStyle') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('选项')">
<el-form-item :label="t('option')">
<div ref="formCheckboxRef">
<div v-for="(option, index) in diyStore.editComponent.options" :key="option.id" class="option-item flex items-center mb-[15px]">
<el-input v-model="diyStore.editComponent.options[index].text" class="!w-[215px]" :placeholder="t('optionPlaceholder')" maxlength="30" clearable />
<span v-if="diyStore.editComponent.options.length > 1" @click="removeOption(index)" class="cursor-pointer ml-[5px] nc-iconfont nc-icon-shanchu-yuangaizhiV6xx"></span>
</div>
</div>
<span class="text-primary cursor-pointer mr-[10px]" @click="addOption">添加单个选项</span>
<span class="text-primary cursor-pointer mr-[10px]" @click="addOption">{{ t('addSingleOption') }}</span>
<el-popover :visible="visible" placement="bottom" :width="300">
<p class="mb-[5px]">批量添加选项</p>
<p class="text-[#888] text-[12px] mb-[5px]">每个选项之间用英文 "," 隔开自动过滤重复内容</p>
<p class="mb-[5px]">{{ t('addMultipleOption') }}</p>
<p class="text-[#888] text-[12px] mb-[5px]">{{ t('addOptionTips') }}</p>
<el-input v-model.trim="optionsValue" type="textarea" clearable maxlength="200" show-word-limit />
<div class="mt-[10px] text-right">
<el-button size="small" text @click="visible = false">取消</el-button>
<el-button size="small" type="primary" @click="batchAddOptions">确定</el-button>
<el-button size="small" text @click="visible = false">{{ t('cancel') }}</el-button>
<el-button size="small" type="primary" @click="batchAddOptions">{{ t('confirm') }}</el-button>
</div>
<template #reference>
<span class="text-primary cursor-pointer" @click="visible = true">批量添加选项</span>
<span class="text-primary cursor-pointer" @click="visible = true">{{ t('addMultipleOption') }}</span>
</template>
</el-popover>
</el-form-item>
@ -102,7 +102,7 @@ diyStore.editComponent.verify = (index: number) => {
let uniqueOptions = uniqueByKey(diyStore.value[index].options, 'text')
if (uniqueOptions.length != diyStore.value[index].options.length) {
res.code = false;
res.message = t('存在重复选项,请检查内容')
res.message = t('errorTipsOne')
}
return res
}
@ -147,7 +147,7 @@ const batchAddOptions = () => {
diyStore.editComponent.options.push(...filteredNewOptions);
} else {
ElMessage({
message: "选项已存在,请重新输入",
message: t('errorTipsTwo'),
type: "error",
});
}

View File

@ -5,7 +5,7 @@
<!-- 表单组件 字段内容设置 -->
<slot name="field"></slot>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('时间格式')">
<el-form-item :label="t('dataFormat')">
<el-radio-group v-model="diyStore.editComponent.dateFormat">
<div class="flex flex-col">
<el-radio label="YYYY年M月D日">{{ dateFormat.format1 }}</el-radio>
@ -17,43 +17,43 @@
</el-form>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('开始日期') }}</h3>
<h3 class="mb-[10px]">{{ t('startDate') }}</h3>
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
<el-form-item :label="t('提示语')">
<el-form-item :label="t('formPlaceholder')">
<el-input v-model.trim="diyStore.editComponent.start.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
</el-form-item>
<el-form-item :label="t('默认值')">
<el-form-item :label="t('defaultValue')">
<el-switch v-model="diyStore.editComponent.start.defaultControl" />
</el-form-item>
<el-form-item v-if="diyStore.editComponent.start.defaultControl">
<el-radio-group v-model="diyStore.editComponent.start.dateWay">
<el-radio label="current">{{ t('当天日期') }}</el-radio>
<el-radio label="diy">{{ t('指定日期') }}</el-radio>
<el-radio label="current">{{ t('currentDate') }}</el-radio>
<el-radio label="diy">{{ t('diyDate') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="diyStore.editComponent.start.defaultControl && diyStore.editComponent.start.dateWay == 'diy'">
<el-date-picker v-model="diyStore.editComponent.field.default.start.date" format="YYYY/MM/DD" value-format="YYYY-MM-DD" type="date" placeholder="请选择日期" @change="startDateChange" />
<el-date-picker v-model="diyStore.editComponent.field.default.start.date" format="YYYY/MM/DD" value-format="YYYY-MM-DD" type="date" :placeholder="t('startDataPlaceholder')" @change="startDateChange" />
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('结束日期') }}</h3>
<h3 class="mb-[10px]">{{ t('endDate') }}</h3>
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
<el-form-item :label="t('提示语')">
<el-form-item :label="t('formPlaceholder')">
<el-input v-model.trim="diyStore.editComponent.end.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
</el-form-item>
<el-form-item :label="t('默认值')">
<el-form-item :label="t('defaultValue')">
<el-switch v-model="diyStore.editComponent.end.defaultControl" />
</el-form-item>
<el-form-item v-if="diyStore.editComponent.end.defaultControl">
<el-radio-group v-model="diyStore.editComponent.end.dateWay">
<el-radio label="current">{{ t('当天日期') }}</el-radio>
<el-radio label="diy">{{ t('指定日期') }}</el-radio>
<el-radio label="current">{{ t('currentDate') }}</el-radio>
<el-radio label="diy">{{ t('diyDate') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="diyStore.editComponent.end.defaultControl && diyStore.editComponent.end.dateWay == 'diy'">
<el-date-picker :disabled-date="disabledEndDate" v-model="diyStore.editComponent.field.default.end.date" format="YYYY/MM/DD" value-format="YYYY-MM-DD" type="date" placeholder="请选择结束日期" @change="endDateChange" />
<el-date-picker :disabled-date="disabledEndDate" v-model="diyStore.editComponent.field.default.end.date" format="YYYY/MM/DD" value-format="YYYY-MM-DD" type="date" :placeholder="t('endDataPlaceholder')" @change="endDateChange" />
</el-form-item>
</el-form>
</div>
@ -67,7 +67,7 @@
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('文字样式') }}</h3>
<h3 class="mb-[10px]">{{ t('textStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] diy-nav-slider" :min="12" :max="18" />
@ -102,101 +102,101 @@ diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
//
diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' }
let starTime = diyStore.value[index].field.default.start.date;
let endTime = diyStore.value[index].field.default.end.date;
let starTime = diyStore.value[index].field.default.start.date
let endTime = diyStore.value[index].field.default.end.date
let today = new Date();
const hours = String(today.getHours()).padStart(2, '0');
const minutes = String(today.getMinutes()).padStart(2, '0');
let today = new Date()
const hours = String(today.getHours()).padStart(2, '0')
const minutes = String(today.getMinutes()).padStart(2, '0')
if(diyStore.editComponent.start.dateWay == 'current'){
starTime = today.toISOString().split('T')[0];
if (diyStore.editComponent.start.dateWay == 'current') {
starTime = today.toISOString().split('T')[0]
}
if(diyStore.editComponent.end.dateWay == 'current'){
endTime = today.toISOString().split('T')[0];
if (diyStore.editComponent.end.dateWay == 'current') {
endTime = today.toISOString().split('T')[0]
}
if(diyStore.editComponent.start.defaultControl && starTime == ''){
if (diyStore.editComponent.start.defaultControl && starTime == '' && diyStore.editComponent.end.dateWay == 'diy') {
res.code = false
res.message = "开始日期不能为空"
res.message = t('startDataTips')
return res
}
if(diyStore.editComponent.end.defaultControl && endTime == ''){
if (diyStore.editComponent.end.defaultControl && endTime == '' && diyStore.editComponent.end.dateWay == 'diy') {
res.code = false
res.message = "结束日期不能为空"
res.message = t('endDataTips')
return res
}
if(diyStore.editComponent.start.defaultControl && diyStore.editComponent.end.defaultControl && timeTurnTimeStamp(starTime) > timeTurnTimeStamp(endTime)){
if (diyStore.editComponent.start.defaultControl && diyStore.editComponent.end.defaultControl && timeTurnTimeStamp(starTime) > timeTurnTimeStamp(endTime)) {
res.code = false
res.message = "开始日期不能大于结束日期"
res.message = t('startEndDataTips')
return res
}
return res
}
const dateFormat: any = reactive({
format1: '',
format2: '',
format3: ''
});
format1: '',
format2: '',
format3: ''
})
// -
const disabledEndDate = (time:Date)=>{
let cutoffDate = null
let bool = false;
if(diyStore.editComponent.start && diyStore.editComponent.start.defaultControl){
if(diyStore.editComponent.start.dateWay == 'diy'){
cutoffDate = new Date(diyStore.editComponent.field.default.start.date);
}else{
cutoffDate = new Date();
}
bool = time.getTime() < cutoffDate.getTime();
}
return bool;
let cutoffDate = null
let bool = false
if (diyStore.editComponent.start && diyStore.editComponent.start.defaultControl) {
if (diyStore.editComponent.start.dateWay == 'diy') {
cutoffDate = new Date(diyStore.editComponent.field.default.start.date)
} else {
cutoffDate = new Date()
}
bool = time.getTime() < cutoffDate.getTime()
}
return bool
}
onMounted(() => {
let today = new Date();
let endDate = new Date();
endDate.setDate(endDate.getDate() + 7); // 7
let today = new Date()
let endDate = new Date()
endDate.setDate(endDate.getDate() + 7) // 7
if(diyStore.editComponent.field.default.start.timestamp){
diyStore.editComponent.field.default.start.date = today.toISOString().split('T')[0];
diyStore.editComponent.field.default.start.timestamp = parseInt(today.getTime() / 1000);
if (diyStore.editComponent.field.default.start.timestamp) {
diyStore.editComponent.field.default.start.date = today.toISOString().split('T')[0]
diyStore.editComponent.field.default.start.timestamp = parseInt(today.getTime() / 1000)
}
if(diyStore.editComponent.field.default.end.timestamp){
diyStore.editComponent.field.default.end.date = endDate.toISOString().split('T')[0];
diyStore.editComponent.field.default.end.timestamp = parseInt(endDate.getTime() / 1000);
if (diyStore.editComponent.field.default.end.timestamp) {
diyStore.editComponent.field.default.end.date = endDate.toISOString().split('T')[0]
diyStore.editComponent.field.default.end.timestamp = parseInt(endDate.getTime() / 1000)
}
let year = today.getFullYear();
let month = String(today.getMonth() + 1).padStart(2, '0');
let day = String(today.getDate()).padStart(2, '0');
let year = today.getFullYear()
let month = String(today.getMonth() + 1).padStart(2, '0')
let day = String(today.getDate()).padStart(2, '0')
const hours = String(today.getHours()).padStart(2, '0');
const minutes = String(today.getMinutes()).padStart(2, '0');
dateFormat.format1 = `${year}${month}${day}`;
dateFormat.format2 = `${year}-${month}-${day}`;
dateFormat.format3 = `${year}/${month}/${day}`;
dateFormat.format4 = `${year}-${month}-${day} ${hours}:${minutes}`;
});
const hours = String(today.getHours()).padStart(2, '0')
const minutes = String(today.getMinutes()).padStart(2, '0')
dateFormat.format1 = `${year}${month}${day}`
dateFormat.format2 = `${year}-${month}-${day}`
dateFormat.format3 = `${year}/${month}/${day}`
dateFormat.format4 = `${year}-${month}-${day} ${hours}:${minutes}`
})
//
const startDateChange = (date)=>{
diyStore.editComponent.field.default.start.date = date;
diyStore.editComponent.field.default.start.timestamp = timeTurnTimeStamp(date);
const startDateChange = (date) => {
diyStore.editComponent.field.default.start.date = date
diyStore.editComponent.field.default.start.timestamp = timeTurnTimeStamp(date)
let endDate = new Date(date)
endDate.setDate(endDate.getDate() + 7);
diyStore.editComponent.field.default.end.date = endDate.toISOString().split('T')[0];
diyStore.editComponent.field.default.end.timestamp = parseInt(endDate.getTime() / 1000);
endDate.setDate(endDate.getDate() + 7)
diyStore.editComponent.field.default.end.date = endDate.toISOString().split('T')[0]
diyStore.editComponent.field.default.end.timestamp = parseInt(endDate.getTime() / 1000)
}
//
const endDateChange = (date)=>{
diyStore.editComponent.field.default.end.date = date;
diyStore.editComponent.field.default.end.timestamp = timeTurnTimeStamp(date);
const endDateChange = (date) => {
diyStore.editComponent.field.default.end.date = date
diyStore.editComponent.field.default.end.timestamp = timeTurnTimeStamp(date)
}
defineExpose({})

View File

@ -5,7 +5,7 @@
<!-- 表单组件 字段内容设置 -->
<slot name="field"></slot>
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
<el-form-item :label="t('日期格式')">
<el-form-item :label="t('dataFormat')">
<el-radio-group v-model="diyStore.editComponent.dateFormat" class="!block">
<el-radio class="!block" label="YYYY年M月D日">{{ dateFormat.format1 }}</el-radio>
<el-radio class="!block" label="YYYY-MM-DD">{{ dateFormat.format2 }}</el-radio>
@ -16,18 +16,18 @@
<el-form-item :label="t('formPlaceholder')">
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
</el-form-item>
<el-form-item :label="t('默认值')">
<el-form-item :label="t('defaultValue')">
<el-switch v-model="diyStore.editComponent.defaultControl"/>
</el-form-item>
<el-form-item v-if="diyStore.editComponent.defaultControl">
<el-radio-group v-model="diyStore.editComponent.dateWay">
<el-radio label="current">{{ t('当天日期') }}</el-radio>
<el-radio label="diy">{{ t('指定日期') }}</el-radio>
<el-radio label="current">{{ t('currentDate') }}</el-radio>
<el-radio label="diy">{{ t('diyDate') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="diyStore.editComponent.defaultControl && diyStore.editComponent.dateWay == 'diy'">
<el-date-picker v-if="diyStore.editComponent.dateFormat != 'YYYY-MM-DD HH:mm'" v-model="diyStore.editComponent.field.default.date" format="YYYY/MM/DD" value-format="YYYY-MM-DD" type="date" placeholder="请选择日期" @change="dateChange"/>
<el-date-picker v-else v-model="diyStore.editComponent.field.default.date" format="YYYY/MM/DD HH:mm" value-format="YYYY-MM-DD HH:mm" type="datetime" placeholder="请选择日期" @change="dateChange"/>
<el-date-picker v-if="diyStore.editComponent.dateFormat != 'YYYY-MM-DD HH:mm'" v-model="diyStore.editComponent.field.default.date" format="YYYY/MM/DD" value-format="YYYY-MM-DD" type="date" :placeholder="t('dataPlaceholder')" @change="dateChange"/>
<el-date-picker v-else v-model="diyStore.editComponent.field.default.date" format="YYYY/MM/DD HH:mm" value-format="YYYY-MM-DD HH:mm" type="datetime" :placeholder="t('dataPlaceholder')" @change="dateChange"/>
</el-form-item>
</el-form>

View File

@ -16,11 +16,11 @@
<el-form-item>
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('内容防重复') }}</span>
<span class="mr-[3px]">{{ t('preventDuplication') }}</span>
<el-tooltip effect="light" placement="top">
<template #content>
<p>该组件填写的内容不能与已提交的数据重复</p>
<p>极端情况下可能存在延时导致限制失效</p>
<p>{{ t('preventDuplicationTipsOne') }}</p>
<p>{{ t('preventDuplicationTipsTwo') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />
@ -33,11 +33,11 @@
<el-form-item class="display-block">
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('隐私保护') }}</span>
<span class="mr-[3px]">{{ t('privacyProtection') }}</span>
<el-tooltip effect="light" placement="top">
<template #content>
<p>会自动将提交的个人信息做加密展示</p>
<p>适用于公开展示收集的数据且不暴露用户隐私</p>
<p>{{ t('privacyProtectionTipsOne') }}</p>
<p>{{ t('privacyProtectionTipsTwo') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />
@ -46,7 +46,7 @@
</div>
</template>
<el-switch v-model="diyStore.editComponent.field.privacyProtection" />
<div class="text-sm text-gray-400">{{ t('提交后自动隐藏中间11位数字仅管理员可查看') }}</div>
<div class="text-sm text-gray-400">{{ t('privacyProtectionTipsThree') }}</div>
</el-form-item>
</el-form>

View File

@ -5,16 +5,16 @@
<!-- 表单组件 字段内容设置 -->
<slot name="field"></slot>
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
<el-form-item :label="t('限制数量')">
<el-input v-model.trim="diyStore.editComponent.limit" :placeholder="t('请输入限制数量')" clearable maxlength="2" />
<el-form-item :label="t('imageLimit')">
<el-input v-model.trim="diyStore.editComponent.limit" :placeholder="t('imageLimitPlaceholder')" clearable maxlength="2" />
</el-form-item>
<!-- <el-form-item :label="t('上传方式')">
<el-form-item :label="t('上传方式')">
<el-checkbox-group v-model="diyStore.editComponent.uploadMode" :min="1">
<el-checkbox label="拍照上传" value="take_pictures" />
<el-checkbox label="从相册选择" value="select_from_album" />
</el-checkbox-group>
</el-form-item> -->
</el-form-item>
</el-form>
<!-- 表单组件 其他设置 -->
@ -44,41 +44,41 @@ diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
//
diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' }
if (diyStore.value[index].limit == '') {
res.code = false;
res.message = t('请输入限制数量')
return res;
}
if (isNaN(diyStore.value[index].limit) || !regExp.number.test(diyStore.value[index].limit)) {
res.code = false;
res.message = t('限制数量格式输入错误');
return res;
}
if (diyStore.value[index].limit < 0) {
res.code = false;
res.message = t('限制数量不能小于0')
return res;
}
if (diyStore.value[index].limit == 0) {
res.code = false;
res.message = t('限制数量必须大于0')
return res;
}
if (diyStore.value[index].limit > 20) {
res.code = false;
res.message = t('限制数量最大不能超过20')
return res;
}
return res
const res = { code: true, message: '' }
if (diyStore.value[index].limit == '') {
res.code = false
res.message = t('imageLimitPlaceholder')
return res
}
if (isNaN(diyStore.value[index].limit) || !regExp.number.test(diyStore.value[index].limit)) {
res.code = false
res.message = t('imageLimitErrorTips')
return res
}
if (diyStore.value[index].limit < 0) {
res.code = false
res.message = t('imageLimitErrorTipsTwo')
return res
}
if (diyStore.value[index].limit == 0) {
res.code = false
res.message = t('imageLimitErrorTipsThree')
return res
}
if (diyStore.value[index].limit > 9) {
res.code = false
res.message = t('imafeLimitErrorTipsFour')
return res
}
return res
}
//
const regExp: any = {
required: /[\S]+/,
number: /^\d{0,10}$/,
digit: /^\d{0,10}(.?\d{0,2})$/,
special: /^\d{0,10}(.?\d{0,3})$/
required: /[\S]+/,
number: /^\d{0,10}$/,
digit: /^\d{0,10}(.?\d{0,2})$/,
special: /^\d{0,10}(.?\d{0,3})$/
}
defineExpose({})

View File

@ -12,7 +12,7 @@
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('defaultValue') }}</span>
<el-tooltip effect="light" :content="t('设置后,默认值会自动填充到输入框,填表人可在此基础上进行修改。')" placement="top">
<el-tooltip effect="light" :content="t('defaultValueTips')" placement="top">
<el-icon>
<QuestionFilled color="#999999" />
</el-icon>
@ -29,11 +29,11 @@
<el-form-item>
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('内容防重复') }}</span>
<span class="mr-[3px]">{{ t('preventDuplication') }}</span>
<el-tooltip effect="light" placement="top">
<template #content>
<p>该组件填写的内容不能与已提交的数据重复</p>
<p>极端情况下可能存在延时导致限制失效</p>
<p>{{ t('preventDuplicationTipsOne') }}</p>
<p>{{ t('preventDuplicationTipsTwo') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />

View File

@ -5,10 +5,10 @@
<!-- 表单组件 字段内容设置 -->
<slot name="field"></slot>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('获取方式')">
<el-form-item :label="t('access')">
<el-radio-group v-model="diyStore.editComponent.mode">
<el-radio class="!mr-[20px]" label="authorized_wechat_location">{{ t('授权微信定位') }}</el-radio>
<el-radio label="open_choose_location">{{ t('手动选择定位') }}</el-radio>
<el-radio class="!mr-[20px]" label="authorized_wechat_location">{{ t('authorizeWeChatLocation') }}</el-radio>
<el-radio label="open_choose_location">{{ t('manuallySelectPositioning') }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
@ -19,11 +19,11 @@
<el-form-item class="display-block">
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('隐私保护') }}</span>
<span class="mr-[3px]">{{ t('privacyProtection') }}</span>
<el-tooltip effect="light" placement="top">
<template #content>
<p>会自动将提交的个人信息做加密展示</p>
<p>适用于公开展示收集的数据且不暴露用户隐私</p>
<p>{{ t('privacyProtectionTipsOne') }}</p>
<p>{{ t('privacyProtectionTipsTwo') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />
@ -32,7 +32,7 @@
</div>
</template>
<el-switch v-model="diyStore.editComponent.field.privacyProtection" />
<div class="text-sm text-gray-400">{{ t('提交后自动隐藏文本,仅管理员可查看') }}</div>
<div class="text-sm text-gray-400">{{ t('privacyProtectionTipsFour') }}</div>
</el-form-item>
</el-form>

View File

@ -16,11 +16,11 @@
<el-form-item>
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('内容防重复') }}</span>
<span class="mr-[3px]">{{ t('preventDuplication') }}</span>
<el-tooltip effect="light" placement="top">
<template #content>
<p>该组件填写的内容不能与已提交的数据重复</p>
<p>极端情况下可能存在延时导致限制失效</p>
<p>{{ t('preventDuplicationTipsOne') }}</p>
<p>{{ t('preventDuplicationTipsTwo') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />
@ -33,11 +33,11 @@
<el-form-item class="display-block">
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('隐私保护') }}</span>
<span class="mr-[3px]">{{ t('privacyProtection') }}</span>
<el-tooltip effect="light" placement="top">
<template #content>
<p>会自动将提交的个人信息做加密展示</p>
<p>适用于公开展示收集的数据且不暴露用户隐私</p>
<p>{{ t('privacyProtectionTipsOne') }}</p>
<p>{{ t('privacyProtectionTipsTwo') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />
@ -46,7 +46,7 @@
</div>
</template>
<el-switch v-model="diyStore.editComponent.field.privacyProtection" />
<div class="text-sm text-gray-400">{{ t('提交后自动隐藏中间5位数字仅管理员可查看') }}</div>
<div class="text-sm text-gray-400">{{ t('privacyProtectionTipsFive') }}</div>
</el-form-item>
</el-form>

View File

@ -8,14 +8,14 @@
<el-form-item :label="t('formPlaceholder')">
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
</el-form-item>
<el-form-item :label="t('单位')">
<el-input v-model.trim="diyStore.editComponent.unit" :placeholder="t('请输入单位')" clearable maxlength="5" show-word-limit />
<el-form-item :label="t('unit')">
<el-input v-model.trim="diyStore.editComponent.unit" :placeholder="t('unitPlaceholder')" clearable maxlength="5" show-word-limit />
</el-form-item>
<el-form-item>
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('defaultValue') }}</span>
<el-tooltip effect="light" :content="t('设置后,默认值会自动填充到输入框,填表人可在此基础上进行修改。')" placement="top">
<el-tooltip effect="light" :content="t('defaultValueTips')" placement="top">
<el-icon>
<QuestionFilled color="#999999" />
</el-icon>
@ -95,10 +95,10 @@ diyStore.editComponent.verify = (index: number) => {
if(diyStore.value[index].field.default){
if (isNaN(diyStore.value[index].field.default) || !regExp.digit.test(diyStore.value[index].field.default)) {
res.code = false;
res.message = t('默认值格式输入错误');
res.message = t('defaultErrorTips');
} else if (diyStore.value[index].field.default < 0) {
res.code = false;
res.message = t('默认值不能小于0')
res.message = t('defaultMustZeroTips')
}
}
return res

View File

@ -5,31 +5,31 @@
<!-- 表单组件 字段内容设置 -->
<slot name="field"></slot>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('样式')">
<el-form-item :label="t('style')">
<el-radio-group v-model="diyStore.editComponent.style">
<el-radio label="style-1">{{ t('默认') }}</el-radio>
<el-radio label="style-2">{{ t('列表') }}</el-radio>
<el-radio label="style-3">{{ t('下拉') }}</el-radio>
<el-radio label="style-1">{{ t('defaultSources') }}</el-radio>
<el-radio label="style-2">{{ t('listStyle') }}</el-radio>
<el-radio label="style-3">{{ t('dropDownStyle') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('选项')">
<el-form-item :label="t('option')">
<div ref="formRadioRef">
<div v-for="(option, index) in diyStore.editComponent.options" :key="option.id" class="option-item flex items-center mb-[15px]">
<el-input v-model="diyStore.editComponent.options[index].text" class="!w-[215px]" :placeholder="t('optionPlaceholder')" clearable maxlength="30" />
<span v-if="diyStore.editComponent.options.length > 1" @click="removeOption(index)" class="cursor-pointer ml-[5px] nc-iconfont nc-icon-shanchu-yuangaizhiV6xx"></span>
</div>
</div>
<span class="text-primary cursor-pointer mr-[10px]" @click="addOption">添加单个选项</span>
<span class="text-primary cursor-pointer mr-[10px]" @click="addOption">{{ t('addSingleOption') }}</span>
<el-popover :visible="visible" placement="bottom" :width="300">
<p class="mb-[5px]">批量添加选项</p>
<p class="text-[#888] text-[12px] mb-[5px]">可添加多个选项每个选项之间用英文","隔开</p>
<p class="mb-[5px]">{{ t('addMultipleOption') }}</p>
<p class="text-[#888] text-[12px] mb-[5px]">{{ t('addOptionTips') }}</p>
<el-input v-model.trim="optionsValue" type="textarea" clearable maxlength="200" show-word-limit />
<div class="mt-[10px] text-right">
<el-button size="small" text @click="visible = false">取消</el-button>
<el-button size="small" type="primary" @click="batchAddOptions">确定</el-button>
<el-button size="small" text @click="visible = false">{{ t('cancel') }}</el-button>
<el-button size="small" type="primary" @click="batchAddOptions">{{ t('confirm') }}</el-button>
</div>
<template #reference>
<span class="text-primary cursor-pointer" @click="visible = true">批量添加选项</span>
<span class="text-primary cursor-pointer" @click="visible = true">{{ t('addMultipleOption') }}</span>
</template>
</el-popover>
</el-form-item>
@ -123,7 +123,7 @@ diyStore.editComponent.verify = (index: number) => {
if (uniqueOptions.length != diyStore.value[index].options.length) {
res.code = false;
res.message = t('存在重复选项,请检查内容')
res.message = t('errorTipsOne')
}
return res
}
@ -167,7 +167,7 @@ const batchAddOptions = () => {
diyStore.editComponent.options.push(...filteredNewOptions);
} else {
ElMessage({
message: "选项已存在,请重新输入",
message: t('errorTipsTwo'),
type: "warning",
});
}

View File

@ -3,47 +3,47 @@
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('按钮位置')" class="display-block">
<el-form-item :label="t('floatBtnButton')" class="display-block">
<el-radio-group v-model="diyStore.editComponent.btnPosition" @change="btnPositionChangeFn">
<el-radio label="follow_content">{{ t('跟随内容') }}</el-radio>
<el-radio label="hover_screen_bottom">{{ t('悬浮屏幕底部') }}</el-radio>
<el-radio label="follow_content">{{ t('followContent') }}</el-radio>
<el-radio label="hover_screen_bottom">{{ t('hoverScreenBottom') }}</el-radio>
</el-radio-group>
<div class="text-sm text-gray-400 mb-[5px] leading-[1.4]" v-show="diyStore.editComponent.btnPosition == 'follow_content'">{{ t('当表单内容多时只有滚动页面至最底部才会显示提交按钮') }}</div>
<div class="text-sm text-gray-400 mb-[5px]" v-show="diyStore.editComponent.btnPosition == 'hover_screen_bottom'">{{ t('按钮悬浮在屏幕底部方便填表人快速提交') }}</div>
<div class="text-sm text-gray-400 mb-[10px] leading-[1.4]">{{ t('若前端以嵌入形式调用表单,提交按钮组件将不显示,相关业务由该页面负责处理') }}</div>
<div class="text-sm text-gray-400 mb-[5px] leading-[1.4]" v-show="diyStore.editComponent.btnPosition == 'follow_content'">{{ t('btnTips') }}</div>
<div class="text-sm text-gray-400 mb-[5px]" v-show="diyStore.editComponent.btnPosition == 'hover_screen_bottom'">{{ t('btnTipsTwo') }}</div>
<div class="text-sm text-gray-400 mb-[10px] leading-[1.4]">{{ t('btnTipsThree') }}</div>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('提交按钮') }}</h3>
<h3 class="mb-[10px]">{{ t('submitBtn') }}</h3>
<el-form label-width="80px" class="px-[10px]" @submit.prevent>
<el-form-item :label="t('按钮名称')">
<el-input v-model.trim="diyStore.editComponent.submitBtn.text" :placeholder="t('请输入按钮名称')" clearable maxlength="10" show-word-limit />
<el-form-item :label="t('submitBtnName')">
<el-input v-model.trim="diyStore.editComponent.submitBtn.text" :placeholder="t('btnNamePlaceholder')" clearable maxlength="10" show-word-limit />
</el-form-item>
<el-form-item :label="t('textColor')">
<el-color-picker v-model="diyStore.editComponent.submitBtn.color" />
</el-form-item>
<el-form-item :label="t('背景色')">
<el-form-item :label="t('subTextBgColor')">
<el-color-picker v-model="diyStore.editComponent.submitBtn.bgColor" />
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('重置按钮') }}</h3>
<h3 class="mb-[10px]">{{ t('resetBtn') }}</h3>
<el-form label-width="80px" class="px-[10px]" @submit.prevent>
<el-form-item :label="t('carouselSearchTabControl')">
<el-switch v-model="diyStore.editComponent.resetBtn.control" />
</el-form-item>
<el-form-item :label="t('按钮名称')">
<el-input v-model.trim="diyStore.editComponent.resetBtn.text" :placeholder="t('请输入按钮名称')" clearable maxlength="10" show-word-limit />
<el-form-item :label="t('submitBtnName')">
<el-input v-model.trim="diyStore.editComponent.resetBtn.text" :placeholder="t('btnNamePlaceholder')" clearable maxlength="10" show-word-limit />
</el-form-item>
<el-form-item :label="t('textColor')">
<el-color-picker v-model="diyStore.editComponent.resetBtn.color" />
</el-form-item>
<el-form-item :label="t('背景色')">
<el-form-item :label="t('subTextBgColor')">
<el-color-picker v-model="diyStore.editComponent.resetBtn.bgColor" />
</el-form-item>
</el-form>
@ -55,7 +55,7 @@
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('按钮样式') }}</h3>
<h3 class="mb-[10px]">{{ t('btnStyle') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('topRounded')">
<el-slider v-model="diyStore.editComponent.topElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
@ -94,12 +94,12 @@ diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' }
if (diyStore.value[index].submitBtn.text == '') {
res.code = false;
res.message = t('请输入提交按钮名称');
res.message = t('submitBtnNamePlaceholder');
return res;
}
if (diyStore.value[index].resetBtn.text == '') {
res.code = false;
res.message = t('请输入重置按钮名称');
res.message = t('resetBtnNamePlaceholder');
return res;
}

View File

@ -12,7 +12,7 @@
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('defaultValue') }}</span>
<el-tooltip effect="light" :content="t('设置后,默认值会自动填充到输入框,填表人可在此基础上进行修改。')" placement="top">
<el-tooltip effect="light" :content="t('defaultValueTips')" placement="top">
<el-icon>
<QuestionFilled color="#999999" />
</el-icon>
@ -21,8 +21,8 @@
</template>
<el-input v-model.trim="diyStore.editComponent.field.default" :placeholder="t('defaultValuePlaceholder')" clearable maxlength="18" show-word-limit />
</el-form-item>
<el-form-item :label="t('显示行数')">
<el-input v-model.trim="diyStore.editComponent.rowCount" :placeholder="t('请输入显示行数')" clearable maxlength="2" show-word-limit />
<el-form-item :label="t('rowCount')">
<el-input v-model.trim="diyStore.editComponent.rowCount" :placeholder="t('rowCountPlaceholder')" clearable maxlength="2" show-word-limit />
</el-form-item>
</el-form>

View File

@ -6,43 +6,43 @@
<slot name="field"></slot>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('开始时间') }}</h3>
<h3 class="mb-[10px]">{{ t('startTime') }}</h3>
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
<el-form-item :label="t('提示语')">
<el-form-item :label="t('formPlaceholder')">
<el-input v-model.trim="diyStore.editComponent.start.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
</el-form-item>
<el-form-item :label="t('默认值')">
<el-form-item :label="t('defaultValue')">
<el-switch v-model="diyStore.editComponent.start.defaultControl"/>
</el-form-item>
<el-form-item v-if="diyStore.editComponent.start.defaultControl">
<el-radio-group v-model="diyStore.editComponent.start.timeWay">
<el-radio label="current">{{ t('当天时间') }}</el-radio>
<el-radio label="diy">{{ t('指定时间') }}</el-radio>
<el-radio label="current">{{ t('currentTime') }}</el-radio>
<el-radio label="diy">{{ t('diyTime') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="diyStore.editComponent.start.defaultControl && diyStore.editComponent.start.timeWay == 'diy'">
<el-time-picker v-model="diyStore.editComponent.field.default.start.date" placeholder="请选择开始时间" format="HH:mm" value-format="HH:mm" @change="startTimePickerChange" />
<el-time-picker v-model="diyStore.editComponent.field.default.start.date" :placeholder="t('startTimePlaceholder')" format="HH:mm" value-format="HH:mm" @change="startTimePickerChange" />
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('结束时间') }}</h3>
<h3 class="mb-[10px]">{{ t('endTime') }}</h3>
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
<el-form-item :label="t('提示语')">
<el-form-item :label="t('formPlaceholder')">
<el-input v-model.trim="diyStore.editComponent.end.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
</el-form-item>
<el-form-item :label="t('默认值')">
<el-form-item :label="t('defaultValue')">
<el-switch v-model="diyStore.editComponent.end.defaultControl" />
</el-form-item>
<el-form-item v-if="diyStore.editComponent.end.defaultControl">
<el-radio-group v-model="diyStore.editComponent.end.timeWay">
<el-radio label="current">{{ t('当天时间') }}</el-radio>
<el-radio label="diy">{{ t('指定时间') }}</el-radio>
<el-radio label="current">{{ t('currentTime') }}</el-radio>
<el-radio label="diy">{{ t('diyTime') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="diyStore.editComponent.end.defaultControl && diyStore.editComponent.end.timeWay == 'diy'">
<el-time-picker :disabled-hours="disabledHours" :disabled-minutes="disabledMinutes" v-model="diyStore.editComponent.field.default.end.date" placeholder="请选择结束时间" format="HH:mm" value-format="HH:mm" />
<el-time-picker :disabled-hours="disabledHours" :disabled-minutes="disabledMinutes" v-model="diyStore.editComponent.field.default.end.date" :placeholder="t('endTimePlaceholder')" format="HH:mm" value-format="HH:mm" />
</el-form-item>
</el-form>
</div>
@ -55,7 +55,7 @@
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('文字样式') }}</h3>
<h3 class="mb-[10px]">{{ t('textStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] diy-nav-slider" :min="12" :max="18" />
@ -105,18 +105,18 @@ diyStore.editComponent.verify = (index: number) => {
if(diyStore.editComponent.start.defaultControl && starTime == ''){
res.code = false
res.message = "开始时间不能为空"
res.message = t('startTimeTips')
return res
}
if(diyStore.editComponent.end.defaultControl && endTime == ''){
res.code = false
res.message = "结束时间不能为空"
res.message = t('endTimeTips')
return res
}
if(diyStore.editComponent.start.defaultControl && diyStore.editComponent.end.defaultControl && timeInvertSecond(starTime) > timeInvertSecond(endTime)){
res.code = false
res.message = "开始时间不能大于结束时间"
res.message = t('startEndTimeTips')
return res
}
return res

View File

@ -8,17 +8,17 @@
<el-form-item :label="t('formPlaceholder')">
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
</el-form-item>
<el-form-item :label="t('默认值')">
<el-form-item :label="t('defaultValue')">
<el-switch v-model="diyStore.editComponent.defaultControl" @change="changeDateDefaultControl" />
</el-form-item>
<el-form-item v-if="diyStore.editComponent.defaultControl">
<el-radio-group v-model="diyStore.editComponent.timeWay">
<el-radio label="current">{{ t('当天时间') }}</el-radio>
<el-radio label="diy">{{ t('指定时间') }}</el-radio>
<el-radio label="current">{{ t('currentTime') }}</el-radio>
<el-radio label="diy">{{ t('diyTime') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="diyStore.editComponent.defaultControl && diyStore.editComponent.timeWay == 'diy'">
<el-time-picker v-model="diyStore.editComponent.field.default" placeholder="请选择时间" format="HH:mm" value-format="HH:mm" />
<el-time-picker v-model="diyStore.editComponent.field.default" :placeholder="t('timePlaceholder')" format="HH:mm" value-format="HH:mm" />
</el-form-item>
</el-form>

View File

@ -0,0 +1,177 @@
<template>
<div>
<el-form :inline="true" :model="tableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('formSelectContentTitle')" prop="title" class="form-item-wrap">
<el-input v-model.trim="tableData.searchParam.title" :placeholder="t('formSelectContentTitlePlaceholder')" />
</el-form-item>
<el-form-item :label="t('formSelectContentTypeName')" prop="type" class="form-item-wrap">
<el-select v-model="tableData.searchParam.type" :placeholder="t('formSelectContentTypeNamePlaceholder')">
<el-option :label="t('formSelectContentTypeAll')" value="" />
<el-option v-for="(item, key) in formType" :label="item.title" :value="key" :key="key" />
</el-select>
</el-form-item>
<el-form-item class="form-item-wrap">
<el-button type="primary" @click="loadList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData.data" size="large" ref="tableRef" v-loading="tableData.loading">
<template #empty>
<span>{{ !tableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column min-width="7%">
<template #default="{ row }">
<el-checkbox v-model="row.checked" @change="handleCheckChange($event,row)" />
</template>
</el-table-column>
<el-table-column prop="page_title" :label="t('formSelectContentTitle')" min-width="65%" />
<el-table-column prop="type_name" :label="t('formSelectContentTypeName')" min-width="25%" />
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="tableData.total"
@size-change="loadList()" @current-change="loadList" />
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, nextTick } from 'vue'
import { t } from '@/lang'
import { getFormType, getDiyFormSelectPageList } from '@/app/api/diy_form'
import { FormInstance, ElMessage } from "element-plus";
const prop = defineProps({
formId: {
type: [Number, String],
default: 0
}
})
const formType: any = reactive({}) //
const searchFormRef = ref<FormInstance>()
const tableRef = ref();
const tableData: any = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
title: '',
type: '',
verify_form_ids: []
}
})
//
const selectData: any = reactive({
form_id: prop.formId
})
//
const loadList = (page: number = 1) => {
tableData.loading = true
tableData.page = page
if (selectData.form_id) {
tableData.searchParam.verify_form_ids = [selectData.form_id]
}
getDiyFormSelectPageList({
page: tableData.page,
limit: tableData.limit,
...tableData.searchParam
}).then(res => {
tableData.loading = false
tableData.data = res.data.data
tableData.data.forEach((item: any) => {
item.checked = item.form_id == selectData.form_id
})
tableData.total = res.data.total
setGoodsSelected()
}).catch(() => {
tableData.loading = false
})
}
//
const loadFormType = (addon = '') => {
getFormType({}).then(res => {
for (let key in formType) {
delete formType[key];
}
for (const key in res.data) {
formType[key] = res.data[key]
}
})
}
loadFormType();
loadList()
const handleCheckChange = (isSelect: any, row: any) => {
if (isSelect) {
selectData.form_id = row.form_id
} else {
selectData.form_id = 0 //
}
setGoodsSelected()
}
//
const setGoodsSelected = () => {
nextTick(() => {
for (let i = 0; i < tableData.data.length; i++) {
tableData.data[i].checked = false
if (selectData.form_id == tableData.data[i].form_id) {
tableData.data[i].checked = true
Object.assign(selectData, tableData.data[i])
}
}
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadList()
}
const getData = () => {
if (selectData.form_id == 0) {
ElMessage({
type: 'warning',
message: `${ t('formSelectContentTips') }`
})
return;
}
return {
name: 'DIY_FORM',
title: selectData.page_title,
url: `/app/pages/index/diy_form?form_id=${ selectData.form_id }`,
action: '',
formId: selectData.form_id
}
}
defineExpose({
getData
})
</script>
<style lang="scss" scoped>
.form-item-wrap {
margin-right: 10px !important;
margin-bottom: 10px !important;
&.last-child {
margin-right: 0 !important;
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div>
<el-dialog v-model="showDialog" :title="t('表单推广')" width="500px" :destroy-on-close="true">
<el-dialog v-model="showDialog" :title="t('formPromotion')" width="500px" :destroy-on-close="true">
<el-tabs v-model="channel" class="mb-[10px]">
<el-tab-pane label="H5" name="h5"></el-tab-pane>
@ -13,13 +13,13 @@
</div>
<div class="px-[20px] flex-1">
<div class="mb-[10px]">{{ t('推广链接') }}</div>
<div class="mb-[10px]">{{ t('promoteUrl') }}</div>
<el-input class="mb-[10px]" readonly :value="wapPreview">
<template #append>
<el-button @click="copyEvent(wapPreview)">{{ t('copy') }}</el-button>
</template>
</el-input>
<a class="text-primary" :href="wapImage" download>{{ t('下载二维码') }}</a>
<a class="text-primary" :href="wapImage" download>{{ t('downLoadQRCode') }}</a>
</div>
</div>
@ -28,14 +28,14 @@
<div class="promote-img flex justify-center items-center bg-[#f8f8f8] w-[150px] h-[150px]">
<el-image :src="img(weappData.path)" v-if="weappData.path" class="w-[150px] h-[150px]">
<template #error>
<div class="w-[150px] h-[150px] text-[14px] text-center leading-[150px]">配置失败</div>
<div class="w-[150px] h-[150px] text-[14px] text-center leading-[150px]">{{ t('configureFailed') }}</div>
</template>
</el-image>
<div v-else class="w-[150px] h-[150px] text-[14px] text-center leading-[150px]">配置失败</div>
<div v-else class="w-[150px] h-[150px] text-[14px] text-center leading-[150px]">{{ t('configureFailed') }}</div>
</div>
<div class="px-[20px] flex-1">
<a class="text-primary" :href="img(weappData.path)" target="_blank" download>{{ t('下载二维码') }}</a>
<a class="text-primary" :href="img(weappData.path)" target="_blank" download>{{ t('downLoadQRCode') }}</a>
</div>
</div>

View File

@ -1,6 +1,6 @@
<template>
<div>
<el-dialog v-model="showDialog" :title="t('提交成功页')" width="850px" :close-on-press-escape="true" :destroy-on-close="true" :close-on-click-modal="false">
<el-dialog v-model="showDialog" :title="t('submitSuccess')" width="850px" :close-on-press-escape="true" :destroy-on-close="true" :close-on-click-modal="false">
<div class="flex flex-1 mt-[24px] mx-[24px] mb-0">
<div class="preview-wrap">
@ -17,19 +17,19 @@
<el-icon><SuccessFilled color="#20bf64" /></el-icon>
</div>
<div class="record-name">
<span class="text-[#1E1E1E] font-bold text-[24px]" v-if="formData.tips_type == 'default'">填写成功</span>
<span class="text-[#1E1E1E] font-bold text-[24px]" v-if="formData.tips_type == 'default'">{{ t('writeSuccess') }}</span>
<span class="text-[#1E1E1E] font-bold text-[16px]" v-else-if="formData.tips_type == 'diy'">{{ formData.tips_text ? formData.tips_text : '填写成功' }}</span>
</div>
<div class="to-detail">
<div class="text-[14px] mt-[16px] py-[4px] px[8px] text-[#576b95]">查看填写详情</div>
<div class="text-[14px] mt-[16px] py-[4px] px[8px] text-[#576b95]">{{ t('viewFillingDetails') }}</div>
</div>
</div>
<div class="relative pt-[8px] pb-[48px] h-[112px]">
<div v-if="formData.success_after_action.finish" class="!mt-[16px] rounded-[3px] mx-auto text-[15px] w-[100px] min-w-[160px] h-[32px] leading-[32px] text-center max-w-[274px] truncate bg-[#20bf64] text-[#ffffff]">
<div class="text-[15px]">完成</div>
<div class="text-[15px]">{{ t('finish') }}</div>
</div>
<div v-if="formData.success_after_action.goback" class="!mt-[16px] rounded-[3px] mx-auto text-[15px] w-[100px] min-w-[160px] h-[32px] leading-[32px] text-center max-w-[274px] truncate bg-[#f2f2f2] text-[#353535]">
<div class="text-[14px]">返回</div>
<div class="text-[14px]">{{ t('back') }}</div>
</div>
</div>
</div>
@ -60,13 +60,13 @@
<div class="flex-1">
<div class="item-wrap">
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">填表人提交后</div>
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">{{ t('afterSubmission') }}</div>
<el-radio-group v-model="formData.submit_after_action" class="!block">
<el-radio label="text" class="!flex">
<span class="mr-[3px]">{{ t('显示文字信息') }}</span>
<span class="mr-[3px]">{{ t('displayTextMessages') }}</span>
<el-tooltip effect="light" placement="top">
<template #content>
<p>{{ t('提交后页面显示文字信息。') }}</p>
<p>{{ t('displayTextMessagesTips') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />
@ -89,16 +89,16 @@
</div>
<div class="item-wrap" v-if="formData.submit_after_action == 'text'">
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">{{ t('提示文字') }}</div>
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">{{ t('promptText') }}</div>
<div>
<el-radio-group v-model="formData.tips_type" class="!block">
<el-radio label="default" class="!block">
<span class="mr-[3px]">{{ t('默认提示') }}</span>
<span class="!text-[#999] text-[12px] ml-[8px]">{{ t('将显示: 填写成功') }}</span>
<span class="mr-[3px]">{{ t('defaultPrompt') }}</span>
<span class="!text-[#999] text-[12px] ml-[8px]">{{ t('defaultPromptTips') }}</span>
</el-radio>
<el-radio label="diy" class="!block">{{ t('自定义提示') }}</el-radio>
<el-radio label="diy" class="!block">{{ t('diyPrompt') }}</el-radio>
</el-radio-group>
<el-input v-if="formData.tips_type == 'diy'" v-model.trim="formData.tips_text" :placeholder="t('感谢你的填写')" class="w-[350px]" maxlength="30" clearable show-word-limit />
<el-input v-if="formData.tips_type == 'diy'" v-model.trim="formData.tips_text" :placeholder="t('tipsTextPlaceholder')" class="w-[350px]" maxlength="30" clearable show-word-limit />
</div>
</div>
@ -106,15 +106,15 @@
<template v-else-if="formData.submit_after_action == 'voucher'">
<div class="item-wrap">
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">{{ t('凭证有效期') }}</div>
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">{{ t('validityPeriodOfVoucher') }}</div>
<div>
<el-radio-group v-model="formData.time_limit_type" class="!block">
<el-radio label="no_limit" class="!block">{{ t('不限制') }}</el-radio>
<el-radio label="no_limit" class="!block">{{ t('noLimit') }}</el-radio>
<el-radio label="specify_time" class="!block">
<span class="mr-[3px]">{{ t('设置固定有效期') }}</span>
<span class="mr-[3px]">{{ t('specifyTime') }}</span>
<el-tooltip effect="light" placement="top">
<template #content>
<p>{{ t('每条记录的凭证有效期都是一样的,例如:会议凭证可设置有效期为会议举行时间。') }}</p>
<p>{{ t('specifyTimeTips') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />
@ -122,10 +122,10 @@
</el-tooltip>
</el-radio>
<el-radio label="submission_time" class="!block">
<span class="mr-[3px]">按提交时间设置有效期</span>
<span class="mr-[3px]">{{ t('submissionTime') }}</span>
<el-tooltip effect="light" placement="top">
<template #content>
<p class="w-[250px]">每条记录的凭证有效期按照提交时间来计算例如优惠凭证的有效期可以设置为领取后3天内有效</p>
<p class="w-[250px]">{{ t('submissionTimeTips') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />
@ -135,44 +135,44 @@
</el-radio-group>
<el-date-picker v-if="formData.time_limit_type == 'specify_time'" v-model="formData.validity_time" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
<div class="flex items-center mt-[5px]" v-if="formData.time_limit_type == 'submission_time'">
<span>提交记录后</span>
<span>{{ t('afterSubmissionRecords') }}</span>
<!-- <div class="flex items-center px-[5px]">-->
<!-- v-model.trim="formData.length"-->
<el-input v-model.trim="formData.submission_time_value" @keyup="filterNumber($event)" size="small" clearable class="!w-[100px] px-[5px]" maxlength="3" />
<el-select v-model="formData.timeUnit" clearable class="!w-[100px] pr-[5px]" size="small">
<el-option v-for="(item, index) in validityOptions" :key="item.value" :label="item.text" :value="item.value"></el-option>
</el-select>
<span>有效</span>
<span>{{ t('effective') }}</span>
</div>
</div>
</div>
<div class="item-wrap">
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">凭证样式</div>
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">{{ t('voucherStyle') }}</div>
<div>
<el-form-item :label="t('码上方标题')">
<el-form-item :label="t('titleAboveTheCode')">
<!-- v-model.trim="formData.active_name"-->
<el-input clearable :placeholder="t('请妥善保存你的核销凭证')" class="input-width" :maxlength="20" />
<el-input clearable :placeholder="t('titleAboveTheCodePlaceholder')" class="input-width" :maxlength="20" />
</el-form-item>
<el-form-item :label="t('码上方内容')">
<el-input clearable :placeholder="t('请输入码上方内容')" class="input-width" :maxlength="20" />
<el-form-item :label="t('contentAboveTheCode')">
<el-input clearable :placeholder="t('contentAboveTheCodePlaceholder')" class="input-width" :maxlength="20" />
<div>
<span class="text-primary cursor-pointer mr-[10px]">添加换行符</span>
<span class="text-primary cursor-pointer">添加字段</span>
<span class="text-primary cursor-pointer mr-[10px]">{{ t('addLinefeeds') }}</span>
<span class="text-primary cursor-pointer">{{ t('addFields') }}</span>
</div>
</el-form-item>
<el-form-item :label="t('码下方内容')">
<el-form-item :label="t('contentBelowTheCode')">
<div class="block">
<el-checkbox class="!block" label="展示提交记录时间" value="" />
<el-checkbox class="!block" :label="t('submissionRecordTime')" value="" />
<el-checkbox class="!block !h-[20px]" label="展示当前时间" value="" />
<div class="text-[#999] ml-[22px]">会以秒进行跳动可起到防作假的功能</div>
<el-checkbox class="!block !h-[20px]" :label="t('currentTime')" value="" />
<div class="text-[#999] ml-[22px]">{{ t('currentTimeTips') }}</div>
<el-checkbox class="!block" label="展示提示文字" value="" />
<el-input class="ml-[22px]" :rows="4" type="textarea" placeholder="感谢你的填写" maxlength="100" />
<el-checkbox class="!block" :label="t('dispalyPromptText')" value="" />
<el-input class="ml-[22px]" :rows="4" type="textarea" :placeholder="t('tipsTextPlaceholder')" maxlength="100" />
<el-checkbox class="!block" label="展示凭证截止时间" value="" />
<el-checkbox class="!block" label="支持填表人保存凭证" value="" />
<el-checkbox class="!block" :label="t('voucherDeadline')" value="" />
<el-checkbox class="!block" :label="t('saveVoucher')" value="" />
</div>
</el-form-item>
</div>
@ -182,7 +182,7 @@
<!-- todo 后续完善 -->
<div class="item-wrap">
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">
<span>后续操作按钮</span>
<span>{{ t('subsequentPperationButtons') }}</span>
<!-- <p class="text-[12px] text-[#999] mt-[4px] font-normal">最多选择2个</p>-->
</div>
<div class="content-list-wrap">
@ -192,15 +192,15 @@
<!-- </el-checkbox>-->
<!-- <p class="text-[#999] text-[12px] pl-[24px] mt-[4px]">提交表单后可转发给微信好友查看支持按钮文案自定义提醒填表人转发给特定人员查看</p>-->
<el-checkbox v-model="formData.success_after_action.finish" label="完成" value="finish">
<div class="text-[#333]">完成</div>
<el-checkbox v-model="formData.success_after_action.finish" :label="t('finish')" value="finish">
<div class="text-[#333]">{{ t('finish') }}</div>
</el-checkbox>
<p class="text-[#999] text-[12px] pl-[24px] mt-[4px]">点击后进入首页</p>
<p class="text-[#999] text-[12px] pl-[24px] mt-[4px]">{{ t('finishTips') }}</p>
<el-checkbox v-model="formData.success_after_action.goback" label="返回" value="goback">
<div class="text-[#333]">返回</div>
<el-checkbox v-model="formData.success_after_action.goback" :label="t('back')" value="goback">
<div class="text-[#333]">{{ t('back') }}</div>
</el-checkbox>
<p class="text-[#999] text-[12px] pl-[24px] mt-[4px]">点击后返回表单</p>
<p class="text-[#999] text-[12px] pl-[24px] mt-[4px]">{{ t('backTips') }}</p>
<!-- </el-checkbox-group>-->
</div>
</div>
@ -210,7 +210,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirm">{{ t('保存') }}</el-button>
<el-button type="primary" @click="confirm">{{ t('save') }}</el-button>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<el-dialog v-model="showDialog" :title="t('填写设置')" width="600px" class="diy-dialog-wrap" :close-on-press-escape="true" :destroy-on-close="true" :close-on-click-modal="false">
<el-dialog v-model="showDialog" :title="t('writeSet')" width="600px" class="diy-dialog-wrap" :close-on-press-escape="true" :destroy-on-close="true" :close-on-click-modal="false">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
@ -33,10 +33,10 @@
</el-select>
</el-form-item>
<el-form-item :label="t('每人可填写次数')" :class="{ '!mb-[5px]' : formData.member_write_type == 'diy' }">
<el-form-item :label="t('apieceFillQuantity')" :class="{ '!mb-[5px]' : formData.member_write_type == 'diy' }">
<el-radio-group v-model="formData.member_write_type">
<el-radio label="no_limit">{{t('不限制')}}</el-radio>
<el-radio label="diy">{{t('自定义')}}</el-radio>
<el-radio label="no_limit">{{t('noLimit')}}</el-radio>
<el-radio label="diy">{{t('diy')}}</el-radio>
</el-radio-group>
</el-form-item>
@ -53,10 +53,10 @@
</div>
</el-form-item>
<el-form-item :label="t('表单可填写总数')" :class="{ '!mb-[5px]' : formData.form_write_type == 'diy' }">
<el-form-item :label="t('fillQuantityTotal')" :class="{ '!mb-[5px]' : formData.form_write_type == 'diy' }">
<el-radio-group v-model="formData.form_write_type">
<el-radio label="no_limit">{{t('不限制')}}</el-radio>
<el-radio label="diy">{{t('自定义')}}</el-radio>
<el-radio label="no_limit">{{t('noLimit')}}</el-radio>
<el-radio label="diy">{{t('diy')}}</el-radio>
</el-radio-group>
</el-form-item>
@ -72,7 +72,7 @@
<span class="mr-[5px]"></span>
<el-tooltip effect="light" placement="top">
<template #content>
<p class="w-[250px]">{{ t('填写限制的校验在极端情况下可能存在延时,从而导致限制失效,不建议商品限时抢购等场景使用该功能') }}</p>
<p class="w-[250px]">{{ t('writeTips') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />
@ -81,11 +81,11 @@
</div>
</el-form-item>
<el-form-item :label="t('可填写时间段')" prop="time_limit_rule">
<el-form-item :label="t('fillInTheTimePeriod')" prop="time_limit_rule">
<el-radio-group v-model="formData.time_limit_type">
<el-radio label="no_limit">{{t('不限制')}}</el-radio>
<el-radio label="specify_time">{{t('设置开始/停止时间')}}</el-radio>
<el-radio label="open_day_time">{{t('设置每日开启时间')}}</el-radio>
<el-radio label="no_limit">{{t('noLimit')}}</el-radio>
<el-radio label="specify_time">{{t('setSpecifyTime')}}</el-radio>
<el-radio label="open_day_time">{{t('openDayTime')}}</el-radio>
</el-radio-group>
<el-date-picker v-if="formData.time_limit_type == 'specify_time'" v-model="formData.time_limit_rule.specify_time" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
<div class="flex items-center mt-[5px]" v-if="formData.time_limit_type == 'open_day_time'">
@ -186,7 +186,7 @@ const formRules = computed(() => {
if(!value.time_value){
callback(new Error(`${unit}数不能为空`))
}else if(!value.num){
callback(new Error(`次数不能为空`))
callback(new Error(t('numCannotNull')))
}else{
callback()
}
@ -210,7 +210,7 @@ const formRules = computed(() => {
if(!value.time_value){
callback(new Error(`${unit}数不能为空`))
}else if(!value.num){
callback(new Error(`次数不能为空`))
callback(new Error(t('numCannotNull')))
}else{
callback()
}
@ -225,12 +225,12 @@ const formRules = computed(() => {
{
validator: (rule: any, value: string, callback: any) => {
if (formData.time_limit_type == 'specify_time' && (!value.specify_time || !value.specify_time.length)) {
callback(new Error('开始/停止时间不能为空'))
callback(new Error(t('timeLimitRuleOne')))
} else if (formData.time_limit_type == 'open_day_time' && (!value.open_day_time || !value.open_day_time.length)) {
callback(new Error('开启时间不能为空'))
callback(new Error(t('timeLimitRuleTwo')))
} else if (formData.time_limit_type == 'open_day_time' && value.open_day_time && value.open_day_time.length) {
if (value.open_day_time[0] == value.open_day_time[1]) {
callback(new Error('开始时间不能等于结束时间'))
callback(new Error(t('timeLimitRuleThree')))
} else {
callback()
}

View File

@ -142,23 +142,23 @@
<template #content>
<!-- 表单布局 页面设置 -->
<div class="edit-attr-item-wrap" v-if="diyStore.currentComponent == 'edit-page'">
<h3 class="mb-[10px]">{{ t('表单布局') }}</h3>
<h3 class="mb-[10px]">{{ t('formLayout') }}</h3>
<el-form label-width="90px" class="px-[10px]">
<el-form-item class="display-block">
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('排版风格') }}</span>
<span class="mr-[3px]">{{ t('layoutStyle') }}</span>
<el-tooltip effect="light" placement="top">
<template #content>
<h6 class="font-bold text-[13px] mb-[5px]">单列平铺</h6>
<p class="ml-[10px]">将所有需要填写的表单内容项直接罗列在页面上</p>
<p class="ml-[10px]">适用于表单内容项较少且项目之间无逻辑关系的情况</p>
<p class="ml-[10px]">其优势在于相对简洁便于操作</p>
<p class="ml-[10px]">但当表单项数量较大时一次性展示全部信息会增加用户的操作负担填写效率较低</p>
<h6 class="font-bold text-[13px] mb-[5px]">{{ t('singleTiling') }}</h6>
<p class="ml-[10px]">{{ t('singleTilingTipsOne') }}</p>
<p class="ml-[10px]">{{ t('singleTilingTipsTwo') }}</p>
<p class="ml-[10px]">{{ t('singleTilingTipsThree') }}</p>
<p class="ml-[10px]">{{ t('singleTilingTipsFour') }}</p>
<h6 class="font-bold text-[13px] mb-[5px] mt-[10px]">左右排列</h6>
<p class="ml-[10px]">将表单分为左右两部分左侧为标题和描述右侧为输入区域</p>
<p class="ml-[10px]">这种布局适用于标题和描述内容较少的情况能够提高表单的紧凑性和用户体验</p>
<h6 class="font-bold text-[13px] mb-[5px] mt-[10px]">{{ t('arrangeSideBySide') }}</h6>
<p class="ml-[10px]">{{ t('arrangeSideBySideTipsOne') }}</p>
<p class="ml-[10px]">{{ t('arrangeSideBySideTipsTwo') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />
@ -167,10 +167,10 @@
</div>
</template>
<el-radio-group v-model="diyStore.global.completeLayout" @change="completeLayoutChange">
<el-radio label="style-1">{{ t('单列平铺‌') }}</el-radio>
<el-radio label="style-2">{{ t('左右排列‌') }}</el-radio>
<el-radio label="style-1">{{ t('singleTiling') }}</el-radio>
<el-radio label="style-2">{{ t('arrangeSideBySide') }}</el-radio>
</el-radio-group>
<div class="text-sm text-gray-400">{{ t('切换后将同步所有表单组件的展示形式') }}</div>
<div class="text-sm text-gray-400">{{ t('layoutStyleTips') }}</div>
</el-form-item>
<el-form-item :label="t('textAlign')" v-show="diyStore.global.completeLayout == 'style-2'">
<el-radio-group v-model="diyStore.global.completeAlign">
@ -178,7 +178,7 @@
<el-radio :label="'right'">{{ t('textAlignRight') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('边框开关')" v-show="diyStore.global.completeLayout == 'style-2'">
<el-form-item :label="t('borderControl')" v-show="diyStore.global.completeLayout == 'style-2'">
<el-switch v-model="diyStore.global.borderControl" />
</el-form-item>
</el-form>
@ -187,12 +187,12 @@
<template #field>
<!-- 表单组件 字段内容设置 -->
<div class="edit-attr-item-wrap" v-if="diyStore.editComponent.componentType == 'diy_form'">
<h3 class="mb-[10px]">{{ t('字段内容') }}</h3>
<h3 class="mb-[10px]">{{ t('fieleContent') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('字段名称')">
<el-form-item :label="t('fieldName')">
<el-input v-model.trim="diyStore.editComponent.field.name" :placeholder="t('fieldNamePlaceholder')" clearable :maxlength="inputMaxLength" show-word-limit />
</el-form-item>
<el-form-item :label="t('字段说明')">
<el-form-item :label="t('filedRemark')">
<el-input v-model.trim="diyStore.editComponent.field.remark.text" :placeholder="t('fieldRemarkPlaceholder')" clearable maxlength="60" show-word-limit />
</el-form-item>
</el-form>
@ -201,7 +201,7 @@
<template #other>
<!-- 表单组件 其他设置 -->
<div class="edit-attr-item-wrap" v-if="diyStore.editComponent.componentType == 'diy_form'">
<h3 class="mb-[10px]">{{ t('其他设置') }}</h3>
<h3 class="mb-[10px]">{{ t('otherSetting') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('isRequired')">
<el-switch v-model="diyStore.editComponent.field.required" />
@ -243,11 +243,11 @@
<el-form-item>
<template #label>
<div class="flex items-center">
<span class="mr-[3px]">{{ t('隐藏该组件') }}</span>
<span class="mr-[3px]">{{ t('hideControl') }}</span>
<el-tooltip effect="light" placement="top">
<template #content>
<p>勾选后填表人填表时看不到该字段</p>
<p>适用于你不再收集该字段又不希望删除已收集的数据</p>
<p>{{ t('hideControlTipsOne') }}</p>
<p>{{ t('hideControlTipsTwo') }}</p>
</template>
<el-icon>
<QuestionFilled color="#999999" />
@ -264,7 +264,7 @@
<!-- 表单组件 字段样式设置 -->
<template v-if="diyStore.editComponent.componentType == 'diy_form'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('文字样式') }}</h3>
<h3 class="mb-[10px]">{{ t('textStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] diy-nav-slider" :min="12" :max="18" />
@ -282,7 +282,7 @@
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('字段说明样式') }}</h3>
<h3 class="mb-[10px]">{{ t('filedRemarkStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.field.remark.fontSize" show-input size="small" class="ml-[10px] diy-nav-slider" :min="12" :max="18" />
@ -750,7 +750,7 @@ const verifyFormComponent = () => {
if (diyStore.value[i].componentType == 'diy_form' && diyStore.value[i].componentName != 'FormSubmit' && diyStore.value[i].field.name == '') {
diyStore.changeCurrentIndex(i, diyStore.value[i])
ElMessage({
message: t('请输入字段名称'),
message: t('fieldNamePlaceholder'),
type: 'warning'
})
return false;

View File

@ -53,29 +53,29 @@
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="100">
<template #default="{ row }">
<div class="flex items-center justify-end">
<el-button type="primary" v-if="row.status == 1" link @click="spreadEvent(row)">{{ t('推广') }}</el-button>
<el-button type="primary" v-if="row.status == 1 && row.type=='DIY_FORM'" link @click="spreadEvent(row)">{{ t('promotion') }}</el-button>
<!-- <el-button type="primary" link @click="toPreview(row)">{{ t('preview') }}</el-button>-->
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button v-if="row.status == 0" type="primary" link @click="deleteEvent(row.form_id)">{{ t('delete') }}</el-button>
<el-button type="primary" link @click="detailEvent(row)">{{ t('详情') }}</el-button>
<el-button type="primary" link @click="detailEvent(row)">{{ t('detail') }}</el-button>
<el-dropdown placement="bottom" trigger="click" class="ml-[12px]">
<el-button type="primary" link>更多</el-button>
<el-button type="primary" link>{{ t('more') }}</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<el-button type="primary" class="w-full" link @click="submitConfigEvent(row)">{{ t('提交成功页') }}</el-button>
<el-dropdown-item v-if="row.type=='DIY_FORM'">
<el-button type="primary" class="w-full" link @click="submitConfigEvent(row)">{{ t('submitSuccess') }}</el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button type="primary" class="w-full" link @click="writeConfigEvent(row)">{{ t('填写设置') }}</el-button>
<el-button type="primary" class="w-full" link @click="writeConfigEvent(row)">{{ t('writeSet') }}</el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-dropdown-item v-if="row.type=='DIY_FORM'">
<el-button type="primary" class="w-full" link @click="openShare(row)">{{ t('shareSet') }}</el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button type="primary" class="w-full" link @click="exportEvent(row)">{{ t('导出') }}</el-button>
<el-button type="primary" class="w-full" link @click="exportEvent(row)">{{ t('export') }}</el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button type="primary" class="w-full" link @click="copyEvent(row.form_id)">{{ t('复制') }}</el-button>
<el-button type="primary" class="w-full" link @click="copyEvent(row.form_id)">{{ t('copy') }}</el-button>
</el-dropdown-item>
</el-dropdown-menu>
</template>
@ -239,12 +239,14 @@ const addEvent = async (formEl: FormInstance | undefined) => {
}
const showClick = (row: any) => {
row.status = row.status === 1 ? 0 : 1
const data = row.status === 1 ? 0 : 1
const obj = {
form_id: row.form_id,
status: row.status,
status: data
}
editFormStatus(obj)
editFormStatus(obj).then((res) => {
row.status = data
})
}
//

View File

@ -1,13 +1,13 @@
<template>
<el-drawer v-model="showDialog" :title="t('数据与统计')" direction="rtl" size="70%" :before-close="handleClose" class="member-detail-drawer">
<el-drawer v-model="showDialog" :title="t('dataAndStatistics')" direction="rtl" size="70%" :before-close="handleClose" class="member-detail-drawer">
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane :label="t('明细数据')" name="detail_data">
<el-tab-pane :label="t('detailData')" name="detail_data">
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="formData.searchParam" ref="searchFormDiyFormRef">
<el-form-item :label="t('填表人')" prop="keyword">
<el-input v-model.trim="formData.searchParam.keyword" placeholder="请输入填表人" />
<el-form-item :label="t('fillInFormPerson')" prop="keyword">
<el-input v-model.trim="formData.searchParam.keyword" :placeholder="t('fillInFormPersonplaceholder')" />
</el-form-item>
<el-form-item :label="t('填表时间')" prop="create_time">
<el-form-item :label="t('fillInFormDate')" prop="create_time">
<el-date-picker v-model="formData.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>
@ -22,7 +22,7 @@
<template #empty>
<span>{{ !formData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column fixed :label="t('填表人信息')" min-width="160">
<el-table-column fixed :label="t('fillInFormPersonInfo')" min-width="160">
<template #default="{ row }">
<div class="flex items-center cursor-pointer" @click="detailEvent(row.member.member_id)">
<div class="min-w-[50px] h-[50px] flex items-center justify-center">
@ -42,7 +42,7 @@
</div>
</template>
</el-table-column>
<el-table-column fixed prop="create_time" :label="t('填表时间')" min-width="120" />
<el-table-column fixed prop="create_time" :label="t('fillInFormDate')" min-width="120" />
<el-table-column v-for="item in formFieldsList" :key="item.field_key" :label="item.field_name" min-width="200">
<template #default="{ row }">
@ -67,11 +67,11 @@
@size-change="loadFormRecordsListFn()" @current-change="loadFormRecordsListFn" />
</div>
</el-tab-pane>
<el-tab-pane :label="t('填表人统计')" name="member_stat">
<el-tab-pane :label="t('fillInFormPersonStatics')" name="member_stat">
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="formMemberList.searchParam" ref="searchFormDiyMemberRef">
<el-form-item :label="t('填表人')" prop="keyword">
<el-input v-model.trim="formMemberList.searchParam.keyword" placeholder="请输入填表人" />
<el-form-item :label="t('fillInFormPerson')" prop="keyword">
<el-input v-model.trim="formMemberList.searchParam.keyword" :placeholder="t('fillInFormPersonplaceholder')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getFormRecordsMemberFn()">{{ t('search') }}</el-button>
@ -84,7 +84,7 @@
<template #empty>
<span>{{ !formMemberList.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column fixed :label="t('填表人信息')" min-width="200">
<el-table-column fixed :label="t('fillInFormPersonInfo')" min-width="200">
<template #default="{ row }">
<div class="flex items-center cursor-pointer" @click="detailEvent(row.member.member_id)">
<div class="min-w-[50px] h-[50px] flex items-center justify-center">
@ -105,7 +105,7 @@
</template>
</el-table-column>
<!-- <el-table-column fixed prop="create_time" :label="t('填表时间')" min-width="120" /> -->
<el-table-column fixed prop="create_time" :label="t('总计(表单填写数)')" min-width="500">
<el-table-column fixed prop="create_time" :label="t('fillInFormTotal')" min-width="500">
<template #default="{ row }" @click="">
{{ row.write_count }}
</template>
@ -119,7 +119,14 @@
</div>
</el-tab-pane>
<el-tab-pane :label="t('字段统计')" name="field_stat">
<el-tab-pane :label="t('fieldStatistics')" name="field_stat">
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="formData.searchParam" ref="searchFormDiyFieldsRef">
<el-form-item>
<el-button type="primary" @click="exportFieldsEvent">{{ t('export') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<el-collapse v-model="activeNames" class="diy-collapse mt-[15px]">
<el-collapse-item :title="item.field_name" :name="item.field_id" v-for="(item, index) in formFieldsStat" :key="index">
<template #title>
@ -143,7 +150,7 @@
</el-tab-pane>
</el-tabs>
<el-dialog v-model="dialogVisible" :title="t('查看信息')" width="400px">
<el-dialog v-model="dialogVisible" :title="t('viewInformation')" width="400px">
<div class="flex flex-col">
<div class="flex mb-[10px]" v-for="(item, index) in formDetail" :key="index">
<div class="flex justify-end w-[100px]">{{ item.label }}</div>
@ -166,6 +173,7 @@
</el-drawer>
<export-sure ref="exportSureDialog" :show="flag" type="diy_form_records" :searchParam="formData.searchParam" @close="handleExportClose" />
<export-sure ref="exportSureDialog" :show="flagMember" type="diy_form_records_member" :searchParam="formMemberList.searchParam" @close="handleMemberExportClose" />
<export-sure ref="exportSureDialog" :show="flagFields" type="diy_form_records_fields" :searchParam="formData.searchParam" @close="handleFieldsExportClose" />
</template>
<script lang="ts" setup>
@ -184,6 +192,7 @@ const formId = ref(0)
const dialogVisible = ref(false)
const searchFormDiyFormRef = ref<FormInstance>()
const searchFormDiyMemberRef = ref<FormInstance>()
const searchFormDiyFieldsRef = ref<FormInstance>()
const handleClose = (done: () => void) => {
showDialog.value = false;
}
@ -236,7 +245,7 @@ const formDetailEvent = (row: any) => {
//
const deleteEvent = (row: any) => {
ElMessageBox.confirm(t('确定删除该条数据吗'), t('warning'),
ElMessageBox.confirm(t('deleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
@ -355,6 +364,14 @@ const exportMemberEvent = () => {
flagMember.value = true
}
const flagFields = ref(false)
const handleFieldsExportClose = (val) => {
flagFields.value = val
}
const exportFieldsEvent = () => {
flagFields.value = true
}
defineExpose({
showDialog,
setFormData

View File

@ -87,7 +87,7 @@
<el-table-column :label="t('applicationForWithdrawalAmount')" align="center" min-width="120" />
<el-table-column :label="t('actualTransferAmount')" align="center" min-width="120" />
<el-table-column :label="t('cashOutCommission')" align="center" min-width="110" />
<el-table-column :label="t('cashOutStatus')" align="center" min-width="100" />
<el-table-column :label="t('cashOutStatus')" align="center" min-width="150" />
<el-table-column :label="t('applyTime')" align="center" min-width="160" />
<el-table-column :label="t('auditTime')" align="center" min-width="160" />
<el-table-column :label="t('transferTime')" align="center" min-width="160" />
@ -103,7 +103,7 @@
<div class="flex items-center cursor-pointer " @click="toMember(row.member.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] rounded-full" v-else src="@/app/assets/images/member_head.png" alt="">
<div class="flex flex flex-col items-baseline" style="width: calc(100% - 60px);">
<div class="flex flex-col items-baseline" style="width: calc(100% - 60px);">
<span class="w-[100%] truncate text-left">{{ row.member.nickname || row.member.username || '' }}</span>
<span class="w-[100%] truncate">{{ row.member.mobile || '' }}</span>
</div>
@ -136,6 +136,14 @@
<span>{{ t('bankAccount') }}{{ row.transfer_account }}</span>
<span>{{ t('bankName') }}{{ row.transfer_bank }}</span>
</div>
<div class="flex items-center" v-else-if="row.transfer_type=='wechatpay'">
<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] rounded-full" v-else src="@/app/assets/images/member_head.png" alt="">
<div class="flex flex-col items-baseline" style="width: calc(100% - 60px);">
<span class="w-[100%] truncate text-left">{{ row.member.nickname || row.member.username || '' }}</span>
<span class="w-[100%] truncate">{{ row.member.mobile || '' }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="apply_money" min-width="120" align="center" />
@ -144,7 +152,12 @@
<el-table-column prop="service_money" align="center" min-width="110" />
<el-table-column prop="status_name" align="center" min-width="100" />
<el-table-column prop="status_name" align="center" min-width="150">
<template #default="{ row }">
<div>{{ row.status_name }}</div>
<div v-if="row.status == 2 && row.transfer_type == 'wechatpay'" class="text-[12px] text-[var(--el-color-success)]">(等待用户收款)</div>
</template>
</el-table-column>
<el-table-column min-width="160" align="center">
<template #default="{ row }">
@ -166,9 +179,11 @@
<el-table-column align="right" fixed="right" width="120">
<template #default="{ row }">
<el-button v-for="(item, index) in operationBtn[row.status.toString()].value" :key="index + 'a'"
@click="fnProcessing(operationBtn[row.status.toString()].clickArr[index], row)"
type="primary" link>{{ item }}</el-button>
<el-button type="primary" link @click="successfulAuditFn(row)" v-if="row.status == 1"> {{ t('successfulAudit') }}</el-button>
<el-button type="primary" link @click="auditFailureFn(row)" v-if="row.status == 1"> {{ t('auditFailure') }}</el-button>
<el-button type="primary" link @click="memberCancelFn(row)" v-if="row.status == 1 || row.status == 2 || row.status == 4"> {{ t('cancelWithdrawal') }}</el-button>
<el-button type="primary" link @click="transferFn(row)" v-if="row.status == 2 && row.transfer_type !== 'wechatpay'"> {{ t('transfer') }}</el-button>
<el-button type="primary" link @click="detailFn(row.id)"> {{ t('detail') }}</el-button>
<el-button type="primary" link @click="handleRemark(row)"> {{ t('remark') }}</el-button>
</template>
</el-table-column>
@ -284,6 +299,11 @@
<div class="input-width"> {{ cashOutInfo.transfer.transfer_remark }} </div>
</el-form-item>
</el-col>
<el-col :span="12" v-if="cashOutInfo.refuse_reason">
<el-form-item :label="t('remark')">
<div class="input-width"> {{ cashOutInfo.refuse_reason }} </div>
</el-form-item>
</el-col>
</el-row>
</el-form>
@ -482,11 +502,10 @@
<script lang="ts" setup>
import { reactive, ref, computed } from 'vue'
import { t } from '@/lang'
import { getCashOutList, getTransfertype, memberTransfer, memberAudit, getCashOutDetail, getCashOutStatusList, getCashOutStat, memberRemark, memberCheck } from '@/app/api/member'
import { getCashOutList, getTransfertype, memberTransfer, memberAudit, getCashOutDetail, getCashOutStatusList, getCashOutStat, memberRemark, memberCheck, memberCancel } from '@/app/api/member'
import { img } from '@/utils/common'
import { ElMessageBox, FormInstance, FormRules } from 'element-plus'
import { useRouter, useRoute } from 'vue-router'
import { AnyObject } from '@/types/global'
const cashOutStatusList = ref([])
const checkStatusList = async () => {
@ -496,32 +515,6 @@ checkStatusList()
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const operationBtn = ref<AnyObject>({
1: {
value: [t('successfulAudit'), t('auditFailure'), t('detail')],
clickArr: ['successfulAuditFn', 'auditFailureFn', 'detailFn']
},
2: {
value: [t('transfer'), t('detail')],
clickArr: ['transferFn', 'detailFn']
},
3: {
value: [t('detail')],
clickArr: ['detailFn']
},
4: {
value: [t('detail')],
clickArr: ['detailFn']
},
'-1': {
value: [t('detail')],
clickArr: ['detailFn']
},
'-2': {
value: [t('detail')],
clickArr: ['detailFn']
}
})
//
const formRules = reactive<FormRules>({})
@ -592,48 +585,14 @@ const loadOrderList = (page: number = 1) => {
}
loadOrderList()
//
const auditFailure = ref({ refuse_reason: '', id: 0, action: 0 })
const auditShowDialog = ref(false)
const fnProcessing = (type: string, data: any) => {
const obj = {}
if (['successfulAuditFn', 'auditFailureFn'].includes(type)) {
obj.id = data.id
if (type == 'successfulAuditFn') {
obj.action = 'agree'
curData.value = data
auditPassShowDialog.value = true
} else {
obj.action = 'refuse'
auditFailure.value = Object.assign(auditFailure.value, obj)
auditShowDialog.value = true
}
} else if (type == 'transferFn') {
if (data.transfer_type == 'wechatpay') {
obj.id = data.id
ElMessageBox.confirm(`${t('isTransfer')}`, `${t('transfer')}`).then(() => {
transferFn(obj)
})
} else {
transferData.value = data
formTransfer.id = data.id
transferShowDialog.value = true
}
} else if (type == 'checkFn') {
checkFn(data.id)
} else {
detailFn(data.id)
}
}
/**
* 转账
* @param data
*/
const transferData = ref({})
const transferData = ref<any>({})
const transferShowDialog = ref(false)
const formTransferRef = ref<FormInstance>()
const formTransfer = reactive({
const formTransfer = reactive<any>({
id: 0,
transfer_voucher: '',
transfer_remark: ''
@ -646,23 +605,25 @@ const formTransferRules = computed(() => {
}
})
const transferFn = (data:any) => {
transferData.value = data
formTransfer.id = data.id
transferShowDialog.value = true
}
const handleTransfer = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
transferFn(formTransfer)
memberTransfer({ ...formTransfer }).then(res => {
transferShowDialog.value = false
loadOrderList()
}).catch(() => {
transferShowDialog.value = false
loadOrderList()
})
}
})
}
const transferFn = (data:any) => {
memberTransfer({ ...data }).then(res => {
transferShowDialog.value = false
loadOrderList()
}).catch(() => {
transferShowDialog.value = false
loadOrderList()
})
}
/**
* 详情
@ -696,6 +657,12 @@ const detailFn = (id:any) => {
const auditPassShowDialog = ref(false)
const curData = ref<any>({})
//
const successfulAuditFn = (data: any) => {
curData.value = data
auditPassShowDialog.value = true
}
const handlePass = () => {
const obj = {
id: curData.value.id,
@ -703,24 +670,55 @@ const handlePass = () => {
}
cashOutAuditFn(obj)
}
/**
* 拒绝审核
*/
const auditFailure = ref({ refuse_reason: '', id: 0, action: '' })
const auditShowDialog = ref(false)
const loading = ref(false)
const auditFailureFn = (data: any) => {
auditFailure.value.id = data.id
auditFailure.value.action = 'refuse'
auditShowDialog.value = true
}
const confirm = () => {
auditShowDialog.value = false
cashOutAuditFn(auditFailure.value)
}
const repeat = ref(false)
const cashOutAuditFn = (data:any) => {
if (repeat.value) return
repeat.value = true
memberAudit({
...data
}).then(res => {
repeat.value = false
auditPassShowDialog.value = false
loadOrderList()
}).catch(() => {
repeat.value = false
auditPassShowDialog.value = false
loadOrderList()
})
}
/**
* 拒绝审核
*/
const confirm = () => {
auditShowDialog.value = false
cashOutAuditFn(auditFailure.value)
//
const memberCancelFn = (data: any) => {
ElMessageBox.confirm(t('cancelTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
memberCancel({ id: data.id }).then((res) => {
loadOrderList()
})
})
}
/**

View File

@ -127,13 +127,11 @@
<span class="mr-3" v-if="copyright.company_name">{{ copyright.company_name }}</span>
</a>
<a href="https://beian.miit.gov.cn/" v-if="copyright.icp" target="_blank">
<span class="mr-3">备案号: {{ copyright.icp }}</span>
<span class="mr-3">{{ copyright.icp }}</span>
</a>
</div>
<div class="flex items-center">
<span class="mx-1" @click="getInfoFn">版权信息</span>
<!-- | <span class="mx-1">隐私政策</span> | <span class="mx-1">联系我们</span> -->
<!-- 版权信息 | 开发者联盟与隐私的声明 | 隐私政策 | 联系我们 | Cookies -->
<div class="flex items-center cursor-pointer">
<span class="mx-1" @click="getInfoFn">版权信息</span> | <span class="mx-1">开发者联盟与隐私的声明</span> | <span class="mx-1">隐私政策</span> | <span class="mx-1">联系我们</span> | <span class="mx-1">Cookies</span>
</div>
</div>
</div>

View File

@ -35,6 +35,15 @@
</div>
<img src="@/app/assets/images/tools/sys_dict_list.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow mb-[24px] mx-[14px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/update_cache')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col">
<span class="text-[16px] text-[#222] font-bold">更新缓存</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">
更新缓存
</div>
</div>
<img src="@/app/assets/images/tools/tools_Update_cache.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow m-[20px] !mr-[0px] !mt-[0px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/detection')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col bg-[var(--el-bg-color)]">
<span class="text-[16px] font-bold">环境监测</span>

View File

@ -144,27 +144,27 @@ const formRules = reactive<FormRules>({
{ 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 < 2 || value > 365) {
callback(t('signPeriodMustZeroTips'))
} else {
callback();
}
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 < 2 || value > 365) {
callback(t('signPeriodMustZeroTips'))
} else {
callback()
}
}
}],
}]
})
/**
* 签到奖励文本请求参数
*/
const contentData = reactive<Record<string, any>>({
gifts: [],
gifts: []
})
/**
@ -211,17 +211,16 @@ const setFormData = async () => {
if (formData.continue_award) {
formData.continue_award.forEach((item: any, index: number) => {
continueSignAwardTableData.data.push(cloneDeep(item))
contentData.gifts = [];
contentData.gifts = []
const val = cloneDeep(item)
delete val['continue_sign'];
delete val['continue_tag'];
delete val['receive_limit'];
delete val['receive_num'];
delete val['continue_sign']
delete val['continue_tag']
delete val['receive_limit']
delete val['receive_num']
contentData.gifts = val
@ -239,7 +238,7 @@ setFormData()
const daySignAwardText = ref([])
const setMemberBenefitsContent = async () => {
const data = await (await getMemberGiftsContent(contentData)).data
daySignAwardText.value = [];
daySignAwardText.value = []
Object.values(data).forEach((el: any) => {
daySignAwardText.value.push(el)
})
@ -254,7 +253,7 @@ const setMemberBenefitsContent = async () => {
*/
const setMemberBenefitsContents = async (content: any, item: any, index: number = 0, tag = 0) => {
const data = await (await getMemberGiftsContent(content)).data
continueSignAwardText.value = [];
continueSignAwardText.value = []
Object.values(data).forEach((el: any) => {
continueSignAwardText.value.push(el)
})
@ -276,7 +275,6 @@ const setMemberBenefitsContents = async (content: any, item: any, index: number
isEdit = false
editIndex = 0
}
/**
@ -313,6 +311,9 @@ const setDaySignAward = async () => {
if (!formData.day_award.hasOwnProperty('balance') && !formData.day_award.hasOwnProperty('point') && formData.day_award.shop_coupon.is_use == 0) {
formData.day_award = ''
}
if (formData.day_award.hasOwnProperty('balance') && formData.day_award.balance.is_use == 1) {
formData.day_award.balance.money = Number(formData.day_award.balance.money)
}
contentData.gifts = formData.day_award
setMemberBenefitsContent()
@ -338,8 +339,8 @@ const continueSignAwardSet = () => {
* 修改连签奖励设置页
*/
const continueSignAwardModify = (flag: boolean, index: any) => {
isEdit = flag;
editIndex = index;
isEdit = flag
editIndex = index
continue_award.value = formData.continue_award[index]
continueSignDialog.value = true
@ -356,18 +357,20 @@ const setContinueSignAward = async () => {
if (!continue_award.value.hasOwnProperty('balance') && !continue_award.value.hasOwnProperty('point') && continue_award.value.shop_coupon.is_use == 0) {
continue_award.value = ''
}
if (continue_award.value.hasOwnProperty('balance') && continue_award.value.balance.is_use == 1) {
continue_award.value.balance.money = Number(continue_award.value.balance.money)
}
if (Object.keys(continue_award.value).length > 0) {
const val = cloneDeep(continue_award.value)
delete val['continue_sign'];
delete val['continue_tag'];
delete val['receive_limit'];
delete val['receive_num'];
delete val['continue_sign']
delete val['continue_tag']
delete val['receive_limit']
delete val['receive_num']
contentData.gifts = val
let index = 0;
let index = 0
if (formData.continue_award.length > 0) {
index = formData.continue_award.length - 1
}
@ -394,7 +397,7 @@ const deleteContinueSignAwardEvent = (index: number) => {
* 使用默认说明
*/
const defaultExplainEvent = () => {
formData.rule_explain = t('ruleExplainDefault');
formData.rule_explain = t('ruleExplainDefault')
}
</script>

View File

@ -38,7 +38,7 @@
<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] rounded-full" v-else src="@/app/assets/images/member_head.png" alt="">
<div class="flex flex flex-col">
<div class="flex flex-col">
<span>{{ row.member.nickname || '' }}</span>
</div>
</div>

View File

@ -19,7 +19,7 @@
<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] rounded-full" v-else src="@/app/assets/images/member_head.png" alt="">
<div class="flex flex flex-col">
<div class="flex flex-col">
<span>{{ row.member.nickname || '' }}</span>
<span>{{ row.member.mobile || '' }}</span>
</div>

View File

@ -90,7 +90,7 @@
<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] rounded-full" v-else src="@/app/assets/images/member_head.png" alt="">
<div class="flex flex flex-col">
<div class="flex flex-col">
<span>{{ row.member.nickname || '' }}</span>
</div>
</div>

View File

@ -93,7 +93,7 @@
<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] rounded-full" v-else src="@/app/assets/images/member_head.png" alt="">
<div class="flex flex flex-col">
<div class="flex flex-col">
<span>{{ row.member.nickname || '' }}</span>
</div>
</div>

View File

@ -5,7 +5,7 @@
<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" :maxlength="5" clearable />
<el-input v-model.trim="formData.money" :maxlength="5" clearable :disabled="formData.is_use == 0" />
</div>
<span class="ml-[15px] el-form-item__label">元红包</span>
</div>

View File

@ -4,7 +4,7 @@
<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 />
<el-input v-model.trim="formData.num" clearable :disabled="formData.is_use == 0" />
</div>
<span class="ml-[15px] el-form-item__label">积分</span>
</el-form-item>

View File

@ -14,7 +14,7 @@
</el-form-item>
<el-form-item :label="t('adjustBalance')" prop="adjust">
<el-input-number v-model="formData.adjust" clearable :min="0" :max="999999" :placeholder="t('adjustPlaceholder')"/>
<el-input-number v-model="formData.adjust" clearable :min="0" :max="999999" :placeholder="t('adjustBalancePlaceholder')" @focus="formData.adjust = ''" class="!w-[200px]"/>
</el-form-item>
<el-form-item :label="t('memo')" prop="memo">

View File

@ -44,7 +44,7 @@
<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] rounded-full" v-else src="@/app/assets/images/member_head.png" alt="">
<div class="flex flex flex-col">
<div class="flex flex-col">
<span>{{ row.member.nickname || '' }}</span>
</div>
</div>

View File

@ -28,7 +28,7 @@
</el-select>
</el-form-item>
<el-form-item :label="t('memberLevel')" prop="member_label">
<el-form-item :label="t('memberLevel')" prop="member_level">
<el-select v-model="memberTableData.searchParam.member_level" collapse-tags clearable :placeholder="t('memberLevelPlaceholder')" class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item['level_name']" :value="item['level_id']" v-for="(item, index) in levelSelectData" :key="index" />
@ -60,7 +60,7 @@
<img class="max-w-[50px] max-h-[50px]" v-if="row.headimg" :src="img(row.headimg)" alt="">
<img class="max-w-[50px] max-h-[50px]" v-else src="@/app/assets/images/member_head.png" alt="">
</div>
<div class="flex flex flex-col">
<div class="flex flex-col">
<span>{{ row.nickname || '' }}</span>
</div>
</div>

View File

@ -72,7 +72,7 @@
<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] rounded-full" v-else src="@/app/assets/images/member_head.png" alt="">
<div class="flex flex flex-col">
<div class="flex flex-col">
<span>{{ row.member.nickname || '' }}</span>
</div>
</div>

View File

@ -229,9 +229,9 @@ import { t } from '@/lang'
import { img } from '@/utils/common'
import { useRoute, useRouter } from 'vue-router'
import { cloneDeep } from 'lodash-es'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ElMessageBox } from 'element-plus'
import usePosterStore from '@/stores/modules/poster'
import { initPoster,addPoster,editPoster,getPosterTemplate,getPreviewPoster } from '@/app/api/poster'
import { initPoster, addPoster, editPoster, getPosterTemplate, getPreviewPoster } from '@/app/api/poster'
const setLayout = inject('setLayout')
setLayout('decorate')
@ -249,8 +249,8 @@ if(route && route.query){
}
const backPath:any = route.query.back
const template = ref('');
const oldTemplate = ref('');
const template = ref('')
const oldTemplate = ref('')
const component = ref([])
const componentType: string[] = reactive([])
@ -268,39 +268,39 @@ const previewIframeStyle = (data: any)=>{
left: '',
right: '',
bottom: ''
};
style.transform = `rotate(${data.angle}deg)`;
style.zIndex = `${data.zIndex}`;
}
style.transform = `rotate(${data.angle}deg)`
style.zIndex = `${data.zIndex}`
switch(data.y) {
case 'top':
style.top = 0;
style.top = 0
break;
case 'center':
style.top = '50%';
style.transform = style.transform + ' translateY(-50%)';
style.top = '50%'
style.transform = style.transform + ' translateY(-50%)'
break;
case 'bottom':
style.bottom = 0;
style.bottom = 0
break;
default:
style.top = data.y + 'px';
style.top = data.y + 'px'
}
switch(data.x) {
case 'left':
style.left = 0;
style.left = 0
break;
case 'center':
style.left = '50%';
style.transform = style.transform + ' translateX(-50%)';
style.left = '50%'
style.transform = style.transform + ' translateX(-50%)'
break;
case 'right':
style.right = 0;
style.right = 0
break;
default:
style.left = data.x + 'px';
style.left = data.x + 'px'
}
// console.log(data.x,data.y)
return style;
return style
}
//
@ -341,14 +341,14 @@ const yAlignList = ref([
},
])
const alignChangeFn = (type: any,data: any)=>{
posterStore.editComponent[type] = data.className;
posterStore.editComponent[type] = data.className
}
//
const isChange = ref(true) // truefalse
const goBack = () => {
if (isChange.value) {
location.href = `${location.origin}${backPath}`;
location.href = `${location.origin}${backPath}`
router.push(backPath)
} else {
//
@ -362,7 +362,7 @@ const goBack = () => {
autofocus: false
}
).then(() => {
location.href = `${location.origin}${backPath}`;
location.href = `${location.origin}${backPath}`
}).catch(() => {
})
}
@ -390,6 +390,9 @@ const loadPosterTemplate = ()=> {
}).then(res => {
if (res.data) {
templatePoster.splice(0, templatePoster.length, ...res.data)
if (posterStore.id) {
template.value = templatePoster.findIndex((item:any) => item.type == posterStore.type)
}
}
})
}
@ -398,7 +401,7 @@ const loadPosterTemplate = ()=> {
watch(
() => template.value,
(newValue, oldValue) => {
oldTemplate.value = oldValue;
oldTemplate.value = oldValue
}
)
@ -413,51 +416,51 @@ const changeTemplatePoster = (index:any)=> {
}).then(() => {
posterStore.changeCurrentIndex(-99)
if (index !== '') {
let data = templatePoster[index].data;
posterStore.global = data.global;
let data = templatePoster[index].data
posterStore.global = data.global
if (data.value.length) {
posterStore.value = data.value
}
} else {
//
posterStore.init();
posterStore.init()
}
}).catch(() => {
//
template.value = oldTemplate.value;
});
template.value = oldTemplate.value
})
}else{
if (index !== '') {
let data = templatePoster[index].data;
posterStore.global = data.global;
let data = templatePoster[index].data
posterStore.global = data.global
if (data.value.length) {
posterStore.value = data.value
}
} else {
//
posterStore.init();
posterStore.init()
}
}
};
}
//
initPoster({
id: route.query.id,
type: route.query.type,
name: route.query.name
}).then( async (res:any)=>{
}).then(async (res:any) => {
const data = res.data
posterStore.init(); //
posterStore.init() //
posterStore.id = data.id;
posterStore.name = data.name;
posterStore.channel = data.channel;
posterStore.status = data.status;
posterStore.isDefault = data.is_default;
posterStore.addon = data.addon;
posterStore.type = data.type;
posterStore.typeName = data.poster_type.name;
posterStore.id = data.id
posterStore.name = data.name
posterStore.channel = data.channel
posterStore.status = data.status
posterStore.isDefault = data.is_default
posterStore.addon = data.addon
posterStore.type = data.type
posterStore.typeName = data.poster_type.name
if (data.value) {
const sources = data.value
@ -497,18 +500,18 @@ const save = (callback: any) => {
posterStore.value.forEach((item:any,index:any, originalArr:any)=> {
const box: any = document.getElementById(item.id)
if (box) {
item.width = box.offsetWidth;
item.height = box.offsetHeight;
item.width = box.offsetWidth
item.height = box.offsetHeight
if (item.type == 'draw') {
// [x,y]
let leftTop = [item.x * 1, item.y * 1]; //
let rightTop = [(item.x + item.width) * 1, item.y * 1]; //
let rightBottom = [(item.x + item.width) * 1, (item.y + item.height) * 1]; //
let leftBottom = [item.x * 1, (item.y + item.height) * 1]; //
item.points = [leftTop, rightTop, rightBottom, leftBottom];
let leftTop = [item.x * 1, item.y * 1] //
let rightTop = [(item.x + item.width) * 1, item.y * 1] //
let rightBottom = [(item.x + item.width) * 1, (item.y + item.height) * 1] //
let leftBottom = [item.x * 1, (item.y + item.height) * 1] //
item.points = [leftTop, rightTop, rightBottom, leftBottom]
}
}
delete item.verify;
delete item.verify
})
let data = {
@ -532,7 +535,7 @@ const save = (callback: any) => {
if (posterStore.id) {
isRepeat.value = false //
} else {
location.href = `${location.origin}${backPath}`;
location.href = `${location.origin}${backPath}`
}
if (callback) callback(res.data.id)
}
@ -554,8 +557,8 @@ const preview = () => {
type:posterStore.type
}).then(((res:any)=>{
if(res.data) {
previewPosterUrl.value = res.data;
previewDialogVisible.value = true;
previewPosterUrl.value = res.data
previewDialogVisible.value = true
}
isRepeat.value = false
}))

View File

@ -66,7 +66,7 @@
</el-card>
<!--添加海报-->
<el-dialog v-model="dialogVisible" :title="t('addPosterTitle')" width="350px">
<el-dialog v-model="dialogVisible" :title="t('addPosterTitle')" width="350px" destroy-on-close="true">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules">
<el-form-item :label="t('posterName')" prop="name">

View File

@ -33,7 +33,7 @@
</el-radio-group>
</el-form-item>
<el-form-item :label="t('transfer')" v-if="formData.is_open" class="items-baseline">
<!-- <el-form-item :label="t('transfer')" v-if="formData.is_open" class="items-baseline">
<div>
<el-radio-group v-model="formData.is_auto_transfer">
<el-radio label="0" size="large">{{t('manualTransfer')}}</el-radio>
@ -41,7 +41,7 @@
</el-radio-group>
<div class="text-[12px] text-[#999] leading-[24px]">{{ t('transferTips') }}</div>
</div>
</el-form-item>
</el-form-item> -->
<el-form-item :label="t('transferMode')" v-if="formData.is_open" class="items-baseline">
<div>
<el-checkbox-group v-model="formData.transfer_type" size="large">

View File

@ -10,22 +10,24 @@
<div class="mt-[20px]">
<div class="flex items-center bg">
<span class="p-[15px] w-[25%] text-[14px]">规则名称</span>
<span class="p-[15px] w-[50%] text-[14px]">规则详情</span>
<span class="p-[15px] w-[15%] text-[14px]">所属应用</span>
<span class="p-[15px] w-[35%] text-[14px]">规则详情</span>
<span class="p-[15px] w-[15%] text-[14px]">是否启用</span>
<span class="p-[15px] w-[10%] text-[14px]">操作</span>
</div>
<div v-for="(item, key) in rules" :key="key" class="flex items-center">
<span class="p-[15px] w-[25%] text-[14px]">{{ item.name }}</span>
<span class="p-[15px] w-[50%] text-[14px] text-[#666]">{{ formData[item.key] && formData[item.key].content ? formData[item.key].content : '--' }}</span>
<span class="p-[15px] w-[15%] text-[14px]">{{ item.addon_name }}</span>
<span class="p-[15px] w-[35%] text-[14px] text-[#666]">{{ rulesData[item.key] && rulesData[item.key].content ? rulesData[item.key].content : '--' }}</span>
<span class="p-[15px] w-[15%] text-[14px] text-[#666]">
<el-tag type="success" v-if="formData[item.key] && formData[item.key].is_use">已启用</el-tag>
<el-tag type="success" v-if="rulesData[item.key] && rulesData[item.key].is_use">已启用</el-tag>
<el-tag type="danger" v-else>未启用</el-tag>
</span>
<span class="p-[15px] w-[10%] text-[14px] text-[#666] text-[var(--el-color-primary)] cursor-pointer" @click="examineFn(key)">配置</span>
</div>
</div>
<el-dialog v-model="ruleDialog" :title="'规则配置'" width="600px" :close-on-press-escape="false" :destroy-on-close="true" :close-on-click-modal="false">
<el-dialog v-model="ruleDialog" :title="'规则配置'" width="600px" :destroy-on-close="true" :close-on-click-modal="false">
<div v-for="(item, key) in rules" :key="key" class="pl-[60px]">
<component :is="item.component" v-model="formData[item.key]" ref="ruleRefs" v-if="item.component && currRule == key"/>
</div>
@ -48,11 +50,13 @@ import { useRoute } from 'vue-router'
import { t } from '@/lang'
import { getGrowthRuleConfig, setGrowthRuleConfig, getGrowthRuleDict } from '@/app/api/member'
import Test from '@/utils/test'
import { cloneDeep } from 'lodash-es'
const route = useRoute()
const pageName = route.meta.title
const rules = ref({})
const formData = ref({})
const rulesData = ref<any>({}) //
const formData = ref<any>({})
const ruleRefs = ref(null)
const loading = ref(true)
@ -66,7 +70,7 @@ getGrowthRuleDict().then(({ data }) => {
const ruleConfigFn = () => {
getGrowthRuleConfig().then(({ data }) => {
!Test.empty(data) && (formData.value = data)
!Test.empty(data) && (rulesData.value = data)
loading.value = false
}).catch(() => {
loading.value = false
@ -94,6 +98,7 @@ const ruleDialog = ref(false)
//
const currRule = ref('')
const examineFn = (key:string) => {
formData.value = cloneDeep(rulesData.value)
ruleDialog.value = true
currRule.value = key
}

View File

@ -66,7 +66,7 @@
<h3 class="panel-title !text-sm">{{ t('themeColor') }}</h3>
<div class="">
<el-color-picker v-model="themeColor[currAddon]" size="large" />
<div class="form-tip text-[#999] mt-2">设置的色调会在前端站点列表体现</div>
<div class="form-tip text-[#999] mt-2">设置的色调会在前端站点列表体现[home/index]用于区分不同的应用</div>
</div>
</el-scrollbar>
</div>
@ -120,9 +120,11 @@ getInstalledAddonList().then(({ data }) => {
const getLayoutConfig = () => {
getLayout().then(({ data }) => {
layoutConfig.value = data
console.log('getLayoutConfig - layoutConfig', layoutConfig.value)
})
getThemecolor().then(({ data }) => {
themeColor.value = data
console.log('getLayoutConfig - themeColor', themeColor.value)
})
}
getLayoutConfig()

View File

@ -33,7 +33,7 @@
</div>
</div>
<el-dialog v-model="ruleDialog" :title="'规则配置'" width="600px" :close-on-press-escape="false" :destroy-on-close="true" :close-on-click-modal="false">
<el-dialog v-model="ruleDialog" :title="'规则配置'" width="600px" :destroy-on-close="true" :close-on-click-modal="false">
<div v-for="(item, key) in rules.grant" :key="key" class="pl-[60px]">
<component :is="item.component" v-model="formData.grant[item.key]" ref="ruleRefs" v-if="item.component && currRule == key"/>
</div>

View File

@ -0,0 +1,149 @@
<template>
<div class="main-container">
<el-card class="box-card mt-[15px] !border-none" shadow="never" v-for="(item, key) in screne" :key="key">
<div class="flex items-center mb-[20px]">
<h3 class="text-[14px] mr-[20px]">{{ item.name }}</h3>
<div class="flex items-center">
<span class="text-[14px] mr-[10px]">{{ t('transferSceneId') }}</span>
<div class="flex items-center">
<el-input v-model.trim="item.scene_id" maxlength="5" class="!w-[60px]" :disabled="item.disabled" @blur="handleInput($event,key,item)" :ref="(el: any) =>{ if(el) inputRefs[key] = el }" v-show="!item.disabled"/>
<div v-show="item.disabled">{{item.scene_id ? item.scene_id : '--'}}</div>
<div @click="handleDisabled(item, key)" class="w-[40xp] flex items-center ml-[8px]"><el-icon size="20" color="var(--el-color-primary)"><Edit /></el-icon></div>
</div>
</div>
</div>
<div>
<div class="flex items-center justify-between p-[10px] table-item-border bg">
<span class="text-base w-[230px]">{{ t('transferType') }}</span>
<span class="text-base w-[230px]">{{ t('recvPerception') }}</span>
<span class="text-base w-[230px]">{{ t('reportInfos') }}</span>
<span class="text-base w-[80px] text-center">{{ t('operation') }}</span>
</div>
<div v-if="Object.values(item.trade_scene_data).length">
<div class="flex items-center justify-between p-[10px] table-item-border" v-for="(subItem, subKey) in item.trade_scene_data" :key="subKey">
<div class="flex w-[230px] flex-shrink-0 text-base">{{ subItem.name }}</div>
<div class="flex w-[230px] flex-shrink-0 text-base">{{ subItem.perception }}</div>
<div class="w-[230px] flex-shrink-0 text-base">
<div v-for="(childItem,childKey) in subItem.infos" :key="childKey">{{ childKey }}{{ childItem }}</div>
</div>
<div class="flex items-center justify-center w-[80px] select-none">
<button class="text-base text-primary" @click="configFn(item,subItem,subKey)">{{ t('deploy') }}</button>
</div>
</div>
</div>
<div v-else class="min-h-[80px] flex items-center justify-center text-base">
{{ t('noData') }}
</div>
</div>
</el-card>
<el-dialog v-model="showDialog" :title="curData.name" width="550px" :destroy-on-close="true">
<el-form :model="formData" label-width="110px" ref="formRef" class="page-form">
<el-form-item :label="t('recvPerception')" prop="perception" :rules="[{ required: true, message: t('recvPerceptionTips'), trigger: 'blur' }]">
<el-select v-model="formData.perception" :placeholder="t('recvPerceptionTips')" clearable class="!w-[300px]">
<el-option v-for="(item,index) in curData.user_recv_perception" :key="index" :label="item" :value="item" />
</el-select>
</el-form-item>
<template v-for="(item, index) in curData.transfer_scene_report_infos" :key="index">
<el-form-item :label="item" :prop="`infos[${item}]`" :rules="[{ required: true, message: `请输入${item}`, trigger: 'blur' }]">
<el-input v-model.trim="formData.infos[item]" maxlength="40" class="!w-[300px]"/>
</el-form-item>
</template>
</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>
</div>
</template>
<script lang="ts" setup>
import { nextTick, ref } from 'vue'
import { t } from '@/lang'
import { getTransferScene, setSceneId, setTradeScene } from '@/app/api/pay'
import { cloneDeep } from 'lodash-es'
import { FormInstance } from 'element-plus'
const screne = ref<any>({})
const loading = ref(false)
const getTransferSceneFn = () => {
getTransferScene().then(res => {
screne.value = res.data
for (const key in screne.value) {
screne.value[key].disabled = true
}
})
}
getTransferSceneFn()
//
const handleInput = (e: any, key: any, data: any) => {
if (e.target.value) {
setSceneId({
scene: key,
scene_id: e.target.value
}).then(() => {
data.disabled = true
getTransferSceneFn()
})
} else {
data.disabled = true
}
}
const inputRefs = ref<any>({})
const handleDisabled = (data: any, key: any) => {
data.disabled = false
nextTick(() => {
inputRefs.value[key].focus()
})
}
const showDialog = ref(false)
const curData = ref<any>({})
const formData = ref({
type: '',
scene: '',
perception: '',
infos: {}
})
const configFn = (data: any, subData: any, type: any) => {
curData.value = cloneDeep(data)
formData.value.type = type
formData.value.scene = subData.scene
formData.value.perception = subData.perception
formData.value.infos = cloneDeep(subData.infos)
showDialog.value = true
}
const formRef = ref<FormInstance>()
/**
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
setTradeScene(formData.value).then(() => {
loading.value = false
showDialog.value = false
getTransferSceneFn()
}).catch(() => {
loading.value = false
})
}
})
}
const cancel = () => {
showDialog.value = false
}
</script>
<style lang="scss" scoped>
.table-item-border {
@apply border-b border-[var(--el-border-color)];
}
</style>

View File

@ -42,11 +42,11 @@
</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-input v-model.trim="formData.password" autocomplete="off" 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-input v-model.trim="formData.confirm_password" autocomplete="off" :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>

View File

@ -73,8 +73,11 @@ import { t } from '@/lang'
import { FormInstance } from 'element-plus'
import { getSiteGroupAll } from '@/app/api/site'
import { addUser, getUserInfo, editUser } from '@/app/api/user'
import useUserStore from '@/stores/modules/user'
import Test from '@/utils/test'
const userStore = useUserStore()
const showDialog = ref(false)
const loading = ref(true)
const formData = ref({
@ -95,13 +98,13 @@ const formRules = computed(() => {
{ required: true, message: t('usernamePlaceholder'), trigger: 'blur' }
],
password: [
{ required: formData.value.uid == 0, message: t('passwordPlaceholder'), trigger: 'blur' }
{ required: userStore.userInfo && userStore.userInfo.is_super_admin == true, message: t('passwordPlaceholder'), trigger: 'blur' }
],
real_name: [
{ required: true, message: t('userRealNamePlaceholder'), trigger: 'blur' }
],
confirm_password: [
{ required: formData.value.uid == 0, message: t('confirmPasswordPlaceholder'), trigger: 'blur' },
{ required: userStore.userInfo && userStore.userInfo.is_super_admin == true, message: t('confirmPasswordPlaceholder'), trigger: 'blur' },
{
validator: (rule: any, value: string, callback: any) => {
if (value != formData.value.password) callback(new Error(t('confirmPasswordError')))

View File

@ -19,7 +19,7 @@
<el-checkbox-group v-model="formData.app" class="flex flex-wrap w-full" v-else>
<template #default>
<div class="flex w-[300px]" v-for="(item, index) in appList" :key="index">
<el-checkbox :label="item.key" name="" class="w-full !h-auto border border-[var(--el-color-info-light-7)] border-solid p-[10px] !mr-[10px] !mb-[10px] rounded-md">
<el-checkbox :label="item.key" class="w-full !h-auto border border-[var(--el-color-info-light-7)] border-solid p-[10px] !mr-[10px] !mb-[10px] rounded-md">
<template #default>
<div class="w-full">
<div class="flex">
@ -28,7 +28,9 @@
<el-image v-else class="w-full h-full">
<template #error>
<div class="image-error">
<el-icon><icon-picture /></el-icon>
<el-icon>
<icon-picture />
</el-icon>
</div>
</template>
</el-image>
@ -51,7 +53,7 @@
<el-checkbox-group v-model="formData.addon" class="flex flex-wrap w-full" v-else>
<template #default>
<div class="flex w-[300px]" v-for="(item, index) in addonList" :key="index">
<el-checkbox :label="item.key" name="" class="w-full !h-auto border border-[var(--el-color-info-light-7)] border-solid p-[10px] !mr-[10px] !mb-[10px] rounded-md">
<el-checkbox :label="item.key" :disabled="item.disabled" @click="prompt(item)" class="w-full !h-auto border border-[var(--el-color-info-light-7)] border-solid p-[10px] !mr-[10px] !mb-[10px] rounded-md">
<template #default>
<div class="w-full">
<div class="flex">
@ -60,7 +62,9 @@
<el-image v-else class="w-full h-full">
<template #error>
<div class="image-error">
<el-icon><icon-picture /></el-icon>
<el-icon>
<icon-picture />
</el-icon>
</div>
</template>
</el-image>
@ -90,14 +94,14 @@
</template>
<script lang="ts" setup async>
import { ref, computed } from 'vue'
import { ref, computed, watch } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { ElMessage, FormInstance } from 'element-plus'
import { ArrowLeft } from '@element-plus/icons-vue'
import { menuRefresh } from '@/app/api/sys'
import { addSiteGroup, editSiteGroup, getSiteGroupInfo } from '@/app/api/site'
import { getInstalledAddonList } from '@/app/api/addon'
import { img, deepClone } from '@/utils/common'
import { deepClone } from '@/utils/common'
import { useRouter, useRoute } from 'vue-router'
const loading = ref(true)
@ -119,8 +123,16 @@ const formData: Record<string, any> = ref({
addon: []
})
let installAddon = []
const getInstalledAddonListFn = async () => {
watch(() => formData.value.app, (val) => {
checkAddon()
}, { deep: true })
watch(() => formData.value.addon, (val) => {
checkAddon()
}, { deep: true })
const installAddon = []
const getInstalledAddonListFn = async() => {
await getInstalledAddonList().then(({ data }) => {
const apps: any[] = []
const addons: any[] = []
@ -128,18 +140,20 @@ const getInstalledAddonListFn = async () => {
Object.keys(data).forEach(key => {
installAddon.push(key)
const item = data[key]
item.disabled = false
item.type == 'addon' ? addons.push(item) : apps.push(item)
})
appList.value = apps
addonList.value = addons
checkAddon()
}).catch()
}
getInstalledAddonListFn()
if (route.query.id) {
getSiteGroupInfo(route.query.id).then((res) => {
let data = deepClone(res.data)
const data = deepClone(res.data)
formData.value = data
loading.value = false
}).catch()
@ -169,11 +183,11 @@ const formRules = computed(() => {
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
const confirm = async(formEl: FormInstance | undefined) => {
if (saveLoading.value || !formEl) return
const save = formData.value.group_id ? editSiteGroup : addSiteGroup
await formEl.validate(async (valid) => {
await formEl.validate(async(valid) => {
if (valid) {
saveLoading.value = true
save(formData.value).then(res => {
@ -189,18 +203,49 @@ const confirm = async (formEl: FormInstance | undefined) => {
}
const menuRefreshFn = () => {
menuRefresh({}).then(res => {
}).catch(() => {})
}).catch(() => {
})
}
//
const checkAddon = () => {
addonList.value.forEach((addon: any) => {
//
if (addon.support_app == '' || formData.value.app.indexOf(addon.support_app) != -1) {
addon.disabled = false
} else {
//
addon.disabled = true
if (formData.value.addon.indexOf(addon.key) != -1) {
formData.value.addon.splice(formData.value.addon.indexOf(addon.key), 1)
}
}
})
}
const prompt = (item: any) => {
if (item.disabled && item.support_app) {
const currApp: any = appList.value.filter((app: any) => {
return app.key == item.support_app
})
if (currApp.length) {
ElMessage({
message: `请先选择 ${ currApp[0].title } 应用`,
type: 'warning'
})
}
}
}
</script>
<style lang="scss" scoped>
.image-error {
background: var(--el-border-color-extra-light);
width: 100%;
height: 100%;
}
.image-error {
background: var(--el-border-color-extra-light);
width: 100%;
height: 100%;
}
:deep(.el-checkbox__label) {
width: 100%;
}
:deep(.el-checkbox__label) {
width: 100%;
}
</style>

View File

@ -77,7 +77,7 @@
<div class="flex items-center">
<img class="w-[50px] h-[50px] mr-[10px]" v-if="row.logo" :src="img(row.logo)" alt="">
<img class="w-[50px] h-[50px] mr-[10px]" v-else src="@/app/assets/images/site_logo.png" alt="">
<div class="flex flex flex-col">
<div class="flex flex-col">
<span>{{ row.site_name || '' }}</span>
</div>
</div>
@ -87,7 +87,7 @@
<el-table-column :label="t('manager')" width="150" align="left">
<template #default="{ row }">
<div class="flex items-center">
<div class="flex flex flex-col">
<div class="flex flex-col">
<span>{{ row.admin.username || '' }}</span>
</div>
</div>

View File

@ -0,0 +1,56 @@
<template>
<div class="main-container h-[500px] w-full p-5 bg-white" v-loading="loading">
<div class="flex flex-wrap px-2 plug-list pb-10">
<div class="flex items-center bg-[#F7F8FA] p-3 w-[295px] relative plug-item mr-4 mb-4 cursor-pointer">
<div class="flex flex-col ml-2">
<span class="text-sm truncate w-[190px]">{{t('dataCache')}}</span>
<span class="text-xs text-gray-400 mt-1 truncate w-[190px]" :title="t('dataCacheDesc')">{{t('dataCacheDesc')}}</span>
</div>
<span class="plug-item-operate" @click="schemaCache()">{{t('refresh')}}</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { t } from '@/lang'
import { clearCache } from '@/app/api/sys'
import { ElMessageBox } from 'element-plus'
const loading = ref<Boolean>(false)
//
const schemaCache = () => {
ElMessageBox.confirm(t('clearCacheTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
loading.value = true
clearCache({}).then(res => {
loading.value = false
}).catch(() => {
loading.value = false
})
})
}
</script>
<style lang="scss" scoped>
.demo-tabs > .el-tabs__content {
padding: 32px;
color: #6b778c;
font-size: 32px;
font-weight: 600;
}
.plug-item{
.plug-item-operate{
@apply text-xs absolute right-3 cursor-pointer;
color: var(--el-color-primary);
}
}
</style>

View File

@ -16,28 +16,43 @@
</el-input>
</slot>
</div>
<el-dialog v-model="showDialog" :title="t('selectLinkTips')" width="40%" :close-on-press-escape="false" :destroy-on-close="true" :close-on-click-modal="false" @close="cancel">
<el-dialog v-model="showDialog" :title="t('selectLinkTips')" width="850px" :destroy-on-close="true" :close-on-click-modal="false" @close="cancel">
<div class="flex items-start">
<el-scrollbar class="w-[140px] border-r h-[350px]">
<div v-for="(item, index) in link" :key="index"
class="h-[40px] leading-[40px] cursor-pointer hover:bg-primary-light-9 px-[10px] hover:text-primary"
:class="[item.name == parentLinkName ? 'bg-primary-light-9 text-primary' : '']"
@click="changeParentLink(item)">
{{ item.title }}
</div>
<el-scrollbar class="w-[140px] border-r !h-[550px] link-wrap">
<template v-for="(item, index) in link" :key="index">
<template v-if="item.type == 'folder'">
<div class="flex h-[40px] leading-[40px] cursor-pointer px-[10px] items-center select-none mr-[10px]"
@click="item.foldSwitch = !item.foldSwitch">
<span class="flex-1">{{ item.title }}</span>
<el-icon>
<ArrowDown v-if="item.foldSwitch" />
<ArrowUp v-else />
</el-icon>
</div>
<div class="child-list-wrap" v-show="item.foldSwitch">
<div v-for="(childItem, childIndex) in item.child_list" :key="childIndex" class="h-[40px] leading-[40px] cursor-pointer hover:bg-primary-light-9 hover:text-primary select-none truncate pl-[25px] mr-[20px]" :class="[ childItem.name == parentLinkName ? 'bg-primary-light-9 text-primary' : '' ]" @click="changeParentLink(childItem)">{{ childItem.title }}</div>
</div>
</template>
<div v-else
class="h-[40px] leading-[40px] cursor-pointer px-[10px] items-center select-none hover:bg-primary-light-9 hover:text-primary mr-[20px]"
:class="[ item.name == parentLinkName ? 'bg-primary-light-9 text-primary' : '' ]"
@click="changeParentLink(item)">
<span>{{ item.title }}</span>
</div>
</template>
</el-scrollbar>
<el-scrollbar class="pl-4 h-[350px] flex-1">
<el-scrollbar class="pl-4 !h-[550px] flex-1">
<el-form label-width="100px" class="px-[10px]">
<template v-if="parentLinkName == 'DIY_LINK'">
<component v-if="dynamicComponentName" :is="dynamicComponentName" v-bind="selectLink" ref="dynamicComponentRefs" />
<template v-else-if="parentLinkName == 'DIY_LINK'">
<div class="mb-[16px]">
<el-form-item :label="t('diyLinkName')">
<el-input v-model="selectLink.title" :placeholder="t('diyLinkNamePlaceholder')" />
<el-input v-model="selectLink.title" :placeholder="t('diyLinkNamePlaceholder')" class="!w-[300px]" />
</el-form-item>
</div>
<div class="mb-[16px]">
<el-form-item :label="t('diyLinkUrl')">
<el-input v-model="selectLink.url" :placeholder="t('diyLinkUrlPlaceholder')" />
<el-input v-model="selectLink.url" :placeholder="t('diyLinkUrlPlaceholder')" class="!w-[300px]" />
</el-form-item>
</div>
<el-form-item label=" ">
@ -47,15 +62,15 @@
<div class="text-sm text-gray-400 select-text">跳转外部链接httphttps开头https://baidu.com</div>
</el-form-item>
</template>
<template v-if="parentLinkName == 'DIY_JUMP_OTHER_APPLET'">
<template v-else-if="parentLinkName == 'DIY_JUMP_OTHER_APPLET'">
<div class="mb-[16px]">
<el-form-item :label="t('diyAppletId')">
<el-input v-model="selectLink.appid" :placeholder="t('diyAppletIdPlaceholder')" clearable maxlength="50" />
<el-input v-model="selectLink.appid" :placeholder="t('diyAppletIdPlaceholder')" clearable maxlength="50" class="!w-[300px]" />
</el-form-item>
</div>
<div class="mb-[16px]">
<el-form-item :label="t('diyAppletPage')">
<el-input v-model="selectLink.page" :placeholder="t('diyAppletPagePlaceholder')" clearable maxlength="100" />
<el-input v-model="selectLink.page" :placeholder="t('diyAppletPagePlaceholder')" clearable maxlength="100" class="!w-[300px]" />
</el-form-item>
</div>
<el-form-item label=" ">
@ -65,10 +80,10 @@
<div class="text-sm text-gray-400 select-text">小程序路径格式如app/pages/index/index</div>
</el-form-item>
</template>
<template v-if="parentLinkName == 'DIY_MAKE_PHONE_CALL'">
<template v-else-if="parentLinkName == 'DIY_MAKE_PHONE_CALL'">
<div class="mb-[16px]">
<el-form-item :label="t('diyMakePhone')">
<el-input v-model="selectLink.mobile" :placeholder="t('diyMakePhonePlaceholder')" clearable maxlength="50" />
<el-input v-model="selectLink.mobile" :placeholder="t('diyMakePhonePlaceholder')" clearable maxlength="30" class="!w-[300px]" />
</el-form-item>
</div>
<el-form-item label=" ">
@ -77,9 +92,9 @@
</template>
<div v-else class="flex flex-wrap">
<div v-for="(item, index) in childList" :key="index"
class="border border-br rounded-[3px] mr-[10px] mb-[10px] px-4 h-[32px] leading-[32px] cursor-pointer hover:bg-primary-light-9 px-[10px] hover:text-primary"
class="border border-br rounded-[3px] mr-[10px] mb-[10px] px-4 h-[32px] leading-[32px] cursor-pointer hover:bg-primary-light-9 px-[10px] hover:text-primary"
:class="{ 'border-primary text-primary': (parentLinkName != 'DIY_PAGE' && item.name == selectLink.name) || (parentLinkName == 'DIY_PAGE' && item.url == selectLink.url) }"
@click="changeChildLink(item)">{{ item.title }}
@click="changeChildLink(item)">{{ item.title }}
</div>
</div>
</el-form>
@ -98,7 +113,7 @@
<script lang="ts" setup>
import { t } from '@/lang'
import { ref, computed } from 'vue'
import { ref, computed, defineAsyncComponent } from 'vue'
import { cloneDeep } from 'lodash-es'
import { getLink } from '@/app/api/diy'
import { ElMessage } from 'element-plus'
@ -106,21 +121,22 @@ import { ElMessage } from 'element-plus'
const prop = defineProps({
modelValue: {
type: Object,
default: () => { }
default: () => {
}
},
ignore:{
type:Array,
default:[]
ignore: {
type: Array,
default: []
}
})
const emit = defineEmits(['update:modelValue', 'confirm','success'])
const emit = defineEmits(['update:modelValue', 'confirm', 'success'])
const value: any = computed({
get () {
get() {
return prop.modelValue
},
set (value) {
set(value) {
emit('update:modelValue', value)
}
})
@ -135,17 +151,37 @@ const parentLinkName = ref('')
const childList: any = ref([])
const dynamicComponentName: any = ref('') //
const dynamicComponentRefs: any = ref(null) // ref
const selectLink: any = ref([])
const modules: any = import.meta.glob('@/**/*.vue')
const show = () => {
getLinkFn(() => {
//
if (value.value.name != '') {
selectLink.value = cloneDeep(value.value)
parentLinkName.value = selectLink.value.parent
for (let key in link.value) {
if (link.value[key].name == parentLinkName.value) {
changeParentLink(link.value[key])
for (const key in link.value) {
if (link.value[key].type == 'folder') {
//
if (link.value[key].name == parentLinkName.value) {
changeParentLink(link.value[key].child_list[0])
} else {
for (let i = 0; i < link.value[key].child_list.length; i++) {
if (link.value[key].child_list[i].name == parentLinkName.value) {
changeParentLink(link.value[key].child_list[i])
break
}
}
}
} else {
if (link.value[key].name == parentLinkName.value) {
changeParentLink(link.value[key])
}
}
}
}
@ -155,7 +191,7 @@ const show = () => {
})
}
const getLinkFn = (callback:any=null)=> {
const getLinkFn = (callback: any = null) => {
getLink({}).then((res: any) => {
link.value = res.data
if (prop.ignore && prop.ignore.length) {
@ -169,12 +205,32 @@ const getLinkFn = (callback:any=null)=> {
}
}
childList.value = Object.values(link.value)[0].child_list
//
for (const field in link.value) {
if (link.value[field].type == 'folder') {
link.value[field].foldSwitch = true
}
}
const firstLink: any = Object.values(link.value)[0]
let parentName: any = ''
if (firstLink.type == 'folder') {
childList.value = firstLink.child_list[0].child_list
parentName = firstLink.child_list[0].name
if (!firstLink.child_list[0].component) {
dynamicComponentName.value = ''
}
} else {
childList.value = firstLink.child_list
parentName = firstLink.parent_name
dynamicComponentName.value = ''
}
if (value.value.name != '') {
selectLink.value = cloneDeep(value.value)
} else {
selectLink.value = {
parent: Object.values(link.value)[0].name
parent: parentName
}
}
parentLinkName.value = selectLink.value.parent
@ -185,8 +241,14 @@ const getLinkFn = (callback:any=null)=> {
//
const changeParentLink = (item: any) => {
childList.value = item.child_list
parentLinkName.value = item.name
childList.value = item.child_list
if (item.component) {
dynamicComponentName.value = item.component
dynamicComponentName.value = defineAsyncComponent(modules[dynamicComponentName.value])
} else {
dynamicComponentName.value = ''
}
}
//
@ -205,14 +267,27 @@ const clear = () => {
}
const save = () => {
if (parentLinkName.value === 'DIY_LINK') {
const fields = ['name', 'parent', 'title', 'url', 'appid', 'mobile', 'action', 'page']
if (dynamicComponentName.value && dynamicComponentRefs.value) {
//
const data = dynamicComponentRefs.value.getData()
if (!data) return
//
for (const key in selectLink.value) {
if (fields.indexOf(key) == -1) {
delete selectLink.value[key]
}
}
Object.assign(selectLink.value, data)
} else if (parentLinkName.value === 'DIY_LINK') {
//
if (!selectLink.value.title) {
ElMessage({
message: t('diyLinkNameNotEmpty'),
type: 'warning'
});
})
return
}
@ -220,22 +295,20 @@ const save = () => {
ElMessage({
message: t('diyLinkUrlNotEmpty'),
type: 'warning'
});
})
return
}
selectLink.value.parent = parentLinkName.value
selectLink.value.name = parentLinkName.value
selectLink.value.action = '';
selectLink.value.action = ''
delete selectLink.value.appid;
delete selectLink.value.mobile;
delete selectLink.value.appid
delete selectLink.value.mobile
} else if (parentLinkName.value == 'DIY_PAGE') {
//
selectLink.value.name = parentLinkName.value
selectLink.value.parent = parentLinkName.value
selectLink.value.action = 'decorate';
delete selectLink.value.appid;
@ -261,13 +334,11 @@ const save = () => {
}
selectLink.value.name = parentLinkName.value
selectLink.value.parent = parentLinkName.value
selectLink.value.title = '微信小程序-' + selectLink.value.appid
selectLink.value.action = '';
delete selectLink.value.url;
delete selectLink.value.mobile;
selectLink.value.action = ''
delete selectLink.value.url
delete selectLink.value.mobile
} else if (parentLinkName.value == 'DIY_MAKE_PHONE_CALL') {
//
@ -275,17 +346,27 @@ const save = () => {
ElMessage({
message: t('diyMakePhoneNotEmpty'),
type: 'warning'
});
})
return
}
selectLink.value.name = parentLinkName.value
selectLink.value.parent = parentLinkName.value
selectLink.value.title = '拨打电话:' + selectLink.value.mobile
selectLink.value.action = '';
selectLink.value.action = ''
delete selectLink.value.url;
delete selectLink.value.appid;
delete selectLink.value.url
delete selectLink.value.appid
}
selectLink.value.parent = parentLinkName.value
//
if (dynamicComponentName.value == '') {
for (const key in selectLink.value) {
if (fields.indexOf(key) == -1) {
delete selectLink.value[key]
}
}
}
value.value = cloneDeep(selectLink.value)
@ -306,7 +387,10 @@ defineExpose({
</script>
<style lang="scss">
.link-input .el-input__inner{
cursor: pointer;
}
.link-wrap{
}
.link-input .el-input__inner {
cursor: pointer;
}
</style>

View File

@ -1,11 +1,13 @@
<template>
<upload-attachment type="image" ref="imageRef" limit="" @confirm="imageSelect" />
<upload-attachment type="video" ref="videoRef" @confirm="videoSelect" />
<vue-ueditor-wrap v-model="content" :config="editorConfig" :editorDependencies="['ueditor.config.js','ueditor.all.js']" ref="editorRef"></vue-ueditor-wrap>
<vue-ueditor-wrap v-model="content" :config="editorConfig"
:editorDependencies="['ueditor.config.js', 'ueditor.all.js']" ref="editorRef"
@ready="handleEditorReady"></vue-ueditor-wrap>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { computed, nextTick, onMounted, ref } from 'vue'
import { getToken, img } from '@/utils/common'
import { VueUeditorWrap } from 'vue-ueditor-wrap'
import storage from '@/utils/storage'
@ -33,10 +35,10 @@ const imageRef: Record<string, any> | null = ref(null)
const videoRef: Record<string, any> | null = ref(null)
const content = computed({
get () {
get() {
return prop.modelValue
},
set (value) {
set(value) {
emit('update:modelValue', value)
}
})
@ -60,7 +62,8 @@ const editorConfig = ref({
initialFrameHeight: prop.height,
//
initialFrameWidth: '100%',
toolbarCallback: function(cmd, editor) {
wordCount: true,
toolbarCallback: function (cmd, editor) {
editorEl = editor
switch (cmd) {
case 'insertimage':
@ -72,6 +75,27 @@ const editorConfig = ref({
}
}
})
//
const handleEditorReady = (editor) => {
//
// editorInstance.addListener('contentChange', () => {
// const rawContent = editorInstance.getContent()
// //
// charCount.value = rawContent.replace(/\s/g, '').length
// // DOM
// updateStatsDisplay(charCount.value)
// })
console.log('扩展原型链', editor)
//
const originalCount = editor.getContentLength; //
//
editor.getContentLength = function () {
const rawContent = editor.getContent();
return rawContent.replace(/[\s\u3000]+/g, '').length;
};
}
const imageSelect = (data: Record<string, any>) => {
data.forEach((item: any) => {

View File

@ -6,7 +6,7 @@
<div v-else class="cursor-pointer">{{ t('addHotArea') }}</div>
</slot>
</div>
<el-dialog v-model="showDialog" :title="t('hotAreaSet')" width="810px" :close-on-press-escape="false" :destroy-on-close="true" :close-on-click-modal="false">
<el-dialog v-model="showDialog" :title="t('hotAreaSet')" width="810px" :destroy-on-close="true" :close-on-click-modal="false">
<div class="flex">

View File

@ -202,6 +202,9 @@ import storage from '@/utils/storage'
const attachmentCategoryName = ref('')
const operate = ref(false)
const repeat = ref(false)
const prop = defineProps({
//
limit: {
@ -261,7 +264,6 @@ const attachmentParam = reactive({
* 查询分组
*/
const getAttachmentCategoryList = debounce(() => {
const getFn = prop.type == 'icon' ? getIconCategoryList : attachmentCategoryList
getFn({
type: prop.type,
@ -304,6 +306,7 @@ const getAttachmentList = debounce((page: number = 1) => {
attachment.loading = false
})
})
getAttachmentList()
watch(() => attachmentParam.cate_id, () => {
@ -314,14 +317,18 @@ watch(() => attachmentParam.cate_id, () => {
* 添加分组
*/
const addAttachmentCategory = (name: string) => {
if (repeat.value) return
repeat.value = true
addCategory({
type: prop.type,
name
}).then(res => {
repeat.value = false
attachmentCategoryName.value = ''
getAttachmentCategoryList(1)
}).catch(() => {
repeat.value = false
})
}
@ -331,13 +338,16 @@ const addAttachmentCategory = (name: string) => {
* @param index
*/
const updateAttachmentCategory = (name: string, index: number) => {
if (repeat.value) return
repeat.value = true
updateCategory({
id: attachmentCategory.data[index].id,
name
}).then(res => {
repeat.value = false
attachmentCategory.data[index].name = name
}).catch(() => {
repeat.value = false
})
}
@ -345,6 +355,8 @@ const updateAttachmentCategory = (name: string, index: number) => {
* 删除分组
*/
const deleteAttachmentCategory = (index: number) => {
if (repeat.value) return
repeat.value = true
ElMessageBox.confirm(t('upload.deleteCategoryTips'), t('warning'),
{
confirmButtonText: t('confirm'),
@ -354,7 +366,9 @@ const deleteAttachmentCategory = (index: number) => {
).then(() => {
deleteCategory(attachmentCategory.data[index].id).then(() => {
attachmentCategory.data.splice(index, 1)
repeat.value = false
}).catch(() => {
repeat.value = false
})
})
}
@ -387,7 +401,7 @@ const upload = computed(() => {
clearTimeout(time.value)
time.value=null
},500)
}else{
clearTimeout(time.value)
time.value=null

View File

@ -16,6 +16,9 @@
"cancel": "取消",
"search": "搜索",
"reset": "重置",
"refresh": "刷新",
"refreshSuccess": "刷新成功",
"select": "选择",
"export": "导出列表",
"exportPlaceholder": "确定要导出数据吗?",
"exportTip": "批量导出数据",
@ -177,5 +180,12 @@
"write": "可写",
"cloudbuildSuccess": "编译完成",
"showDialogCloseTips": "编译任务尚未完成,关闭将取消编译,是否要继续关闭?"
}
},
"formSelectContentTitle": "表单名称",
"formSelectContentTitlePlaceholder": "请输入表单名称",
"formSelectContentTypeName": "表单类型",
"formSelectContentTypeNamePlaceholder": "请选择表单类型",
"formSelectContentTypeAll": "全部",
"formSelectContentTips": "请选择表单"
}

View File

@ -1,5 +1,5 @@
<template>
<div :class="['layout-aside ease-in duration-200 flex ', { 'bright': !dark }]">
<div :class="['layout-aside ease-in duration-200 flex border-t-[1px] border-solid border-[var(--el-color-info-light-8)] box-border', { 'bright': !dark }]">
<div class="flex flex-col border-0 border-r-[1px] border-solid border-[var(--el-color-info-light-8)] box-border">
<!-- <div class="w-full h-[64px] flex justify-center items-center w-[65px]flex-shrink-0">
<div class="w-[40px] h-[40px] rounded-[50%] overflow-hidden">

View File

@ -2,8 +2,8 @@
<el-container class="h-[64px] w-full layout-admin flex items-center justify-between px-[15px]" >
<!-- :class="['h-full px-[10px]',{'layout-header border-b border-color': !dark}]" -->
<div class="flex items-center">
<div class="w-[120px] flex justify-center items-center flex-shrink-0">
<div class="w-[120px] h-[40px] overflow-hidden">
<div class="max-w-[120px] flex justify-center items-center flex-shrink-0">
<div class="max-w-[120px] h-[40px] overflow-hidden">
<el-image style="width: 100%; height: 100%" :src="img(logoUrl)" fit="contain">
<template #error>
<div class="flex justify-center items-center w-full h-full"><img class="max-w-[120px]" src="@/app/assets/images/logo.default.png" alt="" object-fit="contain"></div>

View File

@ -2,8 +2,8 @@
<el-container class="h-[64px] layout-admin flex items-center justify-between px-[15px]" >
<!-- :class="['h-full px-[10px]',{'layout-header border-b border-color': !dark}]" -->
<div class="flex items-center">
<div class="w-[120px] flex justify-center items-center flex-shrink-0">
<div class="w-[120px] h-[40px] overflow-hidden">
<div class="max-w-[120px] flex justify-center items-center flex-shrink-0">
<div class="max-w-[120px] h-[40px] overflow-hidden">
<el-image class="w-full h-full" :src="img(website.icon)" fit="contain">
<template #error>
<div class="flex justify-center items-center w-full h-full"><img class="max-w-[120px]" src="@/app/assets/images/logo.default.png" alt="" object-fit="contain"></div>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff