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 { export interface GlobalComponents {
Attachment: typeof import('./src/components/upload-attachment/attachment.vue')['default'] Attachment: typeof import('./src/components/upload-attachment/attachment.vue')['default']
DiyLink: typeof import('./src/components/diy-link/index.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'] Editor: typeof import('./src/components/editor/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert'] ElAlert: typeof import('element-plus/es')['ElAlert']
ElAside: typeof import('element-plus/es')['ElAside'] 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'] SelectIcon: typeof import('./src/components/select-icon/index.vue')['default']
SpreadPopup: typeof import('./src/components/spread-popup/index.vue')['default'] SpreadPopup: typeof import('./src/components/spread-popup/index.vue')['default']
UploadAttachment: typeof import('./src/components/upload-attachment/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'] UploadFile: typeof import('./src/components/upload-file/index.vue')['default']
UploadImage: typeof import('./src/components/upload-image/index.vue')['default'] UploadImage: typeof import('./src/components/upload-image/index.vue')['default']
UploadVideo: typeof import('./src/components/upload-video/index.vue')['default'] UploadVideo: typeof import('./src/components/upload-video/index.vue')['default']

View File

@ -9,12 +9,18 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.0.10", "@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", "@highlightjs/vue-plugin": "2.1.0",
"@types/lodash-es": "4.17.6", "@types/lodash-es": "4.17.6",
"@vueuse/core": "9.12.0", "@vueuse/core": "9.12.0",
"axios": "1.4.0", "axios": "1.4.0",
"crypto-js": "4.1.1", "crypto-js": "4.1.1",
"css-color-function": "1.3.3", "css-color-function": "1.3.3",
"date-fns": "^4.1.0",
"day": "^0.0.2", "day": "^0.0.2",
"echarts": "5.4.1", "echarts": "5.4.1",
"element-plus": "^2.7.4", "element-plus": "^2.7.4",
@ -963,6 +969,47 @@
"resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.1.6.tgz", "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.1.6.tgz",
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" "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": { "node_modules/@highlightjs/vue-plugin": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/@highlightjs/vue-plugin/-/vue-plugin-2.1.0.tgz", "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", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz",
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" "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": { "node_modules/day": {
"version": "0.0.2", "version": "0.0.2",
"resolved": "https://registry.npmmirror.com/day/-/day-0.0.2.tgz", "resolved": "https://registry.npmmirror.com/day/-/day-0.0.2.tgz",
@ -5003,6 +5059,15 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true "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": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",

View File

@ -10,12 +10,18 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.0.10", "@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", "@highlightjs/vue-plugin": "2.1.0",
"@types/lodash-es": "4.17.6", "@types/lodash-es": "4.17.6",
"@vueuse/core": "9.12.0", "@vueuse/core": "9.12.0",
"axios": "1.4.0", "axios": "1.4.0",
"crypto-js": "4.1.1", "crypto-js": "4.1.1",
"css-color-function": "1.3.3", "css-color-function": "1.3.3",
"date-fns": "^4.1.0",
"day": "^0.0.2", "day": "^0.0.2",
"echarts": "5.4.1", "echarts": "5.4.1",
"element-plus": "^2.7.4", "element-plus": "^2.7.4",

View File

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

View File

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

@ -367,12 +367,16 @@ export function getCashOutDetail(id: number) {
export function memberAudit(params: Record<string, any>) { 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 * @param params
*/ */
export function memberCancel(params: Record<string, any>) { 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
})
} }
@ -391,6 +395,7 @@ export function memberTransfer(params: Record<string, any>) {
export function memberRemark(params: Record<string, any>) { 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 * @param id
@ -453,6 +458,7 @@ export function getGrowthRuleDict() {
export function getPointRuleDict() { export function getPointRuleDict() {
return request.get(`member/dict/point_rule`) return request.get(`member/dict/point_rule`)
} }
/***************************************************** 会员等级 ****************************************************/ /***************************************************** 会员等级 ****************************************************/
/** /**
@ -512,14 +518,14 @@ export function getMemberLevelAll() {
* *
*/ */
export function getMemberBenefitsContent() { export function getMemberBenefitsContent() {
return request.get(`member/benefits/content`); return request.post(`member/benefits/content`);
} }
/** /**
* *
*/ */
export function getMemberGiftsContent(params: Record<string, any>) { export function getMemberGiftsContent(params: Record<string, any>) {
return request.get(`member/gifts/content`, { params }); return request.post(`member/gifts/content`, 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

@ -299,7 +299,7 @@ const getUpgradeTaskFn = () => {
if (!upgradeContent.value) { if (!upgradeContent.value) {
upgradeContent.value = data.upgrade_content 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 return
} }
@ -691,7 +691,7 @@ defineExpose({
white-space: pre-wrap; white-space: pre-wrap;
} }
::v-deep .number-of-steps { :deep(.number-of-steps) {
.el-step__line { .el-step__line {
margin: 0 25px; margin: 0 25px;
background: #dddddd; 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", "encodingAesKeyPlaceholder": "请输入EncodingAESKey",
"cleartextModeTips": "明文模式下,不使用消息体加解密功能,安全系数较低", "cleartextModeTips": "明文模式下,不使用消息体加解密功能,安全系数较低",
"compatibleModeTips": "兼容模式下,明文、密文将共存,方便开发者调试和维护", "compatibleModeTips": "兼容模式下,明文、密文将共存,方便开发者调试和维护",
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高" "safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高",
"wechatBaseUri": "借权域名",
"wechatBaseUriPlaceholder": "",
"wechatBaseUriTips": "默认留空填写后将替换https://open.weixin.gg.com/获取授权!"
} }

View File

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

View File

@ -32,6 +32,10 @@
"contactsTelPlaceholder": "请输入联系电话", "contactsTelPlaceholder": "请输入联系电话",
"logoPlaceholder": "建议图片尺寸210*30像素图片格式jpg、png、jpeg。", "logoPlaceholder": "建议图片尺寸210*30像素图片格式jpg、png、jpeg。",
"iconPlaceholder": "建议图片尺寸100*100像素图片格式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 标题", "metaTitle": "Meta 标题",
"MetaPlaceholder": "请输入Meta 标题", "MetaPlaceholder": "请输入Meta 标题",
"metaDescription": "Meta 描述", "metaDescription": "Meta 描述",

View File

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

View File

@ -45,7 +45,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue' import { ref } from 'vue'
import { getShowMarketing } from '@/app/api/addon'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@ -57,8 +56,6 @@ const marketingList = ref<Record<string, any>[]>([])
const loading = ref(true) const loading = ref(true)
const getMarketingList = async () => { const getMarketingList = async () => {
const res = await getShowMarketing()
marketingList.value = res.data
loading.value = false loading.value = false
} }
getMarketingList() getMarketingList()

View File

@ -19,13 +19,19 @@
<el-form-item :label="t('url')" prop="url"> <el-form-item :label="t('url')" prop="url">
<el-input v-model.trim="sysUserLogTableData.searchParam.url" :placeholder="t('urlPlaceholder')" /> <el-input v-model.trim="sysUserLogTableData.searchParam.url" :placeholder="t('urlPlaceholder')" />
</el-form-item> </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-form-item>
<el-button type="primary" @click="loadSysUserLogList()">{{ t('search') }}</el-button> <el-button type="primary" @click="loadSysUserLogList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button> <el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="flex justify-end items-center w-[20%]"> <div class="flex justify-end items-center w-[10%]">
<div> <div>
<el-button type="primary" class="w-[100px]" @click="clearEvent()">{{ t('清空日志') }}</el-button> <el-button type="primary" class="w-[100px]" @click="clearEvent()">{{ t('清空日志') }}</el-button>
</div> </div>
@ -83,7 +89,8 @@ const sysUserLogTableData = reactive({
searchParam: { searchParam: {
ip: '', ip: '',
username: '', username: '',
url: '' url: '',
create_time:''
} }
}) })

View File

@ -3,9 +3,9 @@
<div class="main-container"> <div class="main-container">
<el-card class="box-card !border-none" shadow="never"> <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> <span class="text-page-title">{{ pageName }}</span>
</div> --> </div>
<div class="flex justify-between items-center mt-[20px]"> <div class="flex justify-between items-center mt-[20px]">
<el-form :inline="true" :model="roleTableData.searchParam" ref="searchFormRef"> <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 /> <el-input v-model.trim="formData.app_secret" :placeholder="t('appSecretPlaceholder')" class="input-width" clearable />
<div class="form-tip">{{ t('wechatAppsecretTips') }}</div> <div class="form-tip">{{ t('wechatAppsecretTips') }}</div>
</el-form-item> </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>
<el-card class="box-card !border-none mt-[15px]" shadow="never"> <el-card class="box-card !border-none mt-[15px]" shadow="never">
@ -140,7 +145,8 @@ const formData = reactive<Record<string, any>>({
token: '', token: '',
encoding_aes_key: '', encoding_aes_key: '',
encryption_type: 'not_encrypt', encryption_type: 'not_encrypt',
is_authorization: 0 is_authorization: 0,
base_uri: ''
}) })
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()

View File

@ -63,6 +63,23 @@
<el-switch v-model="diyStore.global.bottomTabBar.isShow" /> <el-switch v-model="diyStore.global.bottomTabBar.isShow" />
<div class="text-sm text-gray-400">{{ t('tabbarSwitchTips') }}</div> <div class="text-sm text-gray-400">{{ t('tabbarSwitchTips') }}</div>
</el-form-item> </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> </el-form>
</div> </div>
<div class="edit-attr-item-wrap"> <div class="edit-attr-item-wrap">
@ -178,6 +195,7 @@ import { t } from '@/lang'
import { watch, ref } from 'vue' import { watch, ref } from 'vue'
import useDiyStore from '@/stores/modules/diy' import useDiyStore from '@/stores/modules/diy'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import tabbarSelectPopup from './tabbar-select-popup.vue'
const diyStore = useDiyStore() 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-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>
<el-form-item :label="t('marginBottom')" v-if="diyStore.editComponent.ignore.indexOf('marginBottom') == -1"> <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>
<el-form-item :label="t('marginBoth')" v-if="diyStore.editComponent.ignore.indexOf('marginBoth') == -1"> <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" /> <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) diyStore.components.push(com)
} }
} }
loadDiyTemplatePages(data.type) loadDiyTemplatePages(data.type)
// //

View File

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

View File

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

View File

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

View File

@ -159,10 +159,10 @@
<spread-popup ref="spreadPopupRef" /> <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"/> <records-detail ref="recordsDetailDialog"/>
@ -352,7 +352,7 @@ const copyEvent = (id: any) => {
copyForm({ form_id: id }).then((res: any) => { copyForm({ form_id: id }).then((res: any) => {
if (res.code == 1) { if (res.code == 1) {
loadDiyFormList() loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
} }
repeat.value = false repeat.value = false
}).catch(() => { }).catch(() => {
@ -371,7 +371,7 @@ const deleteEvent = (form_id: number) => {
} }
).then(() => { ).then(() => {
deleteDiyForm({ form_ids: [form_id] }).then(() => { deleteDiyForm({ form_ids: [form_id] }).then(() => {
loadDiyFormList() loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
}).catch(() => { }).catch(() => {
}) })
}) })
@ -438,7 +438,7 @@ const batchDeleteForms = () => {
deleteDiyForm({ deleteDiyForm({
form_ids form_ids
}).then(() => { }).then(() => {
loadDiyFormList() loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
repeat.value = false repeat.value = false
}).catch(() => { }).catch(() => {
repeat.value = false repeat.value = false
@ -497,7 +497,7 @@ const shareEvent = async (formEl: FormInstance | undefined) => {
form_id: shareFormId.value, form_id: shareFormId.value,
share: JSON.stringify(shareFormData) share: JSON.stringify(shareFormData)
}).then(() => { }).then(() => {
loadDiyFormList() loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
shareDialogVisible.value = false shareDialogVisible.value = false
}).catch(() => { }).catch(() => {
}) })

View File

@ -1,47 +1,127 @@
<template> <template>
<!--应用市场--> <!--应用市场-->
<div class="main-container"> <div class="main-container bg-body min-h-[70vh]" v-loading="loading">
<el-card class="box-card !border-none" shadow="never"> <el-card class="box-card !border-none" shadow="never" v-if="!loading">
<div class="flex justify-between items-center"> <div class="flex items-center mb-4">
<span class="text-page-title">{{ t("localAppText") }}</span> <div class="flex items-center flex-1">
<h2 class="text-lg font-semibold mr-[20px]">应用列表</h2>
<button class="text-gray-500 text-sm mr-[20px]" @click="activeNameTabFn('installed')" :class="{'!text-primary': activeName == 'installed'}">已安装</button>
<button class="text-gray-500 text-sm mr-[20px]" @click="activeNameTabFn('uninstalled')" :class="{'!text-primary': activeName == 'uninstalled'}">未安装</button>
<button class="text-gray-500 text-sm mr-[20px]" @click="activeNameTabFn('all')" :class="{'!text-primary': activeName == 'all'}">已购买</button>
<button class="text-gray-500 text-sm relative" @click="activeNameTabFn('recentlyUpdated')" :class="{'!text-primary': activeName == 'recentlyUpdated'}">
可更新
<div v-if="localList.recentlyUpdated.length" class="w-[8px] h-[8px] bg-[red] rounded-[8px] z-1 absolute top-[-2px] right-[-2px]"></div>
</button>
</div>
<i class="iconfont cursor-pointer" @click="switchShowType" :class="showType == 'card' ? 'iconliebiao' : 'iconliebiaoqiehuan'"></i>
</div> </div>
<el-tabs v-model="activeName" class="mt-[10px]"> <!-- 系统版本栏 -->
<el-tab-pane :label="t('installLabel')" name="installed"></el-tab-pane> <div class="flex items-center justify-between bg-body p-4 rounded-md border rounded border-solid border-[var(--el-color-info-light-8)]">
<el-tab-pane :label="t('uninstalledLabel')" name="uninstalled"></el-tab-pane> <div class="flex">
<el-tab-pane :label="t('buyLabel')" name="all"></el-tab-pane> <div class="flex items-center">
<el-tab-pane :label="t('recentlyUpdated')" name="recentlyUpdated"> <div class="w-[40px] h-[40px] bg-purple-100 rounded flex items-center justify-center mr-2 relative">
<template #label> <img class="max-w-full max-h-full" src="@/app/assets/images/app_store/system_version.png" alt="" />
<span class="custom-tabs-label"> <div v-if="frameworkVersion != frameworkNewVersion" class="w-[8px] h-[8px] bg-[red] rounded-[8px] z-1 absolute top-[-2px] right-[-2px]"></div>
<span>{{ t('recentlyUpdated') }}</span>
<span v-if="localList['recentlyUpdated'].length > 0" class="w-[15px] h-[15px] bg-[#DA203E] absolute text-[#fff] text-[11px] flex items-center justify-center rounded-full top-[3px] right-[-12px]">{{ localList['recentlyUpdated'].length }}</span>
</span>
</template>
</el-tab-pane>
</el-tabs>
<div class="flex justify-between my-[10px]">
<div class="flex items-center search-form">
<el-input class="!w-[192px] !h-[32px] rounded-[4px]" :placeholder="t('search')" v-model.trim="search_name" @keyup.enter="query">
<template #suffix>
<el-icon class="el-input__icon cursor-pointer" size="14px" @click="query">
<search />
</el-icon>
</template>
</el-input>
<el-select v-model="search_type" placeholder="请选择类型" class="!w-[192px] !h-[32px] rounded-[4px] ml-[20px] " >
<el-option :label="t('全部')" value="" />
<el-option v-for="(label, value) in typeList" :key="value" :label="label" :value="value"></el-option>
</el-select>
<el-button type="primary" @click="query" class="ml-[20px]">{{ t("搜索") }}</el-button>
</div> </div>
<div> <div>
<el-button type="primary" v-show="activeName === 'recentlyUpdated'" @click="batchUpgrade" :loading="upgradeRef?.loading" :disabled="authLoading">{{ t("batchUpgrade") }}</el-button> <p class="text-sm font-bold">系统版本:
<!-- <el-button type="primary" @click="handleCloudBuild" :loading="cloudBuildRef?.loading" :disabled="authLoading">{{ t("cloudBuild") }}</el-button> --> <span v-if="frameworkVersion">V{{frameworkVersion}} ({{frameworkVersionCode}})</span>
</p>
<p class="text-xs text-gray-500">最新版本: V{{ frameworkNewVersion }}</p>
</div>
<el-button class="ml-[25px]" @click="updateInformationFn({key: 'niucloud-admin'})"><el-icon class="mr-[5px]"><DocumentCopy /></el-icon>更新记录</el-button>
</div>
<div class="border-r mx-[25px] border-solid border-[var(--el-color-info-light-8)] hidden xl:inline-block"></div>
<div class="items-center hidden xl:flex">
<div class="flex items-center mr-4">
<div class="w-[40px] h-[40px] bg-green-100 rounded flex items-center justify-center mr-2">
<img class="max-w-full max-h-full" src="@/app/assets/images/app_store/app_manage.png" alt="" />
</div>
<div>
<p class="text-sm font-bold">应用管理</p>
<p class="text-xs text-gray-500">
<span class="mr-[15px] text-success">已安装: {{ localList.installed.length }}</span>
<span class="mr-[15px] text-warning">可更新: {{ localList.recentlyUpdated.length }}</span>
<span class="mr-[15px] text-primary">未安装: {{ localList.uninstalled.length }}</span>
<span class="mr-[15px] text-error">已购买: {{ localList.all.length }}</span>
</p>
</div>
</div>
</div>
</div>
<div class="flex items-center flex-1 w-0 justify-end">
<el-button @click="oneClickRepair"><i class="iconfont iconyijianxiufu mr-[5px]" ></i>一键修复</el-button>
<el-button type="primary" v-show="activeName == 'uninstalled'" @click="batchInstall"><i class="iconfont iconanzhuang1 mr-[5px]"></i>安装<span v-if="batchUpgradeApp.length">({{ batchUpgradeApp.length }})</span></el-button>
<el-button type="primary" v-show="activeName == 'recentlyUpdated'" @click="batchUpgrade"><i class="iconfont iconyijianshengji mr-[5px]"></i>一键升级<span v-if="batchUpgradeApp.length">({{ batchUpgradeApp.length }})</span></el-button>
<el-checkbox label="全选" :model-value="localList[activeName].length && localList[activeName].length == batchUpgradeApp.length" @change="appKeyAllSelect" v-show="activeName == 'uninstalled' || activeName == 'recentlyUpdated'" value="Value A" class="ml-[12px]"/>
</div>
</div>
</el-card>
<div class="flex mb-4 flex-wrap" v-show="showType == 'card'" v-if="localList[activeName].length && !loading">
<div class="rounded-md border p-[16px] pr-[20px] app-card mb-[20px] ml-[20px] cursor-pointer"
@click="appKeySingleSelect($event, row.key)"
:class="{'border-primary': batchUpgradeApp.includes(row.key)}" v-for="row in localList[activeName]" :key="row.key">
<div class="flex justify-between mb-2">
<div class="flex items-center flex-1 w-0">
<div class="w-[48px] h-[48px] bg-purple-100 rounded flex items-center justify-center mr-2 relative">
<div class="w-full h-full overflow-hidden rounded">
<el-image class="w-full h-full overflow-hidden rounded" :src="row.icon" fit="contain">
<template #error>
<div class="flex items-center w-full h-full">
<img class="max-w-full max-h-full" src="@/app/assets/images/icon-addon-one.png" alt="" />
</div>
</template>
</el-image>
<img src="@/app/assets/images/app_store/app_type_addon.png" alt="" class="absolute z-1 right-0 bottom-0" v-if="row.type == 'addon'">
<img src="@/app/assets/images/app_store/app_type_app.png" alt="" class="absolute z-1 right-0 bottom-0" v-else>
</div>
<div class="w-[8px] h-[8px] bg-[red] rounded-[8px] z-1 absolute top-[-2px] right-[-2px]" v-if="row.install_info && Object.keys(row.install_info)?.length && row.install_info.version != row.version"></div>
</div>
<div class="flex-1 w-0">
<p class="text-sm font-medium truncate" :title="row.title">{{ row.title }}</p>
<p class="text-xs text-gray-500 truncate" :title="row.key">{{ row.key }}</p>
</div>
</div>
<el-checkbox @click.stop :model-value="batchUpgradeApp.includes(row.key)" :value="row.key" @change="appKeySingleSelect($event, row.key)" class="!w-[14px] !h-[14px]" v-if="activeName == 'recentlyUpdated' || activeName == 'uninstalled'"></el-checkbox>
</div>
<div class="flex justify-between">
<div class="text-base">
<span>版本: </span>
<span>{{ row.install_info && Object.keys(row.install_info)?.length ? row.install_info.version : row.version }}</span>
<template v-if="row.install_info && Object.keys(row.install_info)?.length && row.install_info.version != row.version">
<i class="iconfont iconyoujiantou text-warning mx-[2px]"></i>
<span class="text-warning">{{ row.version }}</span>
</template>
</div>
<el-button type="primary" link @click.stop="updateInformationFn(row)">更新记录</el-button>
</div> </div>
<div class="flex mt-[20px]">
<template v-if="!row.is_download">
<el-button type="primary" class="flex-1" :loading="downloading == row.key" :disabled="downloading != ''" @click.stop="downEvent(row)"><i class="iconfont iconanzhuang1 mr-[5px]"></i>立即下载</el-button>
</template>
<template v-else-if="!row.install_info || Object.keys(row.install_info).length == 0">
<el-button type="primary" class="flex-1" @click.stop="installAddonFn(row.key)"><i class="iconfont iconanzhuang1 mr-[5px]"></i>立即安装</el-button>
<el-button plain @click.stop="deleteAddonFn(row.key)">删除</el-button>
</template>
<template v-else>
<el-button type="warning" class="flex-1" @click.stop="upgradeAddonFn(row.key)" v-if="row.install_info.version != row.version">
<i class="iconfont icongengxin mr-[5px]"></i>立即更新
</el-button>
<el-button class="flex-1" :disabled="true" v-else>
<el-icon class="mr-[5px]"><Check /></el-icon>已是最新
</el-button>
<el-button plain @click.stop="uninstallAddonFn(row.key)">卸载</el-button>
</template>
</div> </div>
<div class="relative"> </div>
<el-table v-if="localList[activeName].length && !loading" :tree-props="{ children: 'children' }" :default-expand-all="true" :data="info[activeName]" row-key="key" size="large" @selection-change="handleSelectionChange"> </div>
<div class="relative px-[20px] pb-[20px]" v-show="showType == 'list'">
<el-table v-if="localList[activeName].length && !loading" ref="tableRef" :tree-props="{ children: 'children' }" :default-expand-all="true" :data="info[activeName]" row-key="key" size="large">
<el-table-column width="24"> <el-table-column width="24">
<template #default="{ row }"> <template #default="{ row }">
<div class="tree-child-cell" :class="{ 'is-tree-parent': row.children?.length, 'is-tree-child': typeof row.support_app === 'string' && row.support_app !== '' && visibleRowKeys.has(row.support_app)}"> <div class="tree-child-cell" :class="{ 'is-tree-parent': row.children?.length, 'is-tree-child': typeof row.support_app === 'string' && row.support_app !== '' && visibleRowKeys.has(row.support_app)}">
@ -49,7 +129,11 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column type="selection" v-if="activeName === 'recentlyUpdated'" /> <el-table-column v-if="activeName === 'recentlyUpdated' || activeName === 'uninstalled'" width="60px">
<template #default="{ row }">
<el-checkbox @click.stop :model-value="batchUpgradeApp.includes(row.key)" :value="row.key" @change="appKeySingleSelect($event, row.key)"></el-checkbox>
</template>
</el-table-column>
<el-table-column :label="t('appName')" align="left" width="500"> <el-table-column :label="t('appName')" align="left" width="500">
<template #default="{ row }"> <template #default="{ row }">
<div class="flex items-center cursor-pointer relative left-[-10px]"> <div class="flex items-center cursor-pointer relative left-[-10px]">
@ -139,6 +223,8 @@
</el-table> </el-table>
<div class="h-[100px]" v-loading="loading" v-if="loading"></div> <div class="h-[100px]" v-loading="loading" v-if="loading"></div>
</div> </div>
</div>
<el-empty class="mx-auto overview-empty" v-if="!localList.installed.length && !loading && activeName == 'installed' && !authLoading"> <el-empty class="mx-auto overview-empty" v-if="!localList.installed.length && !loading && activeName == 'installed' && !authLoading">
<template #image> <template #image>
<div class="w-[230px] mx-auto"> <div class="w-[230px] mx-auto">
@ -195,7 +281,7 @@
</p> </p>
</template> </template>
</el-empty> </el-empty>
<el-empty class="mx-auto overview-empty" v-if="!localList.recentlyUpdated.length && !loading && authinfo && activeName == 'recentlyUpdated' && !authLoading"> <el-empty class="mx-auto overview-empty" v-if="!localList.recentlyUpdated.length && !loading && activeName == 'recentlyUpdated'">
<template #image> <template #image>
<div class="w-[230px] mx-auto"> <div class="w-[230px] mx-auto">
<img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="" /> <img src="@/app/assets/images/index/apply_empty.png" class="max-w-full" alt="" />
@ -205,7 +291,6 @@
<p class="flex items-center">{{ t("recentlyUpdatedEmpty") }}</p> <p class="flex items-center">{{ t("recentlyUpdatedEmpty") }}</p>
</template> </template>
</el-empty> </el-empty>
</div>
<el-dialog v-model="authCodeApproveDialog" title="授权码认证" width="400px"> <el-dialog v-model="authCodeApproveDialog" title="授权码认证" width="400px">
<el-form :model="formData" label-width="0" ref="formRef" :rules="formRules" class="page-form"> <el-form :model="formData" label-width="0" ref="formRef" :rules="formRules" class="page-form">
@ -265,9 +350,10 @@
<div v-show="installStep == 0" v-loading="!installCheckResult.dir"> <div v-show="installStep == 0" v-loading="!installCheckResult.dir">
<!-- <el-scrollbar max-height="50vh"> --> <!-- <el-scrollbar max-height="50vh"> -->
<div class="min-h-[150px]"> <div class="min-h-[150px]">
<div class="my-3" v-if="installCheckResult.dir"> <el-scrollbar style="height: calc(50vh); overflow: auto">
<div class="mt-3" v-if="installCheckResult.dir">
<p class="pt-[20px] pl-[20px]">{{ t("dirPermission") }}</p> <p class="pt-[20px] pl-[20px]">{{ t("dirPermission") }}</p>
<div v-if="!installCheckResult.is_pass" class="mt-[10px] mx-[20px] text-[14px] cursor-pointer text-primary flex items-center justify-between bg-[#EFF6FF] rounded-[4px] p-[10px]" @click="cloudBuildCheckDirFn"> <div v-if="!installCheckResult.file_permission_is_pass" class="mt-[10px] mx-[20px] text-[14px] cursor-pointer text-primary flex items-center justify-between bg-[#EFF6FF] rounded-[4px] p-[10px]" @click="cloudBuildCheckDirFn">
<div class="flex items-center"> <div class="flex items-center">
<el-icon :size="17"><QuestionFilled /></el-icon> <el-icon :size="17"><QuestionFilled /></el-icon>
<span class="ml-[5px] leading-[20px]">编译权限错误查看解决方案</span></div> <span class="ml-[5px] leading-[20px]">编译权限错误查看解决方案</span></div>
@ -285,7 +371,6 @@
<span>{{ t("status") }}</span> <span>{{ t("status") }}</span>
</el-col> </el-col>
</el-row> </el-row>
<el-scrollbar style="height: calc(300px); overflow: auto">
<el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in installCheckResult.dir.is_readable" :key="index"> <el-row class="pb-[10px] items pl-[15px]" v-for="(item, index) in installCheckResult.dir.is_readable" :key="index">
<el-col :span="18"> <el-col :span="18">
<span>{{ item.dir }}</span> <span>{{ item.dir }}</span>
@ -326,10 +411,18 @@
</span> </span>
</el-col> </el-col>
</el-row> </el-row>
</div>
</div>
<div class="my-3" v-if="installCheckResult.addon_check && installCheckResult.addon_check.length">
<p class="pl-[20px]">插件验证</p>
<div class="px-[20px] pt-[10px] text-[14px]">
<el-alert class="!mb-[10px]" v-for="item in installCheckResult.addon_check" type="error" :closable="false">
<div v-html="item.msg"></div>
</el-alert>
</div>
</div>
</el-scrollbar> </el-scrollbar>
</div> </div>
</div>
</div>
<!-- </el-scrollbar> --> <!-- </el-scrollbar> -->
<div class="flex justify-end"> <div class="flex justify-end">
<el-tooltip effect="dark" placement="top"> <el-tooltip effect="dark" placement="top">
@ -465,7 +558,6 @@
</template> </template>
</el-dialog> </el-dialog>
<!-- 更新信息 --> <!-- 更新信息 -->
</el-card>
</div> </div>
<upgrade-log :upgradeKey="upgradeKey" ref="upgradeLogRef" /> <upgrade-log :upgradeKey="upgradeKey" ref="upgradeLogRef" />
<upgrade ref="upgradeRef" @complete="localListFn" @cloudbuild="handleCloudBuild" /> <upgrade ref="upgradeRef" @complete="localListFn" @cloudbuild="handleCloudBuild" />
@ -473,7 +565,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, watch, h ,computed } from 'vue' import { ref, reactive, watch, h, computed, nextTick } from 'vue'
import { t } from '@/lang' import { t } from '@/lang'
import { import {
getAddonLocal, getAddonLocal,
@ -488,7 +580,7 @@ import {
getAddonInit getAddonInit
} from '@/app/api/addon' } from '@/app/api/addon'
import { deleteAddonDevelop } from '@/app/api/tools' import { deleteAddonDevelop } from '@/app/api/tools'
import { downloadVersion, getAuthInfo, setAuthInfo } from '@/app/api/module' import { downloadVersion, getAuthInfo, setAuthInfo, getFrameworkNewVersion } from '@/app/api/module'
import { getVersions } from '@/app/api/auth' import { getVersions } from '@/app/api/auth'
import { ElMessage, ElMessageBox, ElNotification, FormInstance, FormRules } from 'element-plus' import { ElMessage, ElMessageBox, ElNotification, FormInstance, FormRules } from 'element-plus'
import 'vue-web-terminal/lib/theme/dark.css' import 'vue-web-terminal/lib/theme/dark.css'
@ -501,9 +593,10 @@ import Upgrade from '@/app/components/upgrade/index.vue'
import CloudBuild from '@/app/components/cloud-build/index.vue' import CloudBuild from '@/app/components/cloud-build/index.vue'
import UpgradeLog from '@/app/components/upgrade-log/index.vue' import UpgradeLog from '@/app/components/upgrade-log/index.vue'
const tableRef = ref(null)
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const terminalId = ref(Date.now()); const terminalId = ref(Date.now())
const activeName = ref(storage.get('storeActiveName') || 'installed') const activeName = ref(storage.get('storeActiveName') || 'installed')
const upgradeRef = ref(null) const upgradeRef = ref(null)
const cloudBuildRef = ref(null) const cloudBuildRef = ref(null)
@ -514,10 +607,22 @@ const userStore = useUserStore()
const unloadHintDialog = ref(false) const unloadHintDialog = ref(false)
const terminalRef = ref(null) const terminalRef = ref(null)
const frameworkVersion = ref('') const frameworkVersion = ref('')
const frameworkVersionCode = ref('')
const upgradeLogRef = ref<any>(null) const upgradeLogRef = ref<any>(null)
const showType = ref(storage.get('storeShowType') || 'card')
const frameworkNewVersion = ref('')
getVersions().then((res) => { getVersions().then((res) => {
frameworkVersion.value = res.data.version.version frameworkVersion.value = res.data.version.version
frameworkVersionCode.value = res.data.version.code
}) })
getFrameworkNewVersion().then(({ data }) => {
frameworkNewVersion.value = data.last_version
})
const switchShowType = () => {
showType.value = showType.value == 'card' ? 'list' : 'card'
storage.set({key: 'storeShowType', data: showType.value})
}
const typeList = ref({}) const typeList = ref({})
const getAddonInitFn = () => { const getAddonInitFn = () => {
@ -534,6 +639,12 @@ const downEventHintFn = () => {
const activeNameTabFn = (data: any) => { const activeNameTabFn = (data: any) => {
activeName.value = data activeName.value = data
storage.set({ key: 'storeActiveName', data }) storage.set({ key: 'storeActiveName', data })
if ((data == 'uninstalled' || data == 'recentlyUpdated') && localList.value[activeName.value].length) {
batchUpgradeApp.value = localList.value[activeName.value].map(item => item.key)
} else {
batchUpgradeApp.value = []
}
} }
if (route.query.id) { if (route.query.id) {
activeNameTabFn(route.query.id) activeNameTabFn(route.query.id)
@ -603,26 +714,7 @@ const buildInfo = (list: any[]) => {
return result return result
} }
// const query = () => {
// if (search_name.value == '' || search_name.value == null) {
// info.value.installed = buildInfo(localList.value.installed)
// info.value.uninstalled = buildInfo(localList.value.uninstalled)
// info.value.all = buildInfo(localList.value.all)
// info.value.recentlyUpdated = buildInfo(localList.value.recentlyUpdated)
// return false
// }
// const filteredInstalled = localList.value.installed.filter((el: any) => el.title.indexOf(search_name.value) != -1)
// const filteredUninstalled = localList.value.uninstalled.filter((el: any) => el.title.indexOf(search_name.value) != -1)
// const filteredAll = localList.value.all.filter((el: any) => el.title.indexOf(search_name.value) != -1)
// const filteredRecentlyUpdated = localList.value.recentlyUpdated.filter((el: any) => el.title.indexOf(search_name.value) != -1)
// //
// info.value.installed = buildInfo(filteredInstalled)
// info.value.uninstalled = buildInfo(filteredUninstalled)
// info.value.all = buildInfo(filteredAll)
// info.value.recentlyUpdated = buildInfo(filteredRecentlyUpdated)
// }
const query = () => { const query = () => {
const name = search_name.value const name = search_name.value
const type = search_type.value const type = search_type.value
@ -689,6 +781,7 @@ const localListFn = () => {
appLink.value[item.meta.app] = item.name appLink.value[item.meta.app] = item.name
} }
}) })
activeNameTabFn(activeName.value)
loading.value = false loading.value = false
}).catch(() => { }).catch(() => {
loading.value = false loading.value = false
@ -909,6 +1002,7 @@ const handleCloudInstall = () => {
installStep.value = 1 installStep.value = 1
terminalRef.value.execute('clear') terminalRef.value.execute('clear')
terminalRef.value.execute('开始安装插件') terminalRef.value.execute('开始安装插件')
if (res.data.length) installAfterTips.value = res.data
getInstallTask() getInstallTask()
cloudInstalling.value = false cloudInstalling.value = false
}).catch((res) => { }).catch((res) => {
@ -1155,23 +1249,55 @@ const versionJudge = (row: any) => {
return false return false
} }
let batchUpgradeApp = [] const batchUpgradeApp = ref<String[]>([])
const handleSelectionChange = (e: any) => {
batchUpgradeApp = e.map(item => item.key) const appKeyAllSelect = () => {
if (localList.value[activeName.value].length) {
if (localList.value[activeName.value].length == batchUpgradeApp.value.length) {
batchUpgradeApp.value = []
} else {
batchUpgradeApp.value = localList.value[activeName.value].map(item => item.key)
}
}
}
const appKeySingleSelect = (event: any, key: string) => {
if (activeName.value != 'recentlyUpdated' && activeName.value != 'uninstalled') return
if (batchUpgradeApp.value.includes(key)) {
batchUpgradeApp.value.splice(batchUpgradeApp.value.indexOf(key), 1)
} else {
batchUpgradeApp.value.push(key)
}
} }
const batchUpgrade = () => { const batchUpgrade = () => {
if (!batchUpgradeApp.length) { const appKeys = batchUpgradeApp.value
ElMessage({ message: '请先勾选要升级的插件', type: 'error', duration: 5000 }) if (frameworkVersion.value != frameworkNewVersion.value) {
appKeys.unshift('niucloud-admin')
}
if (!appKeys.length) {
ElMessage({ message: localList.recentlyUpdated.length ? '请先勾选要升级的插件' : '当前已是最新版', type: 'error', duration: 5000 })
return return
} }
upgradeAddonFn(appKeys.toString())
}
upgradeAddonFn(batchUpgradeApp.toString()) const batchInstall = () => {
const appKeys = batchUpgradeApp.value
if (!appKeys.length) {
ElMessage({ message: '请先勾选要安装的插件', type: 'error', duration: 5000 })
return
}
installAddonFn(appKeys.toString())
} }
const visibleRowKeys = computed(() => { const visibleRowKeys = computed(() => {
return new Set((info.value[activeName.value] || []).map(row => row.key)); return new Set((info.value[activeName.value] || []).map(row => row.key))
}); })
const oneClickRepair = () => {
ElMessage({ message: '即将上线敬请期待', duration: 5000 })
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -1383,7 +1509,7 @@ html.dark .table-head-bg {
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
::v-deep .number-of-steps { :deep(.number-of-steps) {
.el-step__line { .el-step__line {
margin: 0 25px; margin: 0 25px;
background: #dddddd; background: #dddddd;
@ -1451,6 +1577,11 @@ html.dark .table-head-bg {
} }
} }
.app-card {
width: calc((100% - 120px) / 5);
min-width: 260px;
}
</style> </style>
<style> <style>

View File

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

View File

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

View File

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

View File

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

View File

@ -148,7 +148,7 @@ const deleteEvent = (id: number) => {
} }
).then(() => { ).then(() => {
deletePrinterTemplate(id).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> <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-form-item :label="t('icp')" prop="icp">
<el-input v-model.trim="formData.icp" :placeholder="t('icpPlaceholder')" class="input-width" clearable maxlength="20"/> <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>
<el-form-item :label="t('govRecord')" > <el-form-item :label="t('govRecord')" >
<el-input v-model.trim="formData.gov_record" :placeholder="t('govRecordPlaceholder')" class="input-width" clearable maxlength="50"/> <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>
<el-form-item :label="t('govUrl')" > <el-form-item :label="t('govUrl')" >
<el-input v-model.trim="formData.gov_url" :placeholder="t('govUrlPlaceholder')" class="input-width" clearable /> <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>
<el-form-item :label="t('marketSupervisionUrl')" > <el-form-item :label="t('marketSupervisionUrl')" >
<el-input v-model.trim="formData.market_supervision_url" rows="4" clearable :placeholder="t('marketSupervisionUrlPlaceholder')" class="input-width" /> <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> </el-form-item>
</div> </div>
</el-card> </el-card>

View File

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

View File

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

View File

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

View File

@ -47,12 +47,13 @@ let editorEl = null
const serverHeaders = {} const serverHeaders = {}
serverHeaders[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken() 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({ const editorConfig = ref({
debug: false, debug: false,
UEDITOR_HOME_URL: import.meta.env.MODE == 'development' ? '/public/ueditor/' : '/admin/ueditor/', 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/', 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, serverHeaders,
// //
autoHeightEnabled: false, autoHeightEnabled: false,
@ -89,6 +90,17 @@ const handleEditorReady = (editor) => {
emit('handleBlur', editor.getContent()) // 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; // const originalCount = editor.getContentLength; //

View File

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

View File

@ -103,7 +103,7 @@
"405": "请求方法未允许", "405": "请求方法未允许",
"408": "请求超时", "408": "请求超时",
"409": "请求跨域", "409": "请求跨域",
"500": "服务器端出错,错误原因:", "500": "服务器内部错误",
"501": "网络未实现", "501": "网络未实现",
"502": "网络错误", "502": "网络错误",
"503": "服务不可用", "503": "服务不可用",
@ -219,5 +219,10 @@
"cloudBuildTips": "是否要进行云编译该操作可能会影响到正在访问的客户是否要继续操作?", "cloudBuildTips": "是否要进行云编译该操作可能会影响到正在访问的客户是否要继续操作?",
"promoteUrl": "推广链接", "promoteUrl": "推广链接",
"downLoadQRCode": "下载二维码", "downLoadQRCode": "下载二维码",
"configureFailed": "配置失败" "configureFailed": "配置失败",
"lefttitle": "左侧标题",
"righttitle": "右侧标题",
"leftDesc": "左侧简介",
"rightDesc": "右侧简介",
"descPlaceholder": "请输入简介内容"
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<template v-if="meta.show"> <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> <template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1"> <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" /> <icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
@ -15,7 +15,7 @@
</template> </template>
</el-sub-menu> </el-sub-menu>
<template v-else> <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> <template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1"> <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" /> <icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
@ -23,7 +23,7 @@
<span class="ml-[10px]">{{ meta.title }}</span> <span class="ml-[10px]">{{ meta.title }}</span>
</template> </template>
</el-menu-item> </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> <template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1"> <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" /> <icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
@ -39,12 +39,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter, useRoute } from 'vue-router' 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 menuItem from './menu-item.vue'
import useSystemStore from '@/stores/modules/system' import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import storage from '@/utils/storage' import storage from '@/utils/storage'
import { findFirstValidRoute ,formatRouters} from '@/router/routers'
import { getShowApp,getShowSpecialMenu } from '@/app/api/addon'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const routers = useUserStore().routers const routers = useUserStore().routers
@ -61,6 +62,9 @@ const props = defineProps({
const systemStore = useSystemStore() const systemStore = useSystemStore()
const meta = computed(() => props.routes.meta) const meta = computed(() => props.routes.meta)
// name
const specialMenuNames = ref<string[]>([])
const specialMenuNamesLevel1 = ref<string[]>([])
const addons = computed(() => { const addons = computed(() => {
const addons:Record<string, any> = {} const addons:Record<string, any> = {}
systemStore?.apps.forEach((item: any) => { addons[item.key] = item }) systemStore?.apps.forEach((item: any) => { addons[item.key] = item })
@ -72,6 +76,13 @@ 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> = {} const addonRouters: Record<string, any> = {}
routers.forEach(item => { routers.forEach(item => {
item.original_name = item.name item.original_name = item.name
@ -85,8 +96,132 @@ routers.forEach(item => {
const addonsMenus = ref(null) 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, () => { watch(route, () => {
const addonKeys = storage.get('defaultAppList')
if (props.routes.name == 'addon_list') { 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;
}
}
if (systemAddonKeys.value.includes(route.meta.addon) && addonRouters[route.meta.addon]) { if (systemAddonKeys.value.includes(route.meta.addon) && addonRouters[route.meta.addon]) {
addonsMenus.value = addonRouters[route.meta.addon] addonsMenus.value = addonRouters[route.meta.addon]
} else if (route.meta.attr && addonRouters[route.meta.attr]) { } else if (route.meta.attr && addonRouters[route.meta.attr]) {
@ -106,7 +241,73 @@ watch(route, () => {
addonsMenus.value = null addonsMenus.value = null
} }
} }
// console.log('addonsMenus', props.routes)
}, { immediate: true }) }, { 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> </script>
<style lang="scss"> <style lang="scss">

View File

@ -31,7 +31,7 @@ import menuItem from './menu-item.vue'
import { img } from '@/utils/common' import { img } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers' import { findFirstValidRoute } from '@/router/routers'
import { getWebConfig } from "@/app/api/sys" import { getWebConfig } from "@/app/api/sys"
import { getShowMarketing } from '@/app/api/addon' // import { getShowApp,getShowSpecialMenu } from '@/app/api/addon'
import storage from '@/utils/storage' import storage from '@/utils/storage'
const systemStore = useSystemStore() const systemStore = useSystemStore()
@ -43,23 +43,30 @@ const addonIndexRoute = userStore.addonIndexRoute
const menuData = ref<Record<string, any>[]>([]) const menuData = ref<Record<string, any>[]>([])
const addonRouters: Record<string, any> = {} const addonRouters: Record<string, any> = {}
const getMarketingList = async () => { // const appList = ref<Record<string, any>[]>([])
const res = await getShowMarketing()
const marketingList = res.data // const getAppList = async () => {
const marketingKeys = marketingList?.marketing?.list?.map(item => item.key) ?? [] // const res = await getShowApp()
// menuData.value.forEach((item, index, arr) => { // appList.value = res.data
// if (marketingKeys.includes(item.name)) {
// arr.splice(index, 1) // storage.set({ key: 'defaultAppList', data: appList.value })
// } // }
// }) // const specialList = ref<Record<string, any>[]>([])
storage.set({ key: 'defaultMarketingKeys', data: marketingKeys }) // 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(() => { onMounted(() => {
getWebConfig().then(({ data }) => { getWebConfig().then(({ data }) => {
webSite.value = data webSite.value = data
}); });
getMarketingList() // getAppList()
// getShowSpecialMenuList()
}) })
routers.forEach(item => { routers.forEach(item => {

View File

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

View File

@ -1,18 +1,18 @@
<template> <template>
<template v-if="meta.show"> <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> <template #title>
<span :class="['ml-[10px]']">{{ meta.title }}</span> <span :class="['ml-[10px]']">{{ meta.title }}</span>
</template> </template>
<menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" /> <menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" />
</el-sub-menu> </el-sub-menu>
<template v-else> <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> <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> <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> </template>
</el-menu-item> </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> <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> <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> </template>
@ -24,12 +24,14 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'vue-router' import { useRouter , useRoute} from 'vue-router'
import { computed } from 'vue' import { computed } from 'vue'
import menuItem from './menu-item.vue' import menuItem from './menu-item.vue'
import useSystemStore from "@/stores/modules/system"; import useSystemStore from "@/stores/modules/system";
import storage from '@/utils/storage'
const router = useRouter() const router = useRouter()
const route = useRoute()
const props = defineProps({ const props = defineProps({
routes: { routes: {
type: Object, type: Object,
@ -39,13 +41,43 @@ const props = defineProps({
const systemStore = useSystemStore() const systemStore = useSystemStore()
const meta = computed(() => props.routes.meta) 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 = computed(() => {
const addons:Record<string, any> = {} const addons:Record<string, any> = {}
systemStore?.apps.forEach((item: any) => { addons[item.key] = item }) systemStore?.apps.forEach((item: any) => { addons[item.key] = item })
systemStore?.addons.forEach((item: any) => { addons[item.key] = item }) systemStore?.addons.forEach((item: any) => { addons[item.key] = item })
return addons 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> </script>
<style lang="scss"> <style lang="scss">

View File

@ -15,7 +15,7 @@
<el-scrollbar class="h-[calc( 100vh - 64px )]"> <el-scrollbar class="h-[calc( 100vh - 64px )]">
<el-menu :default-active="oneMenuActive" :router="true" class="aside-menu" :unique-opened="true" :collapse="systemStore.menuIsCollapse"> <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"> <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"> <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)"/> <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 /> <icon :name="item.meta.icon" class="absolute top-[50%] -translate-y-[50%]" v-else />
@ -32,8 +32,8 @@
<div class="h-[48px]"></div> <div class="h-[48px]"></div>
</el-scrollbar> </el-scrollbar>
</div> </div>
<el-scrollbar v-if="twoMenuData.length" class="two-menu w-[140px]"> <el-scrollbar v-if="twoMenuData.length" class="two-menu w-[152px]">
<div class="w-[140px] h-[64px] flex items-center justify-center text-[16px] border-b-[1px] border-solid border-[var(--el-border-color-lighter)]"> <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 }} {{ route.matched[1].meta.title }}
</div> </div>
@ -51,10 +51,11 @@ import { ref, watch, computed, onMounted, watchEffect } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import useSystemStore from '@/stores/modules/system' import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user' 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 menuItem from './menu-item.vue'
import { img, isUrl } from '@/utils/common' 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' import { cloneDeep } from 'lodash-es'
const systemStore = useSystemStore() const systemStore = useSystemStore()
@ -111,98 +112,228 @@ const marketingList = ref(null);
const oneMenuActive = ref(route.matched[1].name) const oneMenuActive = ref(route.matched[1].name)
const getAppList = async () => { const getAppList = async () => {
const res = await getShowApp(); const res = await getShowApp()
appList.value = res.data; appList.value = res.data
// loading.value = false;
}; storage.set({ key: 'defaultAppList', data: appList.value })
const getMarketingList = async () => { }
const res = await getShowMarketing(); const specialList = ref<Record<string, any>[]>([])
marketingList.value = res.data 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 () => { const specialMenuNames = ref<string[]>([])
await getAppList() // const specialMenuNamesLevel1 = ref<string[]>([])
await getMarketingList()
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 ?? [];
}
}
}
}
})
// watch(route, () => {
// if (route.meta.attr != '') {
// if (route.matched[2]) 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 ?? []
// }
// }
// }
// }
// }, { immediate: true })
// //
const menuOption = ref([]) const menuOption = ref([])
watch(twoMenuData.value, () => { const secondMenuShowWayFn = () => {
menuOption.value = []; menuOption.value = []
if(twoMenuData.value && Object.values(twoMenuData.value).length){ if (oneMenuActive.value !== 'active' && oneMenuActive.value !== 'addon' && twoMenuData.value && Object.values(twoMenuData.value).length) {
let data = cloneDeep(twoMenuData.value); const data = cloneDeep(twoMenuData.value)
for(let key in data){ for (const key in data) {
menuOption.value.push(data[key].name); menuOption.value.push(data[key].name)
} }
} }
}
// 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 ?? [];
// }
// }
// }
// }
// })
// 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 }) }, { immediate: true })
</script> </script>
@ -240,13 +371,13 @@ watch(twoMenuData.value, () => {
} }
.two-menu{ .two-menu{
.aside-menu:not(.el-menu--collapse) { .aside-menu:not(.el-menu--collapse) {
width: 140px; width: 152px;
border: 0; border: 0;
padding-top: 16px; padding-top: 16px;
.el-menu-item{ .el-menu-item{
height: 36px; height: 36px;
margin: 0 8px 4px; margin: 0 12px 4px;
padding: 0 8px !important; padding: 0 !important;
border-radius: 2px; border-radius: 2px;
span{ span{
margin-left: 8px; margin-left: 8px;
@ -265,7 +396,7 @@ watch(twoMenuData.value, () => {
.el-sub-menu__title{ .el-sub-menu__title{
margin: 0 8px 4px; margin: 0 8px 4px;
height: 36px; height: 36px;
padding-left: 8px; padding-left:0px;
border-radius: 2px; border-radius: 2px;
span{ span{
height: 36px; height: 36px;
@ -282,7 +413,35 @@ watch(twoMenuData.value, () => {
} }
} }
.el-menu-item{ .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>
<el-col :span="12"> <el-col :span="12">
<div class="right-panel h-full flex items-center justify-end"> <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> <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 userInfo from './user-info.vue'
import { useFullscreen } from '@vueuse/core' import { useFullscreen } from '@vueuse/core'
import useSystemStore from '@/stores/modules/system' import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import useAppStore from '@/stores/modules/app' import useAppStore from '@/stores/modules/app'
import { useRoute,useRouter } from 'vue-router' import { useRoute,useRouter } from 'vue-router'
import { t } from '@/lang' import { t } from '@/lang'
@ -79,10 +96,61 @@ const route = useRoute()
const router = useRouter() const router = useRouter()
const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
const userStore = useUserStore()
const dark = computed(() => { const dark = computed(() => {
return systemStore.dark 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 // start
const detectionLoginDialog = ref(false) const detectionLoginDialog = ref(false)
const comparisonToken = ref('') const comparisonToken = ref('')

View File

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

View File

@ -1,9 +1,9 @@
@font-face { @font-face {
font-family: "iconfont"; font-family: "iconfont";
/* Project id 3883393 */ /* Project id 3883393 */
src: url('//at.alicdn.com/t/c/font_3883393_6d60cyygl4.woff2?t=1755603992297') format('woff2'), src: url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.woff2?t=1762651161569') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_6d60cyygl4.woff?t=1755603992297') format('woff'), url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.woff?t=1762651161569') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_6d60cyygl4.ttf?t=1755603992297') format('truetype'); url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.ttf?t=1762651161569') format('truetype');
} }
.iconfont { .iconfont {
@ -14,6 +14,78 @@
-moz-osx-font-smoothing: grayscale; -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 { .icona-bijiPC30:before {
content: "\e90b"; content: "\e90b";
} }

View File

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

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

View File

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

View File

@ -1,140 +1,146 @@
<template> <template>
<view class="diy-group" id="componentList"> <view class="diy-group" id="componentList">
<top-tabbar v-if="data.global && Object.keys(data.global).length && data.global.topStatusBar && data.global.topStatusBar.isShow" <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" />
:scrollBool="diyGroup.componentsScrollBool.TopTabbar" 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" /> <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"> <template v-for="(component, index) in data.value" :key="component.id">
<view <view v-show="component.componentIsShow"
v-show="component.componentIsShow"
@click="diyStore.changeCurrentIndex(index, component)" @click="diyStore.changeCurrentIndex(index, component)"
:class="diyGroup.getComponentClass(index,component)" :style="component.pageStyle" :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 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" <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>
: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'"> <template v-if="component.componentName == 'ActiveCube'">
<diy-active-cube :component="component" :global="data.global" :index="index" /> <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 == 'FloatBtn'">
<diy-float-btn :component="component" :global="data.global" :index="index" />
</template> </template>
<template v-if="component.componentName == 'CarouselSearch'"> <template v-if="component.componentName == 'CarouselSearch'">
<diy-carousel-search :scrollBool="diyGroup.componentsScrollBool.CarouselSearch" :component="component" :global="data.global" :index="index" /> <diy-carousel-search ref="diyCarouselSearchRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.CarouselSearch" @update:componentIsShow="component.componentIsShow = $event" />
</template> </template>
<template v-if="component.componentName == 'PictureShow'"> <template v-if="component.componentName == 'FloatBtn'">
<diy-picture-show :component="component" :global="data.global" :index="index" /> <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 == '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>
<template v-if="component.componentName == 'FormAddress'"> <template v-if="component.componentName == 'FormAddress'">
<diy-form-address ref="diyFormAddressRef" :component="component" :global="data.global" :index="index" /> <diy-form-address ref="diyFormAddressRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormAddress" @update:componentIsShow="component.componentIsShow = $event" />
</template> </template>
<template v-if="component.componentName == 'FormImage'"> <template v-if="component.componentName == 'FormCheckbox'">
<diy-form-image ref="diyFormImageRef" :component="component" :global="data.global" :index="index" /> <diy-form-checkbox ref="diyFormCheckboxRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormCheckbox" @update:componentIsShow="component.componentIsShow = $event" />
</template> </template>
<template v-if="component.componentName == 'FormVideo'"> <template v-if="component.componentName == 'FormDate'">
<diy-form-video ref="diyFormVideoRef" :component="component" :global="data.global" :index="index" /> <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>
<template v-if="component.componentName == 'FormFile'"> <template v-if="component.componentName == 'FormFile'">
<diy-form-file ref="diyFormFileRef" :component="component" :global="data.global" :index="index" /> <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> </template>
</view> </view>
</view> </view>
</template> </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"> <template v-if="diyStore.mode == '' && data.global && data.global.bottomTabBar && data.global.bottomTabBar.isShow">
<view class="pt-[20rpx]"></view> <view class="pt-[20rpx]"></view>
<tabbar /> <tabbar :addon="data.global.bottomTabBar.designNav.key" />
</template> </template>
</view> </view>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import topTabbar from '@/components/top-tabbar/top-tabbar.vue' import topTabbar from '@/components/top-tabbar/top-tabbar.vue'
import popAds from '@/components/pop-ads/pop-ads.vue' import popAds from '@/components/pop-ads/pop-ads.vue'
import { useDiyGroup } from './useDiyGroup'
import useDiyStore from '@/app/stores/diy'; import useDiyStore from '@/app/stores/diy';
import { useDiyGroup } from './useDiyGroup';
import { ref,getCurrentInstance } from 'vue'; import { ref,getCurrentInstance } from 'vue';
const props = defineProps(['data']); const props = defineProps(['data']);
@ -144,22 +150,18 @@ const getFormRef = () => {
componentRefs: instance.refs componentRefs: instance.refs
} }
} }
const diyStore = useDiyStore(); const diyStore = useDiyStore();
const diyGroup = useDiyGroup({ const diyGroup = useDiyGroup({
...props, ...props,
getFormRef getFormRef
}); });
const data = ref(diyGroup.data);
const data = ref(diyGroup.data)
// //
diyGroup.onMounted() diyGroup.onMounted();
// //
diyGroup.onPageScroll() diyGroup.onPageScroll();
defineExpose({ defineExpose({
refresh: diyGroup.refresh, refresh: diyGroup.refresh,
getFormRef getFormRef

View File

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

View File

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

View File

@ -131,3 +131,13 @@ export function bind(data: AnyObject) {
export function memberLog(data: AnyObject) { export function memberLog(data: AnyObject) {
return request.post('member/log', data, { showErrorMessage: false }) 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

@ -158,3 +158,10 @@ export function getInitInfo(params: Record<string, any>) {
export function getMemberMobileExist(params: Record<string, any>) { export function getMemberMobileExist(params: Record<string, any>) {
return request.get('member_mobile_exist', params) 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 class="line" :style="{'background-color': getTabColor(currTabIndex == -1)}" v-if="currTabIndex == -1"></view> -->
</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 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 class="line" :style="{'background-color': getTabColor(index == currTabIndex)}" v-if="index == currTabIndex"></view> -->
</view> </view>
</scroll-view> </scroll-view>
@ -148,8 +148,8 @@
<script setup lang="ts"> <script setup lang="ts">
// //
import { ref, reactive, computed, watch, onMounted, nextTick, getCurrentInstance } from 'vue'; import { ref, reactive, computed, watch, onMounted, nextTick, getCurrentInstance, onUnmounted } from 'vue';
import { img } from '@/utils/common'; import { img, getTopFixedStatusName } from '@/utils/common';
import useDiyStore from '@/app/stores/diy'; import useDiyStore from '@/app/stores/diy';
import diyGroup from '@/addon/components/diy/group/index.vue'; import diyGroup from '@/addon/components/diy/group/index.vue';
import { getDiyInfo } from '@/app/api/diy'; import { getDiyInfo } from '@/app/api/diy';
@ -164,6 +164,12 @@ const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') { if (diyStore.mode == 'decorate') {
return diyStore.value[props.index]; return diyStore.value[props.index];
} else { } else {
// h5
if(uni.getStorageSync(getTopFixedStatusName(props.global))){
diyStore.topFixedStatus = uni.getStorageSync(getTopFixedStatusName(props.global));
}else{
diyStore.topFixedStatus = 'home'
}
return props.component; return props.component;
} }
}) })
@ -376,8 +382,8 @@ const changeData = (item: any, index: any) => {
// //
diyStore.topFixedStatus = 'diy' diyStore.topFixedStatus = 'diy'
getDiyInfoFn(item.diy_id); getDiyInfoFn(item.diy_id);
} }
uni.setStorageSync(getTopFixedStatusName(props.global), diyStore.topFixedStatus);
} }
const tabAllPopup = ref(false); const tabAllPopup = ref(false);
@ -389,6 +395,11 @@ const navbarInnerStyle = ref('')
// //
navbarInnerStyle.value += 'padding-top:' + systemStore.menuButtonInfo.top + 'px;'; navbarInnerStyle.value += 'padding-top:' + systemStore.menuButtonInfo.top + 'px;';
// #endif // #endif
// #ifdef APP-PLUS
navbarInnerStyle.value += 'padding-top:' + systemStore.systemInfo.statusBarHeight + 'px;';
// #endif
onMounted(() => { onMounted(() => {
refresh(); refresh();
// //
@ -397,6 +408,7 @@ onMounted(() => {
() => diyComponent.value, () => diyComponent.value,
(newValue, oldValue) => { (newValue, oldValue) => {
if (newValue && newValue.componentName == 'CarouselSearch') { if (newValue && newValue.componentName == 'CarouselSearch') {
diyStore.topFixedStatus = 'home'; // home
refresh(); refresh();
} }
} }
@ -427,11 +439,14 @@ onMounted(() => {
// #endif // #endif
}); });
onUnmounted(() => {
uni.removeStorageSync(getTopFixedStatusName(props.global))
})
const refresh = () => { const refresh = () => {
setModuleLocation(); setModuleLocation();
// //
locationVal.refresh(); locationVal.refresh();
changeData({ source: 'home' }, -1) changeData({ source: 'home' }, -1)
diyComponent.value.swiper.list.forEach((item: any) => { diyComponent.value.swiper.list.forEach((item: any) => {
if (item.imageUrl == '') { if (item.imageUrl == '') {
@ -471,6 +486,7 @@ const getDiyInfoFn = (id: any) => {
diyPageData.value = sources.value; diyPageData.value = sources.value;
diyPageData.value.forEach((item: any, index) => { diyPageData.value.forEach((item: any, index) => {
item.componentIsShow = true //
item.pageStyle = ''; item.pageStyle = '';
if (item.pageStartBgColor) { if (item.pageStartBgColor) {
if (item.pageStartBgColor && item.pageEndBgColor) item.pageStyle += `background:linear-gradient(${ item.pageGradientAngle },${ item.pageStartBgColor },${ item.pageEndBgColor });`; 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) => { diyComponent.value.list.forEach((item: any, index: any) => {
item.imgWidth = 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 2) + 'px) / 2)'; //ratio
item.widthStyle = item.imgWidth; // 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) { if (index <= 1) {
item.imgHeight = maxHeightFirst; item.imgHeight = maxHeightFirst;
} else if (index > 1) { } else if (index > 1) {

View File

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

View File

@ -1,7 +1,7 @@
<template> <template>
<view class="w-screen h-screen" :style="themeColor()"> <view class="w-screen h-screen" :style="themeColor()">
<view class="w-screen h-screen" :style="warpStyle"> <view class="w-screen h-screen" :style="warpStyle">
<!-- #ifdef MP-WEIXIN --> <!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view :style="{'height':headerHeight}"> <view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" /> <top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view> </view>
@ -52,11 +52,11 @@
<!-- 手机号登录 --> <!-- 手机号登录 -->
<view v-if="loginConfig.is_mobile" class="mb-[40rpx] w-full flex items-center justify-center"> <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> <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 --> <!-- #endif -->
<!-- #ifdef MP-WEIXIN --> <!-- #ifdef MP -->
<button v-if="authRegisterLogin && loginConfig.is_mobile" <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)]" 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'}})"> @click="redirect({ url: '/app/pages/auth/login',param:{type:'mobile'}})">

View File

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

View File

@ -1,6 +1,6 @@
<template> <template>
<view class="w-screen h-screen flex flex-col" :style="themeColor()" v-if="type"> <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}"> <view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" /> <top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view> </view>

View File

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

View File

@ -2,7 +2,7 @@
<view :style="themeColor()"> <view :style="themeColor()">
<view class="bg-[var(--page-bg-color)] min-h-screen overflow-hidden" v-if="Object.keys(friendsInfo).length && !loading"> <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"> <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"> <view class="sticky top-0 left-0 right-0 z-100">
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" /> <top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
</view> </view>
@ -52,11 +52,11 @@
<view class="border-0 border-solid border-b-[1rpx] border-[#f6f6f6] mb-[20rpx]"> <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 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"> <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> <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" /> <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> </template>
</u--image> </up-image>
</view> </view>
<view class="ml-[20rpx] flex flex-1 flex-col justify-between"> <view class="ml-[20rpx] flex flex-1 flex-col justify-between">
<view> <view>

View File

@ -2,7 +2,7 @@
<view :style="themeColor()"> <view :style="themeColor()">
<view class="bg-[var(--page-bg-color)] min-h-screen overflow-hidden" v-if="Object.keys(friendsInfo).length && !loading"> <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"> <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"> <view class="sticky top-0 left-0 right-0 z-100">
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" /> <top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
</view> </view>
@ -45,11 +45,11 @@
<view class="border-0 border-solid border-b-[1rpx] border-[#f6f6f6] mb-[20rpx]"> <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 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"> <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> <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" /> <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> </template>
</u--image> </up-image>
</view> </view>
<view class="ml-[20rpx] flex flex-1 flex-col justify-between"> <view class="ml-[20rpx] flex flex-1 flex-col justify-between">
<view> <view>
@ -201,7 +201,8 @@ const getFriendspayInfoFn = (tradeType : string, tradeId : number) => {
}, },
weapp: { weapp: {
...share ...share
} },
isStop:true
}); });
// - end // - end
copyUrlFn(); copyUrlFn();

View File

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

View File

@ -80,8 +80,10 @@ import { redirect } from '@/utils/common'
import { t } from '@/locale' import { t } from '@/locale'
import { addAddress, editAddress, getAddressInfo } from '@/app/api/member' import { addAddress, editAddress, getAddressInfo } from '@/app/api/member'
import manifestJson from '@/manifest.json' 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({ const formData: any = ref({
id: 0, id: 0,
name: '', name: '',
@ -123,7 +125,7 @@ onLoad((data: any) => {
if (uni.getStorageSync('addressInfo')) { if (uni.getStorageSync('addressInfo')) {
Object.assign(formData.value, uni.getStorageSync('addressInfo')) Object.assign(formData.value, uni.getStorageSync('addressInfo'))
} }
formData.value.address = data.name; // formData.value.address = data.name;
getAddress(data.latng); getAddress(data.latng);
const tempArr = getQueryVariable('latng').split(','); const tempArr = getQueryVariable('latng').split(',');
formData.value.lat = tempArr[0]; formData.value.lat = tempArr[0];
@ -133,6 +135,7 @@ onLoad((data: any) => {
if (selectAddress) { if (selectAddress) {
addressType.value = selectAddress.delivery == 'express' ? 'address' : 'locationAddress'; addressType.value = selectAddress.delivery == 'express' ? 'address' : 'locationAddress';
} }
getAreatreeFn()
// #ifdef MP // #ifdef MP
nextTick(() => { nextTick(() => {
if (wxPrivacyPopupRef.value) wxPrivacyPopupRef.value.proactive(); if (wxPrivacyPopupRef.value) wxPrivacyPopupRef.value.proactive();
@ -256,8 +259,13 @@ const save = () => {
// //
const chooseLocation = () => { const chooseLocation = () => {
// #ifdef MP let latitude = systemStore.diyAddressInfo ? systemStore.diyAddressInfo.latitude : '';
let longitude = systemStore.diyAddressInfo ? systemStore.diyAddressInfo.longitude : '';
// #ifndef H5
uni.chooseLocation({ uni.chooseLocation({
latitude,
longitude,
success: (res) => { success: (res) => {
res.latitude && (formData.value.lat = res.latitude) res.latitude && (formData.value.lat = res.latitude)
res.longitude && (formData.value.lng = res.longitude) res.longitude && (formData.value.lng = res.longitude)
@ -301,6 +309,9 @@ const chooseLocation = () => {
const getAddress = (latlng: any) => { const getAddress = (latlng: any) => {
getAddressByLatlng({ latlng }).then((res: any) => { getAddressByLatlng({ latlng }).then((res: any) => {
if (res.data) { if (res.data) {
// #ifdef H5
formData.value.address = res.data.formatted_addresses.recommend;
// #endif
formData.value.full_address = ''; formData.value.full_address = '';
formData.value.full_address += res.data.province != undefined ? res.data.province : ''; formData.value.full_address += res.data.province != undefined ? res.data.province : '';
formData.value.full_address += res.data.city != undefined ? '' + res.data.city : ''; 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.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.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.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.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; formData.value.district_id = res.data.district_id != undefined ? res.data.district_id : 0;
@ -330,20 +340,46 @@ const getQueryVariable = (variable: any) => {
} }
return false; 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 loadingchoosegAddress = ref(false)
const choosegAddress = () =>{ const choosegAddress = () =>{
loadingchoosegAddress.value = true loadingchoosegAddress.value = true
uni.chooseAddress({ uni.chooseAddress({
success(res) { success(res) {
console.log(7744)
loadingchoosegAddress.value = false loadingchoosegAddress.value = false
// -- // --
// const formattedAddress = `${res.userName}-${res.telNumber}-${res.provinceName}${res.cityName}${res.countyName}${res.detailInfoNew || res.detailInfo}`; // 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.name = res.userName
formData.value.mobile = res.telNumber formData.value.mobile = res.telNumber
formData.value.area = res.provinceName + res.cityName + res.countyName formData.value.area = res.provinceName + res.cityName + res.countyName
formData.value.address = res.detailInfo 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) { fail(err) {
loadingchoosegAddress.value = false 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="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="fixed w-full z-2 !bg-[var(--page-bg-color)]">
<view class="pb-[190rpx] text-[#fff] w-full" :style="headerStyle"> <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" /> <top-tabbar :data="param" class="top-header" />
<!-- #endif --> <!-- #endif -->
<view class="leading-[38rpx] text-[28rpx] pl-[60rpx] pt-[100rpx]">{{ t('accountBalance') }}</view> <view class="leading-[38rpx] text-[28rpx] pl-[60rpx] pt-[100rpx]">{{ t('accountBalance') }}</view>
@ -153,6 +153,9 @@ const mescrollTop = computed(() => {
return '718rpx' return '718rpx'
} }
} else { } else {
// #ifdef APP-PLUS
return (pxToRpx(Number(systemStore.systemInfo.statusBarHeight)) + pxToRpx(8) + 632) + 'rpx'
// #endif
if (Object.keys(systemStore.menuButtonInfo).length) { if (Object.keys(systemStore.menuButtonInfo).length) {
return (pxToRpx(Number(systemStore.menuButtonInfo.height)) + pxToRpx(systemStore.menuButtonInfo.top) + pxToRpx(8) + 632) + 'rpx' return (pxToRpx(Number(systemStore.menuButtonInfo.height)) + pxToRpx(systemStore.menuButtonInfo.top) + pxToRpx(8) + 632) + 'rpx'
} else { } 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="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="fixed w-full z-2 !bg-[var(--page-bg-color)]">
<view class="pb-[272rpx]" :style="headerStyle"> <view class="pb-[272rpx]" :style="headerStyle">
<!-- #ifdef MP-WEIXIN --> <!-- #ifdef MP-WEIXIN || APP-PLUS -->
<top-tabbar :data="param" class="top-header" /> <top-tabbar :data="param" class="top-header" />
<!-- #endif --> <!-- #endif -->
</view> </view>

View File

@ -17,12 +17,19 @@
import { t } from '@/locale' import { t } from '@/locale'
import { img } from '@/utils/common'; import { img } from '@/utils/common';
import { ref } from 'vue'; import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
const sendMessageTitle = ref('') const sendMessageTitle = ref('')
const sendMessagePath = ref('') const sendMessagePath = ref('')
const sendMessageImg = ref('') const sendMessageImg = ref('')
sendMessageImg.value = img('static/resource/images/member/contact_service.png') 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,14 +1,14 @@
<template> <template>
<view class="min-h-[100vh] bg-[var(--page-bg-color)] overflow-hidden" :style="themeColor()"> <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="fixed left-0 right-0 top-0 z-99">
<view class="bg-[#fff] px-[var(--sidebar-m)] py-[14rpx] flex items-center"> <view class="bg-[#f6f6f6] px-[var(--sidebar-m)] py-[14rpx] flex items-center">
<view class="search-input"> <view class="search-input bg-[#fff]">
<text class="nc-iconfont nc-icon-sousuo-duanV6xx1 btn" @click="searchTypeFn()"></text> <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()"> <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> <text v-if="keyword" class="nc-iconfont nc-icon-cuohaoV6xx1 clear" @click="keyword=''"></text>
</view> </view>
</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">
<view class="tab-left-item" <view class="tab-left-item"
:class="{'!text-primary class-select':fromType.from_type === item.from_type && fromType.account_data_gt === item.account_data_gt}" :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"> <view :style="themeColor()" class="bg-[var(--page-bg-color)] min-h-[100vh] overflow-hidden">
<loading-page :loading="loading && memberInfo"></loading-page> <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 }"> <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()" /> <top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
<!-- #endif --> <!-- #endif -->
<view> <view>
@ -111,7 +111,7 @@
</view> </view>
</view> </view>
<view v-if="!loading && (!list || !list.length)"> <view v-if="!loading && (!list || !list.length)">
<!-- #ifdef MP --> <!-- #ifdef MP || APP-PLUS -->
<top-tabbar :data="topTabbarDataEmpty" /> <top-tabbar :data="topTabbarDataEmpty" />
<!-- #endif --> <!-- #endif -->
<view class="empty-page" > <view class="empty-page" >

View File

@ -3,7 +3,7 @@
<template v-if="!loading"> <template v-if="!loading">
<view class="w-full bg-[var(--page-bg-color)]"> <view class="w-full bg-[var(--page-bg-color)]">
<view class="pb-[210rpx] relative" :style="headerStyle"> <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" /> <top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
<!-- #endif --> <!-- #endif -->
<view class="text-[70rpx] leading-[90rpx] text-[#fff] pl-[60rpx] font-500 pt-[77rpx] price-font">{{ pointInfo.point || 0 }}</view> <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(() => { 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 return style
}) })

View File

@ -1,8 +1,8 @@
<template> <template>
<view class="bg-[var(--page-bg-color)] min-h-[100vh]" :style="themeColor()"> <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="fixed left-0 right-0 top-0 z-10085">
<view class="bg-[#fff] px-[30rpx] h-[88rpx] flex-center relative z-10084"> <view class="bg-[#f6f6f6] px-[30rpx] h-[88rpx] pt-[10rpx] flex-center relative z-10084">
<view class="search-input"> <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> <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-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> <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> </view>
</u-popup> </u-popup>
</view> </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="flex items-center">
<view class="px-[30rpx] bg-[#fff] rounded-[30rpx] text-[24rpx] leading-[54rpx] mr-[20rpx] text-[#333]" <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}" :class="{'!text-[var(--primary-color)] font-500':amount_type == item.status}"
@ -31,9 +31,9 @@
</view> </view>
</view> </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" <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="flex justify-between items-center">
<view class="text-[#333]"> <view class="text-[#333]">
<text class="text-[26rpx]">{{ item.month_info.year }}</text> <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 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 v-if="info.is_use">
<view class="sigin-header"> <view class="sigin-header">
<!-- #ifdef MP-WEIXIN --> <!-- #ifndef H5 -->
<view v-if="info.rule_explain" class="side-tab" :style="{top: topStyle}" @click="signPopup = true"> <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="nc-iconfont nc-icon-a-meiriqiandaoV6xx-36 icon"></text>
<text class="desc">签到规则</text> <text class="desc">签到规则</text>
@ -154,7 +154,7 @@
</view> </view>
</view> </view>
<view class="h-[100vh] w-[100vw] flex justify-center items-center" v-else> <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" /> <top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
<!-- #endif --> <!-- #endif -->
<u-empty text="签到未开启" width="347rpx" height="265rpx" :icon="img('static/resource/images/system/empty.png')" /> <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 ***********/ /********* 自定义头部 - end ***********/
const headStyle = computed(() => { const headStyle = computed(() => {
// #ifdef MP
let style = pxToRpx(Number(systemStore.menuButtonInfo.height) + systemStore.menuButtonInfo.top + 8) + 382 + 'rpx;' 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 return style
}) })
const topStyle = computed(() => { const topStyle = computed(() => {
// #ifdef MP
let style = pxToRpx(Number(systemStore.menuButtonInfo.height) + systemStore.menuButtonInfo.top + 8) + 50 + 'rpx;' 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 return style
}) })
</script> </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('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('switchLang')" :is-link="true" :value="lang" @click="langSheetShow = true"></u-cell>
<u-cell :title="t('version')" :value="version"></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> </u-cell-group>
</view> </view>
<view class="mb-[var(--top-m)] sidebar-margin card-template !py-[20rpx]"> <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" <u-action-sheet :actions="langList" :show="langSheetShow" :closeOnClickOverlay="true"
:safeAreaInsetBottom="true" :safeAreaInsetBottom="true"
@close="langSheetShow = false" @select="switchLang"></u-action-sheet> @close="langSheetShow = false" @select="switchLang"></u-action-sheet>
<!-- 版本更新弹窗 -->
<!-- #ifdef APP -->
<!-- <update-version ref="updateVersionRef"></update-version> -->
<!-- #endif -->
</view> </view>
</template> </template>
@ -26,10 +38,30 @@
import { ref, reactive, computed } from 'vue' import { ref, reactive, computed } from 'vue'
import useMemberStore from '@/stores/member' import useMemberStore from '@/stores/member'
import { t, language } from '@/locale' import { t, language } from '@/locale'
import updateVersion from '@/components/update-version/update-version.vue'
import useSystemStore from '@/stores/system';
const memberStore = useMemberStore() const memberStore = useMemberStore()
const systemStore = useSystemStore();
const versionInfo = computed(() => {
return systemStore.versionInfo
})
const version = ref(import.meta.env.VITE_APP_VERSION) 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) => { const switchLang = (lang) => {
language.loadAllLocaleMessages('app', lang.value) 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

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

View File

@ -1,8 +1,8 @@
<template> <template>
<view class="bg-[var(--page-bg-color)] min-h-screen overflow-hidden" :style="themeColor()"> <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="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> <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()"> <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> <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> <text class="text-[#303133] text-[26rpx] font-400 nc-iconfont nc-icon-fuzhiV6xx1 ml-[11rpx]" @click.stop="copy(item.code)"></text>
</view> </view>
<view class="flex flex-1 mb-2" v-for="(dataItem,dataIndex) in item.value.list" :key="dataIndex"> <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> <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"/> <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> </template>
</u--image> </up-image>
<view class="flex flex-col flex-1 ml-[20rpx] py-[4rpx]"> <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="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 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, // 页面背景颜色(开始) pageStartBgColor: string, // 页面背景颜色(开始)
pageEndBgColor: string, // 页面背景颜色(结束) pageEndBgColor: string, // 页面背景颜色(结束)
bottomTabBar: any, // 底部导航 bottomTabBar: any, // 底部导航
copyright: any, // 版权信息
bgUrl: string bgUrl: string
}, },
// 组件集合 // 组件集合
@ -38,6 +39,15 @@ const useDiyStore = defineStore('diy', {
bottomTabBar: { bottomTabBar: {
control: true, // 是否允许展示编辑 control: true, // 是否允许展示编辑
isShow: true, // 是否显示 isShow: true, // 是否显示
designNav:{
title:'',
key:''
}
},
copyright:{
control: true, // 是否允许展示编辑
isShow: false, // 是否显示
textColor: '#ccc',
}, },
bgUrl: '' bgUrl: ''
}, },

View File

@ -17,8 +17,8 @@
</view> </view>
<view class="flex-1 pr-[10rpx]" v-else></view> <view class="flex-1 pr-[10rpx]" v-else></view>
</view> </view>
<scroll-view scroll-y="true" class="h-[50vh]" :scroll-top="scrollTop" scroll-with-animation @touchmove.stop> <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 h-[50vh]"> <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-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 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> </view>
@ -138,6 +138,7 @@ watch(() => selected.city, (nval) => {
} }
} }
if (!data.length) { if (!data.length) {
currSelect.value = 'city'
emits('complete', selected) emits('complete', selected)
show.value = false show.value = false
} }
@ -155,40 +156,40 @@ const emits = defineEmits(['complete'])
// //
const scrollToSelected = (type: string, selectedIndex: number) => { const scrollToSelected = (type: string, selectedIndex: number) => {
// //
const itemHeight = 80 // 80rpx const itemHeight = 40 // 80rpx
const targetScrollTop = Math.max(0, (selectedIndex - 2) * itemHeight) // 3 const targetScrollTop = Math.max(0, (selectedIndex - 2) * itemHeight) // 3
scrollTop.value = targetScrollTop scrollTop.value = targetScrollTop
} }
// //
const resetScrollTop = () => { const resetScrollTop = () => {
scrollTop.value = 0 scrollTop.value = 1;
nextTick(() => {
scrollTop.value = 0;
});
} }
// // currSelectnextTickDOM
watch(() => currSelect.value, (newVal) => { watch(() => currSelect.value, (newVal) => {
// nextTick(() => { // DOM
resetScrollTop()
setTimeout(() => {
if (newVal === 'province' && selected.province) { if (newVal === 'province' && selected.province) {
const index = areaList.province.findIndex((item: any) => item.id === selected.province.id) const index = areaList.province.findIndex((item: any) => item.id === selected.province.id);
if (index >= 0) { if (index >= 0) {
scrollToSelected('province', index) scrollToSelected('province', index); //
} }
} else if (newVal === 'city' && selected.city) { } else if (newVal === 'city' && selected.city) {
const index = areaList.city.findIndex((item: any) => item.id === selected.city.id) const index = areaList.city.findIndex((item: any) => item.id === selected.city.id);
if (index >= 0) { if (index >= 0) {
scrollToSelected('city', index) scrollToSelected('city', index); //
} }
} else if (newVal === 'district' && selected.district) { } else if (newVal === 'district' && selected.district) {
const index = areaList.district.findIndex((item: any) => item.id === selected.district.id) const index = areaList.district.findIndex((item: any) => item.id === selected.district.id);
if (index >= 0) { if (index >= 0) {
scrollToSelected('district', index) scrollToSelected('district', index); //
} }
} }
}, 150) });
}) }, { immediate: true }); // immediate
/** /**
* 监听区县变更 * 监听区县变更
@ -204,7 +205,6 @@ watch(() => selected.district, (nval) => {
// //
const handleProvinceClick = (item: any) => { const handleProvinceClick = (item: any) => {
selected.province = item selected.province = item
//
nextTick(() => { nextTick(() => {
resetScrollTop() resetScrollTop()
}) })
@ -213,7 +213,6 @@ const handleProvinceClick = (item: any) => {
// //
const handleCityClick = (item: any) => { const handleCityClick = (item: any) => {
selected.city = item selected.city = item
//
nextTick(() => { nextTick(() => {
resetScrollTop() resetScrollTop()
}) })

View File

@ -8,7 +8,7 @@
:style="itemStyle(index)" :style="itemStyle(index)"
:class="{ 'scale-in': item.isNew }" :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> <template #error>
<image <image
class="w-[30rpx] h-[30rpx] rounded-full" class="w-[30rpx] h-[30rpx] rounded-full"
@ -16,7 +16,7 @@
mode="aspectFill" mode="aspectFill"
/> />
</template> </template>
</u--image> </up-image>
</view> </view>
</view> </view>
</view> </view>

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

View File

@ -11,12 +11,12 @@
</view> </view>
<view v-if="data.customer_phone" class="w-full h-[1rpx] bg-dashed-style my-[40rpx]"></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="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" /> <image class="w-[226rpx] h-[226rpx]" :src="img(data.customer_qrcode)" mode="widthFix" />
</view> </view>
<view class="text-[26rpx] text-[#333] mt-[20rpx]">扫描二维码添加客服</view> <view class="text-[26rpx] text-[#333] mt-[20rpx]">扫描二维码添加客服</view>
</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> </scroll-view>
</view> </view>
<!-- <view @click="servicesDataShow = false" class="mt-[50rpx] nc-iconfont nc-icon-cuohaoV6xx1 !text-[50rpx] text-[#fff]"></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> </view>
</scroll-view> </scroll-view>
<view class="btn-wrap"> <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>
</view> </view>
</u-popup> </u-popup>
@ -145,6 +145,18 @@ const confirmPay = () => {
location.href = res.data.url location.href = res.data.url
} }
// #endif // #endif
// #ifndef H5
uni.requestPayment({
provider: 'alipay',
...res.data,
success: (res: any) => {
toPayResult()
},
fail: (res: any) => {
loading.value = false
}
})
// #endif
break; break;
default: default:
if (res.data.url) { if (res.data.url) {

View File

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

View File

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

View File

@ -46,7 +46,6 @@ if (!addon && configStore.addon) {
} }
const tabbar: any = reactive({}) const tabbar: any = reactive({})
const setTabbar = () => { const setTabbar = () => {
let list = cloneDeep(useConfigStore().tabbarList); let list = cloneDeep(useConfigStore().tabbarList);
if (list.length == 1) { if (list.length == 1) {
@ -96,6 +95,19 @@ const setTabbar = () => {
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( watch(
() => props.addon, () => props.addon,
(newValue, oldValue) => { (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="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 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 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 class="title-wrap" :style="styleOneFontSize">{{ data.title }}</view>
</view> </view>
<view v-if="topStatusBarData.style == 'style-2'" class="content-wrap" :style="navbarInnerStyle" @click="diyStore.toRedirect(topStatusBarData.link)"> <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"> <script setup lang="ts">
import { ref, computed, onMounted, getCurrentInstance, nextTick } from 'vue'; 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 useSystemStore from '@/stores/system';
import useDiyStore from '@/app/stores/diy'; import useDiyStore from '@/app/stores/diy';
import { useLocation } from '@/hooks/useLocation' import { useLocation } from '@/hooks/useLocation'
@ -82,20 +82,13 @@ const props = defineProps({
isFill: { isFill: {
type: Boolean, type: Boolean,
default: true default: true
},
//
mustFill: {
type: Boolean,
default: false
} }
}) })
// - // -
const isFill = computed(() => { const isFill = computed(() => {
let bool = true; let bool = true;
if(props.mustFill){ if (diyStore.imageAdsSameScreen) {
return props.mustFill
}else if (diyStore.imageAdsSameScreen) {
bool = false; bool = false;
} else { } else {
bool = props.isFill; bool = props.isFill;
@ -201,6 +194,8 @@ const isBackShow = computed(() => {
let bool = false; let bool = false;
if (props.isBack && pages.length > 1) { if (props.isBack && pages.length > 1) {
bool = true; bool = true;
} else if (currRoute() == 'app/pages/auth/index') {
bool = true;
} }
return bool; return bool;
}) })
@ -212,7 +207,14 @@ const goBack = () => {
if (typeof props.customBack === 'function') { if (typeof props.customBack === 'function') {
props.customBack(); props.customBack();
} else { } else {
uni.navigateBack(); if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
redirect({
url: '/addon/shop/pages/index',
mode: 'reLaunch'
});
}
} }
} }
/******************************* 返回按钮-end ***********************/ /******************************* 返回按钮-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, updateWechatOpenid,
wechatUser, wechatUser,
wechatUserLogin, wechatUserLogin,
updateWechatOpenidByH5 updateWechatOpenidByH5,
wxappLogin
} from '@/app/api/auth' } from '@/app/api/auth'
import { getWechatAuthCode } from '@/app/api/system' import { getWechatAuthCode } from '@/app/api/system'
import useMemberStore from '@/stores/member' import useMemberStore from '@/stores/member'
@ -23,8 +23,7 @@ export function useLogin() {
const config = useConfigStore() const config = useConfigStore()
const systemStore = useSystemStore() const systemStore = useSystemStore()
// #ifdef MP-WEIXIN // #ifdef MP
if (!uni.getStorageSync('autoLoginLock') && config.login.is_bind_mobile) { if (!uni.getStorageSync('autoLoginLock') && config.login.is_bind_mobile) {
uni.setStorageSync('isBindMobile', true) // 强制绑定手机号标识 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) { if (config.login.is_username && !config.login.is_mobile && !config.login.is_auth_register) {
redirect({ url: '/app/pages/auth/login', param: { type: 'username' } }) 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) { } 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 // #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 // #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
} }
/** /**
@ -341,6 +368,19 @@ export function useLogin() {
// #endif // #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) => { const updateWechatOpenidForH5 = (wx_openid: any) => {

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