This commit is contained in:
CQ 2025-11-21 14:46:35 +08:00
parent 54ae267bc5
commit 643eadfd14
124 changed files with 8341 additions and 14122 deletions

View File

@ -9,6 +9,7 @@ declare module '@vue/runtime-core' {
export interface GlobalComponents {
Attachment: typeof import('./src/components/upload-attachment/attachment.vue')['default']
DiyLink: typeof import('./src/components/diy-link/index.vue')['default']
DiyPage: typeof import('./src/components/diy-page/index.vue')['default']
Editor: typeof import('./src/components/editor/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAside: typeof import('element-plus/es')['ElAside']
@ -89,6 +90,7 @@ declare module '@vue/runtime-core' {
SelectIcon: typeof import('./src/components/select-icon/index.vue')['default']
SpreadPopup: typeof import('./src/components/spread-popup/index.vue')['default']
UploadAttachment: typeof import('./src/components/upload-attachment/index.vue')['default']
UploadAudio: typeof import('./src/components/upload-audio/index.vue')['default']
UploadFile: typeof import('./src/components/upload-file/index.vue')['default']
UploadImage: typeof import('./src/components/upload-image/index.vue')['default']
UploadVideo: typeof import('./src/components/upload-video/index.vue')['default']

View File

@ -9,12 +9,18 @@
"version": "1.0.0",
"dependencies": {
"@element-plus/icons-vue": "2.0.10",
"@fullcalendar/core": "^6.1.19",
"@fullcalendar/daygrid": "^6.1.19",
"@fullcalendar/interaction": "^6.1.19",
"@fullcalendar/vue3": "^6.1.19",
"@heroicons/vue": "^2.2.0",
"@highlightjs/vue-plugin": "2.1.0",
"@types/lodash-es": "4.17.6",
"@vueuse/core": "9.12.0",
"axios": "1.4.0",
"crypto-js": "4.1.1",
"css-color-function": "1.3.3",
"date-fns": "^4.1.0",
"day": "^0.0.2",
"echarts": "5.4.1",
"element-plus": "^2.7.4",
@ -963,6 +969,47 @@
"resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.1.6.tgz",
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
},
"node_modules/@fullcalendar/core": {
"version": "6.1.19",
"resolved": "https://registry.npmmirror.com/@fullcalendar/core/-/core-6.1.19.tgz",
"integrity": "sha512-z0aVlO5e4Wah6p6mouM0UEqtRf1MZZPt4mwzEyU6kusaNL+dlWQgAasF2cK23hwT4cmxkEmr4inULXgpyeExdQ==",
"dependencies": {
"preact": "~10.12.1"
}
},
"node_modules/@fullcalendar/daygrid": {
"version": "6.1.19",
"resolved": "https://registry.npmmirror.com/@fullcalendar/daygrid/-/daygrid-6.1.19.tgz",
"integrity": "sha512-IAAfnMICnVWPjpT4zi87i3FEw0xxSza0avqY/HedKEz+l5MTBYvCDPOWDATpzXoLut3aACsjktIyw9thvIcRYQ==",
"peerDependencies": {
"@fullcalendar/core": "~6.1.19"
}
},
"node_modules/@fullcalendar/interaction": {
"version": "6.1.19",
"resolved": "https://registry.npmmirror.com/@fullcalendar/interaction/-/interaction-6.1.19.tgz",
"integrity": "sha512-GOciy79xe8JMVp+1evAU3ytdwN/7tv35t5i1vFkifiuWcQMLC/JnLg/RA2s4sYmQwoYhTw/p4GLcP0gO5B3X5w==",
"peerDependencies": {
"@fullcalendar/core": "~6.1.19"
}
},
"node_modules/@fullcalendar/vue3": {
"version": "6.1.19",
"resolved": "https://registry.npmmirror.com/@fullcalendar/vue3/-/vue3-6.1.19.tgz",
"integrity": "sha512-j5eUSxx0xIy3ADljo0f5B9PhjqXnCQ+7nUMPfsslc2eGVjp4F74YvY3dyd6OBbg13IvpsjowkjncGipYMQWmTA==",
"peerDependencies": {
"@fullcalendar/core": "~6.1.19",
"vue": "^3.0.11"
}
},
"node_modules/@heroicons/vue": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/@heroicons/vue/-/vue-2.2.0.tgz",
"integrity": "sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==",
"peerDependencies": {
"vue": ">= 3"
}
},
"node_modules/@highlightjs/vue-plugin": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/@highlightjs/vue-plugin/-/vue-plugin-2.1.0.tgz",
@ -2624,6 +2671,15 @@
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz",
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
},
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/day": {
"version": "0.0.2",
"resolved": "https://registry.npmmirror.com/day/-/day-0.0.2.tgz",
@ -5003,6 +5059,15 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"node_modules/preact": {
"version": "10.12.1",
"resolved": "https://registry.npmmirror.com/preact/-/preact-10.12.1.tgz",
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",

View File

@ -10,12 +10,18 @@
},
"dependencies": {
"@element-plus/icons-vue": "2.0.10",
"@fullcalendar/core": "^6.1.19",
"@fullcalendar/daygrid": "^6.1.19",
"@fullcalendar/interaction": "^6.1.19",
"@fullcalendar/vue3": "^6.1.19",
"@heroicons/vue": "^2.2.0",
"@highlightjs/vue-plugin": "2.1.0",
"@types/lodash-es": "4.17.6",
"@vueuse/core": "9.12.0",
"axios": "1.4.0",
"crypto-js": "4.1.1",
"css-color-function": "1.3.3",
"date-fns": "^4.1.0",
"day": "^0.0.2",
"echarts": "5.4.1",
"element-plus": "^2.7.4",
@ -56,4 +62,4 @@
"vite": "4.1.0",
"vue-tsc": "1.0.24"
}
}
}

View File

@ -147,7 +147,10 @@ div.edui-box {
overflow: visible;
z-index: 1 !important;
}
/* 全屏状态 */
.edui-default .edui-editor.edui-fullscreen {
z-index: 999 !important;
}
.edui-editor div {
width: auto;
height: auto;

View File

@ -96,18 +96,17 @@ export function getInstalledAddonList() {
* @returns
*/
export function getShowApp() {
return request.get('addon/list/showapp')
return request.get('addon/showCustomer')
}
/**
*
*
* @returns
*/
export function getShowMarketing() {
return request.get('showMarketing')
export function getShowSpecialMenu() {
return request.get('addon/special_menu')
}
export function getAddonInit() {
return request.get('addon/init')
}

68
admin/src/app/api/app.ts Normal file
View File

@ -0,0 +1,68 @@
import request from '@/utils/request'
/**
* app配置
* @returns
*/
export function getAppConfig() {
return request.get('channel/app/config')
}
/**
* app配置
* @param params
* @returns
*/
export function setAppConfig(params: Record<string, any>) {
return request.put('channel/app/config', params, { showSuccessMessage: true })
}
export function getVersionList(params: Record<string, any>) {
return request.get('channel/app/version', { params })
}
export function getVersionInfo(id: number) {
return request.get(`channel/app/version/${id}`)
}
export function getAppPlatform() {
return request.get(`channel/app/platfrom`)
}
/**
*
* @param params
* @returns
*/
export function addVersion(params: Record<string, any>) {
return request.post('channel/app/version', params, { showSuccessMessage: true })
}
/**
*
* @param params
*/
export function editVersion(params: Record<string, any>) {
return request.put(`channel/app/version/${ params.id }`, params, { showSuccessMessage: true })
}
/**
*
* @param siteId
*/
export function deleteVersion(params: Record<string, any>) {
return request.delete(`channel/app/version/${ params.id }`)
}
export function getBuildLog(key: string) {
return request.get(`channel/app/build/log/${ key }`)
}
export function releaseVersion(id: number) {
return request.put(`channel/app/version/${ id }/release`, {}, { showSuccessMessage: true })
}
export function generateSingCert(params: Record<string, any>) {
return request.post(`channel/app/generate_sing_cert`, params, { showSuccessMessage: true });
}

View File

@ -18,7 +18,7 @@ export function getMemberList(params: Record<string, any>) {
* @returns
*/
export function getMemberInfo(id: number) {
return request.get(`member/member/${id}`);
return request.get(`member/member/${ id }`);
}
/**
@ -61,7 +61,7 @@ export function getRegisterChannelType(params: Record<string, any>) {
* @param member_id
*/
export function deleteMember(member_id: number) {
return request.delete(`member/member/${member_id}`, { showSuccessMessage: true })
return request.delete(`member/member/${ member_id }`, { showSuccessMessage: true })
}
/***************************************************** 会员标签 ****************************************************/
@ -81,7 +81,7 @@ export function getMemberLabelList(params: Record<string, any>) {
* @returns
*/
export function getMemberLabelInfo(label_id: number) {
return request.get(`member/label/${label_id}`);
return request.get(`member/label/${ label_id }`);
}
/**
@ -98,7 +98,7 @@ export function addMemberLabel(params: Record<string, any>) {
* @param params
*/
export function updateMemberLabel(params: Record<string, any>) {
return request.put(`member/label/${params.label_id}`, params, { showSuccessMessage: true })
return request.put(`member/label/${ params.label_id }`, params, { showSuccessMessage: true })
}
/**
@ -107,7 +107,7 @@ export function updateMemberLabel(params: Record<string, any>) {
* @returns
*/
export function deleteMemberLabel(label_id: number) {
return request.delete(`member/label/${label_id}`, { showSuccessMessage: true })
return request.delete(`member/label/${ label_id }`, { showSuccessMessage: true })
}
/**
@ -122,7 +122,7 @@ export function getMemberLabelAll() {
* @param params
*/
export function editMemberDetail(params: Record<string, any>) {
return request.put(`member/member/modify/${params.member_id}/${params.field}`, params, { showSuccessMessage: true })
return request.put(`member/member/modify/${ params.member_id }/${ params.field }`, params, { showSuccessMessage: true })
}
/**
@ -143,7 +143,7 @@ export function memberBatchModify(params: Record<string, any>) {
* @param change_type
*/
export function getChangeTypeList(change_type: string) {
return request.get(`member/account/change_type/${change_type}`)
return request.get(`member/account/change_type/${ change_type }`)
}
/**
@ -321,7 +321,7 @@ export function getBalanceStatus() {
*
*/
export function getAccountType(params: Record<string, any>) {
return request.get(`member/account/change_type/${params.account_type}`)
return request.get(`member/account/change_type/${ params.account_type }`)
}
@ -357,7 +357,7 @@ export function getCashOutList(params: Record<string, any>) {
* @param id
*/
export function getCashOutDetail(id: number) {
return request.get(`member/cash_out/${id}`, {})
return request.get(`member/cash_out/${ id }`, {})
}
/**
@ -365,14 +365,18 @@ export function getCashOutDetail(id: number) {
* @param params
*/
export function memberAudit(params: Record<string, any>) {
return request.put(`member/cash_out/audit/${params.id}/${params.action}`, params, { showSuccessMessage: true })
return request.put(`member/cash_out/audit/${ params.id }/${ params.action }`, params, { showSuccessMessage: true })
}
/**
*
* @param params
*/
export function memberCancel(params: Record<string, any>) {
return request.put(`member/cash_out/cancel/${params.id}`, params, { showSuccessMessage: true,showErrorMessage: true })
return request.put(`member/cash_out/cancel/${ params.id }`, params, {
showSuccessMessage: true,
showErrorMessage: true
})
}
@ -381,7 +385,7 @@ export function memberCancel(params: Record<string, any>) {
* @param params
*/
export function memberTransfer(params: Record<string, any>) {
return request.put(`member/cash_out/transfer/${params.id}`, params, { showSuccessMessage: true })
return request.put(`member/cash_out/transfer/${ params.id }`, params, { showSuccessMessage: true })
}
/**
@ -389,14 +393,15 @@ export function memberTransfer(params: Record<string, any>) {
* @param params
*/
export function memberRemark(params: Record<string, any>) {
return request.put(`member/cash_out/remark/${params.id}`, params, { showSuccessMessage: true })
return request.put(`member/cash_out/remark/${ params.id }`, params, { showSuccessMessage: true })
}
/**
*
* @param id
*/
export function memberCheck(id: number) {
return request.put(`member/cash_out/check/${id}`, {}, { showSuccessMessage: true })
return request.put(`member/cash_out/check/${ id }`, {}, { showSuccessMessage: true })
}
/**
@ -404,7 +409,7 @@ export function memberCheck(id: number) {
* @param params
*/
export function editMemberStatus(params: Record<string, any>) {
return request.put(`member/setstatus/${params.status}`, params, { showSuccessMessage: true })
return request.put(`member/setstatus/${ params.status }`, params, { showSuccessMessage: true })
}
/**
@ -453,6 +458,7 @@ export function getGrowthRuleDict() {
export function getPointRuleDict() {
return request.get(`member/dict/point_rule`)
}
/***************************************************** 会员等级 ****************************************************/
/**
@ -470,7 +476,7 @@ export function getMemberLevelPageList(params: Record<string, any>) {
* @returns
*/
export function getMemberLevelInfo(level_id: number) {
return request.get(`member/level/${level_id}`);
return request.get(`member/level/${ level_id }`);
}
/**
@ -487,7 +493,7 @@ export function addMemberLevel(params: Record<string, any>) {
* @param params
*/
export function updateMemberLevel(params: Record<string, any>) {
return request.put(`member/level/${params.level_id}`, params, { showSuccessMessage: true })
return request.put(`member/level/${ params.level_id }`, params, { showSuccessMessage: true })
}
/**
@ -496,7 +502,7 @@ export function updateMemberLevel(params: Record<string, any>) {
* @returns
*/
export function deleteMemberLevel(level_id: number) {
return request.delete(`member/level/${level_id}`, { showSuccessMessage: true })
return request.delete(`member/level/${ level_id }`, { showSuccessMessage: true })
}
/**
@ -512,14 +518,14 @@ export function getMemberLevelAll() {
*
*/
export function getMemberBenefitsContent() {
return request.get(`member/benefits/content`);
return request.post(`member/benefits/content`);
}
/**
*
*/
export function getMemberGiftsContent(params: Record<string, any>) {
return request.get(`member/gifts/content`, { params });
return request.post(`member/gifts/content`, params);
}
/**
@ -555,7 +561,7 @@ export function getMemberAddress(params: Record<string, any>) {
}
/**
*
*
*/
export function addMemberAddress(params: Record<string, any>) {
return request.post(`member/address`, params);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

View File

@ -249,7 +249,7 @@ import Storage from '@/utils/storage'
import { useRouter } from 'vue-router'
const router = useRouter()
const terminalId = ref(Date.now());
const terminalId = ref(Date.now());
const showDialog = ref<boolean>(false)
const upgradeContent = ref<null | AnyObject>(null)
const isAllowUpgrade = ref(true) //
@ -299,7 +299,7 @@ const getUpgradeTaskFn = () => {
if (!upgradeContent.value) {
upgradeContent.value = data.upgrade_content
if (upgradeContent.value || !data.upgrade_content || !Array.isArray(data.upgrade_content.content)) {
if (!data.upgrade_content || !Array.isArray(data.upgrade_content.content)) {
return
}
@ -691,7 +691,7 @@ defineExpose({
white-space: pre-wrap;
}
::v-deep .number-of-steps {
:deep(.number-of-steps) {
.el-step__line {
margin: 0 25px;
background: #dddddd;

View File

@ -0,0 +1,13 @@
{
"accessFlow": "接入流程",
"versionManage": "版本管理",
"title": "APP端管理",
"appInlet": "App接入流程",
"uniappApp": "uni-app应用开通",
"appAttestation1": "点击进入Dcloud官网开发者后台,创建或选择应用并维护好应用平台信息",
"clickAccess": "点击接入",
"appSetting": "App端配置",
"settingInfo": "点击配置",
"releaseVersion": "发布版本",
"toCreate": "去创建"
}

View File

@ -0,0 +1,17 @@
{
"wechatAppInfo": "微信应用信息",
"wechatAppid": "微信移动应用AppID",
"wechatAppidTips": "用于app端 微信登录 微信支付 微信分享",
"wechatAppsecret": "微信移动应用AppSecret",
"appidPlaceholder": "请输入AppID",
"appSecretPlaceholder": "请输入AppSecret",
"appInfo": "应用信息",
"uniAppId": "uniapp应用id",
"uniAppIdTips": "uniapp应用id需在Dcloud开发者中心创建",
"toCreate": "前往Dcloud官网",
"appName": "应用名称",
"applicationId": "安卓应用包名",
"applicationIdTips": "安卓应用的包名是Android系统中用于唯一标识应用的字符串采用反向域名格式如com.example.myapp。每个应用在系统中拥有唯一的包名用于区分不同应用",
"androidAppKey": "安卓离线打包Key",
"androidAppKeyTips": "安卓离线打包Key在Dcloud开发者中心 - 应用管理 - 点击应用 - 各平台信息 创建以及查看离线AppKey"
}

View File

@ -0,0 +1,55 @@
{
"accessFlow": "接入流程",
"versionManage": "版本管理",
"versionCode":"版本号",
"versionCodePlaceholder":"请输入版本号",
"versionCodeTips": "应用版本号必须是整数取值范围1~2147483647;必须高于上一版本设置的值",
"versionName":"版本名称",
"versionNamePlaceholder":"请输入版本名称",
"versionDesc":"",
"versionDescPlaceholder":"请输入",
"icon":"应用图标",
"iconPlaceholder":"请输入应用图标",
"push":"消息推送图标",
"pushPlaceholder":"请输入消息推送图标",
"splash":"应用启动页图标",
"splashPlaceholder":"请输入应用启动页的图标",
"platform":"客户端",
"packagePath":"安装包路径",
"packagePathPlaceholder":"请输入安装包路径",
"status":"状态",
"statusPlaceholder":"请输入状态",
"addAppVersion":"添加版本",
"updateAppVersion":"编辑版本",
"startDate":"请选择开始时间",
"endDate":"请选择结束时间",
"isForcedUpgrade": "强制升级",
"versionDesc": "更新内容",
"isForcedUpgradeTitle": "是否强制升级",
"releaseTime": "发布时间",
"release": "发布",
"resourceFile": "上传资源文件",
"androidResourceFileTips": "只能上传apk文件",
"iosResourceFileTips": "只能上传wgt文件",
"index": "序号",
"next": "下一步",
"prev": "上一步",
"certType": "证书类型",
"certFile": "证书文件",
"certAlias": "证书别名",
"certKeyPassword": "证书密码",
"certStorePassword": "证书库密码",
"publicCertTips": "niucloud提供的公共测试证书证书的描述信息都是测试数据任何人都可以使用仅适合应用开发期间体验测试使用",
"privateCertTips": "Android平台打包发布apk应用需要使用数字证书.keystore文件进行签名用于表明开发者身份。",
"download": "下载",
"failReason": "失败原因",
"appVersionReleaseTips": "发布后无法再对该版本进行修改,确定要发布该版本吗?",
"appVersionDeleteTips": "确定要删除该版本吗?",
"upgradeType": "升级方式",
"seeBuildLog": "查看打包日志",
"buildLog": "打包日志",
"authTips": "上传代码需先绑定授权码如果已有授权请先进行绑定没有授权可到niucloud官网购买云服务之后再进行操作",
"toBind": "绑定授权",
"toNiucloud": "去niucloud官网",
"siteAuthTips": "上传代码需先绑定授权码,请联系平台管理员进行绑定"
}

View File

@ -29,5 +29,8 @@
"encodingAesKeyPlaceholder": "请输入EncodingAESKey",
"cleartextModeTips": "明文模式下,不使用消息体加解密功能,安全系数较低",
"compatibleModeTips": "兼容模式下,明文、密文将共存,方便开发者调试和维护",
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高"
}
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高",
"wechatBaseUri": "借权域名",
"wechatBaseUriPlaceholder": "",
"wechatBaseUriTips": "默认留空填写后将替换https://open.weixin.gg.com/获取授权!"
}

View File

@ -7,5 +7,6 @@
"validTimeTips": "过期后将重新获取定位信息",
"validTimePlaceholder": "请输入定位有效期",
"validTimeFormatTips": "格式输入错误",
"validTimeNotZeroTips": "定位有效期不能小于5分钟"
}
"validTimeNotZeroTips": "定位有效期不能小于5分钟",
"aMapKey": "高德地图KEY"
}

View File

@ -32,6 +32,10 @@
"contactsTelPlaceholder": "请输入联系电话",
"logoPlaceholder": "建议图片尺寸210*30像素图片格式jpg、png、jpeg。",
"iconPlaceholder": "建议图片尺寸100*100像素图片格式jpg、png、jpeg。",
"siteLoginLogo": "站点登录Logo",
"siteLoginLogoTips": "站点端登录Logo建议图片尺寸132*40像素图片格式jpg、png、jpeg。",
"siteLoginBgImg": "站点登录背景图",
"siteLoginBgImgTips": "站点端登录背景图建议图片尺寸1920*1280像素图片格式jpg、png、jpeg。",
"metaTitle": "Meta 标题",
"MetaPlaceholder": "请输入Meta 标题",
"metaDescription": "Meta 描述",

View File

@ -50,6 +50,7 @@ import { img } from '@/utils/common'
import useUserStore from '@/stores/modules/user'
import { useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
const addonIndexRoute = useUserStore().addonIndexRoute
const router = useRouter()
@ -65,10 +66,17 @@ getAppList()
const toLink = (item: any) => {
if (item.url) {
router.push(item.url)
// is_target=true
if (item.url.indexOf('is_target=true') != -1) {
const url = router.resolve(item.url)
window.open(url.href)
} else {
router.push(item.url)
}
} else {
addonIndexRoute[item.key] && router.push({ name: addonIndexRoute[item.key] })
}
storage.set({ key: 'activeAppKey', data: item.key })
}
</script>

View File

@ -1,51 +1,50 @@
<template>
<!--营销管理-->
<div class="main-container" v-loading="loading">
<el-card class="box-card !border-none" shadow="never">
<!--营销管理-->
<div class="main-container" v-loading="loading">
<el-card class="box-card !border-none" shadow="never">
<template v-if="Object.keys(marketingList).length">
<template v-if="Object.keys(marketingList).length">
<template v-for="(item, index) in marketingList" :key="index + 'b'">
<div class="flex justify-between items-center">
<span class="text-page-title">{{ item.title }}</span>
<template v-for="(item, index) in marketingList" :key="index + 'b'">
<div class="flex justify-between items-center">
<span class="text-page-title">{{ item.title }}</span>
</div>
<div class="flex flex-wrap plug-list pb-10 plug-large">
<div class="cursor-pointer mt-[20px] mr-4 bg-[#f7f7f7]" v-for="(childItem,childIndex) in item.list" :key="childIndex" @click="toLink(childItem)">
<div class="w-[264px] flex py-[20px] px-[17px] app-item relative">
<el-image class="w-[40px] h-[40px] mr-[10px] rounded-[6px] overflow-hidden" :src="img(childItem.icon)" fit="contain">
<template #error>
<div class="image-slot">
<img class="w-[40px] h-[40px]" src="@/app/assets/images/index/app_default.png" />
</div>
<div class="flex flex-wrap plug-list pb-10 plug-large">
<div class="cursor-pointer mt-[20px] mr-4 bg-[#f7f7f7]" v-for="(childItem,childIndex) in item.list" :key="childIndex" @click="toLink(childItem)">
<div class="w-[264px] flex py-[20px] px-[17px] app-item relative">
<el-image class="w-[40px] h-[40px] mr-[10px] rounded-[6px] overflow-hidden" :src="img(childItem.icon)" fit="contain">
<template #error>
<div class="image-slot">
<img class="w-[40px] h-[40px]" src="@/app/assets/images/index/app_default.png" />
</div>
</template>
</el-image>
<div class="flex flex-col justify-between w-[180px]">
<div class="text-[14px] flex items-center">
<span class="app-text max-w-[170px]">{{ childItem.title }}</span>
<!-- <span class="iconfont iconxiaochengxu2 text-[#00b240] ml-[4px] !text-[14px]"></span>-->
</div>
<!-- <el-icon color="#666">
<QuestionFilled />
</el-icon> -->
<p class="app-text text-[12px] text-[#999]">{{childItem.desc}}</p>
</div>
</div>
</div>
</div>
</template>
</template>
<div class="empty flex items-center justify-center" v-if="!loading && !Object.keys(marketingList).length">
<el-empty :description="t('emptyAppData')" />
</template>
</el-image>
<div class="flex flex-col justify-between w-[180px]">
<div class="text-[14px] flex items-center">
<span class="app-text max-w-[170px]">{{ childItem.title }}</span>
<!-- <span class="iconfont iconxiaochengxu2 text-[#00b240] ml-[4px] !text-[14px]"></span>-->
</div>
<!-- <el-icon color="#666">
<QuestionFilled />
</el-icon> -->
<p class="app-text text-[12px] text-[#999]">{{childItem.desc}}</p>
</div>
</div>
</div>
</el-card>
</div>
</div>
</template>
</template>
<div class="empty flex items-center justify-center" v-if="!loading && !Object.keys(marketingList).length">
<el-empty :description="t('emptyAppData')" />
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { getShowMarketing } from '@/app/api/addon'
import { img } from '@/utils/common'
import useUserStore from '@/stores/modules/user'
import { useRouter } from 'vue-router'
@ -57,38 +56,36 @@ const marketingList = ref<Record<string, any>[]>([])
const loading = ref(true)
const getMarketingList = async () => {
const res = await getShowMarketing()
marketingList.value = res.data
loading.value = false
loading.value = false
}
getMarketingList()
const toLink = (item: any) => {
if (item.url) {
// is_target=true
if (item.url.indexOf('is_target=true') != -1) {
const url = router.resolve(item.url)
window.open(url.href)
} else {
router.push(item.url)
}
if (item.url) {
// is_target=true
if (item.url.indexOf('is_target=true') != -1) {
const url = router.resolve(item.url)
window.open(url.href)
} else {
addonIndexRoute[item.key] && router.push({ name: addonIndexRoute[item.key] })
router.push(item.url)
}
} else {
addonIndexRoute[item.key] && router.push({ name: addonIndexRoute[item.key] })
}
}
</script>
<style lang="scss" scoped>
.app-text {
overflow: hidden;
/* 超出部分隐藏 */
white-space: nowrap;
/* 禁止文本换行 */
text-overflow: ellipsis;
/* 显示省略号 */
}
.app-item:hover{
transition: 0.5s;
box-shadow: 0 2px 8px 0 rgba(0,0,0,0.1);
}
.app-text {
overflow: hidden;
/* 超出部分隐藏 */
white-space: nowrap;
/* 禁止文本换行 */
text-overflow: ellipsis;
/* 显示省略号 */
}
.app-item:hover{
transition: 0.5s;
box-shadow: 0 2px 8px 0 rgba(0,0,0,0.1);
}
</style>

View File

@ -19,13 +19,19 @@
<el-form-item :label="t('url')" prop="url">
<el-input v-model.trim="sysUserLogTableData.searchParam.url" :placeholder="t('urlPlaceholder')" />
</el-form-item>
<el-form-item :label="t('操作时间')" prop="create_time">
<el-date-picker v-model="sysUserLogTableData.searchParam.create_time" type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
:end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadSysUserLogList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
<div class="flex justify-end items-center w-[20%]">
<div class="flex justify-end items-center w-[10%]">
<div>
<el-button type="primary" class="w-[100px]" @click="clearEvent()">{{ t('清空日志') }}</el-button>
</div>
@ -83,7 +89,8 @@ const sysUserLogTableData = reactive({
searchParam: {
ip: '',
username: '',
url: ''
url: '',
create_time:''
}
})

View File

@ -3,9 +3,9 @@
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<!-- <div class="flex justify-between items-center">
<div class="flex justify-between items-center">
<span class="text-page-title">{{ pageName }}</span>
</div> -->
</div>
<div class="flex justify-between items-center mt-[20px]">
<el-form :inline="true" :model="roleTableData.searchParam" ref="searchFormRef">

View File

@ -0,0 +1,119 @@
<template>
<!--微信公众号-->
<div class="main-container">
<el-card class="card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-page-title">{{ pageName }}</span>
</div>
<el-tabs v-model="activeName" class="my-[20px]" @tab-change="handleClick">
<el-tab-pane :label="t('accessFlow')" name="/channel/app" />
<el-tab-pane :label="t('versionManage')" name="/channel/app/version" />
</el-tabs>
<div class="p-[20px]">
<h3 class="panel-title !text-sm">{{ t("appInlet") }}</h3>
<el-row>
<el-col :span="20">
<el-steps class="!mt-[10px]" :active="3" direction="vertical">
<el-step>
<template #title>
<p class="text-[14px] font-[700]">
{{ t("uniappApp") }}
</p>
</template>
<template #description>
<span class="text-[#999]">{{ t("appAttestation1") }}</span>
<div class="mt-[20px] mb-[40px] h-[32px]">
<el-button type="primary" @click="linkEvent('https://dcloud.io/')">{{ t("toCreate") }}</el-button>
</div>
</template>
</el-step>
<el-step>
<template #title>
<p class="text-[14px] font-[700]">
{{ t("appSetting") }}
</p>
</template>
<template #description>
<!-- <span class="text-[#999]">{{ t("wechatSetting1") }}</span>-->
<div class="mt-[20px] mb-[40px] h-[32px]">
<el-button type="primary" @click="router.push('/channel/app/config')">{{ t("settingInfo") }}</el-button>
</div>
</template>
</el-step>
<el-step>
<template #title>
<p class="text-[14px] font-[700]">
{{ t("versionManage") }}
</p>
</template>
<template #description>
<!-- <span class="text-[#999]">{{ t("wechatAccess") }}</span>-->
<div class="mt-[20px] mb-[40px] h-[32px]">
<el-button type="primary" plain @click="router.push('/channel/app/version')">{{ t("releaseVersion") }}</el-button>
</div>
</template>
</el-step>
</el-steps>
</el-col>
</el-row>
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { t } from '@/lang'
import { img } from '@/utils/common'
import { getWechatConfig } from '@/app/api/wechat'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const activeName = ref('/channel/app')
const qrcode = ref('')
const wechatConfig = ref({})
const onShowGetWechatConfig = async () => {
await getWechatConfig().then(({ data }) => {
wechatConfig.value = data
qrcode.value = data.qr_code
})
}
onMounted(async () => {
await onShowGetWechatConfig()
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
onShowGetWechatConfig()
}
})
})
onUnmounted(() => {
document.removeEventListener('visibilitychange', () => {
})
})
const linkEvent = (url: string) => {
window.open(url, '_blank')
}
const handleClick = (val: any) => {
router.push({ path: activeName.value })
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,286 @@
<template>
<el-dialog v-model="showDialog" :title="formData.id ? t('updateAppVersion') : t('addAppVersion')" width="60%" class="diy-dialog-wrap" :destroy-on-close="true">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<div v-show="step == 1" class="h-[400px]">
<el-form-item :label="t('versionName')" prop="version_name">
<el-input v-model="formData.version_name" clearable :placeholder="t('versionNamePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('versionCode')" prop="version_code">
<el-input v-model="formData.version_code" clearable :placeholder="t('versionCodePlaceholder')" class="input-width" />
<div class="form-tip">{{ t('versionCodeTips') }}</div>
</el-form-item>
<el-form-item :label="t('versionDesc')" prop="version_desc">
<el-input v-model="formData.version_desc" type="textarea" rows="6" clearable :placeholder="t('versionDescPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('platform')" prop="platform">
<el-radio-group v-model="formData.platform">
<el-radio :label="key" size="large" v-for="(item, key) in appPlatform">{{ item }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('isForcedUpgrade')" prop="is_forced_upgrade">
<el-switch v-model="formData.is_forced_upgrade" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
</div>
<div v-show="step == 2" class="h-[400px]">
<el-scrollbar>
<el-form-item :label="t('upgradeType')">
<el-radio-group v-model="formData.upgrade_type">
<el-radio label="app" size="large">APP整包升级</el-radio>
<el-radio label="hot" size="large">wgt资源包升级</el-radio>
<el-radio label="market" size="large">应用市场升级</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('resourceFile')" v-show="formData.upgrade_type == 'app'">
<el-radio-group v-model="formData.package_type">
<el-radio label="file" size="large">上传资源包</el-radio>
<el-radio label="cloud" size="large">云打包</el-radio>
</el-radio-group>
</el-form-item>
<div v-show="formData.package_type == 'file' && formData.upgrade_type != 'market'">
<el-form-item label="" prop="package_path">
<div class="input-width" >
<upload-file v-model="formData.package_path" :accept="accept" api="sys/document/app_package"></upload-file>
</div>
<div class="form-tip" v-if="formData.upgrade_type == 'app'">{{ t('androidResourceFileTips') }}</div>
<div class="form-tip" v-if="formData.upgrade_type == 'hot'">{{ t('iosResourceFileTips') }}</div>
</el-form-item>
</div>
<div v-show="formData.upgrade_type == 'market'">
<el-form-item label="应用市场链接" prop="package_path">
<el-input v-model="formData.package_path" clearable class="input-width" />
</el-form-item>
</div>
<div v-show="formData.package_type == 'cloud'">
<el-form-item :label="t('icon')" prop="build.icon">
<div class="input-width" >
<upload-file v-model="formData.build.icon" accept=".zip" api="sys/document/applet"></upload-file>
</div>
<div class="form-tip !leading-[1.5]">应用图标和启动界面图片 icon.png为应用的图标 push.png为推送消息的图标 splash.png为应用启动页的图标 将icon.pngpush.pngsplash.png放置到drawabledrawable-ldpidrawable-mdpidrawable-hdpidrawable-xhdpidrawable-xxhdpi文件夹下压缩成压缩包上传
具体详情可查看 <span class="text-primary cursor-pointer" @click="windowOpen('https://nativesupport.dcloud.net.cn/AppDocs/usesdk/android.html')">uniapp App离线打包</span>配置应用图标和启动界面片段</div>
<div class="form-tip !leading-[1.5]">只支持上传.zip 在drawable的根目录进行压缩</div>
</el-form-item>
<el-form-item :label="t('certType')">
<el-radio-group v-model="formData.cert.type">
<el-radio label="public" size="large">公共证书</el-radio>
<el-radio label="private" size="large">自有证书</el-radio>
</el-radio-group>
<div class="form-tip">{{ t('publicCertTips') }}</div>
<div class="form-tip !leading-[1.5]">{{ t('privateCertTips') }}<span class="text-primary cursor-pointer" @click="windowOpen('https://ask.dcloud.net.cn/article/35777')">Android平台签名证书说明</span></div>
<div class="form-tip flex items-center">证书可以自己生成也可通过niucloud提供的<span class="text-primary cursor-pointer" @click="generateSingCertRef.open()">证书生成工具生成</span></div>
</el-form-item>
<div v-show="formData.cert.type == 'private'">
<el-form-item :label="t('certFile')" prop="cert.file">
<div class="input-width" >
<upload-file v-model="formData.cert.file" accept="" api="sys/document/android_cert"></upload-file>
</div>
</el-form-item>
<el-form-item :label="t('certAlias')" prop="cert.key_alias">
<el-input v-model="formData.cert.key_alias" clearable :placeholder="t('versionDescPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('certKeyPassword')" prop="cert.key_password">
<el-input v-model="formData.cert.key_password" clearable :placeholder="t('versionDescPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('certStorePassword')" prop="cert.store_password">
<el-input v-model="formData.cert.store_password" clearable :placeholder="t('versionDescPlaceholder')" class="input-width" />
</el-form-item>
</div>
</div>
</el-scrollbar>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<view v-show="step == 1">
<el-button type="primary" class="ml-3" @click="step = 2">{{
t('next')
}}</el-button>
</view>
<view v-show="step == 2">
<el-button type="primary" class="ml-3" @click="step = 1">{{
t('prev')
}}</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{ t('confirm') }}</el-button>
</view>
</span>
</template>
</el-dialog>
<generate-sing-cert ref="generateSingCertRef"/>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { addVersion, editVersion, getVersionInfo, getAppPlatform } from '@/app/api/app'
import GenerateSingCert from '@/app/views/channel/app/components/generate-sing-cert.vue'
const showDialog = ref(false)
const loading = ref(false)
const appPlatform = ref({})
const step = ref(1)
const generateSingCertRef = ref(null)
const getAppPlatformFn = async () => {
await getAppPlatform().then(({ data }) => {
appPlatform.value = data
initialFormData.platform = Object.keys(data)[0]
})
}
getAppPlatformFn()
const accept = computed(() => {
if (formData.upgrade_type == 'app') return '.apk'
if (formData.upgrade_type == 'hot') return '.wgt'
return ''
})
/**
* 表单数据
*/
const initialFormData = {
id: '',
version_code: '',
version_name: '',
version_desc: '',
platform: '',
is_forced_upgrade: 0,
package_path: '',
package_type: 'file',
upgrade_type: 'app',
build: {
icon: '',
},
cert: {
type: 'public',
file: '',
key_alias: '',
key_password: '',
store_password: ''
}
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
watch(() => formData.upgrade_type, () => {
if (formData.upgrade_type == 'app' || formData.upgrade_type == 'hot') {
formData.package_type = 'file'
}
formData.package_path = ''
formData.cert.type = 'public'
})
//
const formRules = computed(() => {
return {
version_code: [
{ required: true, message: t('versionCodePlaceholder'), trigger: 'blur' },
{
trigger: 'blur',
validator: (rule: any, value: any, callback: any) => {
if (isNaN(value) || !/^\d{0,10}$/.test(value)) {
callback(new Error(t('versionCodeTips')))
} else if (value < 0) {
callback(new Error(t('versionCodeTips')))
} else {
callback()
}
}
}
],
version_name: [
{ required: true, message: t('versionNamePlaceholder'), trigger: 'blur' }
],
package_path: [
{ required: formData.upgrade_type != 'market' && formData.package_type == 'file', message: '请上传资源文件', trigger: 'blur' },
{ required: formData.upgrade_type == 'market', message: '请输入应用市场链接', trigger: 'blur' }
],
'build.icon': [
{ required: formData.package_type == 'cloud', message: '请上传图标文件', trigger: 'blur' },
],
'cert.file': [
{ required: formData.package_type == 'cloud' && formData.cert.type == 'private', message: '请上传证书文件', trigger: 'blur' }
],
'cert.key_alias': [
{ required: formData.package_type == 'cloud' && formData.cert.type == 'private', message: '请输入证书别名', trigger: 'blur' }
],
'cert.key_password': [
{ required: formData.package_type == 'cloud' && formData.cert.type == 'private', message: '请上传证书密码', trigger: 'blur' }
],
'cert.store_password': [
{ required: formData.package_type == 'cloud' && formData.cert.type == 'private', message: '请上传证书库密码', trigger: 'blur' }
]
}
})
const emit = defineEmits(['complete'])
/**
* 确认
* @param formEl
*/
const confirm = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
const save = formData.id ? editVersion : addVersion
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
save(formData).then(res => {
loading.value = false
showDialog.value = false
emit('complete')
}).catch(() => {
loading.value = false
})
}
})
}
const setFormData = async (row: any = null) => {
Object.assign(formData, initialFormData)
loading.value = true
if (row) {
const data = await (await getVersionInfo(row.id)).data
if (data) Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
}
loading.value = false
}
watch(() => showDialog.value, () => {
step.value = 1
})
const windowOpen = (url: string) => {
window.open(url)
}
defineExpose({
showDialog,
setFormData
})
</script>
<style lang="scss" scoped></style>
<style lang="scss">
.diy-dialog-wrap .el-form-item__label{
height: auto !important;
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<el-dialog v-model="showDialog" title="生成Android证书" width="50%" class="diy-dialog-wrap" :destroy-on-close="true">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form-item label="证书别名" prop="key_alias">
<el-input v-model="formData.key_alias" clearable placeholder="" class="input-width" />
<div class="form-tip">只支持字母从证书文件中读取证书时需要别名</div>
</el-form-item>
<el-form-item label="证书密码" prop="key_password">
<el-input v-model="formData.key_password" clearable placeholder="" class="input-width" />
<div class="form-tip">只支持字母或数字密码至少 6 设置好后请牢记密码</div>
</el-form-item>
<el-form-item label="证书库密码" prop="store_password">
<el-input v-model="formData.store_password" clearable placeholder="" class="input-width" />
</el-form-item>
<el-form-item label="有效期" prop="limit">
<div class="flex items-center">
<el-input v-model="formData.limit" clearable placeholder="" class="w-[100px]" />
<div class="form-tip ml-2"></div>
</div>
<div class="form-tip"> 1 - 100 年之间</div>
</el-form-item>
<div class="text-primary cursor-pointer pl-[120px] my-[10px]" @click="moreInfo = !moreInfo">
{{ moreInfo ? '点击收起' : '点击展开填写更多信息 '}}
</div>
<view v-show="moreInfo">
<el-form-item label="域名">
<el-input v-model="formData.cn" clearable placeholder="" class="input-width" />
</el-form-item>
<el-form-item label="组织名称">
<el-input v-model="formData.o" clearable placeholder="" class="input-width" />
<div class="form-tip">如公司名称或者其他名称</div>
</el-form-item>
<el-form-item label="部门">
<el-input v-model="formData.ou" clearable placeholder="" class="input-width" />
<div class="form-tip">部门名称 IT 研发部等</div>
</el-form-item>
<el-form-item label="国家地区">
<el-input v-model="formData.c" clearable placeholder="" class="input-width" />
<div class="form-tip">输入国家/地区代号两个字母中国为CN</div>
</el-form-item>
<el-form-item label="省份">
<el-input v-model="formData.st" clearable placeholder="" class="input-width" />
<div class="form-tip">所在省份名称如Beijing</div>
</el-form-item>
<el-form-item label="城市">
<el-input v-model="formData.l" clearable placeholder="" class="input-width" />
<div class="form-tip">所在城市名称如Beijing</div>
</el-form-item>
</view>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">生成</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import type { FormInstance } from 'element-plus'
import { generateSingCert } from '@/app/api/app'
import { img } from '@/utils/common'
const showDialog = ref(false)
const moreInfo = ref(false)
const loading = ref(false)
const initialFormData = {
key_alias: '',
key_password: '',
store_password: '',
limit: 30,
cn: '',
o: '',
ou: '',
c: '',
st: '',
l: ''
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
key_alias: [
{ required: true, message: '请输入证书别名', trigger: 'blur' }
],
key_password: [
{ required: true, message: '请输入证书密码', trigger: 'blur' }
],
store_password: [
{ required: true, message: '请输入证书库密码', trigger: 'blur' }
],
limit: [
{ required: true, message: '请输入有效期', trigger: 'blur' },
{
trigger: 'blur',
validator: (rule: any, value: any, callback: any) => {
if (isNaN(value) || !/^\d{0,10}$/.test(value)) {
callback(new Error('有效期必须是数字'))
} else if (value < 0) {
callback(new Error('有效期必须为 1 - 100 之间的数字'))
} else if (value > 100) {
callback(new Error('有效期必须为 1 - 100 之间的数字'))
} else {
callback()
}
}
}
],
organization_name: [
{ required: true, message: '请输入所有者', trigger: 'blur' }
]
}
})
const confirm = async (formEl: FormInstance | undefined) => {
if (formEl) {
await formEl.validate()
loading.value = true
generateSingCert(formData).then(res => {
loading.value = false
showDialog.value = false
window.open(img(res.data), '_blank')
})
}
}
const open = async (row: any = null) => {
Object.assign(formData, initialFormData)
showDialog.value = true
}
defineExpose({
open
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,129 @@
<template>
<!--小程序配置-->
<div class="main-container">
<el-card class="card !border-none" shadow="never">
<el-page-header :content="pageName" :icon="ArrowLeft" @back="back()" />
</el-card>
<el-form class="page-form mt-[15px]" :model="formData" label-width="170px" ref="formRef" :rules="formRules" v-loading="loading">
<el-card class="box-card !border-none mt-[15px]" shadow="never">
<h3 class="panel-title !text-sm">{{ t('appInfo') }}</h3>
<el-form-item :label="t('uniAppId')" prop="uni_app_id">
<el-input v-model.trim="formData.uni_app_id" placeholder="" class="input-width" clearable/>
<div class="form-tip flex items-center">
{{ t('uniAppIdTips') }}
<el-button link type="primary" @click="windowOpen('https://www.dcloud.io/')">{{ t('toCreate') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="t('appName')" prop="app_name">
<el-input v-model.trim="formData.app_name" placeholder="" class="input-width" clearable/>
</el-form-item>
<el-form-item :label="t('androidAppKey')" prop="android_app_key">
<el-input v-model.trim="formData.android_app_key" placeholder="" class="input-width" clearable/>
<div class="form-tip">
{{ t('androidAppKeyTips') }}
<span class="text-primary cursor-pointer" @click="windowOpen('https://nativesupport.dcloud.net.cn/AppDocs/usesdk/appkey.html')">查看详情</span>
</div>
</el-form-item>
<el-form-item :label="t('applicationId')" prop="application_id">
<el-input v-model.trim="formData.application_id" placeholder="" class="input-width" clearable/>
<div class="form-tip">
{{ t('applicationIdTips') }}
</div>
</el-form-item>
</el-card>
<el-card class="box-card !border-none mt-[15px]" shadow="never">
<h3 class="panel-title !text-sm">{{ t('wechatAppInfo') }}</h3>
<el-form-item :label="t('wechatAppid')" prop="app_id">
<el-input v-model.trim="formData.wechat_app_id" :placeholder="t('appidPlaceholder')" class="input-width" clearable/>
<div class="form-tip">
{{ t('wechatAppidTips') }}
</div>
</el-form-item>
<el-form-item :label="t('wechatAppsecret')" prop="app_secret">
<el-input v-model.trim="formData.wechat_app_secret" :placeholder="t('appSecretPlaceholder')" class="input-width" clearable />
</el-form-item>
</el-card>
</el-form>
<div class="fixed-footer-wrap">
<div class="fixed-footer">
<el-button type="primary" :loading="loading" @click="save(formRef)">{{ t('save') }}</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, computed } from 'vue'
import { t } from '@/lang'
import { getAppConfig, setAppConfig } from '@/app/api/app'
import { FormInstance } from "element-plus"
import { useRoute, useRouter } from "vue-router"
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const loading = ref(true)
const formData = reactive<Record<string, any>>({
uni_app_id: '',
app_name: '',
android_app_key: '',
application_id: '',
wechat_app_id: '',
wechat_app_secret: ''
})
const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
}
})
/**
* 获取app配置
*/
getAppConfig().then(res => {
Object.assign(formData, res.data)
loading.value = false
})
/**
* 保存
*/
const save = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
setAppConfig(formData).then(() => {
loading.value = false
}).catch(() => {
loading.value = false
})
}
})
}
const windowOpen = (url: string) => {
window.open(url)
}
const back = () => {
router.push('/channel/app')
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,339 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-lg">{{pageName}}</span>
</div>
<el-tabs v-model="activeName" class="my-[20px]" @tab-change="handleClick">
<el-tab-pane :label="t('accessFlow')" name="/channel/app" />
<el-tab-pane :label="t('versionManage')" name="/channel/app/version" />
</el-tabs>
<el-alert type="info">
<template #default>
<div class="flex items-center">
<div>
<p>使用云打包提交成功后请先不要离开该页面稍待几分钟等待打包结果的返回</p>
</div>
</div>
</template>
</el-alert>
<div class="mt-[20px]">
<el-button type="primary" @click="addEvent" :disabled="loading">{{ t('addAppVersion') }}</el-button>
</div>
<div class="mt-[10px]">
<el-table :data="appVersionTable.data" size="large" v-loading="appVersionTable.loading">
<template #empty>
<span>{{ !appVersionTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column type="index" width="90" :label="t('index')" />
<el-table-column prop="version_code" :label="t('versionCode')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="version_name" :label="t('versionName')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="version_desc" :label="t('versionDesc')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="platform_name" :label="t('platform')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="status_name" :label="t('status')" min-width="120" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<el-button link :loading="row.status == 'creating'">{{ row.status_name }}</el-button>
</template>
</el-table-column>
<el-table-column prop="status" :label="t('isForcedUpgradeTitle')" min-width="120" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
{{ row.is_forced_upgrade ? '是' : '否' }}
</template>
</el-table-column>
<el-table-column prop="package_path" :label="t('packagePath')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('releaseTime')" min-width="120" :show-overflow-tooltip="true">
<template #default="{ row }">
<text v-if="row.release_time != 0">
{{ row.release_time }}
</text>
</template>
</el-table-column>
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="200px">
<template #default="{ row }">
<el-button type="primary" link v-if="row.release_time == 0" @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button type="primary" link v-if="row.status == 'upload_success'" @click="releaseEvent(row)">{{ t('release') }}</el-button>
<el-button type="primary" link v-if="row.status == 'creating'" @click="seeBuildLog(row)">{{ t('seeBuildLog') }}</el-button>
<el-button type="primary" link v-if="row.package_path && row.upgrade_type != 'market'" @click="downloadEvent(row)">{{ t('download') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="appVersionTable.page" v-model:page-size="appVersionTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="appVersionTable.total"
@size-change="loadAppVersionList()" @current-change="loadAppVersionList" />
</div>
</div>
<edit ref="editAppVersionDialog" @complete="loadAppVersionList" />
</el-card>
<el-dialog v-model="failReasonDialogVisible" :title="t('failReason')" width="60%">
<el-scrollbar class="h-[60vh] w-full whitespace-pre-wrap p-[20px]">
<div v-html="failReason"></div>
</el-scrollbar>
</el-dialog>
</div>
<el-dialog v-model="showDialog" :title="t('buildLog')" width="850px" :close-on-click-modal="false" :close-on-press-escape="false" :before-close="dialogClose">
<div class="h-[370px]">
<terminal ref="terminalRef" :name="`upgrade-${terminalId}`" context="" :init-log="null" :show-header="false" :show-log-time="true" @exec-cmd="onExecCmd" />
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { img, getAppType } from '@/utils/common'
import { ElMessageBox, FormInstance } from 'element-plus'
import { getVersionList, getBuildLog, deleteVersion, releaseVersion } from '@/app/api/app'
import Edit from '@/app/views/channel/app/components/app-version-edit.vue'
import { useRoute, useRouter } from 'vue-router'
import { Terminal, TerminalFlash } from 'vue-web-terminal'
import 'vue-web-terminal/lib/theme/dark.css'
import { getAuthInfo } from '@/app/api/module'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const activeName = ref('/channel/app/version')
const terminalRef: any = ref(null)
const appVersionTable = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
platfrom: ''
}
})
const showDialog = ref(false)
const loading = ref(true)
const authCode = ref('')
getAuthInfo().then(res => {
if (res.data.data && res.data.data.auth_code) {
authCode.value = res.data.data.auth_code
}
loading.value = false
}).catch(() => {
loading.value = false
})
const handleClick = (val: any) => {
router.push({ path: activeName.value })
}
const searchFormRef = ref<FormInstance>()
/**
* 获取app版本管理列表
*/
const loadAppVersionList = (page: number = 1) => {
appVersionTable.loading = true
appVersionTable.page = page
getVersionList({
page: appVersionTable.page,
limit: appVersionTable.limit,
...appVersionTable.searchParam
}).then(res => {
appVersionTable.loading = false
appVersionTable.data = res.data.data
appVersionTable.total = res.data.total
if (page == 1 && appVersionTable.data.length && appVersionTable.data[0].status == 'creating') getAppBuildLogFn(appVersionTable.data[0].task_key)
}).catch(() => {
appVersionTable.loading = false
})
}
loadAppVersionList()
const editAppVersionDialog: Record<string, any> | null = ref(null)
/**
* 添加app版本管理
*/
const addEvent = () => {
if (!authCode.value) {
authElMessageBox()
return
}
editAppVersionDialog.value.setFormData()
editAppVersionDialog.value.showDialog = true
}
/**
* 编辑app版本管理
* @param data
*/
const editEvent = (data: any) => {
editAppVersionDialog.value.setFormData(data)
editAppVersionDialog.value.showDialog = true
}
/**
* 删除app版本管理
*/
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('appVersionDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
deleteVersion({ id }).then(() => {
loadAppVersionList()
}).catch(() => {
})
})
}
const releaseEvent = (data: any) => {
ElMessageBox.confirm(t('appVersionReleaseTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
releaseVersion(data.id).then(() => {
loadAppVersionList()
}).catch(() => {
})
})
}
let buildLog = []
const getAppBuildLogFn = (key: string) => {
getBuildLog(key).then(res => {
if (res.data) {
if (res.data.status == '') {
if (showDialog.value) {
if (!buildLog.length) {
terminalRef.value.execute('clear')
terminalRef.value.execute('开始打包')
}
res.data.build_log.data[0].forEach((item) => {
if (!buildLog.includes(item.action)) {
terminalRef.value.pushMessage({ content: `${item.action}` })
buildLog.push(item.action)
}
})
}
setTimeout(() => {
getAppBuildLogFn(key)
}, 2000)
} else {
if (res.data.status == 'fail' && showDialog.value) {
terminalRef.value.pushMessage({ content: res.data.fail_reason, class: 'error' })
} else {
showDialog.value = false
}
loadAppVersionList()
buildLog = []
}
}
})
}
const seeBuildLog = () => {
showDialog.value = true;
}
/**
* 升级进度动画
*/
let flashInterval: any = null
const terminalFlash = new TerminalFlash()
const onExecCmd = (key, command, success, failed, name) => {
if (command == '开始打包') {
success(terminalFlash)
const frames = makeIterator(['/', '——', '\\', '|'])
flashInterval = setInterval(() => {
terminalFlash.flush('> ' + frames.next().value)
}, 150)
}
}
const makeIterator = (array: string[]) => {
let nextIndex = 0
return {
next () {
if (nextIndex + 1 == array.length) {
nextIndex = 0
}
return { value: array[nextIndex++] }
}
}
}
const failReason = ref('')
const failReasonDialogVisible = ref(false)
const handleFailReason = (data: any) => {
failReason.value = data.fail_reason
failReasonDialogVisible.value = true
}
const downloadEvent = (data: any) => {
window.open(img(data.package_path), '_blank')
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadAppVersionList()
}
const authElMessageBox = () => {
if (getAppType() == 'admin') {
ElMessageBox.confirm(
t('authTips'),
t('warning'),
{
distinguishCancelAndClose: true,
confirmButtonText: t('toBind'),
cancelButtonText: t('toNiucloud')
}
).then(() => {
router.push({ path: '/app/authorize' })
}).catch((action: string) => {
if (action === 'cancel') {
window.open('https://www.niucloud.com/app')
}
})
} else {
ElMessageBox.alert(t('siteAuthTips'), t('warning'))
}
}
</script>
<style lang="scss" scoped>
/* 多行超出隐藏 */
.multi-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>

View File

@ -36,6 +36,11 @@
<el-input v-model.trim="formData.app_secret" :placeholder="t('appSecretPlaceholder')" class="input-width" clearable />
<div class="form-tip">{{ t('wechatAppsecretTips') }}</div>
</el-form-item>
<el-form-item :label="t('wechatBaseUri')" prop="base_uri" v-if="!formData.is_authorization">
<el-input v-model.trim="formData.base_uri" :placeholder="t('wechatBaseUriPlaceholder')" class="input-width" clearable />
<div class="form-tip">{{ t('wechatBaseUriTips') }}</div>
</el-form-item>
</el-card>
<el-card class="box-card !border-none mt-[15px]" shadow="never">
@ -140,7 +145,8 @@ const formData = reactive<Record<string, any>>({
token: '',
encoding_aes_key: '',
encryption_type: 'not_encrypt',
is_authorization: 0
is_authorization: 0,
base_uri: ''
})
const formRef = ref<FormInstance>()

View File

@ -63,6 +63,23 @@
<el-switch v-model="diyStore.global.bottomTabBar.isShow" />
<div class="text-sm text-gray-400">{{ t('tabbarSwitchTips') }}</div>
</el-form-item>
<el-form-item :label="t('选择导航')">
<tabbar-select-popup v-model="diyStore.global.bottomTabBar.designNav" />
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap" v-if="diyStore.global.copyright.control">
<h3 class="mb-[10px]">{{ t('版权信息内容') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('版权信息')" class="display-block">
<el-switch v-model="diyStore.global.copyright.isShow" />
<div class="text-sm text-gray-400">{{ t('此处控制当前页面版权信息是否显示') }}</div>
</el-form-item>
<el-form-item :label="t('文字颜色')" class="display-block">
<el-color-picker v-model="diyStore.global.copyright.textColor" show-alpha />
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
@ -178,6 +195,7 @@ import { t } from '@/lang'
import { watch, ref } from 'vue'
import useDiyStore from '@/stores/modules/diy'
import { img } from '@/utils/common'
import tabbarSelectPopup from './tabbar-select-popup.vue'
const diyStore = useDiyStore()

View File

@ -0,0 +1,211 @@
<template>
<div>
<div @click="show">
<slot>
<el-input v-model="selectData.title" :placeholder="t('请选择底部导航')" readonly class="link-input">
<template #suffix>
<div @click.stop="clear">
<el-icon v-if="selectData.key">
<Close />
</el-icon>
<el-icon v-else>
<ArrowRight />
</el-icon>
</div>
</template>
</el-input>
</slot>
</div>
<el-dialog v-model="showDialog" :title="t('底部导航选择')" width="850px" :destroy-on-close="true" :close-on-click-modal="false">
<el-table class="" :data="tableData.data" size="large" v-loading="tableData.loading" height="400px">
<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="title" :label="t('title')" min-width="30%" >
<template #default="{ row }">
<span>{{ row.info.title }}</span>
</template>
</el-table-column>
<el-table-column prop="key" :label="t('key')" min-width="30%"/>
<el-table-column :label="t('type')" min-width="30%">
<template #default="{ row }">
<span>{{ row.info.type === 'app' ? t('app') : t('addon') }}</span>
</template>
</el-table-column>
</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="loadBottomNavList()" @current-change="loadBottomNavList" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="save">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { t } from '@/lang'
import { ref, reactive, nextTick,computed } from 'vue'
import { FormInstance, ElMessage } from "element-plus";
import { getDiyBottomList } from '@/app/api/diy'
import { cloneDeep } from 'lodash-es'
const prop = defineProps({
modelValue: {
type: Object,
default: () => {
return {
key: '',
title: '',
}
}
},
ignore: {
type: Array,
default: []
}
})
const clear = () => {
selectData.value = {
key: '',
title: ''
}
setTimesSelected()
}
const emit = defineEmits(['update:modelValue', 'confirm', 'success'])
const selectData: any = computed({
get() {
return prop.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
const searchFormRef = ref<FormInstance>()
const timeListTableRef = ref()
const showDialog = ref(false)
const show = () => {
showDialog.value = true
loadBottomNavList()
}
const tableData: any = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
})
//
const loadBottomNavList = (page: number = 1) => {
tableData.loading = true
tableData.page = page
getDiyBottomList({}).then(res => {
tableData.loading = false
const len = Math.ceil(res.data.length / tableData.limit)
const data = cloneDeep(res.data)
const dataGather = []
for (let i = 0; i < len; i++) {
dataGather[i] = data.splice(0, tableData.limit)
}
tableData.data = dataGather[tableData.page - 1]
tableData.data.forEach((item: any) => {
item.checked = item.key == selectData.value.key
})
tableData.total = res.data.length
setTimesSelected();
}).catch(() => {
tableData.loading = false
})
}
loadBottomNavList()
const handleCheckChange = (isSelect: any, row: any) => {
if (isSelect) {
selectData.value = {
key: row.key,
title: row.info.title
};
} else {
selectData.value = {
key: '',
title: ''
};
}
setTimesSelected()
}
// //
const setTimesSelected = () => {
nextTick(() => {
for (let i = 0; i < tableData.data.length; i++) {
tableData.data[i].checked = false
if (selectData.value.key == tableData.data[i].key) {
tableData.data[i].checked = true
selectData.value.key = tableData.data[i].key
selectData.value.title = tableData.data[i].info.title
}
}
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadBottomNavList()
}
const save = () => {
if (selectData.value.key == '') {
ElMessage({
type: 'warning',
message: `${ t('请选择底部导航') }`
})
return;
}
selectData.value = {
key: selectData.value.key,
title: selectData.value.title
}
showDialog.value = false;
}
defineExpose({
show,
showDialog,
})
</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

@ -185,7 +185,7 @@
<el-slider v-model="diyStore.editComponent.margin.top" show-input size="small" :min="-100" class="ml-[10px] diy-nav-slider" />
</el-form-item>
<el-form-item :label="t('marginBottom')" v-if="diyStore.editComponent.ignore.indexOf('marginBottom') == -1">
<el-slider v-model="diyStore.editComponent.margin.bottom" show-input size="small" class="ml-[10px] diy-nav-slider" />
<el-slider v-model="diyStore.editComponent.margin.bottom" show-input size="small" class="ml-[10px] diy-nav-slider" :min="-100" />
</el-form-item>
<el-form-item :label="t('marginBoth')" v-if="diyStore.editComponent.ignore.indexOf('marginBoth') == -1">
<el-slider v-model="diyStore.editComponent.margin.both" show-input size="small" class="ml-[10px] diy-nav-slider" />
@ -455,7 +455,6 @@ initPage({
diyStore.components.push(com)
}
}
loadDiyTemplatePages(data.type)
//

View File

@ -256,7 +256,7 @@ const editEvent = (data: any) => {
// 使
const setUse = (id: any) => {
setUseDiyPage({ id }).then(() => {
loadDiyPageList()
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
})
}
const repeat = ref(false)
@ -275,7 +275,7 @@ const copyEvent = (id: any) => {
copyDiy({ id }).then((res: any) => {
if (res.code == 1) {
loadDiyPageList()
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
}
repeat.value = false
}).catch(err => {
@ -294,7 +294,7 @@ const deleteEvent = (id: number) => {
}
).then(() => {
deleteDiyPage(id).then(() => {
loadDiyPageList()
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
}).catch(() => {
})
})
@ -358,7 +358,7 @@ const shareEvent = async (formEl: FormInstance | undefined) => {
id: shareFormId.value,
share: JSON.stringify(shareFormData)
}).then(() => {
loadDiyPageList()
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
shareDialogVisible.value = false
}).catch(() => {
})

View File

@ -35,7 +35,7 @@
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="bottomNavTableData.page" v-model:page-size="bottomNavTableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="bottomNavTableData.total"
@size-change="loadbottomNavList()" @current-change="loadbottomNavList" />
@size-change="loadBottomNavList()" @current-change="loadBottomNavList" />
</div>
</el-card>

View File

@ -13,7 +13,7 @@
<el-table-column label="应用" min-width="120" >
<template #default="{ row }">
<div class="flex items-center">
<el-image class="w-[40px] h-[40px] rounded-md overflow-hidden" :src="row.icon" fit="contain">
<el-image class="w-[40px] h-[40px] rounded-md overflow-hidden" :src="img(row.icon)" fit="contain">
<template #error>
<div class="flex items-center w-full h-full">
<img class="w-full h-full" src="@/app/assets/images/icon-addon.png" alt="">
@ -54,8 +54,9 @@
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { reactive, ref, watch, computed } from 'vue'
import { t } from '@/lang'
import { img } from '@/utils/common'
import { getDiyTheme } from '@/app/api/diy'
import { useRoute } from 'vue-router'
import themeList from './components/theme-list.vue'
@ -90,7 +91,7 @@ const editEvent = (data)=> {
<!-- 设置弹窗标题 -->
<style scoped>
/* 使用深度选择器 */
::v-deep .custom-theme-dialog .el-dialog__title {
:deep(.custom-theme-dialog .el-dialog__title) {
font-size: 16px;
}
</style>

View File

@ -159,10 +159,10 @@
<spread-popup ref="spreadPopupRef" />
<!-- 表单提交成功页弹出框 -->
<form-submit-popup ref="formSubmitPopupRef" @complete="loadDiyFormList" />
<form-submit-popup ref="formSubmitPopupRef" @complete="loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)" />
<!-- 表单填写设置弹出框 -->
<form-write-popup ref="formWritePopupRef" @complete="loadDiyFormList" />
<form-write-popup ref="formWritePopupRef" @complete="loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)" />
<records-detail ref="recordsDetailDialog"/>
@ -352,7 +352,7 @@ const copyEvent = (id: any) => {
copyForm({ form_id: id }).then((res: any) => {
if (res.code == 1) {
loadDiyFormList()
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
}
repeat.value = false
}).catch(() => {
@ -371,7 +371,7 @@ const deleteEvent = (form_id: number) => {
}
).then(() => {
deleteDiyForm({ form_ids: [form_id] }).then(() => {
loadDiyFormList()
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
}).catch(() => {
})
})
@ -438,7 +438,7 @@ const batchDeleteForms = () => {
deleteDiyForm({
form_ids
}).then(() => {
loadDiyFormList()
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
repeat.value = false
}).catch(() => {
repeat.value = false
@ -497,7 +497,7 @@ const shareEvent = async (formEl: FormInstance | undefined) => {
form_id: shareFormId.value,
share: JSON.stringify(shareFormData)
}).then(() => {
loadDiyFormList()
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
shareDialogVisible.value = false
}).catch(() => {
})

File diff suppressed because it is too large Load Diff

View File

@ -147,7 +147,7 @@ const deleteEvent = (id: number) => {
}
).then(() => {
deleteMemberLevel(id).then(() => {
loadMemberLevelList()
loadMemberLevelList(getTablePageStorage(memberLevelTableData.searchParam).page);
}).catch(() => {
})
})

View File

@ -529,17 +529,20 @@ const save = (callback: any) => {
const api = posterStore.id ? editPoster : addPoster
api(data).then((res: any) => {
isRepeat.value = false
// isRepeat.value = false
if (res.code == 1) {
if (posterStore.id) {
isRepeat.value = false //
} else {
// isRepeat.value = false
location.href = `${location.origin}${backPath}`
}
if (callback) callback(res.data.id)
}
}).catch(() => {
isRepeat.value = false
}).finally(() => {
// isRepeat.value = false
})
}

View File

@ -6,7 +6,7 @@
<span class="text-page-title">{{ pageName }}</span>
<el-button type="primary" class="w-[100px]" @click="dialogVisible = true">{{ t('添加海报') }}</el-button>
</div>
<div class="mt-[20px]" v-if="!isImagick">
<div class="mt-[20px]" v-if="!isImagick && !posterTableData.loading">
<el-alert type="warning" show-icon :closable="false">
<template #title>
<span class="!text-[14px]">检测到PHP未安装ImageMagick扩展需安装后才能使用海报功能</span>
@ -225,7 +225,7 @@ const modifyPosterStatusFn = (id:any, status:any) => {
modifyPosterStatus({
id, status
}).then((res) => {
loadPosterPageList()
loadPosterPageList(getTablePageStorage(posterTableData.searchParam).page)
isRepeat.value = false
})
}
@ -237,7 +237,7 @@ const modifyPosterDefaultFn = (id:any) => {
modifyPosterDefault({
id
}).then((res) => {
loadPosterPageList()
loadPosterPageList(getTablePageStorage(posterTableData.searchParam).page)
isRepeat.value = false
})
}
@ -254,7 +254,7 @@ const deleteEvent = (id: number) => {
if (isRepeat.value) return
isRepeat.value = true
deletePoster(id).then(() => {
loadPosterPageList()
loadPosterPageList(getTablePageStorage(posterTableData.searchParam).page)
isRepeat.value = false
}).catch(() => {
isRepeat.value = false
@ -277,7 +277,7 @@ const previewPoster = (data: any) => {
if (res.data) {
previewPosterUrl.value = res.data
previewDialogVisible.value = true
}
}
isRepeat.value = false
})
}
@ -287,11 +287,10 @@ const resetForm = (formEl: FormInstance | undefined) => {
formEl.resetFields()
loadPosterPageList()
}
const isImagick = ref(false)
const isImagick = ref(true)
// imagemagick
const checkImagickFn = () => {
checkImagick().then((res:any) => {
console.log(res)
isImagick.value = res.data
})

View File

@ -129,7 +129,7 @@ const modifyPrinterStatusEvent = (printer_id: any, status: any) => {
printer_id,
status
}).then((res) => {
loadPrinterList()
loadPrinterList(getTablePageStorage(printerTable.searchParam).page)
isRepeat.value = false
})
}
@ -163,7 +163,7 @@ const deleteEvent = (id: number) => {
if (repeat.value) return
repeat.value = true
deletePrinter(id).then(() => {
loadPrinterList()
loadPrinterList(getTablePageStorage(printerTable.searchParam).page)
repeat.value = false
}).catch(() => {
repeat.value = false
@ -214,7 +214,7 @@ const refreshTokenEvent = (printer_id: any) => {
if (repeat.value) return
repeat.value = true
refreshPrinterToken(printer_id).then((res: any) => {
loadPrinterList()
loadPrinterList(getTablePageStorage(printerTable.searchParam).page)
repeat.value = false
}).catch(() => {
repeat.value = false

View File

@ -148,7 +148,7 @@ const deleteEvent = (id: number) => {
}
).then(() => {
deletePrinterTemplate(id).then(() => {
loadPrinterTemplateList()
loadPrinterTemplateList(getTablePageStorage(printerTemplateTable.searchParam).page)
})
})
}

View File

@ -22,19 +22,19 @@
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('putOnRecordEdit') }}</h3>
<el-form-item :label="t('icp')" prop="icp">
<el-input v-model.trim="formData.icp" :placeholder="t('icpPlaceholder')" class="input-width" clearable maxlength="20"/>
<div class="form-tip">{{ t('网站的ICP备案号显示在H5和PC端底部') }}</div>
<div class="form-tip">{{ t('网站的ICP备案号,显示在PC端底部') }}</div>
</el-form-item>
<el-form-item :label="t('govRecord')" >
<el-input v-model.trim="formData.gov_record" :placeholder="t('govRecordPlaceholder')" class="input-width" clearable maxlength="50"/>
<div class="form-tip">{{ t('公安部门登记的备案信息显示在pc底部') }}</div>
<div class="form-tip">{{ t('公安部门登记的备案信息,显示在PC底部') }}</div>
</el-form-item>
<el-form-item :label="t('govUrl')" >
<el-input v-model.trim="formData.gov_url" :placeholder="t('govUrlPlaceholder')" class="input-width" clearable />
<div class="form-tip">{{ t('H5和PC底部显示的网站公安点击跳转的链接') }}</div>
<div class="form-tip">{{ t('PC底部显示的网站公安点击跳转的链接') }}</div>
</el-form-item>
<el-form-item :label="t('marketSupervisionUrl')" >
<el-input v-model.trim="formData.market_supervision_url" rows="4" clearable :placeholder="t('marketSupervisionUrlPlaceholder')" class="input-width" />
<div class="form-tip">{{ t('H5和PC底部显示的市场监督管理局点击跳转的链接') }}</div>
<div class="form-tip">{{ t('PC底部显示的市场监督管理局点击跳转的链接') }}</div>
</el-form-item>
</div>
</el-card>

View File

@ -10,7 +10,11 @@
<el-form-item :label="t('mapKey')" prop="key">
<el-input v-model.trim="formData.key" class="input-width" clearable />
<span class="ml-2 cursor-pointer tutorial-btn" @click="tutorialFn">{{ t('clickTutorial') }}</span>
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn">{{ t('clickSecretKey') }}</span>
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn('https://lbs.qq.com/dev/console/key/manage')">{{ t('clickSecretKey') }}</span>
</el-form-item>
<el-form-item :label="t('aMapKey')" prop="key">
<el-input v-model.trim="formData.amap_key" class="input-width" clearable />
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn('https://lbs.amap.com/')">{{ t('clickSecretKey') }}</span>
</el-form-item>
<el-form-item :label="t('isOpen')" prop="is_open">
<el-switch v-model="formData.is_open" :active-value="1" :inactive-value="0" />
@ -42,6 +46,7 @@ const loading = ref(true)
const formRef = ref<FormInstance>()
const formData = reactive({
key: '',
amap_key: '',
is_open: 0,
valid_time: 0
})
@ -71,9 +76,7 @@ const formRules = computed(() => {
const setFormData = async () => {
loading.value = true
const service_data = await (await getMap()).data
formData.key = service_data.key
formData.is_open = service_data.is_open
formData.valid_time = service_data.valid_time
Object.assign(formData, service_data)
loading.value = false
}
setFormData()
@ -107,8 +110,8 @@ const tutorialFn = () => {
/**
* 点击访问腾讯地图
*/
const secretFn = () => {
window.open('https://lbs.qq.com/dev/console/key/manage')
const secretFn = (url: string) => {
window.open(url)
}
</script>

View File

@ -858,7 +858,7 @@ const batchDelete = () => {
background-color: var(--el-table-header-bg-color);
}
::v-deep .number-of-steps {
:deep(.number-of-steps) {
.el-step__line {
margin: 0 25px;
background: #dddddd;

View File

@ -126,7 +126,9 @@ const prop = defineProps({
},
ignore: {
type: Array,
default: []
default: () => {
return [] // ['DIY_MAKE_PHONE_CALL']
}
}
})

View File

@ -47,12 +47,13 @@ let editorEl = null
const serverHeaders = {}
serverHeaders[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
const baseUrl = import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`
const editorConfig = ref({
debug: false,
UEDITOR_HOME_URL: import.meta.env.MODE == 'development' ? '/public/ueditor/' : '/admin/ueditor/',
UEDITOR_CORS_URL: import.meta.env.MODE == 'development' ? location.origin + '/ueditor/' : location.origin + '/admin/ueditor/',
serverUrl: `${import.meta.env.VITE_APP_BASE_URL}sys/ueditor`,
serverUrl: `${baseUrl}sys/ueditor`,
serverHeaders,
//
autoHeightEnabled: false,
@ -89,6 +90,17 @@ const handleEditorReady = (editor) => {
emit('handleBlur', editor.getContent()) //
})
//
editor.addListener('fullscreenchanged', (type, fullscreen) =>{
const editorDom = editor.ui.getDom()
if (fullscreen) {
editorDom.classList.add('edui-fullscreen')
} else {
editorDom.classList.remove('edui-fullscreen')
}
console.log('全屏切换', fullscreen)
})
//
const originalCount = editor.getContentLength; //

View File

@ -23,6 +23,10 @@ const prop = defineProps({
api: {
type: String,
default: 'sys/document/document'
},
accept: {
type: String,
default: '.doc,.docx,.xml,.txt,.pem,.zip,.rar,.7z,.crt,.key,.xls,.xlsx'
}
})
@ -37,22 +41,26 @@ const value = computed({
}
})
const upload: Record<string, any> = {
action: `${import.meta.env.VITE_APP_BASE_URL}/${prop.api}`,
showFileList: false,
headers: {},
accept: '.doc,.docx,.xml,.txt,.pem,.zip,.rar,.7z,.crt,.key,.xls,.xlsx',
onSuccess: (response: any, uploadFile: UploadFile) => {
if (response.code != undefined && response.code != 1) {
ElMessage({ message: response.msg, type: 'error' })
return
}
value.value = response.data.url
ElMessage({ message: t('upload.success'), type: 'success' })
}
}
upload.headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
const upload = computed(() => {
const headers: Record<string, any> = {}
headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
const baseURL = import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`
return {
action: `${baseURL}${prop.api}`,
showFileList: false,
accept: prop.accept,
headers,
onSuccess: (response: any, uploadFile: UploadFile) => {
if (response.code != undefined && response.code != 1) {
ElMessage({ message: response.msg, type: 'error' })
return
}
value.value = response.data.url
ElMessage({ message: t('upload.success'), type: 'success' })
}
}
})
</script>
<style lang="scss">

View File

@ -1,223 +1,228 @@
{
"edit": "编辑",
"delete": "删除",
"info": "详情",
"createTime": "创建时间",
"sort": "排序",
"status": "状态",
"operation": "操作",
"more": "更多",
"statusNormal": "正常",
"statusDeactivate": "停用",
"startUsing": "启用",
"confirm": "确认",
"save": "保存",
"back": "返回",
"cancel": "取消",
"search": "搜索",
"reset": "重置",
"refresh": "刷新",
"refreshSuccess": "刷新成功",
"select": "选择",
"export": "导出列表",
"exportPlaceholder": "确定要导出数据吗?",
"exportTip": "批量导出数据",
"exportConfirm": "确定并导出",
"warning": "提示",
"isShow": "是否显示",
"show": "显示",
"hidden": "隐藏",
"icon": "图标",
"userName": "用户名",
"headImg": "头像",
"accountNumber": "账号",
"accountSettings": "账号设置",
"realName": "名称",
"realNamePlaceholder": "请输入用户名称",
"password": "密码",
"confirmPassword": "确认密码",
"image": "图片",
"video": "视频",
"rename": "重命名",
"lookOver": "查看",
"selectAll": "全选",
"yes": "是",
"no": "否",
"copy": "复制",
"complete": "完成",
"copySuccess": "复制成功",
"notSupportCopy": "浏览器不支持一键复制,请手动进行复制",
"selectPlaceholder": "全部",
"provincePlaceholder": "请选择省",
"cityPlaceholder": "请选择市",
"districtPlaceholder": "请选择区/县",
"emptyData": "暂无数据",
"emptyQrCode": "暂无二维码",
"fileErr": "无法显示",
"upload": {
"root": "上传",
"selectimage": "选择图片",
"selectvideo": "选择视频",
"selecticon": "选择图标",
"selectnews": "选择图文",
"uploadimage": "上传图片",
"uploadvideo": "上传视频",
"addAttachmentCategory": "添加分组",
"attachmentCategoryPlaceholder": "请输入分组名称",
"attachmentEmpty": "暂无附件,请点击上传按钮上传",
"iconEmpty": "暂无图标",
"deleteCategoryTips": "确定要删除该分组吗?",
"deleteAttachmentTips": "确定要删除所选附件吗?如所选附件已被使用删除将会受到影响,请谨慎操作!",
"move": "移动",
"moveCategory": "移动分组",
"moveTo": "移动至",
"placeholderimageName": "请输入图片名称",
"placeholdervideoName": "请输入视频名称",
"placeholdericonName": "请输入图标名称",
"success": "上传成功",
"triggerUpperLimit": "可选数量已达上限",
"mediaEmpty": "暂无素材,请点击上传按钮上传"
},
"tabs": {
"closeLeft": "关闭左侧",
"closeRight": "关闭右侧",
"closeOther": "关闭其他"
},
"layout": {
"layoutSetting": "主题设置",
"darkMode": "黑暗模式",
"sidebarMode": "主题风格",
"themeColor": "主题颜色",
"detectionLoginOperation": "确定",
"detectionLoginContent": "已检测到有其他账号登录,需要刷新后才能继续操作。",
"detectionLoginTip": "提示",
"layoutStyle": "布局风格",
"tab": "开启标签栏"
},
"axios": {
"unknownError": "未知错误",
"400": "错误的请求",
"401": "请重新登录",
"403": "拒绝访问",
"404": "请求错误",
"405": "请求方法未允许",
"408": "请求超时",
"409": "请求跨域",
"500": "服务器端出错,错误原因:",
"501": "网络未实现",
"502": "网络错误",
"503": "服务不可用",
"504": "网络超时",
"505": "http版本不支持该请求",
"timeout": "网络请求超时!",
"requestError": "请求错误",
"errNetwork": "网络请求错误",
"baseUrlError": " 接口请求错误,请检查VITE_APP_BASE_URL参数配置或者伪静态配置, <a style='text-decoration: underline;' href='https://www.kancloud.cn/niucloud/niucloud-admin-develop/3213750' target='blank'>点击查看相关手册</a>"
},
"linkPlaceholder": "请选择跳转链接",
"selectLinkTips": "链接选择",
"diyLinkName": "链接名称",
"diyLinkNamePlaceholder": "请输入链接名称",
"diyLinkNameNotEmpty": "链接名称不能为空",
"diyLinkUrl": "跳转路径",
"diyLinkUrlPlaceholder": "请输入跳转路径",
"diyLinkUrlNotEmpty": "跳转路径不能为空",
"diyAppletId": "小程序AppID",
"diyAppletIdPlaceholder": "请输入要打开的小程序的appid",
"diyAppletIdNotEmpty": "小程序AppID不能为空",
"diyAppletPage": "小程序路径",
"diyAppletPagePlaceholder": "请输入要打开的小程序路径",
"diyAppletPageNotEmpty": "小程序路径不能为空",
"diyMakePhone": "电话号码",
"diyMakePhonePlaceholder": "请输入电话号码",
"diyMakePhoneNotEmpty": "电话号码不能为空",
"returnToPreviousPage": "返回",
"preview": "预览",
"emptyApp": "暂未安装任何应用",
"newInfo": "最新消息",
"visitWap": "访问店铺",
"mapSetting": "地图设置",
"mapKey": "腾讯地图KEY",
"indexTemplate": "首页模版",
"indexSwitch": "切换首页",
"indexWarning": "你确定要切换首页吗?",
"originalPassword": "原始密码",
"newPassword": "新密码",
"passwordCopy": "确认密码",
"passwordTip": "修改密码时必填.不修改密码时留空",
"originalPasswordPlaceholder": "请输入原始密码",
"passwordPlaceholder": "请输入新密码",
"originalPasswordHint": "原始密码不能为空",
"newPasswordHint": "请输入确认密码",
"doubleCipherHint": "两次新密码不同",
"confirmPasswordError": "两次新密码不同",
"upgrade": {
"upgradeButton": "立即升级",
"title": "升级",
"upgradingTips": "有正在执行的升级任务,",
"clickView": "点击查看",
"dirPermission": "目录读写权限",
"path": "路径",
"demand": "要求",
"readable": "可读",
"write": "可写",
"upgradeSuccess": "升级成功",
"localBuild": "本地编译",
"cloudBuild": "重试",
"rollback": "回滚",
"showDialogCloseTips": "升级任务尚未完成,关闭将取消升级,是否要继续关闭?",
"upgradeCompleteTips": "升级完成后还必须要重新编译admin wap web端以免影响到程序正常运行。",
"upgradeTips": "应用和插件升级时,系统会自动备份当前程序及数据库。升级功能不会造成您当前程序的损坏或者数据的丢失,请放心使用,但是升级过程可能会因为兼容性等各种原因出现意外的升级错误,当出现错误时,系统将会自动回退上一版本!若回退失败,请参考链接<a href='https://www.kancloud.cn/niushop/niushop_v6/3228611' target='_blank' class='text-primary'> https://www.kancloud.cn/niushop/niushop_v6/3228611 </a>手动回退上一版本!",
"knownToKnow": "我已知晓,不需要再次提示",
"cloudBuildErrorTips": "一键云编译队列任务过多,请等待几分钟后重试!",
"isNeedBackup": "是否需要备份",
"isNeedBackupTips": "检测到已存在备份,此次升级是否需要备份,不需要备份在升级出现异常时将会恢复最近的一次备份。",
"isNeedBackupBtn": "查看备份记录",
"option": "选项",
"isNeedCloudbuild": "是否需要云编译",
"cloudbuildTips": "此次升级的同时是否需要进行云编译"
},
"cloudbuild": {
"title": "云编译",
"executingTips": "有正在执行的编译任务,",
"clickView": "点击查看",
"dirPermission": "目录读写权限",
"path": "路径",
"demand": "要求",
"readable": "可读",
"write": "可写",
"cloudbuildSuccess": "编译完成",
"showDialogCloseTips": "编译任务尚未完成,关闭将取消编译,是否要继续关闭?"
},
"formSelectContentTitle": "表单名称",
"formSelectContentTitlePlaceholder": "请输入表单名称",
"formSelectContentTypeName": "表单类型",
"formSelectContentTypeNamePlaceholder": "请选择表单类型",
"formSelectContentTypeAll": "全部",
"formSelectContentTips": "请选择表单",
"appName": "应用名/版本信息",
"appIdentification": "应用标识",
"introduction": "简介",
"type": "类型",
"localAppText": "插件管理",
"upgrade2": "升级",
"installLabel": "已安装",
"uninstalledLabel": "未安装",
"buyLabel": "已购买",
"cloudBuild": "云编译",
"newVersion": "最新版本",
"tipText": "标识指开发应用或插件的文件夹名称",
"gxx": "更新信息",
"return": "返回",
"nextStep": "下一步",
"prev": "上一步",
"viewUpgradeContent": "查看升级内容",
"testDirectoryPermissions": "检测目录权限",
"backupFiles": "备份文件",
"startUpgrade": "开始升级",
"upgradeEnd": "升级完成",
"cloudBuildTips": "是否要进行云编译该操作可能会影响到正在访问的客户是否要继续操作?",
"promoteUrl": "推广链接",
"downLoadQRCode": "下载二维码",
"configureFailed": "配置失败"
}
"edit": "编辑",
"delete": "删除",
"info": "详情",
"createTime": "创建时间",
"sort": "排序",
"status": "状态",
"operation": "操作",
"more": "更多",
"statusNormal": "正常",
"statusDeactivate": "停用",
"startUsing": "启用",
"confirm": "确认",
"save": "保存",
"back": "返回",
"cancel": "取消",
"search": "搜索",
"reset": "重置",
"refresh": "刷新",
"refreshSuccess": "刷新成功",
"select": "选择",
"export": "导出列表",
"exportPlaceholder": "确定要导出数据吗?",
"exportTip": "批量导出数据",
"exportConfirm": "确定并导出",
"warning": "提示",
"isShow": "是否显示",
"show": "显示",
"hidden": "隐藏",
"icon": "图标",
"userName": "用户名",
"headImg": "头像",
"accountNumber": "账号",
"accountSettings": "账号设置",
"realName": "名称",
"realNamePlaceholder": "请输入用户名称",
"password": "密码",
"confirmPassword": "确认密码",
"image": "图片",
"video": "视频",
"rename": "重命名",
"lookOver": "查看",
"selectAll": "全选",
"yes": "是",
"no": "否",
"copy": "复制",
"complete": "完成",
"copySuccess": "复制成功",
"notSupportCopy": "浏览器不支持一键复制,请手动进行复制",
"selectPlaceholder": "全部",
"provincePlaceholder": "请选择省",
"cityPlaceholder": "请选择市",
"districtPlaceholder": "请选择区/县",
"emptyData": "暂无数据",
"emptyQrCode": "暂无二维码",
"fileErr": "无法显示",
"upload": {
"root": "上传",
"selectimage": "选择图片",
"selectvideo": "选择视频",
"selecticon": "选择图标",
"selectnews": "选择图文",
"uploadimage": "上传图片",
"uploadvideo": "上传视频",
"addAttachmentCategory": "添加分组",
"attachmentCategoryPlaceholder": "请输入分组名称",
"attachmentEmpty": "暂无附件,请点击上传按钮上传",
"iconEmpty": "暂无图标",
"deleteCategoryTips": "确定要删除该分组吗?",
"deleteAttachmentTips": "确定要删除所选附件吗?如所选附件已被使用删除将会受到影响,请谨慎操作!",
"move": "移动",
"moveCategory": "移动分组",
"moveTo": "移动至",
"placeholderimageName": "请输入图片名称",
"placeholdervideoName": "请输入视频名称",
"placeholdericonName": "请输入图标名称",
"success": "上传成功",
"triggerUpperLimit": "可选数量已达上限",
"mediaEmpty": "暂无素材,请点击上传按钮上传"
},
"tabs": {
"closeLeft": "关闭左侧",
"closeRight": "关闭右侧",
"closeOther": "关闭其他"
},
"layout": {
"layoutSetting": "主题设置",
"darkMode": "黑暗模式",
"sidebarMode": "主题风格",
"themeColor": "主题颜色",
"detectionLoginOperation": "确定",
"detectionLoginContent": "已检测到有其他账号登录,需要刷新后才能继续操作。",
"detectionLoginTip": "提示",
"layoutStyle": "布局风格",
"tab": "开启标签栏"
},
"axios": {
"unknownError": "未知错误",
"400": "错误的请求",
"401": "请重新登录",
"403": "拒绝访问",
"404": "请求错误",
"405": "请求方法未允许",
"408": "请求超时",
"409": "请求跨域",
"500": "服务器内部错误",
"501": "网络未实现",
"502": "网络错误",
"503": "服务不可用",
"504": "网络超时",
"505": "http版本不支持该请求",
"timeout": "网络请求超时!",
"requestError": "请求错误",
"errNetwork": "网络请求错误",
"baseUrlError": " 接口请求错误,请检查VITE_APP_BASE_URL参数配置或者伪静态配置, <a style='text-decoration: underline;' href='https://www.kancloud.cn/niucloud/niucloud-admin-develop/3213750' target='blank'>点击查看相关手册</a>"
},
"linkPlaceholder": "请选择跳转链接",
"selectLinkTips": "链接选择",
"diyLinkName": "链接名称",
"diyLinkNamePlaceholder": "请输入链接名称",
"diyLinkNameNotEmpty": "链接名称不能为空",
"diyLinkUrl": "跳转路径",
"diyLinkUrlPlaceholder": "请输入跳转路径",
"diyLinkUrlNotEmpty": "跳转路径不能为空",
"diyAppletId": "小程序AppID",
"diyAppletIdPlaceholder": "请输入要打开的小程序的appid",
"diyAppletIdNotEmpty": "小程序AppID不能为空",
"diyAppletPage": "小程序路径",
"diyAppletPagePlaceholder": "请输入要打开的小程序路径",
"diyAppletPageNotEmpty": "小程序路径不能为空",
"diyMakePhone": "电话号码",
"diyMakePhonePlaceholder": "请输入电话号码",
"diyMakePhoneNotEmpty": "电话号码不能为空",
"returnToPreviousPage": "返回",
"preview": "预览",
"emptyApp": "暂未安装任何应用",
"newInfo": "最新消息",
"visitWap": "访问店铺",
"mapSetting": "地图设置",
"mapKey": "腾讯地图KEY",
"indexTemplate": "首页模版",
"indexSwitch": "切换首页",
"indexWarning": "你确定要切换首页吗?",
"originalPassword": "原始密码",
"newPassword": "新密码",
"passwordCopy": "确认密码",
"passwordTip": "修改密码时必填.不修改密码时留空",
"originalPasswordPlaceholder": "请输入原始密码",
"passwordPlaceholder": "请输入新密码",
"originalPasswordHint": "原始密码不能为空",
"newPasswordHint": "请输入确认密码",
"doubleCipherHint": "两次新密码不同",
"confirmPasswordError": "两次新密码不同",
"upgrade": {
"upgradeButton": "立即升级",
"title": "升级",
"upgradingTips": "有正在执行的升级任务,",
"clickView": "点击查看",
"dirPermission": "目录读写权限",
"path": "路径",
"demand": "要求",
"readable": "可读",
"write": "可写",
"upgradeSuccess": "升级成功",
"localBuild": "本地编译",
"cloudBuild": "重试",
"rollback": "回滚",
"showDialogCloseTips": "升级任务尚未完成,关闭将取消升级,是否要继续关闭?",
"upgradeCompleteTips": "升级完成后还必须要重新编译admin wap web端以免影响到程序正常运行。",
"upgradeTips": "应用和插件升级时,系统会自动备份当前程序及数据库。升级功能不会造成您当前程序的损坏或者数据的丢失,请放心使用,但是升级过程可能会因为兼容性等各种原因出现意外的升级错误,当出现错误时,系统将会自动回退上一版本!若回退失败,请参考链接<a href='https://www.kancloud.cn/niushop/niushop_v6/3228611' target='_blank' class='text-primary'> https://www.kancloud.cn/niushop/niushop_v6/3228611 </a>手动回退上一版本!",
"knownToKnow": "我已知晓,不需要再次提示",
"cloudBuildErrorTips": "一键云编译队列任务过多,请等待几分钟后重试!",
"isNeedBackup": "是否需要备份",
"isNeedBackupTips": "检测到已存在备份,此次升级是否需要备份,不需要备份在升级出现异常时将会恢复最近的一次备份。",
"isNeedBackupBtn": "查看备份记录",
"option": "选项",
"isNeedCloudbuild": "是否需要云编译",
"cloudbuildTips": "此次升级的同时是否需要进行云编译"
},
"cloudbuild": {
"title": "云编译",
"executingTips": "有正在执行的编译任务,",
"clickView": "点击查看",
"dirPermission": "目录读写权限",
"path": "路径",
"demand": "要求",
"readable": "可读",
"write": "可写",
"cloudbuildSuccess": "编译完成",
"showDialogCloseTips": "编译任务尚未完成,关闭将取消编译,是否要继续关闭?"
},
"formSelectContentTitle": "表单名称",
"formSelectContentTitlePlaceholder": "请输入表单名称",
"formSelectContentTypeName": "表单类型",
"formSelectContentTypeNamePlaceholder": "请选择表单类型",
"formSelectContentTypeAll": "全部",
"formSelectContentTips": "请选择表单",
"appName": "应用名/版本信息",
"appIdentification": "应用标识",
"introduction": "简介",
"type": "类型",
"localAppText": "插件管理",
"upgrade2": "升级",
"installLabel": "已安装",
"uninstalledLabel": "未安装",
"buyLabel": "已购买",
"cloudBuild": "云编译",
"newVersion": "最新版本",
"tipText": "标识指开发应用或插件的文件夹名称",
"gxx": "更新信息",
"return": "返回",
"nextStep": "下一步",
"prev": "上一步",
"viewUpgradeContent": "查看升级内容",
"testDirectoryPermissions": "检测目录权限",
"backupFiles": "备份文件",
"startUpgrade": "开始升级",
"upgradeEnd": "升级完成",
"cloudBuildTips": "是否要进行云编译该操作可能会影响到正在访问的客户是否要继续操作?",
"promoteUrl": "推广链接",
"downLoadQRCode": "下载二维码",
"configureFailed": "配置失败",
"lefttitle": "左侧标题",
"righttitle": "右侧标题",
"leftDesc": "左侧简介",
"rightDesc": "右侧简介",
"descPlaceholder": "请输入简介内容"
}

View File

@ -1,118 +1,319 @@
<template>
<template v-if="meta.show">
<el-sub-menu v-if="routes.children" :index="String(routes.name)">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
</div>
<span class="ml-[10px]">{{ meta.title }}</span>
</template>
<menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" :level="props.level + 1" />
<template v-if="routes.name == 'addon_list' || routes.name == 'marketing_list'">
<template v-if="addonsMenus">
<menu-item :routes="addonsMenus" :key="index" :level="props.level + 1"/>
</template>
</template>
</el-sub-menu>
<template v-else>
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
</div>
<span class="ml-[10px]">{{ meta.title }}</span>
</template>
</el-menu-item>
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else>
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
</div>
<span class="ml-[10px]">{{ meta.title }}</span>
</template>
</el-menu-item>
<template v-if="meta.show">
<el-sub-menu v-if="hasVisibleChild" :index="String(routes.name)">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
</div>
<span class="ml-[10px]">{{ meta.title }}</span>
</template>
<menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" :level="props.level + 1" />
<template v-if="routes.name == 'addon_list' || routes.name == 'marketing_list'">
<template v-if="addonsMenus">
<menu-item :routes="addonsMenus" :key="index" :level="props.level + 1"/>
</template>
<div v-if="routes.is_border" class="!border-0 !border-t-[1px] border-solid mx-[25px] bg-[#f7f7f7] my-[5px]"></div>
</template>
</el-sub-menu>
<template v-else>
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
</div>
<span class="ml-[10px]">{{ meta.title }}</span>
</template>
</el-menu-item>
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-else>
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
</div>
<span class="ml-[10px]">{{ meta.title }}</span>
</template>
</el-menu-item>
</template>
<div v-if="routes.is_border" class="!border-0 !border-t-[1px] border-solid mx-[25px] bg-[#f7f7f7] my-[5px]"></div>
</template>
</template>
<script lang="ts" setup>
import { useRouter, useRoute } from 'vue-router'
import { ref, computed, watch } from 'vue'
import { ref, computed, watch , onMounted, onUnmounted} from 'vue'
import menuItem from './menu-item.vue'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import storage from '@/utils/storage'
import { findFirstValidRoute ,formatRouters} from '@/router/routers'
import { getShowApp,getShowSpecialMenu } from '@/app/api/addon'
const router = useRouter()
const route = useRoute()
const routers = useUserStore().routers
const props = defineProps({
routes: {
type: Object,
required: true
},
level: {
type: Number,
default: 1
}
routes: {
type: Object,
required: true
},
level: {
type: Number,
default: 1
}
})
const systemStore = useSystemStore()
const meta = computed(() => props.routes.meta)
// name
const specialMenuNames = ref<string[]>([])
const specialMenuNamesLevel1 = ref<string[]>([])
const addons = computed(() => {
const addons:Record<string, any> = {}
systemStore?.apps.forEach((item: any) => { addons[item.key] = item })
systemStore?.addons.forEach((item: any) => { addons[item.key] = item })
return addons
const addons:Record<string, any> = {}
systemStore?.apps.forEach((item: any) => { addons[item.key] = item })
systemStore?.addons.forEach((item: any) => { addons[item.key] = item })
return addons
})
const systemAddonKeys = computed(() => {
return systemStore?.addons.map((item: any) => item.key)
return systemStore?.addons.map((item: any) => item.key)
})
const hasVisibleChild = computed(() => {
if (!props.routes.children || !Array.isArray(props.routes.children)) {
return false
}
return props.routes.children.some(child => child.meta?.show === 1)
})
const addonRouters: Record<string, any> = {}
routers.forEach(item => {
item.original_name = item.name
if (item.meta.addon) {
addonRouters[item.meta.addon] = item
}
if (item.meta.attr) {
addonRouters[item.meta.attr] = item
}
item.original_name = item.name
if (item.meta.addon) {
addonRouters[item.meta.addon] = item
}
if (item.meta.attr) {
addonRouters[item.meta.attr] = item
}
})
const addonsMenus = ref(null)
// name
const collectSpecialMenuNames = (menus: any[]) => {
const names: string[] = []
const traverse = (children: any[]) => {
children.forEach(child => {
if (child.name) {
names.push(child.name)
}
//
if (child.children && Array.isArray(child.children)) {
traverse(child.children)
}
})
}
menus.forEach(menu => {
if (menu.children && Array.isArray(menu.children)) {
traverse(menu.children)
}
})
return names
}
// name
const collectSpecialMenuNamesLevel1 = (menus: any[]) =>{
const names: string[] = []
menus.forEach(menu => {
if (menu.name) {
names.push(menu.name)
}
})
return names
}
// addonKeys key
const getAddonAllKeys = (addonData) => {
if (!addonData || typeof addonData !== 'object') return [];
const allKeys = [];
Object.values(addonData).forEach(category => {
if (Array.isArray(category.list)) {
category.list.forEach(item => {
if (item.key) allKeys.push(item.key);
});
}
});
return allKeys;
};
// specialMenusKeys show
const handleSpecialMenus = () => {
const specialMenusKeys = storage.get('specialAppList')
if (Array.isArray(specialMenusKeys) && specialMenusKeys.length) {
const processedSpecialMenus = JSON.parse(JSON.stringify(specialMenusKeys));
const activeAppKey = storage.get('activeAppKey');
// name
processedSpecialMenus.forEach(menu => {
if (menu.children && Array.isArray(menu.children)) {
const traverseChildren = (children) => {
children.forEach(child => {
if (child && child.is_show !== undefined) {
child.is_show = (child.menu_key === activeAppKey) ? 1 : 0;
}
});
};
traverseChildren(menu.children);
}
});
// children
const filteredSpecialMenus = processedSpecialMenus.filter(menu => {
return menu.children && menu.children.length > 0;
});
return formatRouters(filteredSpecialMenus);
}
return [];
};
//
const handleJump = (routeName: string) => {
//
const isInSpecialMenus = specialMenuNames.value.includes(routeName)
// activeAppKey
if (!isInSpecialMenus) {
storage.remove('activeAppKey')
} else {
}
const processedSpecialMenus = handleSpecialMenus();
specialMenuNamesLevel1.value = collectSpecialMenuNamesLevel1(processedSpecialMenus)
//
if (specialMenuNamesLevel1.value.includes(routeName)) {
routeName = 'addon_list'
}
//
const query = route.name === routeName
? { refresh: Date.now() } //
: {};
//
router.push({ name: routeName, query });
}
watch(route, () => {
if (props.routes.name == 'addon_list') {
if (systemAddonKeys.value.includes(route.meta.addon) && addonRouters[route.meta.addon]) {
addonsMenus.value = addonRouters[route.meta.addon]
} else if (route.meta.attr && addonRouters[route.meta.attr]) {
addonsMenus.value = addonRouters[route.meta.attr]
} else {
addonsMenus.value = null
}
const addonKeys = storage.get('defaultAppList')
if (props.routes.name == 'addon_list') {
const addonAllKeys = getAddonAllKeys(addonKeys);
if (props.routes.children) {
//
props.routes.children = props.routes.children.filter(child => {
return !child.name || !addonAllKeys.includes(child.name);
});
//
const processedSpecialMenus = handleSpecialMenus();
if (processedSpecialMenus.length) {
const newChildren = [...(props.routes.children || [])];
processedSpecialMenus.forEach(special => {
const index = newChildren.findIndex(child => child.name === special.name);
if (index !== -1) {
newChildren[index] = special;
} else {
newChildren.push(special);
}
});
props.routes.children = newChildren;
}
}
const marketingKeys = storage.get('defaultMarketingKeys')
const matchedName = route.matched[1]?.name
if (props.routes.name == 'marketing_list') {
if (marketingKeys && marketingKeys.includes(matchedName)) {
addonsMenus.value = route.matched[1] ?? []
addonsMenus.value.meta.show = 1
} else {
addonsMenus.value = null
}
if (systemAddonKeys.value.includes(route.meta.addon) && addonRouters[route.meta.addon]) {
addonsMenus.value = addonRouters[route.meta.addon]
} else if (route.meta.attr && addonRouters[route.meta.attr]) {
addonsMenus.value = addonRouters[route.meta.attr]
} else {
addonsMenus.value = null
}
}
const marketingKeys = storage.get('defaultMarketingKeys')
const matchedName = route.matched[1]?.name
if (props.routes.name == 'marketing_list') {
if (marketingKeys && marketingKeys.includes(matchedName)) {
addonsMenus.value = route.matched[1] ?? []
addonsMenus.value.meta.show = 1
} else {
addonsMenus.value = null
}
}
// console.log('addonsMenus', props.routes)
}, { immediate: true })
const getAppList = async () => {
const res = await getShowApp()
const res2 = await getShowSpecialMenu()
storage.set({ key: 'specialAppList', data: res2.data.list })
storage.set({ key: 'defaultAppList', data: res.data })
if (props.routes.name == 'addon_list') {
const addonAllKeys = getAddonAllKeys(res.data);
if (props.routes.children) {
//
props.routes.children = props.routes.children.filter(child => {
return !child.name || !addonAllKeys.includes(child.name);
});
//
const processedSpecialMenus = handleSpecialMenus();
if (processedSpecialMenus.length) {
const newChildren = [...(props.routes.children || [])];
processedSpecialMenus.forEach(special => {
const index = newChildren.findIndex(child => child.name === special.name);
if (index !== -1) {
newChildren[index] = special;
} else {
newChildren.push(special);
}
});
props.routes.children = newChildren;
}
}
}
}
// localStorage activeAppKey
onMounted(() => {
//
if(props.routes.name == 'addon_list'){
getAppList();
}
const processedSpecialMenus = handleSpecialMenus();
specialMenuNames.value = collectSpecialMenuNames(processedSpecialMenus)
specialMenuNamesLevel1.value = collectSpecialMenuNamesLevel1(processedSpecialMenus)
const handleStorageChange = (event: StorageEvent) => {
if (event.key === 'activeAppKey') {
if (props.routes.name == 'addon_list') {
const processedSpecialMenus = handleSpecialMenus();
if (processedSpecialMenus.length && props.routes.children) {
const newChildren = [...(props.routes.children || [])];
processedSpecialMenus.forEach(special => {
const index = newChildren.findIndex(child => child.name === special.name);
if (index !== -1) {
newChildren[index] = special;
} else {
newChildren.push(special);
}
});
props.routes.children = newChildren;
}
}
}
};
window.addEventListener('storage', handleStorageChange);
//
onUnmounted(() => {
window.removeEventListener('storage', handleStorageChange);
});
});
</script>
<style lang="scss">
.el-sub-menu{
.el-icon{
width: auto;
}
.el-icon{
width: auto;
}
}
</style>

View File

@ -1,25 +1,25 @@
<template>
<el-container class="w-[200px] h-screen layout-aside flex flex-col">
<el-header class="logo-wrap flex items-center justify-center h-[64px]">
<div class="logo flex items-center justify-center m-auto w-full h-[64px]" v-if="!systemStore.menuIsCollapse">
<template v-if="webSite">
<img class="max-h-[40px] max-w-[70%]" v-if="webSite.logo" :src="img(webSite.logo)" alt="">
<img class="max-h-[40px] max-w-[70%]" src="@/app/assets/images/icon-addon-one.png" alt="" v-else>
</template>
</div>
<div class="logo flex items-center justify-center h-[64px]" v-else>
<i class="text-3xl iconfont iconyunkongjian"></i>
</div>
</el-header>
<el-main class="menu-wrap">
<el-scrollbar>
<el-menu :default-active="route.name" :router="true" class="aside-menu h-full" :unique-opened="true" :collapse="systemStore.menuIsCollapse" >
<menu-item v-for="(route, index) in menuData" :routes="route" :key="index" />
</el-menu>
<div class="h-[48px]"></div>
</el-scrollbar>
</el-main>
</el-container>
<el-container class="w-[200px] h-screen layout-aside flex flex-col">
<el-header class="logo-wrap flex items-center justify-center h-[64px]">
<div class="logo flex items-center justify-center m-auto w-full h-[64px]" v-if="!systemStore.menuIsCollapse">
<template v-if="webSite">
<img class="max-h-[40px] max-w-[70%]" v-if="webSite.logo" :src="img(webSite.logo)" alt="">
<img class="max-h-[40px] max-w-[70%]" src="@/app/assets/images/icon-addon-one.png" alt="" v-else>
</template>
</div>
<div class="logo flex items-center justify-center h-[64px]" v-else>
<i class="text-3xl iconfont iconyunkongjian"></i>
</div>
</el-header>
<el-main class="menu-wrap">
<el-scrollbar>
<el-menu :default-active="route.name" :router="true" class="aside-menu h-full" :unique-opened="true" :collapse="systemStore.menuIsCollapse" >
<menu-item v-for="(route, index) in menuData" :routes="route" :key="index" />
</el-menu>
<div class="h-[48px]"></div>
</el-scrollbar>
</el-main>
</el-container>
</template>
<script lang="ts" setup>
@ -31,7 +31,7 @@ import menuItem from './menu-item.vue'
import { img } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers'
import { getWebConfig } from "@/app/api/sys"
import { getShowMarketing } from '@/app/api/addon'
// import { getShowApp,getShowSpecialMenu } from '@/app/api/addon'
import storage from '@/utils/storage'
const systemStore = useSystemStore()
@ -43,104 +43,111 @@ const addonIndexRoute = userStore.addonIndexRoute
const menuData = ref<Record<string, any>[]>([])
const addonRouters: Record<string, any> = {}
const getMarketingList = async () => {
const res = await getShowMarketing()
const marketingList = res.data
const marketingKeys = marketingList?.marketing?.list?.map(item => item.key) ?? []
// menuData.value.forEach((item, index, arr) => {
// if (marketingKeys.includes(item.name)) {
// arr.splice(index, 1)
// }
// })
storage.set({ key: 'defaultMarketingKeys', data: marketingKeys })
}
// const appList = ref<Record<string, any>[]>([])
// const getAppList = async () => {
// const res = await getShowApp()
// appList.value = res.data
// storage.set({ key: 'defaultAppList', data: appList.value })
// }
// const specialList = ref<Record<string, any>[]>([])
// const getShowSpecialMenuList = async () => {
// const res = await getShowSpecialMenu()
// // specialList.value = formatRouters(res.data.list)
// specialList.value = res.data.list
// storage.set({ key: 'specialAppList', data: specialList.value })
// }
onMounted(() => {
getWebConfig().then(({ data }) => {
webSite.value = data
});
getMarketingList()
getWebConfig().then(({ data }) => {
webSite.value = data
});
// getAppList()
// getShowSpecialMenuList()
})
routers.forEach(item => {
item.original_name = item.name
if (item.meta.addon == '') {
if (item.meta.attr == '' && item.name != 'sign' && item.name != 'verify') {
if (item.children && item.children.length) {
item.name = findFirstValidRoute(item.children)
}
menuData.value.push(item)
}
} else if (item.meta.addon != '' && systemStore?.apps.length == 1 && systemStore?.apps[0].key == item.meta.addon && item.meta.show) {
if (item.children) {
item.children.forEach((citem: Record<string, any>) => {
citem.original_name = citem.name
if (citem.children && citem.children.length) {
citem.name = findFirstValidRoute(citem.children)
}
})
menuData.value.unshift(...item.children)
} else {
menuData.value.unshift(item)
}
} else {
addonRouters[item.meta.addon] = item
item.original_name = item.name
if (item.meta.addon == '') {
if (item.meta.attr == '' && item.name != 'sign' && item.name != 'verify') {
if (item.children && item.children.length) {
item.name = findFirstValidRoute(item.children)
}
menuData.value.push(item)
}
} else if (item.meta.addon != '' && systemStore?.apps.length == 1 && systemStore?.apps[0].key == item.meta.addon && item.meta.show) {
if (item.children) {
item.children.forEach((citem: Record<string, any>) => {
citem.original_name = citem.name
if (citem.children && citem.children.length) {
citem.name = findFirstValidRoute(citem.children)
}
})
menuData.value.unshift(...item.children)
} else {
menuData.value.unshift(item)
}
} else {
addonRouters[item.meta.addon] = item
}
// ,
// menuData.value.sort((a, b) => {
// if (a.meta.sort && b.meta.sort) {
// return b.meta.sort - a.meta.sort
// } else if (a.meta.sort) {
// return -1
// } else if (b.meta.sort) {
// return 1
// } else {
// return 0
// }
// })
// ,
// menuData.value.sort((a, b) => {
// if (a.meta.sort && b.meta.sort) {
// return b.meta.sort - a.meta.sort
// } else if (a.meta.sort) {
// return -1
// } else if (b.meta.sort) {
// return 1
// } else {
// return 0
// }
// })
})
//
if (systemStore?.apps.length > 1) {
const routers:Record<string, any>[] = []
systemStore?.apps.forEach((item: Record<string, any>) => {
if (addonRouters[item.key]) {
addonRouters[item.key].name = addonIndexRoute[item.key]
routers.push(addonRouters[item.key])
}
})
menuData.value.unshift(...routers)
const routers:Record<string, any>[] = []
systemStore?.apps.forEach((item: Record<string, any>) => {
if (addonRouters[item.key]) {
addonRouters[item.key].name = addonIndexRoute[item.key]
routers.push(addonRouters[item.key])
}
})
menuData.value.unshift(...routers)
// ,
// menuData.value.sort((a, b) => {
// if (a.meta.sort && b.meta.sort) {
// return b.meta.sort - a.meta.sort
// } else if (a.meta.sort) {
// return -1
// } else if (b.meta.sort) {
// return 1
// } else {
// return 0
// }
// })
// ,
// menuData.value.sort((a, b) => {
// if (a.meta.sort && b.meta.sort) {
// return b.meta.sort - a.meta.sort
// } else if (a.meta.sort) {
// return -1
// } else if (b.meta.sort) {
// return 1
// } else {
// return 0
// }
// })
}
</script>
<style lang="scss">
.menu-wrap {
padding: 0!important;
padding: 0!important;
.el-menu {
border-right: 0!important;
.el-menu {
border-right: 0!important;
.el-menu-item, .el-sub-menu__title {
--el-menu-item-height: 40px;
}
.el-sub-menu .el-menu-item {
--el-menu-sub-item-height: 40px;
}
.el-menu-item, .el-sub-menu__title {
--el-menu-item-height: 40px;
}
.el-sub-menu .el-menu-item {
--el-menu-sub-item-height: 40px;
}
}
}
</style>

View File

@ -114,32 +114,43 @@ const getParentTitleChain=(meta:any) =>{
return titles.join(' - ')
}
const flattenRoutes = (routes:any, parent = null)=> {
// 2. flattenRoutes parentShow show
const flattenRoutes = (routes: any, parent = null, parentShow = 1) => {
let flat = [];
routes.forEach(route => {
const { path, name, meta = {}, short_title, children } = route
const isLeaf = meta.type ==1 && meta.show==1
if(isLeaf){
const title = meta.title || short_title || ''
const parentTitleChain = getParentTitleChain(meta)
const fullTitle = parentTitleChain ? `${parentTitleChain} - ${title}` : title
const item = {
path,
name,
title,
parent_title: parentTitleChain,
full_title: fullTitle
};
flat.push(item);
routes.forEach(route => {
const { path, name, meta = {}, short_title, children } = route;
// show = show1 && show1
// show 1
const currentShow = meta.show === undefined ? 1 : meta.show;
const finalShow = currentShow && parentShow; //
// type=1 + show=1+
const isLeaf = meta.type === 1 && finalShow === 1;
if (isLeaf) {
const title = meta.title || short_title || '';
const parentTitleChain = getParentTitleChain(meta);
const fullTitle = parentTitleChain ? `${parentTitleChain} - ${title}` : title;
const item = {
path,
name,
title,
parent_title: parentTitleChain,
full_title: fullTitle
};
flat.push(item);
}
// finalShow parentShow
if (children && children.length > 0) {
flat = flat.concat(flattenRoutes(children, route))
flat = flat.concat(flattenRoutes(children, route, finalShow));
}
});
return flat;
}
};
const flatRoutes = flattenRoutes(routers)
const selectedRoute = ref('')
const handleRouteSelect = (name:any) => {

View File

@ -1,18 +1,18 @@
<template>
<template v-if="meta.show">
<el-sub-menu v-if="routes.children" :index="String(routes.name)">
<el-sub-menu v-if="hasVisibleChild" :index="String(routes.name)">
<template #title>
<span :class="['ml-[10px]']">{{ meta.title }}</span>
</template>
<menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" />
</el-sub-menu>
<template v-else>
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
<template #title>
<span :class="[{'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}, {'ml-[10px]': routes.meta.class == 2, 'ml-[15px]': routes.meta.class == 3}]">{{ meta.title }}</span>
</template>
</el-menu-item>
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else>
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-else>
<template #title>
<span :class="[{'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}, {'ml-[10px]': routes.meta.class == 2, 'ml-[15px]': routes.meta.class == 3}]">{{ meta.title }}</span>
</template>
@ -24,12 +24,14 @@
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import { useRouter , useRoute} from 'vue-router'
import { computed } from 'vue'
import menuItem from './menu-item.vue'
import useSystemStore from "@/stores/modules/system";
import storage from '@/utils/storage'
const router = useRouter()
const route = useRoute()
const props = defineProps({
routes: {
type: Object,
@ -39,13 +41,43 @@ const props = defineProps({
const systemStore = useSystemStore()
const meta = computed(() => props.routes.meta)
const hasVisibleChild = computed(() => {
if (!props.routes.children || !Array.isArray(props.routes.children)) {
return false
}
return props.routes.children.some(child => child.meta?.show === 1)
})
const addons = computed(() => {
const addons:Record<string, any> = {}
systemStore?.apps.forEach((item: any) => { addons[item.key] = item })
systemStore?.addons.forEach((item: any) => { addons[item.key] = item })
return addons
})
//
const handleJump = (routeName: string) => {
//
const specialMenuNames = storage.get('specialMenuNames')
const specialMenuNamesLevel1 = storage.get('specialMenuNamesLevel1')
const isInSpecialMenus = specialMenuNames.includes(routeName)
// activeAppKey
if (!isInSpecialMenus) {
storage.remove('activeAppKey')
} else {
}
//
if (specialMenuNamesLevel1.includes(routeName)) {
routeName = 'addon_list'
}
//
const query = route.name === routeName
? { refresh: Date.now() } //
: {};
//
router.push({ name: routeName, query });
}
</script>
<style lang="scss">

View File

@ -15,7 +15,7 @@
<el-scrollbar class="h-[calc( 100vh - 64px )]">
<el-menu :default-active="oneMenuActive" :router="true" class="aside-menu" :unique-opened="true" :collapse="systemStore.menuIsCollapse">
<template v-for="(item, index) in oneMenuData" :key="index">
<el-menu-item :index="item.original_name" @click="router.push({ name: item.name })" v-if="item.meta.show">
<el-menu-item :index="item.original_name" @click="handleJump(item.name)" v-if="item.meta.show">
<div v-if="item.meta.icon" class="w-[16px] h-[16px] relative flex justify-center">
<el-image class="w-[16px] h-[16px] rounded-[50%] overflow-hidden" :src="item.meta.icon" fit="fill" v-if="isUrl(item.meta.icon)"/>
<icon :name="item.meta.icon" class="absolute top-[50%] -translate-y-[50%]" v-else />
@ -32,8 +32,8 @@
<div class="h-[48px]"></div>
</el-scrollbar>
</div>
<el-scrollbar v-if="twoMenuData.length" class="two-menu w-[140px]">
<div class="w-[140px] h-[64px] flex items-center justify-center text-[16px] border-b-[1px] border-solid border-[var(--el-border-color-lighter)]">
<el-scrollbar v-if="twoMenuData.length" class="two-menu w-[152px]">
<div class="w-[152px] h-[64px] flex items-center justify-center text-[16px] border-b-[1px] border-solid border-[var(--el-border-color-lighter)]">
{{ route.matched[1].meta.title }}
</div>
@ -51,10 +51,11 @@ import { ref, watch, computed, onMounted, watchEffect } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import { getShowApp,getShowMarketing } from '@/app/api/addon'
import { getShowApp,getShowSpecialMenu } from '@/app/api/addon'
import menuItem from './menu-item.vue'
import { img, isUrl } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers'
import { findFirstValidRoute,formatRouters } from '@/router/routers'
import storage from '@/utils/storage'
import { cloneDeep } from 'lodash-es'
const systemStore = useSystemStore()
@ -111,98 +112,228 @@ const marketingList = ref(null);
const oneMenuActive = ref(route.matched[1].name)
const getAppList = async () => {
const res = await getShowApp();
appList.value = res.data;
// loading.value = false;
};
const getMarketingList = async () => {
const res = await getShowMarketing();
marketingList.value = res.data
const res = await getShowApp()
appList.value = res.data
storage.set({ key: 'defaultAppList', data: appList.value })
}
const specialList = ref<Record<string, any>[]>([])
const getShowSpecialMenuList = async () => {
const res = await getShowSpecialMenu()
// specialList.value = formatRouters(res.data.list)
specialList.value = res.data.list
storage.set({ key: 'specialAppList', data: specialList.value })
}
onMounted(async () => {
await getAppList() //
await getMarketingList()
const specialMenuNames = ref<string[]>([])
const specialMenuNamesLevel1 = ref<string[]>([])
onMounted(() => {
getAppList()
getShowSpecialMenuList()
const processedSpecialMenus = handleSpecialMenus();
specialMenuNames.value = collectSpecialMenuNames(processedSpecialMenus)
specialMenuNamesLevel1.value = collectSpecialMenuNamesLevel1(processedSpecialMenus)
storage.set({ key: 'specialMenuNames', data: specialMenuNames.value })
storage.set({ key: 'specialMenuNamesLevel1', data: specialMenuNamesLevel1.value })
})
watchEffect(() => {
// if (!appList.value || loading.value) return; //
const addonKeys = appList.value?.addon?.list?.map(item => item.key) ?? [];
const toolKeys = appList.value?.tool?.list?.map(item => item.key) ?? [];
const allKeys = [...addonKeys, ...toolKeys];
const marketingKeys = marketingList.value?.marketing?.list?.map(item => item.key) ?? [];
const matchedName = route.matched[1]?.name;
if (allKeys.includes(matchedName)) {
oneMenuActive.value = "addon";
twoMenuData.value = route.matched[1]?.children ?? [];
} else if (marketingKeys.includes(matchedName)) {
oneMenuActive.value = "active";
twoMenuData.value = route.matched[1]?.children ?? [];
} else if (route.meta.attr !== "") {
oneMenuActive.value = route.matched[2]?.name;
twoMenuData.value = route.matched[1]?.children ?? [];
} else {
//
if (systemStore?.apps.length > 1) {
twoMenuData.value = route.matched[1]?.children;
oneMenuActive.value = route.matched[1]?.name;
} else {
//
const oneMenu = route.matched[1];
if (oneMenu.meta.addon === "") {
oneMenuActive.value = route.matched[1]?.name;
twoMenuData.value = route.matched[1]?.children ?? [];
} else {
if (oneMenu.meta.addon === systemStore?.apps[0]?.key) {
oneMenuActive.value = route.matched[2]?.name;
twoMenuData.value = route.matched[2]?.children ?? [];
} else {
oneMenuActive.value = route.matched[1]?.name;
twoMenuData.value = route.matched[1]?.children ?? [];
}
}
//
const menuOption = ref([])
const secondMenuShowWayFn = () => {
menuOption.value = []
if (oneMenuActive.value !== 'active' && oneMenuActive.value !== 'addon' && twoMenuData.value && Object.values(twoMenuData.value).length) {
const data = cloneDeep(twoMenuData.value)
for (const key in data) {
menuOption.value.push(data[key].name)
}
}
})
}
// watch(route, () => {
// if (route.meta.attr != '') {
// if (route.matched[2]) oneMenuActive.value = route.matched[2].name
// twoMenuData.value = route.matched[1].children ?? []
// watchEffect(() => {
// // if (!appList.value || loading.value) return; //
// const addonKeys = appList.value?.addon?.list?.map(item => item.key) ?? [];
// const toolKeys = appList.value?.tool?.list?.map(item => item.key) ?? [];
// const allKeys = [...addonKeys, ...toolKeys];
// const marketingKeys = marketingList.value?.marketing?.list?.map(item => item.key) ?? [];
// const matchedName = route.matched[1]?.name;
// if (allKeys.includes(matchedName)) {
// oneMenuActive.value = "addon";
// twoMenuData.value = route.matched[1]?.children ?? [];
// } else if (marketingKeys.includes(matchedName)) {
// oneMenuActive.value = "active";
// twoMenuData.value = route.matched[1]?.children ?? [];
// } else if (route.meta.attr !== "") {
// oneMenuActive.value = route.matched[2]?.name;
// twoMenuData.value = route.matched[1]?.children ?? [];
// } else {
// //
// if (systemStore?.apps.length > 1) {
// twoMenuData.value = route.matched[1].children
// oneMenuActive.value = route.matched[1].name
// twoMenuData.value = route.matched[1]?.children;
// oneMenuActive.value = route.matched[1]?.name;
// } else {
// //
// const oneMenu = route.matched[1]
// if (oneMenu.meta.addon == '') {
// oneMenuActive.value = route.matched[1].name
// twoMenuData.value = route.matched[1].children ?? []
// const oneMenu = route.matched[1];
// if (oneMenu.meta.addon === "") {
// oneMenuActive.value = route.matched[1]?.name;
// twoMenuData.value = route.matched[1]?.children ?? [];
// } else {
// if (oneMenu.meta.addon == systemStore?.apps[0].key) {
// oneMenuActive.value = route.matched[2].name
// twoMenuData.value = route.matched[2].children ?? []
// if (oneMenu.meta.addon === systemStore?.apps[0]?.key) {
// oneMenuActive.value = route.matched[2]?.name;
// twoMenuData.value = route.matched[2]?.children ?? [];
// } else {
// oneMenuActive.value = route.matched[1].name
// twoMenuData.value = route.matched[1].children ?? []
// oneMenuActive.value = route.matched[1]?.name;
// twoMenuData.value = route.matched[1]?.children ?? [];
// }
// }
// }
// }
// }, { immediate: true })
// })
//
const menuOption = ref([])
watch(twoMenuData.value, () => {
menuOption.value = [];
if(twoMenuData.value && Object.values(twoMenuData.value).length){
let data = cloneDeep(twoMenuData.value);
for(let key in data){
menuOption.value.push(data[key].name);
// addonKeys key
const getAddonAllKeys = (addonData) => {
if (!addonData || typeof addonData !== 'object') return [];
const allKeys = [];
Object.values(addonData).forEach(category => {
if (Array.isArray(category.list)) {
category.list.forEach(item => {
if (item.key) allKeys.push(item.key);
});
}
});
return allKeys;
};
// specialMenusKeys show
const handleSpecialMenus = () => {
const specialMenusKeys = storage.get('specialAppList')
if (Array.isArray(specialMenusKeys) && specialMenusKeys.length) {
const processedSpecialMenus = JSON.parse(JSON.stringify(specialMenusKeys));
const activeAppKey = storage.get('activeAppKey');
// name
processedSpecialMenus.forEach(menu => {
if (menu.children && Array.isArray(menu.children)) {
const traverseChildren = (children) => {
children.forEach(child => {
if (child && child.is_show !== undefined) {
child.is_show = (child.menu_key === activeAppKey) ? 1 : 0;
}
});
};
traverseChildren(menu.children);
}
});
// children
const filteredSpecialMenus = processedSpecialMenus.filter(menu => {
return menu.children && menu.children.length > 0;
});
return formatRouters(filteredSpecialMenus);
}
return [];
};
// name
const collectSpecialMenuNames = (menus: any[]) => {
const names: string[] = []
const traverse = (children: any[]) => {
children.forEach(child => {
if (child.name) {
names.push(child.name)
}
//
if (child.children && Array.isArray(child.children)) {
traverse(child.children)
}
})
}
menus.forEach(menu => {
if (menu.children && Array.isArray(menu.children)) {
traverse(menu.children)
}
})
return names
}
// name
const collectSpecialMenuNamesLevel1 = (menus: any[]) =>{
const names: string[] = []
menus.forEach(menu => {
if (menu.name) {
names.push(menu.name)
}
})
return names
}
//
const handleJump = (routeName: string) => {
//
const isInSpecialMenus = specialMenuNames.value.includes(routeName)
// activeAppKey
if (!isInSpecialMenus) {
storage.remove('activeAppKey')
} else {
}
//
router.push({ name: routeName })
}
watch(route, () => {
if (route.meta.attr != '') {
oneMenuActive.value = route.matched[1].name
twoMenuData.value = route.matched[1].children ?? []
} else {
//
if (systemStore?.apps.length > 1) {
twoMenuData.value = route.matched[1].children
oneMenuActive.value = route.matched[1].name
} else {
//
const oneMenu = route.matched[1]
if (oneMenu.meta.addon == '') {
oneMenuActive.value = route.matched[1].name
twoMenuData.value = route.matched[1].children ?? []
} else {
if (oneMenu.meta.addon == systemStore?.apps[0].key) {
oneMenuActive.value = route.matched[2].name
twoMenuData.value = route.matched[2].children ?? []
} else {
oneMenuActive.value = route.matched[1].name
twoMenuData.value = route.matched[1].children ?? []
}
}
}
}
secondMenuShowWayFn()
const addonKeys = storage.get('defaultAppList')
const addonAllKeys = getAddonAllKeys(addonKeys);
twoMenuData.value = twoMenuData.value.filter((child) =>{
return !child.name || !addonAllKeys.includes(child.name);
});
if(oneMenuActive.value == 'addon'){
// twoMenuData addon_list
const processedSpecialMenus = handleSpecialMenus();
if (processedSpecialMenus.length) {
// addon_list twoMenuData
const addonListIndex = twoMenuData.value.findIndex(
(item) => item.name === 'addon_list'
);
if (addonListIndex !== -1) {
// addon_list
twoMenuData.value.splice(
addonListIndex + 1,
0,
...processedSpecialMenus
);
} else {
// addon_list twoMenuData
twoMenuData.value.push(...processedSpecialMenus);
}
}
}
}, { immediate: true })
</script>
@ -240,13 +371,13 @@ watch(twoMenuData.value, () => {
}
.two-menu{
.aside-menu:not(.el-menu--collapse) {
width: 140px;
width: 152px;
border: 0;
padding-top: 16px;
.el-menu-item{
height: 36px;
margin: 0 8px 4px;
padding: 0 8px !important;
margin: 0 12px 4px;
padding: 0 !important;
border-radius: 2px;
span{
margin-left: 8px;
@ -265,7 +396,7 @@ watch(twoMenuData.value, () => {
.el-sub-menu__title{
margin: 0 8px 4px;
height: 36px;
padding-left: 8px;
padding-left:0px;
border-radius: 2px;
span{
height: 36px;
@ -282,7 +413,35 @@ watch(twoMenuData.value, () => {
}
}
.el-menu-item{
padding-left: 20px !important;
padding-left: 0px !important;
}
.el-sub-menu{
margin-bottom: 8px;
.el-sub-menu__title{
margin: 0 8px 4px;
height: 36px;
padding-left: 18px;
border-radius: 2px;
span{
height: 36px;
display: flex;
align-items: center;
font-size: 14px;
}
&:hover{
background-color: #f7f7f7;
color: var(--el-color-primary);
}
.el-icon.el-sub-menu__icon-arrow{
right: 5px;
}
}
.el-menu-item{
padding-left: 40px !important;
span{
margin-left: 0 !important;
}
}
}
}
}

View File

@ -22,6 +22,22 @@
</el-col>
<el-col :span="12">
<div class="right-panel h-full flex items-center justify-end">
<div class="flex items-center flex-shrink-0 hidden-xs-only">
<el-popover placement="bottom" :width="330" trigger="click" v-model:visible="isMenuSearch" >
<template #reference>
<i class="iconfont icona-sousuoV6xx-36 cursor-pointer px-[8px] !text-[14px]"></i>
</template>
<template #default>
<div class="flex items-center">
<el-select v-model="selectedRoute" filterable class="!w-[250px] mr-[20px] menu-select" :teleported="false" clearable @change="handleRouteSelect">
<el-option v-for="item in flatRoutes" :key="item.name" :label="item.full_title" :value="item.name" >
</el-option>
</el-select>
<el-button type="primary" link @click="isMenuSearch = false">{{t('取消')}}</el-button>
</div>
</template>
</el-popover>
</div>
<!-- 预览-->
<i class="iconfont iconicon_huojian1 cursor-pointer px-[8px]" :title="t('visitWap')" @click="toPreview"></i>
<!-- 预览 只有站点时展示-->
@ -66,6 +82,7 @@ import switchLang from './switch-lang.vue'
import userInfo from './user-info.vue'
import { useFullscreen } from '@vueuse/core'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import useAppStore from '@/stores/modules/app'
import { useRoute,useRouter } from 'vue-router'
import { t } from '@/lang'
@ -79,10 +96,61 @@ const route = useRoute()
const router = useRouter()
const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
const userStore = useUserStore()
const dark = computed(() => {
return systemStore.dark
})
const isMenuSearch = ref(false)
const routers = userStore.routers
const getParentTitleChain=(meta:any) =>{
let titles = []
let current = meta?.parent_route
while (current) {
if (current.short_title) {
titles.unshift(current.short_title)
}
current = current.parent_route
}
return titles.join(' - ')
}
const flattenRoutes = (routes:any, parent = null)=> {
let flat = [];
routes.forEach(route => {
const { path, name, meta = {}, short_title, children } = route
const isLeaf = meta.type ==1 && meta.show==1
if(isLeaf){
const title = meta.title || short_title || ''
const parentTitleChain = getParentTitleChain(meta)
const fullTitle = parentTitleChain ? `${parentTitleChain} - ${title}` : title
const item = {
path,
name,
title,
parent_title: parentTitleChain,
full_title: fullTitle
};
flat.push(item);
}
if (children && children.length > 0) {
flat = flat.concat(flattenRoutes(children, route))
}
});
return flat;
}
const flatRoutes = flattenRoutes(routers)
const selectedRoute = ref('')
const handleRouteSelect = (name:any) => {
if (name) {
router.push({ name })
isMenuSearch.value = false
}
}
// start
const detectionLoginDialog = ref(false)
const comparisonToken = ref('')

View File

@ -77,8 +77,18 @@ const useDiyStore = defineStore('diy', {
bottomTabBar: {
control: true, // 是否允许展示编辑
isShow: true, // 是否显示
designNav:{ //类型
title: "", // 标题
key: "", // 组件标识
}
},
// 版权信息
copyright: {
control: true, // 是否允许展示编辑
isShow: false, // 是否显示
textColor : "#ccc", // 文字颜色
},
// 弹框 count不弹出 -1首次弹出 1每次弹出 0
popWindow: {
imgUrl: "",
@ -171,6 +181,17 @@ const useDiyStore = defineStore('diy', {
bottomTabBar: {
control: true, // 是否允许展示编辑
isShow: true, // 是否显示
designNav:{ //类型
title: "", // 标题
key: "", // 组件标识
}
},
// 版权信息
copyright: {
control: true, // 是否允许展示编辑
isShow: true, // 是否显示
textColor : "#ccc", // 文字颜色
},
// 弹框 count不弹出 -1首次弹出 1每次弹出 0

View File

@ -1,9 +1,9 @@
@font-face {
font-family: "iconfont";
/* Project id 3883393 */
src: url('//at.alicdn.com/t/c/font_3883393_6d60cyygl4.woff2?t=1755603992297') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_6d60cyygl4.woff?t=1755603992297') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_6d60cyygl4.ttf?t=1755603992297') format('truetype');
src: url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.woff2?t=1762651161569') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.woff?t=1762651161569') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.ttf?t=1762651161569') format('truetype');
}
.iconfont {
@ -14,6 +14,78 @@
-moz-osx-font-smoothing: grayscale;
}
.icona-zhulixiangqingpc30:before {
content: "\e914";
}
.icona-zuidijiapc30:before {
content: "\e915";
}
.icona-zhulipc30:before {
content: "\e916";
}
.icona-zhulijiapc30:before {
content: "\e917";
}
.icona-kanhoujiapc30:before {
content: "\e918";
}
.icona-jiagepc30:before {
content: "\e919";
}
.icona-zhuliwanfapc30:before {
content: "\e91a";
}
.icona-zhulipc301:before {
content: "\e91b";
}
.icona-zhulilunbopc30:before {
content: "\e91c";
}
.icona-canyuxinxipc30:before {
content: "\e91d";
}
.icona-zhulixiangqingpc301:before {
content: "\e91e";
}
.iconyoujiantou:before {
content: "\e913";
}
.iconanzhuang1:before {
content: "\e90d";
}
.icongengxin:before {
content: "\e90e";
}
.iconliebiao:before {
content: "\e90f";
}
.iconyijianshengji:before {
content: "\e910";
}
.iconliebiaoqiehuan:before {
content: "\e911";
}
.iconyijianxiufu:before {
content: "\e912";
}
.icona-bijiPC30:before {
content: "\e90b";
}

View File

@ -131,8 +131,9 @@ export function img(path: string): string {
if (typeof path == 'string' && path.startsWith('/')) path = path.replace(/^\//, '')
if (typeof imgDomain == 'string' && imgDomain.endsWith('/')) imgDomain = imgDomain.slice(0, -1)
return isUrl(path) ? path : `${imgDomain}/${path}`
if(path){
return isUrl(path) ? path : `${imgDomain}/${path}`
}
}
/**

15581
uni-app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -53,6 +53,7 @@
"@dcloudio/uni-mp-toutiao": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-weixin": "3.0.0-3080720230703001",
"@dcloudio/uni-quickapp-webview": "3.0.0-3080720230703001",
"@uni-helper/vite-plugin-uni-layouts": "^0.1.11",
"html2canvas": "^1.4.1",
"image-tools": "^1.4.0",
"lodash-es": "^4.17.21",
@ -77,6 +78,7 @@
"@types/sortablejs": "^1.15.0",
"@vue/tsconfig": "^0.1.3",
"sass": "^1.54.5",
"sass-loader": "10.4.1",
"typescript": "^4.9.4",
"vite": "4.0.4",
"vite-plugin-windicss": "^1.8.10",

View File

@ -9,7 +9,6 @@ import { useLogin } from '@/hooks/useLogin'
import { useShare } from '@/hooks/useShare'
onLaunch((data: any) => {
//
launchInterceptor()
const systemStore = useSystemStore()
@ -55,7 +54,6 @@ onLaunch((data: any) => {
}
}, false);
try {
uni.hideTabBar() // tabbar
} catch (e) {
@ -207,6 +205,10 @@ onLaunch((data: any) => {
//
uni.setStorageSync('isOnLoad', true); //
// #ifdef APP-PLUS
systemStore.getVersionInfoFn()
// #endif
})
onShow(() => {

View File

@ -1,170 +1,172 @@
<template>
<view class="diy-group" id="componentList">
<top-tabbar v-if="data.global && Object.keys(data.global).length && data.global.topStatusBar && data.global.topStatusBar.isShow"
:scrollBool="diyGroup.componentsScrollBool.TopTabbar" ref="topTabbarRef" :data="data.global" />
<top-tabbar :scrollBool="diyGroup.componentsScrollBool.TopTabbar" v-if="data.global && Object.keys(data.global).length && data.global.topStatusBar && data.global.topStatusBar.isShow" ref="topTabbarRef" :data="data.global" />
<pop-ads v-if="data.global && Object.keys(data.global).length && data.global.popWindow && data.global.popWindow.show" ref="popAbsRef" :data="data.global" />
<template v-for="(component, index) in data.value" :key="component.id">
<view
v-show="component.componentIsShow"
@click="diyStore.changeCurrentIndex(index, component)"
:class="diyGroup.getComponentClass(index,component)" :style="component.pageStyle"
>
<view class="relative" :style="{ marginTop : component.margin.top < 0 ? (component.margin.top * 2) + 'rpx' : '0' }">
<!-- 装修模式下设置负上边距后超出的内容禁止选中设置 -->
<view v-if="diyGroup.isShowPlaceHolder(index,component)" class="absolute w-full z-1"
:style="{ height : (component.margin.top * 2 * -1) + 'rpx' }"
@click.stop="diyGroup.placeholderEvent"></view>
<template v-if="component.componentName == 'GraphicNav'">
<diy-graphic-nav :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'HorzBlank'">
<diy-horz-blank :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'HorzLine'">
<diy-horz-line :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'HotArea'">
<diy-hot-area :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'ImageAds'">
<diy-image-ads :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'MemberInfo'">
<diy-member-info :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'MemberLevel'">
<diy-member-level :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'Notice'">
<diy-notice :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'RubikCube'">
<diy-rubik-cube :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'Text'">
<diy-text :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'RichText'">
<diy-rich-text :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'ActiveCube'">
<diy-active-cube :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FloatBtn'">
<diy-float-btn :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'CarouselSearch'">
<diy-carousel-search :scrollBool="diyGroup.componentsScrollBool.CarouselSearch" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'PictureShow'">
<diy-picture-show :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormSubmit'">
<diy-form-submit :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormInput'">
<diy-form-input ref="diyFormInputRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormTextarea'">
<diy-form-textarea ref="diyFormTextareaRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormIdentity'">
<diy-form-identity ref="diyFormIdentityRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormEmail'">
<diy-form-email ref="diyFormEmailRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormMobile'">
<diy-form-mobile ref="diyFormMobileRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormWechatName'">
<diy-form-wechat-name ref="diyFormWechatNameRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormNumber'">
<diy-form-number ref="diyFormNumberRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormRadio'">
<diy-form-radio ref="diyFormRadioRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormCheckbox'">
<diy-form-checkbox ref="diyFormCheckboxRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormTable'">
<diy-form-table ref="diyFormTableRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormDate'">
<diy-form-date ref="diyFormDateRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormDateScope'">
<diy-form-date-scope ref="diyFormDateScopeRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormTime'">
<diy-form-time ref="diyFormTimeRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormTimeScope'">
<diy-form-time-scope ref="diyFormTimeScopeRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormLocation'">
<diy-form-location ref="diyFormLocationRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormAddress'">
<diy-form-address ref="diyFormAddressRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormImage'">
<diy-form-image ref="diyFormImageRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormVideo'">
<diy-form-video ref="diyFormVideoRef" :component="component" :global="data.global" :index="index" />
</template>
<template v-if="component.componentName == 'FormFile'">
<diy-form-file ref="diyFormFileRef" :component="component" :global="data.global" :index="index" />
</template>
<view v-show="component.componentIsShow"
@click="diyStore.changeCurrentIndex(index, component)"
:class="diyGroup.getComponentClass(index,component)" :style="component.pageStyle">
<view class="relative" :style="{ marginTop : component.margin.top < 0 ? (component.margin.top * 2) + 'rpx' : '0', marginBottom : component.margin.bottom < 0 ? (component.margin.bottom * 2) + 'rpx' : '0' }">
<!-- 装修模式下设置负上边距后超出的内容禁止选中设置 -->
<view v-if="diyGroup.isShowPlaceHolder(index,component)" class="absolute w-full z-1" :style="{ height : (component.margin.top * 2 * -1) + 'rpx' }" @click.stop="diyGroup.placeholderEvent"></view>
<template v-if="component.componentName == 'ActiveCube'">
<diy-active-cube ref="diyActiveCubeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ActiveCube" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'CarouselSearch'">
<diy-carousel-search ref="diyCarouselSearchRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.CarouselSearch" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FloatBtn'">
<diy-float-btn ref="diyFloatBtnRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FloatBtn" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormAddress'">
<diy-form-address ref="diyFormAddressRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormAddress" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormCheckbox'">
<diy-form-checkbox ref="diyFormCheckboxRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormCheckbox" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormDate'">
<diy-form-date ref="diyFormDateRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormDate" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormDateScope'">
<diy-form-date-scope ref="diyFormDateScopeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormDateScope" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormEmail'">
<diy-form-email ref="diyFormEmailRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormEmail" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormFile'">
<diy-form-file ref="diyFormFileRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormFile" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormIdentity'">
<diy-form-identity ref="diyFormIdentityRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormIdentity" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormIdentityPrivacy'">
<diy-form-identity-privacy ref="diyFormIdentityPrivacyRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormIdentityPrivacy" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormImage'">
<diy-form-image ref="diyFormImageRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormImage" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormInput'">
<diy-form-input ref="diyFormInputRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormInput" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormLocation'">
<diy-form-location ref="diyFormLocationRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormLocation" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormMobile'">
<diy-form-mobile ref="diyFormMobileRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormMobile" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormNumber'">
<diy-form-number ref="diyFormNumberRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormNumber" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormPrivacy'">
<diy-form-privacy ref="diyFormPrivacyRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormPrivacy" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormPrivacyPop'">
<diy-form-privacy-pop ref="diyFormPrivacyPopRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormPrivacyPop" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormRadio'">
<diy-form-radio ref="diyFormRadioRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormRadio" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormSubmit'">
<diy-form-submit ref="diyFormSubmitRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormSubmit" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormTable'">
<diy-form-table ref="diyFormTableRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTable" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormTextarea'">
<diy-form-textarea ref="diyFormTextareaRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTextarea" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormTime'">
<diy-form-time ref="diyFormTimeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTime" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormTimeScope'">
<diy-form-time-scope ref="diyFormTimeScopeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTimeScope" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormVideo'">
<diy-form-video ref="diyFormVideoRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormVideo" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'FormWechatName'">
<diy-form-wechat-name ref="diyFormWechatNameRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormWechatName" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'GraphicNav'">
<diy-graphic-nav ref="diyGraphicNavRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.GraphicNav" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'HorzBlank'">
<diy-horz-blank ref="diyHorzBlankRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.HorzBlank" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'HorzLine'">
<diy-horz-line ref="diyHorzLineRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.HorzLine" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'HotArea'">
<diy-hot-area ref="diyHotAreaRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.HotArea" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'ImageAds'">
<diy-image-ads ref="diyImageAdsRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ImageAds" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'MemberInfo'">
<diy-member-info ref="diyMemberInfoRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.MemberInfo" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'MemberLevel'">
<diy-member-level ref="diyMemberLevelRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.MemberLevel" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'Notice'">
<diy-notice ref="diyNoticeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.Notice" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'PictureShow'">
<diy-picture-show ref="diyPictureShowRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.PictureShow" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'RichText'">
<diy-rich-text ref="diyRichTextRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.RichText" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'RubikCube'">
<diy-rubik-cube ref="diyRubikCubeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.RubikCube" @update:componentIsShow="component.componentIsShow = $event" />
</template>
<template v-if="component.componentName == 'Text'">
<diy-text ref="diyTextRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.Text" @update:componentIsShow="component.componentIsShow = $event" />
</template>
</view>
</view>
</template>
<template v-if="diyStore.mode == '' && data.global && diyGroup.showCopyright.value && data.global.copyright && data.global.copyright.isShow">
<copy-right :textColor="data.global.copyright.textColor" />
</template>
<template v-if="diyStore.mode == '' && data.global && data.global.bottomTabBar && data.global.bottomTabBar.isShow">
<view class="pt-[20rpx]"></view>
<tabbar />
<tabbar :addon="data.global.bottomTabBar.designNav.key" />
</template>
</view>
</template>
<script lang="ts" setup>
import topTabbar from '@/components/top-tabbar/top-tabbar.vue'
import popAds from '@/components/pop-ads/pop-ads.vue'
import { useDiyGroup } from './useDiyGroup'
import useDiyStore from '@/app/stores/diy';
import { ref, getCurrentInstance } from 'vue';
import topTabbar from '@/components/top-tabbar/top-tabbar.vue'
import popAds from '@/components/pop-ads/pop-ads.vue'
import useDiyStore from '@/app/stores/diy';
import { useDiyGroup } from './useDiyGroup';
import { ref,getCurrentInstance } from 'vue';
const props = defineProps(['data']);
const instance: any = getCurrentInstance();
const getFormRef = () => {
return {
componentRefs: instance.refs
}
}
const props = defineProps(['data']);
const instance: any = getCurrentInstance();
const getFormRef = () => {
return {
componentRefs: instance.refs
}
}
const diyStore = useDiyStore();
const diyGroup = useDiyGroup({
...props,
getFormRef
});
const data = ref(diyGroup.data);
const diyStore = useDiyStore();
//
diyGroup.onMounted();
const diyGroup = useDiyGroup({
...props,
getFormRef
});
const data = ref(diyGroup.data)
//
diyGroup.onMounted()
//
diyGroup.onPageScroll()
defineExpose({
refresh: diyGroup.refresh,
getFormRef
})
//
diyGroup.onPageScroll();
defineExpose({
refresh: diyGroup.refresh,
getFormRef
})
</script>
<style lang="scss" scoped>
@import './index.scss';
@import './index.scss';
</style>

View File

@ -5,6 +5,8 @@ import { onPageScroll, onHide, onShow } from '@dcloudio/uni-app';
import useDiyStore from '@/app/stores/diy';
export function useDiyGroup(params: any = {}) {
// 新增:控制版权显示的状态,默认隐藏
const showCopyright = ref(false);
let scrollVal: any = ""; //组件滚动值集合
const componentsScrollBool: any = ref({}); //组件是否根据滚动进行相应改变
@ -98,6 +100,9 @@ export function useDiyGroup(params: any = {}) {
componentsScrollBool.value[key] = -1;
}
}
// 新增:页面加载完成后,显示版权
showCopyright.value = true;
}, 500)
});
});
@ -174,11 +179,13 @@ export function useDiyGroup(params: any = {}) {
scrollV: scrollValStr.value,
data: data.value,
componentsScrollBool: componentsScrollBool.value,
showCopyright, // 新增:暴露版权显示状态
placeholderEvent,
refresh,
isShowPlaceHolder,
getComponentClass,
onPageScroll: onPageScrollLifeCycle,
onMounted: onMountedLifeCycle
}
}

View File

@ -0,0 +1,3 @@
{
"prompt": "template"
}

View File

@ -130,4 +130,14 @@ export function bind(data: AnyObject) {
*/
export function memberLog(data: AnyObject) {
return request.post('member/log', data, { showErrorMessage: false })
}
/**
* app授权登录
*/
export function wxappLogin(data: AnyObject) {
if (uni.getStorageSync('pid')) {
data.pid = uni.getStorageSync('pid');
}
return request.post('wxapp/login', data, { showErrorMessage: false })
}

View File

@ -157,4 +157,11 @@ export function getInitInfo(params: Record<string, any>) {
*/
export function getMemberMobileExist(params: Record<string, any>) {
return request.get('member_mobile_exist', params)
}
}
/**
*
*/
export function getNewVersion(params: Record<string, any>) {
return request.get('app/newversion', params)
}

View File

@ -65,7 +65,7 @@
<!-- <view class="line" :style="{'background-color': getTabColor(currTabIndex == -1)}" v-if="currTabIndex == -1"></view> -->
</view>
<view v-for="(item, index) in diyComponent.tab.list" class="scroll-item" :class="[{ active: index == currTabIndex }]" @click="changeData(item,index)" :id="'a' + index" :key="index">
<view class="name" :style="{'color': getTabColor(index == currTabIndex)}">{{ item.text }}</view>
<view class="name" :style="{'color': getTabColor(index == currTabIndex)}">{{ item ? item.text : '' }}</view>
<!-- <view class="line" :style="{'background-color': getTabColor(index == currTabIndex)}" v-if="index == currTabIndex"></view> -->
</view>
</scroll-view>
@ -148,8 +148,8 @@
<script setup lang="ts">
//
import { ref, reactive, computed, watch, onMounted, nextTick, getCurrentInstance } from 'vue';
import { img } from '@/utils/common';
import { ref, reactive, computed, watch, onMounted, nextTick, getCurrentInstance, onUnmounted } from 'vue';
import { img, getTopFixedStatusName } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
import diyGroup from '@/addon/components/diy/group/index.vue';
import { getDiyInfo } from '@/app/api/diy';
@ -164,6 +164,12 @@ const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
// h5
if(uni.getStorageSync(getTopFixedStatusName(props.global))){
diyStore.topFixedStatus = uni.getStorageSync(getTopFixedStatusName(props.global));
}else{
diyStore.topFixedStatus = 'home'
}
return props.component;
}
})
@ -376,8 +382,8 @@ const changeData = (item: any, index: any) => {
//
diyStore.topFixedStatus = 'diy'
getDiyInfoFn(item.diy_id);
}
uni.setStorageSync(getTopFixedStatusName(props.global), diyStore.topFixedStatus);
}
const tabAllPopup = ref(false);
@ -389,6 +395,11 @@ const navbarInnerStyle = ref('')
//
navbarInnerStyle.value += 'padding-top:' + systemStore.menuButtonInfo.top + 'px;';
// #endif
// #ifdef APP-PLUS
navbarInnerStyle.value += 'padding-top:' + systemStore.systemInfo.statusBarHeight + 'px;';
// #endif
onMounted(() => {
refresh();
//
@ -397,6 +408,7 @@ onMounted(() => {
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'CarouselSearch') {
diyStore.topFixedStatus = 'home'; // home
refresh();
}
}
@ -427,11 +439,14 @@ onMounted(() => {
// #endif
});
onUnmounted(() => {
uni.removeStorageSync(getTopFixedStatusName(props.global))
})
const refresh = () => {
setModuleLocation();
//
locationVal.refresh();
changeData({ source: 'home' }, -1)
diyComponent.value.swiper.list.forEach((item: any) => {
if (item.imageUrl == '') {
@ -471,6 +486,7 @@ const getDiyInfoFn = (id: any) => {
diyPageData.value = sources.value;
diyPageData.value.forEach((item: any, index) => {
item.componentIsShow = true //
item.pageStyle = '';
if (item.pageStartBgColor) {
if (item.pageStartBgColor && item.pageEndBgColor) item.pageStyle += `background:linear-gradient(${ item.pageGradientAngle },${ item.pageStartBgColor },${ item.pageEndBgColor });`;

View File

@ -321,8 +321,9 @@ const calcFourSquare = () => {
}
});
diyComponent.value.list.forEach((item: any, index: any) => {
item.imgWidth = 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 2) + 'px) / 2)';
item.widthStyle = item.imgWidth;
//ratio
// item.imgWidth = 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 2) + 'px) / 2)';
item.widthStyle = 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 2) + 'px) / 2)';
if (index <= 1) {
item.imgHeight = maxHeightFirst;
} else if (index > 1) {

View File

@ -1,6 +1,6 @@
<template>
<view class="w-screen h-screen flex flex-col" :style="themeColor()">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view>
@ -28,7 +28,7 @@
</u-form>
<view class="mt-[100rpx]">
<view v-if="config.agreement_show" class="flex items-center mb-[20rpx] py-[10rpx]" @click.stop="agreeChange">
<u-checkbox-group @change="agreeChange">
<u-checkbox-group @change="agreeChange">
<u-checkbox activeColor="var(--primary-color)" :checked="isAgree" shape="circle" size="24rpx" :customStyle="{ 'marginTop': '4rpx' }" />
</u-checkbox-group>
<view class="text-[24rpx] text-[var(--text-color-light6)] flex items-center flex-wrap">

View File

@ -1,7 +1,7 @@
<template>
<view class="w-screen h-screen" :style="themeColor()">
<view class="w-screen h-screen" :style="warpStyle">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view>
@ -16,7 +16,7 @@
</view>
<view v-else class="h-[90rpx] w-[300rpx]"></view>
</view>
<view class="text-[var(--text-color-light6)]] text-[28rpx] text-center leading-[34rpx] min-h-[34rpx] mt-[40rpx]">{{ loginConfig.desc }}</view>
<view class="text-[var(--text-color-light6)]] text-[28rpx] text-center leading-[34rpx] min-h-[34rpx] mt-[40rpx]">{{ loginConfig.desc }}</view>
<view class="mt-[181rpx]">
<!-- #ifdef H5 -->
<!-- 微信公众号快捷登录开启自动注册的情况下才能使用 -->
@ -52,11 +52,11 @@
<!-- 手机号登录 -->
<view v-if="loginConfig.is_mobile" class="mb-[40rpx] w-full flex items-center justify-center">
<!-- #ifdef H5 -->
<!-- #ifndef MP -->
<button class="w-[630rpx] h-[88rpx] !mx-[0] !bg-[#fff] border-[var(--primary-color)] border-solid border-[2rpx] text-[26rpx] rounded-[44rpx] leading-[84rpx] !text-[var(--primary-color)]" @click="redirect({ url: '/app/pages/auth/login',param:{type:'mobile'}})">{{ t('mobileLogin') }}</button>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP -->
<button v-if="authRegisterLogin && loginConfig.is_mobile"
class="w-[630rpx] h-[88rpx] !mx-[0] !bg-[#fff] border-[var(--primary-color)] border-solid border-[2rpx] text-[26rpx] rounded-[44rpx] leading-[84rpx] !text-[var(--primary-color)]"
@click="redirect({ url: '/app/pages/auth/login',param:{type:'mobile'}})">
@ -272,7 +272,7 @@ const oneClickLogin = (callback: any = null, data: any = null) => {
mask: true
});
loginLoading.value = true
if (!callback) {
callback = () => {
loginLoading.value = false

View File

@ -1,6 +1,6 @@
<template>
<view class="w-screen h-screen flex flex-col " :style="themeColor()" v-if="type">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view>
@ -28,9 +28,12 @@
class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx"
placeholderClass="!text-[var(--text-color-light9)] text-[26rpx]">
<template #suffix>
<!-- #ifndef APP-PLUS -->
<view @click="changePassword" v-if="formData.password">
<u-icon :name="isPassword?'eye-off':'eye-fill'" color="#b9b9b9" size="20"></u-icon>
<u-icon name="eye-off" color="#b9b9b9" size="20" v-if="isPassword"></u-icon>
<u-icon name="eye-fill" color="#b9b9b9" size="20" v-else></u-icon>
</view>
<!-- #endif -->
</template>
</u-input>
</u-form-item>
@ -163,7 +166,7 @@ onLoad(async(option: any) => {
// #ifdef MP-WEIXIN
uni.getStorageSync('openid') && (Object.assign(formData, { weapp_openid: uni.getStorageSync('openid') }))
// #endif
if (option.type) {
if (option.type == 'mobile') {
if (configStore.login.is_mobile) {
@ -205,6 +208,12 @@ onLoad(async(option: any) => {
isShowQuickLogin.value = false;
}
// #endif
// #ifdef APP-PLUS
if (systemStore.appConfig.wechat_app_id) {
isShowQuickLogin.value = true;
}
// #endif
})
const formData = reactive({
@ -285,8 +294,8 @@ const handleLogin = () => {
loading.value = true
const login = type.value == 'username' ? usernameLogin : mobileLogin
login(formData).then((res: any) => {
memberStore.setToken(res.data.token)
// todo
// if (configStore.login.is_bind_mobile && !res.data.mobile) {
@ -300,6 +309,7 @@ const handleLogin = () => {
}
const toLink = () => {
// #ifndef APP-PLUS
const pages = getCurrentPages(); //
if (pages.length > 1) {
const currentPage = pages[pages.length - 2].route;
@ -314,6 +324,17 @@ const toLink = () => {
} else {
redirect({ url: '/app/pages/auth/index', mode: 'redirectTo' })
}
// #endif
// #ifdef APP-PLUS
useLogin().getAuthCode({
successCallback: () => {
if (!getToken()) {
redirect({ url: '/app/pages/auth/bind', param: { 'register_type': 'wechat' }, mode: 'redirectTo' })
}
}
})
// #endif
}
const toResetpwd = () =>{

View File

@ -1,6 +1,6 @@
<template>
<view class="w-screen h-screen flex flex-col" :style="themeColor()" v-if="type">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view>

View File

@ -1,6 +1,6 @@
<template>
<view class="w-screen h-screen flex flex-col" :style="themeColor()">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view>

View File

@ -2,7 +2,7 @@
<view :style="themeColor()">
<view class="bg-[var(--page-bg-color)] min-h-screen overflow-hidden" v-if="Object.keys(friendsInfo).length && !loading">
<view :style="{background: 'url(' + img('static/resource/images/app/friendpay_money.png') + ') left bottom / cover no-repeat'}" class="pb-[194rpx] overflow-hidden">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view class="sticky top-0 left-0 right-0 z-100">
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
</view>
@ -37,8 +37,8 @@
<text class="nc-iconfont nc-icon-jichuxinxiV6xx text-[26rpx] "></text>
</view>
</view>
</view>
<view class="card-template sidebar-margin mb-[var(--top-m)]" v-if="friendsInfo.config.pay_info_switch">
<template v-if="JSON.stringify(friendsInfo.trade_info) !== '[]' && friendsInfo.trade_info.item_list.length">
@ -52,11 +52,11 @@
<view class="border-0 border-solid border-b-[1rpx] border-[#f6f6f6] mb-[20rpx]">
<view v-for="(item, index) in friendsInfo.trade_info.item_list" class="flex justify-between" :class="{' mb-[34rpx]': (index + 1) != friendsInfo.trade_info.length }">
<view class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden flex-shrink-0">
<u--image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
<up-image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
<template #error>
<image class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill" />
</template>
</u--image>
</up-image>
</view>
<view class="ml-[20rpx] flex flex-1 flex-col justify-between">
<view>

View File

@ -2,7 +2,7 @@
<view :style="themeColor()">
<view class="bg-[var(--page-bg-color)] min-h-screen overflow-hidden" v-if="Object.keys(friendsInfo).length && !loading">
<view :style="{background: 'url(' + img('static/resource/images/app/friendpay_bg.png') + ') left bottom /100% no-repeat'}" class="pb-[168rpx] overflow-hidden">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view class="sticky top-0 left-0 right-0 z-100">
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
</view>
@ -45,11 +45,11 @@
<view class="border-0 border-solid border-b-[1rpx] border-[#f6f6f6] mb-[20rpx]">
<view v-for="(item, index) in friendsInfo.trade_info.item_list" class="flex justify-between mb-[30rpx]">
<view class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden flex-shrink-0">
<u--image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
<up-image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
<template #error>
<image class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill" />
</template>
</u--image>
</up-image>
</view>
<view class="ml-[20rpx] flex flex-1 flex-col justify-between">
<view>
@ -201,7 +201,8 @@ const getFriendspayInfoFn = (tradeType : string, tradeId : number) => {
},
weapp: {
...share
}
},
isStop:true
});
// - end
copyUrlFn();

View File

@ -19,7 +19,10 @@
<!-- 小程序隐私协议 -->
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
<!-- #endif -->
<!-- 版本更新弹窗 -->
<!-- #ifdef APP -->
<update-version ref="updateVersionRef"></update-version>
<!-- #endif -->
</view>
</template>
@ -29,6 +32,8 @@ import { useDiy } from '@/hooks/useDiy'
import { redirect } from '@/utils/common';
import { useShare } from '@/hooks/useShare'
import diyGroup from '@/addon/components/diy/group/index.vue'
import updateVersion from '@/components/update-version/update-version.vue'
import useSystemStore from '@/stores/system';
const { setShare } = useShare()
@ -42,6 +47,11 @@ const diyGroupRef = ref(null)
const wxPrivacyPopupRef: any = ref(null)
const collectTipRef: any = ref(null)
const updateVersionRef: any = ref(null)
const systemStore = useSystemStore();
const versionInfo = systemStore.versionInfo
//
diy.onLoad();
@ -52,8 +62,17 @@ diy.onShow((data: any) => {
// title: diyData.title
// })
} else if (data.page) {
//
redirect({ url: data.page, mode: 'reLaunch' })
// -- app
if(versionInfo && versionInfo.is_forced_upgrade){
nextTick(() => {
if(updateVersionRef.value){
updateVersionRef.value.open()
}
})
}else{
//
redirect({ url: data.page, mode: 'reLaunch' })
}
}
let share = data.share ? JSON.parse(data.share) : null;
setShare(share);

View File

@ -80,8 +80,10 @@ import { redirect } from '@/utils/common'
import { t } from '@/locale'
import { addAddress, editAddress, getAddressInfo } from '@/app/api/member'
import manifestJson from '@/manifest.json'
import { getAddressByLatlng } from '@/app/api/system'
import { getAddressByLatlng,getAreatree } from '@/app/api/system'
import useSystemStore from '@/stores/system';
const systemStore = useSystemStore();
const formData: any = ref({
id: 0,
name: '',
@ -123,7 +125,7 @@ onLoad((data: any) => {
if (uni.getStorageSync('addressInfo')) {
Object.assign(formData.value, uni.getStorageSync('addressInfo'))
}
formData.value.address = data.name;
// formData.value.address = data.name;
getAddress(data.latng);
const tempArr = getQueryVariable('latng').split(',');
formData.value.lat = tempArr[0];
@ -133,6 +135,7 @@ onLoad((data: any) => {
if (selectAddress) {
addressType.value = selectAddress.delivery == 'express' ? 'address' : 'locationAddress';
}
getAreatreeFn()
// #ifdef MP
nextTick(() => {
if (wxPrivacyPopupRef.value) wxPrivacyPopupRef.value.proactive();
@ -256,8 +259,13 @@ const save = () => {
//
const chooseLocation = () => {
// #ifdef MP
let latitude = systemStore.diyAddressInfo ? systemStore.diyAddressInfo.latitude : '';
let longitude = systemStore.diyAddressInfo ? systemStore.diyAddressInfo.longitude : '';
// #ifndef H5
uni.chooseLocation({
latitude,
longitude,
success: (res) => {
res.latitude && (formData.value.lat = res.latitude)
res.longitude && (formData.value.lng = res.longitude)
@ -301,6 +309,9 @@ const chooseLocation = () => {
const getAddress = (latlng: any) => {
getAddressByLatlng({ latlng }).then((res: any) => {
if (res.data) {
// #ifdef H5
formData.value.address = res.data.formatted_addresses.recommend;
// #endif
formData.value.full_address = '';
formData.value.full_address += res.data.province != undefined ? res.data.province : '';
formData.value.full_address += res.data.city != undefined ? '' + res.data.city : '';
@ -308,7 +319,6 @@ const getAddress = (latlng: any) => {
formData.value.address_name = formData.value.full_address.replace(/-/g, '');
formData.value.area = (res.data.province + res.data.city + res.data.district) || res.data.full_address;
formData.value.province_id = res.data.province_id != undefined ? res.data.province_id : 0;
formData.value.city_id = res.data.city_id != undefined ? res.data.city_id : 0;
formData.value.district_id = res.data.district_id != undefined ? res.data.district_id : 0;
@ -330,20 +340,46 @@ const getQueryVariable = (variable: any) => {
}
return false;
}
const areaTree = ref([]); //
const getAreatreeFn = () => {
getAreatree(3).then(res => {
areaTree.value = res.data
}).catch()
}
// idid
const findIdByNameAndParentId = (list, name, parentId = 0) => {
for (const item of list) {
if (item.name === name && item.pid === parentId) {
return item.id;
}
if (item.child && item.child.length > 0) {
const childId = findIdByNameAndParentId(item.child, name, item.id);
if (childId) {
return childId;
}
}
}
return '';
};
const loadingchoosegAddress = ref(false)
const choosegAddress = () =>{
loadingchoosegAddress.value = true
uni.chooseAddress({
success(res) {
console.log(7744)
loadingchoosegAddress.value = false
// --
// const formattedAddress = `${res.userName}-${res.telNumber}-${res.provinceName}${res.cityName}${res.countyName}${res.detailInfoNew || res.detailInfo}`;
console.log(res)
formData.value.name = res.userName
formData.value.mobile = res.telNumber
formData.value.area = res.provinceName + res.cityName + res.countyName
formData.value.address = res.detailInfo
formData.value.name = res.userName
formData.value.mobile = res.telNumber
formData.value.area = res.provinceName + res.cityName + res.countyName
formData.value.address = res.detailInfo
// id
formData.value.province_id = findIdByNameAndParentId(areaTree.value, res.provinceName);
// idid
formData.value.city_id = findIdByNameAndParentId(areaTree.value, res.cityName, formData.value.province_id);
// idid
formData.value.district_id = findIdByNameAndParentId(areaTree.value, res.countyName, formData.value.cityId);
},
fail(err) {
loadingchoosegAddress.value = false

View File

@ -2,7 +2,7 @@
<view class="min-h-[100vh] !bg-[var(--page-bg-color)]" :style="themeColor()" v-if="memberStore.info">
<view class="fixed w-full z-2 !bg-[var(--page-bg-color)]">
<view class="pb-[190rpx] text-[#fff] w-full" :style="headerStyle">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<top-tabbar :data="param" class="top-header" />
<!-- #endif -->
<view class="leading-[38rpx] text-[28rpx] pl-[60rpx] pt-[100rpx]">{{ t('accountBalance') }}</view>
@ -153,6 +153,9 @@ const mescrollTop = computed(() => {
return '718rpx'
}
} else {
// #ifdef APP-PLUS
return (pxToRpx(Number(systemStore.systemInfo.statusBarHeight)) + pxToRpx(8) + 632) + 'rpx'
// #endif
if (Object.keys(systemStore.menuButtonInfo).length) {
return (pxToRpx(Number(systemStore.menuButtonInfo.height)) + pxToRpx(systemStore.menuButtonInfo.top) + pxToRpx(8) + 632) + 'rpx'
} else {

View File

@ -2,7 +2,7 @@
<view class="bg-[var(--page-bg-color)] min-h-[100vh] w-full" :style="themeColor()" v-if="memberStore.info">
<view class="fixed w-full z-2 !bg-[var(--page-bg-color)]">
<view class="pb-[272rpx]" :style="headerStyle">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<top-tabbar :data="param" class="top-header" />
<!-- #endif -->
</view>

View File

@ -17,12 +17,19 @@
import { t } from '@/locale'
import { img } from '@/utils/common';
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
const sendMessageTitle = ref('')
const sendMessagePath = ref('')
const sendMessageImg = ref('')
sendMessageImg.value = img('static/resource/images/member/contact_service.png')
onLoad((data: any) => {
data.send_title && (sendMessageTitle.value = data.send_title)
data.send_path && (sendMessagePath.value = decodeURIComponent(data.send_path))
data.send_img && (sendMessageImg.value = decodeURIComponent(data.send_img))
})
</script>
<style lang="scss" scoped>

View File

@ -1,14 +1,14 @@
<template>
<view class="min-h-[100vh] bg-[var(--page-bg-color)] overflow-hidden" :style="themeColor()">
<view class="fixed left-0 right-0 top-0 z-99">
<view class="bg-[#fff] px-[var(--sidebar-m)] py-[14rpx] flex items-center">
<view class="search-input">
<view class="bg-[#f6f6f6] px-[var(--sidebar-m)] py-[14rpx] flex items-center">
<view class="search-input bg-[#fff]">
<text class="nc-iconfont nc-icon-sousuo-duanV6xx1 btn" @click="searchTypeFn()"></text>
<input class="input" maxlength="50" type="text" v-model="keyword" placeholder="请输入搜索关键词" placeholderClass="text-[var(--text-color-light9)] text-[24rpx]" confirm-type="search" @confirm="searchTypeFn()">
<text v-if="keyword" class="nc-iconfont nc-icon-cuohaoV6xx1 clear" @click="keyword=''"></text>
</view>
</view>
<view class="tab-style-1 pt-[4rpx] bg-[#fff]">
<view class="tab-style-1 pt-[4rpx] bg-[#f6f6f6]">
<view class="tab-left">
<view class="tab-left-item"
:class="{'!text-primary class-select':fromType.from_type === item.from_type && fromType.account_data_gt === item.account_data_gt}"

View File

@ -2,7 +2,7 @@
<view :style="themeColor()" class="bg-[var(--page-bg-color)] min-h-[100vh] overflow-hidden">
<loading-page :loading="loading && memberInfo"></loading-page>
<view v-if="!loading && memberInfo && list && list.length" class="min-h-[100vh] overflow-hidden flex flex-col" :style="{backgroundColor: currLevelInfo.level_style.bg_color }">
<!-- #ifdef MP -->
<!-- #ifdef MP || APP-PLUS -->
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
<!-- #endif -->
<view>
@ -111,7 +111,7 @@
</view>
</view>
<view v-if="!loading && (!list || !list.length)">
<!-- #ifdef MP -->
<!-- #ifdef MP || APP-PLUS -->
<top-tabbar :data="topTabbarDataEmpty" />
<!-- #endif -->
<view class="empty-page" >
@ -119,7 +119,7 @@
<text class="desc">暂无会员等级</text>
</view>
</view>
</view>
</template>

View File

@ -3,7 +3,7 @@
<template v-if="!loading">
<view class="w-full bg-[var(--page-bg-color)]">
<view class="pb-[210rpx] relative" :style="headerStyle">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
<!-- #endif -->
<view class="text-[70rpx] leading-[90rpx] text-[#fff] pl-[60rpx] font-500 pt-[77rpx] price-font">{{ pointInfo.point || 0 }}</view>
@ -124,7 +124,10 @@ const headerStyle = computed(() => {
}
})
const topStyle = computed(() => {
let style = Object.keys(systemStore.menuButtonInfo).length ? (pxToRpx(Number(systemStore.menuButtonInfo.height)) + pxToRpx(systemStore.menuButtonInfo.top) + 50) + 'rpx;' : '50rpx'
let style = Object.keys(systemStore.menuButtonInfo).length ? (pxToRpx(Number(systemStore.menuButtonInfo.height)) + pxToRpx(systemStore.menuButtonInfo.top) + 50) + 'rpx' : '50rpx'
// #ifdef APP-PLUS
style = '120rpx'
// #endif
return style
})

View File

@ -1,8 +1,8 @@
<template>
<view class="bg-[var(--page-bg-color)] min-h-[100vh]" :style="themeColor()">
<view class="fixed left-0 right-0 top-0 z-10085">
<view class="bg-[#fff] px-[30rpx] h-[88rpx] flex-center relative z-10084">
<view class="search-input">
<view class="bg-[#f6f6f6] px-[30rpx] h-[88rpx] pt-[10rpx] flex-center relative z-10084">
<view class="search-input bg-[#fff]">
<view class="flex-1 text-[24rpx] leading-[60rpx] text-[var(--text-color-light9)]" :class="{'!text-[#333]':from_type}" @click="typePopup = true">{{ from_type_name || '请选择来源用途' }}</view>
<text class="nc-iconfont nc-icon-shangV6xx-1 !text-[26rpx] ml-[18rpx] !text-[var(--text-color-light6)]" v-if="typePopup" @click="typePopup = false"></text>
<text class="nc-iconfont nc-icon-xiaV6xx !text-[26rpx] ml-[18rpx] !text-[var(--text-color-light6)]" v-else @click="typePopup = true"></text>
@ -19,7 +19,7 @@
</view>
</u-popup>
</view>
<view class="px-[var(--sidebar-m)] py-[30rpx] flex items-center justify-between">
<view class="px-[var(--sidebar-m)] pb-[20rpx] pt-[20rpx] bg-[#f6f6f6] flex items-center justify-between">
<view class="flex items-center">
<view class="px-[30rpx] bg-[#fff] rounded-[30rpx] text-[24rpx] leading-[54rpx] mr-[20rpx] text-[#333]"
:class="{'!text-[var(--primary-color)] font-500':amount_type == item.status}"
@ -31,9 +31,9 @@
</view>
</view>
</view>
<mescroll-body ref="mescrollRef" @init="mescrollInit" :down="{ use: false }" @up="getPointListFn" top="202rpx">
<mescroll-body ref="mescrollRef" @init="mescrollInit" :down="{ use: false }" @up="getPointListFn" top="185rpx">
<view v-for="(item,index) in pointList" :key="index"
class="sidebar-margin card-template mb-[var(--top-m)]">
class="sidebar-margin card-template mt-[var(--top-m)]">
<view class="flex justify-between items-center">
<view class="text-[#333]">
<text class="text-[26rpx]">{{ item.month_info.year }}</text>

View File

@ -3,7 +3,7 @@
<view class="min-h-screen overflow-hidden" v-if="Object.values(info).length" :class="{ 'bg-[#F6F6F6]' : info && info.is_use }">
<view v-if="info.is_use">
<view class="sigin-header">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifndef H5 -->
<view v-if="info.rule_explain" class="side-tab" :style="{top: topStyle}" @click="signPopup = true">
<text class="nc-iconfont nc-icon-a-meiriqiandaoV6xx-36 icon"></text>
<text class="desc">签到规则</text>
@ -154,7 +154,7 @@
</view>
</view>
<view class="h-[100vh] w-[100vw] flex justify-center items-center" v-else>
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
<!-- #endif -->
<u-empty text="签到未开启" width="347rpx" height="265rpx" :icon="img('static/resource/images/system/empty.png')" />
@ -470,11 +470,22 @@ let topTabbarData = topTabarObj.setTopTabbarParam({ title: '我的签到' })
/********* 自定义头部 - end ***********/
const headStyle = computed(() => {
// #ifdef MP
let style = pxToRpx(Number(systemStore.menuButtonInfo.height) + systemStore.menuButtonInfo.top + 8) + 382 + 'rpx;'
// #endif
// #ifdef APP-PLUS
let style = pxToRpx(systemStore.systemInfo.statusBarHeight + 8) + 382 + 'rpx'
// #endif
return style
})
const topStyle = computed(() => {
// #ifdef MP
let style = pxToRpx(Number(systemStore.menuButtonInfo.height) + systemStore.menuButtonInfo.top + 8) + 50 + 'rpx;'
// #endif
// #ifdef APP-PLUS
let style = pxToRpx(systemStore.systemInfo.statusBarHeight + 8) + 50 + 'rpx'
// #endif
console.log(style)
return style
})
</script>

View File

@ -5,6 +5,14 @@
<u-cell :title="t('personalSettings')" :is-link="true" url="/app/pages/member/personal"></u-cell>
<u-cell :title="t('switchLang')" :is-link="true" :value="lang" @click="langSheetShow = true"></u-cell>
<u-cell :title="t('version')" :value="version"></u-cell>
<!-- #ifdef APP -->
<u-cell :title="'检测到新版本' + versionInfo.version_name" :is-link="true" @click="checkUpdate" v-if="versionInfo">
<template #value>
<text class="u-slot-value flex items-center">立即升级<text class="ml-[5px] w-[10rpx] h-[10rpx] bg-[red] rounded-[10rpx]"></text></text>
</template>
</u-cell>
<u-cell title="版本更新" :is-link="true" value="检查版本更新" @click="checkUpdate" v-else/>
<!-- #endif -->
</u-cell-group>
</view>
<view class="mb-[var(--top-m)] sidebar-margin card-template !py-[20rpx]">
@ -19,6 +27,10 @@
<u-action-sheet :actions="langList" :show="langSheetShow" :closeOnClickOverlay="true"
:safeAreaInsetBottom="true"
@close="langSheetShow = false" @select="switchLang"></u-action-sheet>
<!-- 版本更新弹窗 -->
<!-- #ifdef APP -->
<!-- <update-version ref="updateVersionRef"></update-version> -->
<!-- #endif -->
</view>
</template>
@ -26,10 +38,30 @@
import { ref, reactive, computed } from 'vue'
import useMemberStore from '@/stores/member'
import { t, language } from '@/locale'
import updateVersion from '@/components/update-version/update-version.vue'
import useSystemStore from '@/stores/system';
const memberStore = useMemberStore()
const systemStore = useSystemStore();
const versionInfo = computed(() => {
return systemStore.versionInfo
})
const version = ref(import.meta.env.VITE_APP_VERSION)
// #ifdef APP
plus.runtime.getProperty(plus.runtime.appid, (inf) => {
//
version.value = inf.version
})
// #endif
// #ifndef APP
//app
const systemInfo = uni.getSystemInfoSync();
version.value = systemInfo.appVersion;
// #endif
const updateVersionRef: any = ref(null)
/**
* 支持的语言列表
@ -52,6 +84,18 @@ const lang = computed(() => {
const switchLang = (lang) => {
language.loadAllLocaleMessages('app', lang.value)
}
const checkUpdate = ()=>{
if(versionInfo.value && versionInfo.value.version_name){
systemStore.showUpdateVersion()
// uni.$emit('showUpdateVersion',{})
}else{
uni.showToast({
title: '当前版本已是最新版本!',
icon: 'none'
});
}
}
</script>
<style lang="scss" scoped>

View File

@ -124,7 +124,7 @@ const checkIsVerifier = () => {
}
const scanCode = () => {
// #ifdef MP
// #ifndef H5
uni.scanCode({
onlyFromCamera: true,
success: res => {

View File

@ -1,8 +1,8 @@
<template>
<view class="bg-[var(--page-bg-color)] min-h-screen overflow-hidden" :style="themeColor()">
<view class="fixed left-0 right-0 top-0 z-99 bg-[#fff]">
<view class="fixed left-0 right-0 top-0 z-99 bg-[#f6f6f6]">
<view class="py-[14rpx] flex items-center justify-between px-[20rpx]">
<view class="flex-1 search-input mr-[20rpx]">
<view class="flex-1 search-input bg-[#fff] mr-[20rpx]">
<text @click.stop="searchTypeFn()" class="nc-iconfont nc-icon-sousuo-duanV6xx1 btn"></text>
<input class="input" maxlength="50" type="text" v-model="keyword" placeholder="请输入搜索关键词" placeholderClass="text-[var(--text-color-light9)] text-[24rpx]" confirm-type="search" @confirm="searchTypeFn()">
<text v-if="keyword" class="nc-iconfont nc-icon-cuohaoV6xx1 clear" @click="keyword=''"></text>
@ -21,11 +21,11 @@
<text class="text-[#303133] text-[26rpx] font-400 nc-iconfont nc-icon-fuzhiV6xx1 ml-[11rpx]" @click.stop="copy(item.code)"></text>
</view>
<view class="flex flex-1 mb-2" v-for="(dataItem,dataIndex) in item.value.list" :key="dataIndex">
<u--image width="130rpx" height="130rpx" :radius="'var(--goods-rounded-big)'" :src="img(dataItem.cover ? dataItem.cover : '')" mode="aspectFill">
<up-image width="130rpx" height="130rpx" :radius="'var(--goods-rounded-big)'" :src="img(dataItem.cover ? dataItem.cover : '')" mode="aspectFill">
<template #error>
<image class="w-[130rpx] h-[130rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"/>
</template>
</u--image>
</up-image>
<view class="flex flex-col flex-1 ml-[20rpx] py-[4rpx]">
<view class="max-w-[490rpx] leading-[1.3] truncate text-[28rpx] text-[#303133]">{{ dataItem.name }}</view>
<view class="mt-[14rpx] truncate text-[24rpx] text-[var(--text-color-light9)] max-w-[490rpx] " v-if="item.sub_name">{{ item.sub_name }}</view>

View File

@ -13,6 +13,7 @@ interface Diy {
pageStartBgColor: string, // 页面背景颜色(开始)
pageEndBgColor: string, // 页面背景颜色(结束)
bottomTabBar: any, // 底部导航
copyright: any, // 版权信息
bgUrl: string
},
// 组件集合
@ -38,6 +39,15 @@ const useDiyStore = defineStore('diy', {
bottomTabBar: {
control: true, // 是否允许展示编辑
isShow: true, // 是否显示
designNav:{
title:'',
key:''
}
},
copyright:{
control: true, // 是否允许展示编辑
isShow: false, // 是否显示
textColor: '#ccc',
},
bgUrl: ''
},

View File

@ -17,8 +17,8 @@
</view>
<view class="flex-1 pr-[10rpx]" v-else></view>
</view>
<scroll-view scroll-y="true" class="h-[50vh]" :scroll-top="scrollTop" scroll-with-animation @touchmove.stop>
<view class="flex p-[30rpx] pt-[0] text-sm font-500 h-[50vh]">
<scroll-view scroll-y="true" class="h-[700rpx] overflow-y-auto" :scroll-top="scrollTop" scroll-with-animation @touchmove.stop>
<view class="flex p-[30rpx] pt-[0] text-sm font-500">
<view v-if="areaList.province.length" class="flex-1 pr-[10rpx]" :style="{ opacity: currSelect == 'province' ? 1 : 0, pointerEvents: currSelect == 'province' ? 'auto' : 'none',height: currSelect == 'province' ? 'auto' : '0',overflow: currSelect == 'province' ? 'auto' : 'hidden' }">
<view v-for="(item, index) in areaList.province" :key="item.id" class="h-[80rpx] flex items-center" :class="{'text-[var(--primary-color)]': selected.province && selected.province.id == item.id }" @click="handleProvinceClick(item)">{{ item.name }}</view>
</view>
@ -138,6 +138,7 @@ watch(() => selected.city, (nval) => {
}
}
if (!data.length) {
currSelect.value = 'city'
emits('complete', selected)
show.value = false
}
@ -155,40 +156,40 @@ const emits = defineEmits(['complete'])
//
const scrollToSelected = (type: string, selectedIndex: number) => {
//
const itemHeight = 80 // 80rpx
const itemHeight = 40 // 80rpx
const targetScrollTop = Math.max(0, (selectedIndex - 2) * itemHeight) // 3
scrollTop.value = targetScrollTop
}
//
const resetScrollTop = () => {
scrollTop.value = 0
scrollTop.value = 1;
nextTick(() => {
scrollTop.value = 0;
});
}
//
// currSelectnextTickDOM
watch(() => currSelect.value, (newVal) => {
//
resetScrollTop()
setTimeout(() => {
if (newVal === 'province' && selected.province) {
const index = areaList.province.findIndex((item: any) => item.id === selected.province.id)
if (index >= 0) {
scrollToSelected('province', index)
}
} else if (newVal === 'city' && selected.city) {
const index = areaList.city.findIndex((item: any) => item.id === selected.city.id)
if (index >= 0) {
scrollToSelected('city', index)
}
} else if (newVal === 'district' && selected.district) {
const index = areaList.district.findIndex((item: any) => item.id === selected.district.id)
if (index >= 0) {
scrollToSelected('district', index)
}
}
}, 150)
})
nextTick(() => { // DOM
if (newVal === 'province' && selected.province) {
const index = areaList.province.findIndex((item: any) => item.id === selected.province.id);
if (index >= 0) {
scrollToSelected('province', index); //
}
} else if (newVal === 'city' && selected.city) {
const index = areaList.city.findIndex((item: any) => item.id === selected.city.id);
if (index >= 0) {
scrollToSelected('city', index); //
}
} else if (newVal === 'district' && selected.district) {
const index = areaList.district.findIndex((item: any) => item.id === selected.district.id);
if (index >= 0) {
scrollToSelected('district', index); //
}
}
});
}, { immediate: true }); // immediate
/**
* 监听区县变更
@ -204,7 +205,6 @@ watch(() => selected.district, (nval) => {
//
const handleProvinceClick = (item: any) => {
selected.province = item
//
nextTick(() => {
resetScrollTop()
})
@ -213,7 +213,6 @@ const handleProvinceClick = (item: any) => {
//
const handleCityClick = (item: any) => {
selected.city = item
//
nextTick(() => {
resetScrollTop()
})

View File

@ -3,12 +3,12 @@
<view class="carousel-track" :style="trackStyle">
<view
v-for="(item, index) in displayList"
:key="item.key"
:key="item.key"
class="carousel-item"
:style="itemStyle(index)"
:class="{ 'scale-in': item.isNew }"
>
<u--image width="30rpx" height="30rpx" radius="15rpx" :src="img(item.src)" mode="aspectFill">
<up-image width="30rpx" height="30rpx" radius="15rpx" :src="img(item.src)" mode="aspectFill">
<template #error>
<image
class="w-[30rpx] h-[30rpx] rounded-full"
@ -16,7 +16,7 @@
mode="aspectFill"
/>
</template>
</u--image>
</up-image>
</view>
</view>
</view>
@ -39,9 +39,9 @@ const EASING = 'cubic-bezier(0.25, 0.1, 0.25, 1)'; // 平滑曲线
const slideDistance = ITEM_WIDTH - OVERLAP;
//
const displayList = ref<{
src: string;
isNew: boolean;
const displayList = ref<{
src: string;
isNew: boolean;
key: string; // keyv-for
}[]>([]);
let currentIndex = 0;
@ -82,8 +82,8 @@ const itemStyle = (index: number) => ({
*/
const trackStyle = computed(() => ({
transform: `translateX(${trackOffset.value}rpx)`,
transition: isTransitioning.value
? `transform ${ANIMATION_DURATION}ms ${EASING}`
transition: isTransitioning.value
? `transform ${ANIMATION_DURATION}ms ${EASING}`
: 'none',
display: 'flex',
//
@ -188,4 +188,4 @@ onUnmounted(() => {
.carousel-item.scale-in {
transform: scale(0.9);
}
</style>
</style>

View File

@ -0,0 +1,46 @@
<template>
<view>
<template v-if="diyStore.mode == 'decorate'">
<view class="flex flex-col justify-center items-center p-[30rpx] text-[22rpx]" :style="{ color:textColor}">
示例版权信息
</view>
</template>
<tempalte v-else>
<view class="flex flex-col justify-center items-center p-[30rpx]" :style="{ color:textColor}">
<img :src="img(systemStore.copyright.logo)" mode="heightFix" class="max-h-[60rpx]" v-if="systemStore.copyright?.logo" />
<view class="text-[22rpx] mt-[20rpx]" v-if="systemStore.copyright?.copyright_desc" @click="systemStore.copyright?.copyright_link && redirect({ url: systemStore.copyright.copyright_link})">
{{ systemStore.copyright.copyright_desc }}
</view>
<view class="text-[22rpx] mt-[20rpx]" v-if="systemStore.copyright?.icp">
备案号{{ systemStore.copyright.icp }}
</view>
<view class="text-[22rpx] mt-[20rpx] flex items-center" v-if="systemStore.copyright?.gov_record" @click="systemStore.copyright?.gov_url && redirect({ url: systemStore.copyright.gov_url})">
<img :src="img('static/resource/images/copy_right.png')" mode="heightFix" class="w-[28rpx] h-[28rpx] mr-[10rpx]" />
{{ systemStore.copyright.gov_record }}
</view>
</view>
</tempalte>
</view>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue'
import { redirect,img } from '@/utils/common'
import useSystemStore from '@/stores/system';
const systemStore = useSystemStore()
import useDiyStore from '@/app/stores/diy';
const diyStore = useDiyStore();
const prop = defineProps({
textColor: {
type: String,
default: '#ccc'
},
})
</script>
<style scoped lang="scss">
</style>

View File

@ -10,7 +10,9 @@
:send-message-title="props.sendMessageTitle"
:send-message-path="props.sendMessagePath"
:send-message-img="props.sendMessageImg"
:show-message-card="true">
:show-message-card="true"
@contact="handleContact"
>
</button>
<u-popup :show="popupShow" @close="popupShow = false" mode="center" :round="5" :safeAreaInsetBottom="false">
@ -33,6 +35,7 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import useSystemStore from '@/stores/system'
import { redirect } from '@/utils/common';
const props = defineProps({
sendMessageTitle: {
@ -58,7 +61,7 @@ const siteInfo: any = computed(() => {
const popupShow = ref(false);
const contactService = () => {
// #ifdef H5
// #ifndef MP-WEIXIN
popupShow.value = true;
// #endif
}
@ -70,6 +73,12 @@ const callPhone = () => {
});
}
const handleContact = (e) => {
if (e.detail && e.detail.path) {
redirect({ mode: 'redirectTo', url: e.detail.path, param: e.detail.query || {} })
}
}
defineExpose({})
</script>

View File

@ -11,12 +11,12 @@
</view>
<view v-if="data.customer_phone" class="w-full h-[1rpx] bg-dashed-style my-[40rpx]"></view>
<view class="px-[24rpx] flex flex-col items-center justify-center" v-if="data.customer_qrcode">
<view class="border border-[#eee] border-solid rounded-[10rpx] border-[1rpx] w-[240rpx] h-[240rpx] flex items-center justify-center">
<view class="border border-[#eee] border-solid rounded-[10rpx] border-[1rpx] p-[10rpx] flex items-center justify-center">
<image class="w-[226rpx] h-[226rpx]" :src="img(data.customer_qrcode)" mode="widthFix" />
</view>
<view class="text-[26rpx] text-[#333] mt-[20rpx]">扫描二维码添加客服</view>
</view>
<view class="px-[50rpx] mt-[16rpx] text-[22rpx] text-[#999] leading-[1.5]" style="text-indent: 2em;">{{ data.customer_guided_words }}</view>
<view class="px-[50rpx] mt-[16rpx] text-center text-[22rpx] text-[#999] leading-[1.5]">{{ data.customer_guided_words }}</view>
</scroll-view>
</view>
<!-- <view @click="servicesDataShow = false" class="mt-[50rpx] nc-iconfont nc-icon-cuohaoV6xx1 !text-[50rpx] text-[#fff]"></view> -->

View File

@ -25,7 +25,7 @@
</view>
</scroll-view>
<view class="btn-wrap">
<button class="primary-btn-bg btn" hover-class="none" :loading="loading" @click="confirmPay">{{ t('pay.confirmPay') }}</button>
<button class="primary-btn-bg bg-[var(--primary-color)] btn" hover-class="none" :loading="loading" @click="confirmPay">{{ t('pay.confirmPay') }}</button>
</view>
</view>
</u-popup>
@ -145,6 +145,18 @@ const confirmPay = () => {
location.href = res.data.url
}
// #endif
// #ifndef H5
uni.requestPayment({
provider: 'alipay',
...res.data,
success: (res: any) => {
toPayResult()
},
fail: (res: any) => {
loading.value = false
}
})
// #endif
break;
default:
if (res.data.url) {
@ -169,7 +181,7 @@ const confirmPay = () => {
uni.$on('checkIsReturnAfterPayment', () => {
const data = uni.getStorageSync('paymenting')
if (uni.getStorageSync('paymenting')) {
redirect({
url: '/app/pages/pay/result',
param: {
@ -236,7 +248,7 @@ const toPayResult = () => {
})
return
}
redirect({
url: '/app/pages/pay/result',
param: { trade_type: payInfo.value?.trade_type, trade_id: payInfo.value?.trade_id },

View File

@ -4,7 +4,7 @@
<u-popup :show="sharePopupShow" type="bottom" @close="sharePopupClose" overlayOpacity="0.8">
<view @touchmove.prevent.stop>
<view class="share-content">
<!-- #ifdef MP || APP-PLUS -->
<!-- #ifdef MP -->
<view class="share-box">
<button class="share-btn" :plain="true" open-type="share">
<view class="text-[#07c160] iconfont iconweixin11"></view>
@ -21,6 +21,22 @@
</button>
</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<view class="share-box">
<button class="share-btn" :plain="true" @click="shareSession">
<view class="text-[#07c160] iconfont iconweixin11"></view>
<text>分享给好友</text>
</button>
</view>
<view class="share-box">
<button class="share-btn" :plain="true" @click="shareWechatMoments">
<image :src="img('static/resource/images/app/wechat_moments.png')"></image>
<text>分享到朋友圈</text>
</button>
</view>
<!-- #endif -->
</view>
<view class="share-footer" @click="sharePopupClose">
<text class="text-[#333]">取消分享</text>
@ -46,6 +62,7 @@
import { ref } from 'vue';
import { img, copy } from '@/utils/common';
import useSystemStore from "@/stores/system";
import { useShare } from '@/hooks/useShare'
const props = defineProps({
copyUrl: { // "/wap/addon/shop_fenxiao/pages/goods"
@ -106,6 +123,16 @@ const sharePopupClose = () => {
emits('close');
}
// #ifdef APP-PLUS
const shareSession = () => {
useShare().onShareAppMessage();
}
const shareWechatMoments = () => {
useShare().onShareTimeline();
}
// #endif
defineExpose({
openShare
})

View File

@ -113,6 +113,7 @@ const props = defineProps({
const emits = defineEmits(['close'])
const sharePopupShow = ref(false);
const posterType = ref(props.posterType)
//
const copyUrl = () => {
@ -134,7 +135,8 @@ const copyUrl = () => {
});
}
const openShare = () => {
const openShare = (data: any) => {
posterType.value = data?.type || posterType.value || ''
sharePopupShow.value = true
loadPoster();
}
@ -154,7 +156,7 @@ const loadPoster = () => {
isPosterImg.value = false;
let obj = {
id: props.posterId,
type: props.posterType,
type: posterType.value,
param: props.posterParam
}
let startTime = Date.parse(new Date());

View File

@ -46,7 +46,6 @@ if (!addon && configStore.addon) {
}
const tabbar: any = reactive({})
const setTabbar = () => {
let list = cloneDeep(useConfigStore().tabbarList);
if (list.length == 1) {
@ -96,6 +95,19 @@ const setTabbar = () => {
setTabbar()
// "if (!props.addon)" configStore.addon
watch(
() => useConfigStore().addon,
(newAddon, oldAddon) => {
// props.addon configStore.addon
if (!props.addon && newAddon !== oldAddon) {
addon = newAddon; // addon
setTabbar(); // TabBar
}
},
{ immediate: true, deep: true } // +
);
watch(
() => props.addon,
(newValue, oldValue) => {

View File

@ -3,7 +3,7 @@
<view class="u-navbar z-100" :class="{'fixed': props.scrollBool != -1, 'absolute': props.scrollBool == -1}" :style="{ backgroundColor: bgColor}">
<view class="navbar-inner" :style="{ width: '100%', height: placeholderHeight + 'px' }">
<view v-if="topStatusBarData.style == 'style-1'" class="content-wrap" :class="[topStatusBarData.textAlign]" :style="navbarInnerStyle">
<view class="back-wrap -ml-[16rpx] text-[26px] nc-iconfont nc-icon-zuoV6xx" :class="{'!text-transparent': !isBackShow}" :style="{ color: titleTextColor }" @tap="goBack"></view>
<view v-if="isBackShow" class="back-wrap -ml-[16rpx] text-[26px] nc-iconfont nc-icon-zuoV6xx" :class="{'!text-transparent': !isBackShow}" :style="{ color: titleTextColor }" @tap="goBack"></view>
<view class="title-wrap" :style="styleOneFontSize">{{ data.title }}</view>
</view>
<view v-if="topStatusBarData.style == 'style-2'" class="content-wrap" :style="navbarInnerStyle" @click="diyStore.toRedirect(topStatusBarData.link)">
@ -45,7 +45,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, getCurrentInstance, nextTick } from 'vue';
import { redirect, img } from '@/utils/common';
import { redirect, img ,currRoute} from '@/utils/common';
import useSystemStore from '@/stores/system';
import useDiyStore from '@/app/stores/diy';
import { useLocation } from '@/hooks/useLocation'
@ -82,20 +82,13 @@ const props = defineProps({
isFill: {
type: Boolean,
default: true
},
//
mustFill: {
type: Boolean,
default: false
}
})
// -
const isFill = computed(() => {
let bool = true;
if(props.mustFill){
return props.mustFill
}else if (diyStore.imageAdsSameScreen) {
if (diyStore.imageAdsSameScreen) {
bool = false;
} else {
bool = props.isFill;
@ -201,7 +194,9 @@ const isBackShow = computed(() => {
let bool = false;
if (props.isBack && pages.length > 1) {
bool = true;
}
} else if (currRoute() == 'app/pages/auth/index') {
bool = true;
}
return bool;
})
@ -212,7 +207,14 @@ const goBack = () => {
if (typeof props.customBack === 'function') {
props.customBack();
} else {
uni.navigateBack();
if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
redirect({
url: '/addon/shop/pages/index',
mode: 'reLaunch'
});
}
}
}
/******************************* 返回按钮-end ***********************/

View File

@ -0,0 +1,287 @@
<template>
<view @touchmove.prevent.stop class="update-version">
<u-popup :show="show" @close="closeFn" mode="center" :round="10" :closeOnClickOverlay="!editionForce">
<view class="update-version-wrap flex flex-col" :style="{'background-image': 'url('+ img('static/resource/images/version_update_bg.png') +')'}">
<view class="flex flex-col justify-center mx-[10rpx]">
<view class="text-[50rpx] font-bold">
发现新版本
</view>
<view class="text-[24rpx] mt-[12rpx] self-start rounded-bl-[14rpx] rounded-tr-[14rpx] py-[4rpx] px-[16rpx] text-[#FA0F1A] bg-[#FFD3D4]">
{{ versionInfo.version_name }}
</view>
</view>
<scroll-view scroll-y="true" class="mt-[40rpx] mb-[20rpx] mx-[10rpx] max-h-[290rpx]">
<view class="text-[26rpx] text-[#333] text-with-newline">
{{versionInfo.version_desc}}
</view>
</scroll-view>
<view class="flex gap-[20rpx] mt-[auto]">
<view v-if="!editionForce" class="rounded-[60rpx] h-[76rpx] w-[240rpx] flex items-center justify-center bg-[#F5F5F5] text-[28rpx] text-[#666]" @click="closeFn" :disabled="downloading">下次更新</view>
<view class="rounded-[60rpx] h-[76rpx] w-[240rpx] flex items-center justify-center active-btn text-[#fff] text-[28rpx] text-[#999]" @click="confirm" :disabled="downloading">
<text v-if="!downloading">下载更新</text>
<text v-else>下载中({{downloadProgress}}%)</text>
</view>
</view>
<!-- 下载进度条 -->
<view class="mt-[30rpx] mx-[10rpx]" v-if="downloading">
<view class="w-full h-[10rpx] bg-[#E0E0E0] rounded-full overflow-hidden">
<view class="h-full bg-[#F11C0C] rounded-full" :style="{ width: downloadProgress + '%' }" :class="downloading ? 'downloading-animation' : ''"></view>
</view>
</view>
</view>
<view v-if="!editionForce" class="nc-iconfont nc-icon-cuohaoV6xx1 text-[#fff] text-center mt-[40rpx] text-[44rpx]" @click="closeFn"></view>
</u-popup>
</view>
</template>
<script setup lang="ts">
import { ref, version, computed } from 'vue'
import {onBackPress, onHide} from "@dcloudio/uni-app";
import useSystemStore from '@/stores/system';
import { img, getUrl } from '@/utils/common';
const systemStore = useSystemStore();
const show = computed({
get() {
return systemStore.updateVersionPopup
},
set(value: boolean){
systemStore.updateVersionPopup = false
}
})
const downloading = ref(false)
const downloadProgress = ref(0)
const versionInfo = ref({})
const editionForce = ref(0)
const open = () => {
show.value = true
}
const closeFn = () => {
if (!show.value) return
//
if (!downloading.value) {
const lock = uni.getStorageSync('update_version_close_lock') || {}
lock[versionInfo.value.version_code] = true
uni.setStorageSync('update_version_close_lock', lock)
show.value = false
}else{
uni.showToast({
title: '下载过程中不允许关闭',
icon: 'none'
});
}
}
const initFn = ()=>{
useSystemStore().getVersionInfoFn(async (res) => {
versionInfo.value = res
versionInfo.value.version_desc = versionInfo.value.version_desc.replace(/\\n/g, '\n')
editionForce.value = Number(res.is_forced_upgrade)
})
}
initFn()
onBackPress(()=>{
//
if (editionForce.value) {
return true;
}
})
onHide(() => {
editionForce.value = 0;
show.value = false
})
const confirm = () => {
if(downloading.value) return false
if(versionInfo.value.upgrade_type == 'hot'){
uni.downloadFile({
url: versionInfo.value.package_path,
success: res => {
if (res.statusCode === 200) {
plus.runtime.install(
res.tempFilePath, {
force: true //truefalse
},
function() {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,请重启应用',
showCancel: false,
success: function(res) {
if (res.confirm) {
// console.log('');
plus.runtime.restart()
}
}
});
// console.log('install success...');
},
function(e) {
console.error('install fail...');
}
);
}
}
});
}else{
//apk .apk
if (versionInfo.value.upgrade_type == 'app' && versionInfo.value.package_path.includes('.apk')) {
editionForce.value = 0;
// #ifdef APP-PLUS
plus.runtime.openURL(getUrl(versionInfo.value.package_path));
// #endif
show.value = false
/* downloading.value = true
const downloadTask = plus.downloader.createDownload(getUrl(versionInfo.value.package_path), {
filename: '_downloads/' // downloads
}, function(d, status) {
console.log('整包下载 - d',d,d.filename)
console.log('整包下载 - status',status)
if (status === 200) {
// APK
plus.runtime.openURL(d.filename, function(error) {
console.error('打开文件失败:- error' + error);
console.error('打开文件失败:' + error.message);
uni.showToast({
title: '无法打开此文件',
icon: 'none'
});
})
} else {
console.log('APK下载失败: ' + status);
}
downloading.value = false
show.value = false
downloadProgress.value = 0
})
//
downloadTask.addEventListener('statechanged', function(task, status) {
if (task.state === 3) { // 3
//
const progress = (task.downloadedSize / task.totalSize) * 100
downloadProgress.value = progress.toFixed(0)
console.log(`下载进度: ${progress.toFixed(0)}%`);
}
});
downloadTask.start()
*/
} else if(versionInfo.value.upgrade_type == 'market') {
// h5
editionForce.value = 0; // */
// #ifdef APP-PLUS
plus.runtime.openURL(getUrl(versionInfo.value.package_path));
// #endif
show.value = false
}else {
downloading.value = true
//wgt .wgt
download();
}
}
}
const download = () =>{
// #ifdef APP-PLUS
const downloadTask = uni.downloadFile({
url: versionInfo.value.package_path,
success: res => {
uni.showToast({
title: `下载状态${res.statusCode}`,
icon:'none'
})
if (res.statusCode === 200) {
plus.runtime.install(
res.tempFilePath,
{
force: true //truefalse
},
function() {
if (versionInfo.value.upgrade_type == 'hot') {
plus.runtime.restart();
}
},
function(e) {
//wgt
editionForce.value = 0;
uni.showToast({
title:e.message,
icon:'none',
duration:2500
})
setTimeout(()=>{
show.value = false
},2000)
}
);
if (versionInfo.value.upgrade_type != 'hot') {
// app
editionForce.value = 0;
show.value = false
}
}
},
fail: (err) => {
console.error('下载失败:', err)
uni.showToast({
title: err,
icon: 'none'
})
downloading.value = false
downloadProgress.value = 0
}
});
//
downloadTask.onProgressUpdate(res => {
downloadProgress.value = res.progress;
});
// #endif
}
defineExpose({
open,
close: closeFn
})
</script>
<style lang="scss" scoped>
.update-version {
:deep(.u-popup__content) {
background-color: transparent;
.update-version-wrap{
background-size: contain;
width: 564rpx;
height: 686rpx;
padding: 130rpx 30rpx 40rpx;
box-sizing: border-box;
}
}
.active-btn{
background: linear-gradient( 90deg, #FBA165 0%, #F11C0C 100%);
transition: all 0.3s ease;
}
.active-btn:active {
opacity: 0.8;
}
.active-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.downloading-animation {
transition: width 0.3s ease;
}
}
.text-with-newline {
/* 关键样式:让\n生效 */
white-space: pre-line;
line-height: 1.6;
}
</style>

View File

@ -5,8 +5,8 @@ import {
updateWechatOpenid,
wechatUser,
wechatUserLogin,
updateWechatOpenidByH5
updateWechatOpenidByH5,
wxappLogin
} from '@/app/api/auth'
import { getWechatAuthCode } from '@/app/api/system'
import useMemberStore from '@/stores/member'
@ -23,8 +23,7 @@ export function useLogin() {
const config = useConfigStore()
const systemStore = useSystemStore()
// #ifdef MP-WEIXIN
// #ifdef MP
if (!uni.getStorageSync('autoLoginLock') && config.login.is_bind_mobile) {
uni.setStorageSync('isBindMobile', true) // 强制绑定手机号标识
}
@ -38,7 +37,7 @@ export function useLogin() {
// 如果只开启了账号密码登录,就不需要跳转到登录中间页了,直接进入普通账号密码登录页面
// #ifdef MP-WEIXIN
// #ifdef MP
if (config.login.is_username && !config.login.is_mobile && !config.login.is_auth_register) {
redirect({ url: '/app/pages/auth/login', param: { type: 'username' } })
} else if (systemStore.initStatus == 'finish' && !config.login.is_username && !config.login.is_mobile && !config.login.is_auth_register) {
@ -69,6 +68,16 @@ export function useLogin() {
}
}
// #endif
// #ifdef APP-PLUS
if (config.login.is_username && !config.login.is_mobile) {
redirect({ url: '/app/pages/auth/login', param: { type: 'username' } })
} else if (systemStore.initStatus == 'finish' && !config.login.is_username && !config.login.is_mobile) {
uni.showToast({ title: '商家未开启登录注册', icon: 'none' })
} else {
redirect({ url: '/app/pages/auth/index' })
}
// #endif
})
}
@ -223,6 +232,24 @@ export function useLogin() {
})
// #endif
// #ifdef APP-PLUS
wxappLogin(obj).then((res) => {
if (res.data.token) {
useMemberStore().setToken(res.data.token, () => {
handleLoginBack();
})
} else {
uni.setStorageSync('openid', res.data.openid)
uni.setStorageSync('unionid', res.data.unionid)
if(res.data.nickname) uni.setStorageSync('nickname', res.data.nickname)
if(res.data.avatar) uni.setStorageSync('avatar', res.data.avatar)
}
if (params.successCallback) params.successCallback(res.data)
}).catch((err) => {
uni.showToast({ title: err.msg, icon: 'none' })
})
// #endif
}
/**
@ -340,7 +367,20 @@ export function useLogin() {
})
// #endif
// #ifdef APP-PLUS
uni.login({
"provider": "weixin",
"onlyAuthorize": true, // 微信登录仅请求授权认证
success: function(event){
const { code } = event
authLogin({ code })
},
fail: function (err) {
uni.showToast({ title: err.msg, icon: 'none' })
}
})
// #endif
}
const updateWechatOpenidForH5 = (wx_openid: any) => {

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