mirror of
https://gitee.com/niucloud-team/niucloud-admin.git
synced 2026-03-26 07:13:08 +00:00
fix
This commit is contained in:
parent
54ae267bc5
commit
643eadfd14
2
admin/components.d.ts
vendored
2
admin/components.d.ts
vendored
@ -9,6 +9,7 @@ declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
Attachment: typeof import('./src/components/upload-attachment/attachment.vue')['default']
|
||||
DiyLink: typeof import('./src/components/diy-link/index.vue')['default']
|
||||
DiyPage: typeof import('./src/components/diy-page/index.vue')['default']
|
||||
Editor: typeof import('./src/components/editor/index.vue')['default']
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElAside: typeof import('element-plus/es')['ElAside']
|
||||
@ -89,6 +90,7 @@ declare module '@vue/runtime-core' {
|
||||
SelectIcon: typeof import('./src/components/select-icon/index.vue')['default']
|
||||
SpreadPopup: typeof import('./src/components/spread-popup/index.vue')['default']
|
||||
UploadAttachment: typeof import('./src/components/upload-attachment/index.vue')['default']
|
||||
UploadAudio: typeof import('./src/components/upload-audio/index.vue')['default']
|
||||
UploadFile: typeof import('./src/components/upload-file/index.vue')['default']
|
||||
UploadImage: typeof import('./src/components/upload-image/index.vue')['default']
|
||||
UploadVideo: typeof import('./src/components/upload-video/index.vue')['default']
|
||||
|
||||
65
admin/package-lock.json
generated
65
admin/package-lock.json
generated
@ -9,12 +9,18 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "2.0.10",
|
||||
"@fullcalendar/core": "^6.1.19",
|
||||
"@fullcalendar/daygrid": "^6.1.19",
|
||||
"@fullcalendar/interaction": "^6.1.19",
|
||||
"@fullcalendar/vue3": "^6.1.19",
|
||||
"@heroicons/vue": "^2.2.0",
|
||||
"@highlightjs/vue-plugin": "2.1.0",
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@vueuse/core": "9.12.0",
|
||||
"axios": "1.4.0",
|
||||
"crypto-js": "4.1.1",
|
||||
"css-color-function": "1.3.3",
|
||||
"date-fns": "^4.1.0",
|
||||
"day": "^0.0.2",
|
||||
"echarts": "5.4.1",
|
||||
"element-plus": "^2.7.4",
|
||||
@ -963,6 +969,47 @@
|
||||
"resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.1.6.tgz",
|
||||
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
|
||||
},
|
||||
"node_modules/@fullcalendar/core": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/@fullcalendar/core/-/core-6.1.19.tgz",
|
||||
"integrity": "sha512-z0aVlO5e4Wah6p6mouM0UEqtRf1MZZPt4mwzEyU6kusaNL+dlWQgAasF2cK23hwT4cmxkEmr4inULXgpyeExdQ==",
|
||||
"dependencies": {
|
||||
"preact": "~10.12.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/daygrid": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/@fullcalendar/daygrid/-/daygrid-6.1.19.tgz",
|
||||
"integrity": "sha512-IAAfnMICnVWPjpT4zi87i3FEw0xxSza0avqY/HedKEz+l5MTBYvCDPOWDATpzXoLut3aACsjktIyw9thvIcRYQ==",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/interaction": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/@fullcalendar/interaction/-/interaction-6.1.19.tgz",
|
||||
"integrity": "sha512-GOciy79xe8JMVp+1evAU3ytdwN/7tv35t5i1vFkifiuWcQMLC/JnLg/RA2s4sYmQwoYhTw/p4GLcP0gO5B3X5w==",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/vue3": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/@fullcalendar/vue3/-/vue3-6.1.19.tgz",
|
||||
"integrity": "sha512-j5eUSxx0xIy3ADljo0f5B9PhjqXnCQ+7nUMPfsslc2eGVjp4F74YvY3dyd6OBbg13IvpsjowkjncGipYMQWmTA==",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.19",
|
||||
"vue": "^3.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroicons/vue": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/@heroicons/vue/-/vue-2.2.0.tgz",
|
||||
"integrity": "sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==",
|
||||
"peerDependencies": {
|
||||
"vue": ">= 3"
|
||||
}
|
||||
},
|
||||
"node_modules/@highlightjs/vue-plugin": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@highlightjs/vue-plugin/-/vue-plugin-2.1.0.tgz",
|
||||
@ -2624,6 +2671,15 @@
|
||||
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz",
|
||||
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/day": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/day/-/day-0.0.2.tgz",
|
||||
@ -5003,6 +5059,15 @@
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.12.1",
|
||||
"resolved": "https://registry.npmmirror.com/preact/-/preact-10.12.1.tgz",
|
||||
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
|
||||
@ -10,12 +10,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "2.0.10",
|
||||
"@fullcalendar/core": "^6.1.19",
|
||||
"@fullcalendar/daygrid": "^6.1.19",
|
||||
"@fullcalendar/interaction": "^6.1.19",
|
||||
"@fullcalendar/vue3": "^6.1.19",
|
||||
"@heroicons/vue": "^2.2.0",
|
||||
"@highlightjs/vue-plugin": "2.1.0",
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@vueuse/core": "9.12.0",
|
||||
"axios": "1.4.0",
|
||||
"crypto-js": "4.1.1",
|
||||
"css-color-function": "1.3.3",
|
||||
"date-fns": "^4.1.0",
|
||||
"day": "^0.0.2",
|
||||
"echarts": "5.4.1",
|
||||
"element-plus": "^2.7.4",
|
||||
|
||||
@ -147,7 +147,10 @@ div.edui-box {
|
||||
overflow: visible;
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
/* 全屏状态 */
|
||||
.edui-default .edui-editor.edui-fullscreen {
|
||||
z-index: 999 !important;
|
||||
}
|
||||
.edui-editor div {
|
||||
width: auto;
|
||||
height: auto;
|
||||
|
||||
@ -96,18 +96,17 @@ export function getInstalledAddonList() {
|
||||
* @returns
|
||||
*/
|
||||
export function getShowApp() {
|
||||
return request.get('addon/list/showapp')
|
||||
return request.get('addon/showCustomer')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取营销列表
|
||||
* 获取站点应用特殊
|
||||
* @returns
|
||||
*/
|
||||
export function getShowMarketing() {
|
||||
return request.get('showMarketing')
|
||||
export function getShowSpecialMenu() {
|
||||
return request.get('addon/special_menu')
|
||||
}
|
||||
|
||||
|
||||
export function getAddonInit() {
|
||||
return request.get('addon/init')
|
||||
}
|
||||
|
||||
68
admin/src/app/api/app.ts
Normal file
68
admin/src/app/api/app.ts
Normal 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 });
|
||||
}
|
||||
@ -367,12 +367,16 @@ export function getCashOutDetail(id: number) {
|
||||
export function memberAudit(params: Record<string, any>) {
|
||||
return request.put(`member/cash_out/audit/${ params.id }/${ params.action }`, params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员取消提现
|
||||
* @param params
|
||||
*/
|
||||
export function memberCancel(params: Record<string, any>) {
|
||||
return request.put(`member/cash_out/cancel/${params.id}`, params, { showSuccessMessage: true,showErrorMessage: true })
|
||||
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>) {
|
||||
return request.put(`member/cash_out/remark/${ params.id }`, params, { showSuccessMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查打款进度
|
||||
* @param id
|
||||
@ -453,6 +458,7 @@ export function getGrowthRuleDict() {
|
||||
export function getPointRuleDict() {
|
||||
return request.get(`member/dict/point_rule`)
|
||||
}
|
||||
|
||||
/***************************************************** 会员等级 ****************************************************/
|
||||
|
||||
/**
|
||||
@ -512,14 +518,14 @@ export function getMemberLevelAll() {
|
||||
* 获取会员权益内容
|
||||
*/
|
||||
export function getMemberBenefitsContent() {
|
||||
return request.get(`member/benefits/content`);
|
||||
return request.post(`member/benefits/content`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员礼包内容
|
||||
*/
|
||||
export function getMemberGiftsContent(params: Record<string, any>) {
|
||||
return request.get(`member/gifts/content`, { params });
|
||||
return request.post(`member/gifts/content`, params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
BIN
admin/src/app/assets/images/app_store/app_manage.png
Normal file
BIN
admin/src/app/assets/images/app_store/app_manage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
admin/src/app/assets/images/app_store/app_type_addon.png
Normal file
BIN
admin/src/app/assets/images/app_store/app_type_addon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 518 B |
BIN
admin/src/app/assets/images/app_store/app_type_app.png
Normal file
BIN
admin/src/app/assets/images/app_store/app_type_app.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 478 B |
BIN
admin/src/app/assets/images/app_store/system_version.png
Normal file
BIN
admin/src/app/assets/images/app_store/system_version.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 834 B |
@ -299,7 +299,7 @@ const getUpgradeTaskFn = () => {
|
||||
if (!upgradeContent.value) {
|
||||
upgradeContent.value = data.upgrade_content
|
||||
|
||||
if (upgradeContent.value || !data.upgrade_content || !Array.isArray(data.upgrade_content.content)) {
|
||||
if (!data.upgrade_content || !Array.isArray(data.upgrade_content.content)) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -691,7 +691,7 @@ defineExpose({
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
::v-deep .number-of-steps {
|
||||
:deep(.number-of-steps) {
|
||||
.el-step__line {
|
||||
margin: 0 25px;
|
||||
background: #dddddd;
|
||||
|
||||
13
admin/src/app/lang/zh-cn/channel.app.access.json
Normal file
13
admin/src/app/lang/zh-cn/channel.app.access.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"accessFlow": "接入流程",
|
||||
"versionManage": "版本管理",
|
||||
"title": "APP端管理",
|
||||
"appInlet": "App接入流程",
|
||||
"uniappApp": "uni-app应用开通",
|
||||
"appAttestation1": "点击进入Dcloud官网开发者后台,创建或选择应用并维护好应用平台信息",
|
||||
"clickAccess": "点击接入",
|
||||
"appSetting": "App端配置",
|
||||
"settingInfo": "点击配置",
|
||||
"releaseVersion": "发布版本",
|
||||
"toCreate": "去创建"
|
||||
}
|
||||
17
admin/src/app/lang/zh-cn/channel.app.config.json
Normal file
17
admin/src/app/lang/zh-cn/channel.app.config.json
Normal 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"
|
||||
}
|
||||
55
admin/src/app/lang/zh-cn/channel.app.version.json
Normal file
55
admin/src/app/lang/zh-cn/channel.app.version.json
Normal 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": "上传代码需先绑定授权码,请联系平台管理员进行绑定"
|
||||
}
|
||||
@ -29,5 +29,8 @@
|
||||
"encodingAesKeyPlaceholder": "请输入EncodingAESKey",
|
||||
"cleartextModeTips": "明文模式下,不使用消息体加解密功能,安全系数较低",
|
||||
"compatibleModeTips": "兼容模式下,明文、密文将共存,方便开发者调试和维护",
|
||||
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高"
|
||||
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高",
|
||||
"wechatBaseUri": "借权域名",
|
||||
"wechatBaseUriPlaceholder": "",
|
||||
"wechatBaseUriTips": "默认留空,填写后将替换https://open.weixin.gg.com/获取授权!"
|
||||
}
|
||||
@ -7,5 +7,6 @@
|
||||
"validTimeTips": "过期后将重新获取定位信息",
|
||||
"validTimePlaceholder": "请输入定位有效期",
|
||||
"validTimeFormatTips": "格式输入错误",
|
||||
"validTimeNotZeroTips": "定位有效期不能小于5分钟"
|
||||
"validTimeNotZeroTips": "定位有效期不能小于5分钟",
|
||||
"aMapKey": "高德地图KEY"
|
||||
}
|
||||
@ -32,6 +32,10 @@
|
||||
"contactsTelPlaceholder": "请输入联系电话",
|
||||
"logoPlaceholder": "建议图片尺寸:210*30像素;图片格式:jpg、png、jpeg。",
|
||||
"iconPlaceholder": "建议图片尺寸:100*100像素;图片格式:jpg、png、jpeg。",
|
||||
"siteLoginLogo": "站点登录Logo",
|
||||
"siteLoginLogoTips": "站点端登录Logo,建议图片尺寸:132*40像素;图片格式:jpg、png、jpeg。",
|
||||
"siteLoginBgImg": "站点登录背景图",
|
||||
"siteLoginBgImgTips": "站点端登录背景图,建议图片尺寸:1920*1280像素;图片格式:jpg、png、jpeg。",
|
||||
"metaTitle": "Meta 标题",
|
||||
"MetaPlaceholder": "请输入Meta 标题",
|
||||
"metaDescription": "Meta 描述",
|
||||
|
||||
@ -50,6 +50,7 @@ import { img } from '@/utils/common'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { t } from '@/lang'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const addonIndexRoute = useUserStore().addonIndexRoute
|
||||
const router = useRouter()
|
||||
@ -65,10 +66,17 @@ getAppList()
|
||||
|
||||
const toLink = (item: any) => {
|
||||
if (item.url) {
|
||||
// 判断如果携带is_target=true就通过新窗口打开
|
||||
if (item.url.indexOf('is_target=true') != -1) {
|
||||
const url = router.resolve(item.url)
|
||||
window.open(url.href)
|
||||
} else {
|
||||
router.push(item.url)
|
||||
}
|
||||
} else {
|
||||
addonIndexRoute[item.key] && router.push({ name: addonIndexRoute[item.key] })
|
||||
}
|
||||
storage.set({ key: 'activeAppKey', data: item.key })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -45,7 +45,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { getShowMarketing } from '@/app/api/addon'
|
||||
import { img } from '@/utils/common'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import { useRouter } from 'vue-router'
|
||||
@ -57,8 +56,6 @@ const marketingList = ref<Record<string, any>[]>([])
|
||||
|
||||
const loading = ref(true)
|
||||
const getMarketingList = async () => {
|
||||
const res = await getShowMarketing()
|
||||
marketingList.value = res.data
|
||||
loading.value = false
|
||||
}
|
||||
getMarketingList()
|
||||
|
||||
@ -19,13 +19,19 @@
|
||||
<el-form-item :label="t('url')" prop="url">
|
||||
<el-input v-model.trim="sysUserLogTableData.searchParam.url" :placeholder="t('urlPlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('操作时间')" prop="create_time">
|
||||
<el-date-picker v-model="sysUserLogTableData.searchParam.create_time" type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
|
||||
:end-placeholder="t('endDate')" />
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadSysUserLogList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="flex justify-end items-center w-[20%]">
|
||||
<div class="flex justify-end items-center w-[10%]">
|
||||
<div>
|
||||
<el-button type="primary" class="w-[100px]" @click="clearEvent()">{{ t('清空日志') }}</el-button>
|
||||
</div>
|
||||
@ -83,7 +89,8 @@ const sysUserLogTableData = reactive({
|
||||
searchParam: {
|
||||
ip: '',
|
||||
username: '',
|
||||
url: ''
|
||||
url: '',
|
||||
create_time:''
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
|
||||
<!-- <div class="flex justify-between items-center">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mt-[20px]">
|
||||
<el-form :inline="true" :model="roleTableData.searchParam" ref="searchFormRef">
|
||||
|
||||
119
admin/src/app/views/channel/app/access.vue
Normal file
119
admin/src/app/views/channel/app/access.vue
Normal 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>
|
||||
286
admin/src/app/views/channel/app/components/app-version-edit.vue
Normal file
286
admin/src/app/views/channel/app/components/app-version-edit.vue
Normal 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.png、push.png、splash.png放置到drawable,drawable-ldpi,drawable-mdpi,drawable-hdpi,drawable-xhdpi,drawable-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>
|
||||
@ -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>
|
||||
129
admin/src/app/views/channel/app/config.vue
Normal file
129
admin/src/app/views/channel/app/config.vue
Normal 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>
|
||||
339
admin/src/app/views/channel/app/version.vue
Normal file
339
admin/src/app/views/channel/app/version.vue
Normal 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>
|
||||
@ -36,6 +36,11 @@
|
||||
<el-input v-model.trim="formData.app_secret" :placeholder="t('appSecretPlaceholder')" class="input-width" clearable />
|
||||
<div class="form-tip">{{ t('wechatAppsecretTips') }}</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('wechatBaseUri')" prop="base_uri" v-if="!formData.is_authorization">
|
||||
<el-input v-model.trim="formData.base_uri" :placeholder="t('wechatBaseUriPlaceholder')" class="input-width" clearable />
|
||||
<div class="form-tip">{{ t('wechatBaseUriTips') }}</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card !border-none mt-[15px]" shadow="never">
|
||||
@ -140,7 +145,8 @@ const formData = reactive<Record<string, any>>({
|
||||
token: '',
|
||||
encoding_aes_key: '',
|
||||
encryption_type: 'not_encrypt',
|
||||
is_authorization: 0
|
||||
is_authorization: 0,
|
||||
base_uri: ''
|
||||
})
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
@ -63,6 +63,23 @@
|
||||
<el-switch v-model="diyStore.global.bottomTabBar.isShow" />
|
||||
<div class="text-sm text-gray-400">{{ t('tabbarSwitchTips') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('选择导航')">
|
||||
<tabbar-select-popup v-model="diyStore.global.bottomTabBar.designNav" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="edit-attr-item-wrap" v-if="diyStore.global.copyright.control">
|
||||
<h3 class="mb-[10px]">{{ t('版权信息内容') }}</h3>
|
||||
<el-form label-width="80px" class="px-[10px]">
|
||||
<el-form-item :label="t('版权信息')" class="display-block">
|
||||
<el-switch v-model="diyStore.global.copyright.isShow" />
|
||||
<div class="text-sm text-gray-400">{{ t('此处控制当前页面版权信息是否显示') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('文字颜色')" class="display-block">
|
||||
<el-color-picker v-model="diyStore.global.copyright.textColor" show-alpha />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="edit-attr-item-wrap">
|
||||
@ -178,6 +195,7 @@ import { t } from '@/lang'
|
||||
import { watch, ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
import { img } from '@/utils/common'
|
||||
import tabbarSelectPopup from './tabbar-select-popup.vue'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
|
||||
|
||||
211
admin/src/app/views/diy/components/tabbar-select-popup.vue
Normal file
211
admin/src/app/views/diy/components/tabbar-select-popup.vue
Normal 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>
|
||||
@ -185,7 +185,7 @@
|
||||
<el-slider v-model="diyStore.editComponent.margin.top" show-input size="small" :min="-100" class="ml-[10px] diy-nav-slider" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('marginBottom')" v-if="diyStore.editComponent.ignore.indexOf('marginBottom') == -1">
|
||||
<el-slider v-model="diyStore.editComponent.margin.bottom" show-input size="small" class="ml-[10px] diy-nav-slider" />
|
||||
<el-slider v-model="diyStore.editComponent.margin.bottom" show-input size="small" class="ml-[10px] diy-nav-slider" :min="-100" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('marginBoth')" v-if="diyStore.editComponent.ignore.indexOf('marginBoth') == -1">
|
||||
<el-slider v-model="diyStore.editComponent.margin.both" show-input size="small" class="ml-[10px] diy-nav-slider" />
|
||||
@ -455,7 +455,6 @@ initPage({
|
||||
diyStore.components.push(com)
|
||||
}
|
||||
}
|
||||
|
||||
loadDiyTemplatePages(data.type)
|
||||
|
||||
// 加载预览
|
||||
|
||||
@ -256,7 +256,7 @@ const editEvent = (data: any) => {
|
||||
// 设为使用
|
||||
const setUse = (id: any) => {
|
||||
setUseDiyPage({ id }).then(() => {
|
||||
loadDiyPageList()
|
||||
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
|
||||
})
|
||||
}
|
||||
const repeat = ref(false)
|
||||
@ -275,7 +275,7 @@ const copyEvent = (id: any) => {
|
||||
|
||||
copyDiy({ id }).then((res: any) => {
|
||||
if (res.code == 1) {
|
||||
loadDiyPageList()
|
||||
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
|
||||
}
|
||||
repeat.value = false
|
||||
}).catch(err => {
|
||||
@ -294,7 +294,7 @@ const deleteEvent = (id: number) => {
|
||||
}
|
||||
).then(() => {
|
||||
deleteDiyPage(id).then(() => {
|
||||
loadDiyPageList()
|
||||
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
@ -358,7 +358,7 @@ const shareEvent = async (formEl: FormInstance | undefined) => {
|
||||
id: shareFormId.value,
|
||||
share: JSON.stringify(shareFormData)
|
||||
}).then(() => {
|
||||
loadDiyPageList()
|
||||
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page)
|
||||
shareDialogVisible.value = false
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="bottomNavTableData.page" v-model:page-size="bottomNavTableData.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="bottomNavTableData.total"
|
||||
@size-change="loadbottomNavList()" @current-change="loadbottomNavList" />
|
||||
@size-change="loadBottomNavList()" @current-change="loadBottomNavList" />
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<el-table-column label="应用" min-width="120" >
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center">
|
||||
<el-image class="w-[40px] h-[40px] rounded-md overflow-hidden" :src="row.icon" fit="contain">
|
||||
<el-image class="w-[40px] h-[40px] rounded-md overflow-hidden" :src="img(row.icon)" fit="contain">
|
||||
<template #error>
|
||||
<div class="flex items-center w-full h-full">
|
||||
<img class="w-full h-full" src="@/app/assets/images/icon-addon.png" alt="">
|
||||
@ -54,8 +54,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { reactive, ref, watch, computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { img } from '@/utils/common'
|
||||
import { getDiyTheme } from '@/app/api/diy'
|
||||
import { useRoute } from 'vue-router'
|
||||
import themeList from './components/theme-list.vue'
|
||||
@ -90,7 +91,7 @@ const editEvent = (data)=> {
|
||||
<!-- 设置弹窗标题 -->
|
||||
<style scoped>
|
||||
/* 使用深度选择器 */
|
||||
::v-deep .custom-theme-dialog .el-dialog__title {
|
||||
:deep(.custom-theme-dialog .el-dialog__title) {
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -159,10 +159,10 @@
|
||||
<spread-popup ref="spreadPopupRef" />
|
||||
|
||||
<!-- 表单提交成功页弹出框 -->
|
||||
<form-submit-popup ref="formSubmitPopupRef" @complete="loadDiyFormList" />
|
||||
<form-submit-popup ref="formSubmitPopupRef" @complete="loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)" />
|
||||
|
||||
<!-- 表单填写设置弹出框 -->
|
||||
<form-write-popup ref="formWritePopupRef" @complete="loadDiyFormList" />
|
||||
<form-write-popup ref="formWritePopupRef" @complete="loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)" />
|
||||
|
||||
<records-detail ref="recordsDetailDialog"/>
|
||||
|
||||
@ -352,7 +352,7 @@ const copyEvent = (id: any) => {
|
||||
|
||||
copyForm({ form_id: id }).then((res: any) => {
|
||||
if (res.code == 1) {
|
||||
loadDiyFormList()
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
}
|
||||
repeat.value = false
|
||||
}).catch(() => {
|
||||
@ -371,7 +371,7 @@ const deleteEvent = (form_id: number) => {
|
||||
}
|
||||
).then(() => {
|
||||
deleteDiyForm({ form_ids: [form_id] }).then(() => {
|
||||
loadDiyFormList()
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
@ -438,7 +438,7 @@ const batchDeleteForms = () => {
|
||||
deleteDiyForm({
|
||||
form_ids
|
||||
}).then(() => {
|
||||
loadDiyFormList()
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
repeat.value = false
|
||||
}).catch(() => {
|
||||
repeat.value = false
|
||||
@ -497,7 +497,7 @@ const shareEvent = async (formEl: FormInstance | undefined) => {
|
||||
form_id: shareFormId.value,
|
||||
share: JSON.stringify(shareFormData)
|
||||
}).then(() => {
|
||||
loadDiyFormList()
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
shareDialogVisible.value = false
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
@ -1,47 +1,127 @@
|
||||
<template>
|
||||
<!--应用市场-->
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ t("localAppText") }}</span>
|
||||
<div class="main-container bg-body min-h-[70vh]" v-loading="loading">
|
||||
<el-card class="box-card !border-none" shadow="never" v-if="!loading">
|
||||
<div class="flex items-center mb-4">
|
||||
<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>
|
||||
|
||||
<el-tabs v-model="activeName" class="mt-[10px]">
|
||||
<el-tab-pane :label="t('installLabel')" name="installed"></el-tab-pane>
|
||||
<el-tab-pane :label="t('uninstalledLabel')" name="uninstalled"></el-tab-pane>
|
||||
<el-tab-pane :label="t('buyLabel')" name="all"></el-tab-pane>
|
||||
<el-tab-pane :label="t('recentlyUpdated')" name="recentlyUpdated">
|
||||
<template #label>
|
||||
<span class="custom-tabs-label">
|
||||
<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 class="flex items-center justify-between bg-body p-4 rounded-md border rounded border-solid border-[var(--el-color-info-light-8)]">
|
||||
<div class="flex">
|
||||
<div class="flex items-center">
|
||||
<div class="w-[40px] h-[40px] bg-purple-100 rounded flex items-center justify-center mr-2 relative">
|
||||
<img class="max-w-full max-h-full" src="@/app/assets/images/app_store/system_version.png" alt="" />
|
||||
<div v-if="frameworkVersion != frameworkNewVersion" class="w-[8px] h-[8px] bg-[red] rounded-[8px] z-1 absolute top-[-2px] right-[-2px]"></div>
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" v-show="activeName === 'recentlyUpdated'" @click="batchUpgrade" :loading="upgradeRef?.loading" :disabled="authLoading">{{ t("batchUpgrade") }}</el-button>
|
||||
<!-- <el-button type="primary" @click="handleCloudBuild" :loading="cloudBuildRef?.loading" :disabled="authLoading">{{ t("cloudBuild") }}</el-button> -->
|
||||
<p class="text-sm font-bold">系统版本:
|
||||
<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 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 class="relative">
|
||||
<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>
|
||||
|
||||
<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">
|
||||
<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)}">
|
||||
@ -49,7 +129,11 @@
|
||||
</div>
|
||||
</template>
|
||||
</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">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center cursor-pointer relative left-[-10px]">
|
||||
@ -139,6 +223,8 @@
|
||||
</el-table>
|
||||
<div class="h-[100px]" v-loading="loading" v-if="loading"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty class="mx-auto overview-empty" v-if="!localList.installed.length && !loading && activeName == 'installed' && !authLoading">
|
||||
<template #image>
|
||||
<div class="w-[230px] mx-auto">
|
||||
@ -195,7 +281,7 @@
|
||||
</p>
|
||||
</template>
|
||||
</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>
|
||||
<div class="w-[230px] mx-auto">
|
||||
<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>
|
||||
</template>
|
||||
</el-empty>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="authCodeApproveDialog" title="授权码认证" width="400px">
|
||||
<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">
|
||||
<!-- <el-scrollbar max-height="50vh"> -->
|
||||
<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>
|
||||
<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">
|
||||
<el-icon :size="17"><QuestionFilled /></el-icon>
|
||||
<span class="ml-[5px] leading-[20px]">编译权限错误,查看解决方案</span></div>
|
||||
@ -285,7 +371,6 @@
|
||||
<span>{{ t("status") }}</span>
|
||||
</el-col>
|
||||
</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-col :span="18">
|
||||
<span>{{ item.dir }}</span>
|
||||
@ -326,10 +411,18 @@
|
||||
</span>
|
||||
</el-col>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </el-scrollbar> -->
|
||||
<div class="flex justify-end">
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
@ -465,7 +558,6 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 更新信息 -->
|
||||
</el-card>
|
||||
</div>
|
||||
<upgrade-log :upgradeKey="upgradeKey" ref="upgradeLogRef" />
|
||||
<upgrade ref="upgradeRef" @complete="localListFn" @cloudbuild="handleCloudBuild" />
|
||||
@ -473,7 +565,7 @@
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
getAddonLocal,
|
||||
@ -488,7 +580,7 @@ import {
|
||||
getAddonInit
|
||||
} from '@/app/api/addon'
|
||||
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 { ElMessage, ElMessageBox, ElNotification, FormInstance, FormRules } from 'element-plus'
|
||||
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 UpgradeLog from '@/app/components/upgrade-log/index.vue'
|
||||
|
||||
const tableRef = ref(null)
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const terminalId = ref(Date.now());
|
||||
const terminalId = ref(Date.now())
|
||||
const activeName = ref(storage.get('storeActiveName') || 'installed')
|
||||
const upgradeRef = ref(null)
|
||||
const cloudBuildRef = ref(null)
|
||||
@ -514,10 +607,22 @@ const userStore = useUserStore()
|
||||
const unloadHintDialog = ref(false)
|
||||
const terminalRef = ref(null)
|
||||
const frameworkVersion = ref('')
|
||||
const frameworkVersionCode = ref('')
|
||||
const upgradeLogRef = ref<any>(null)
|
||||
const showType = ref(storage.get('storeShowType') || 'card')
|
||||
const frameworkNewVersion = ref('')
|
||||
getVersions().then((res) => {
|
||||
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 getAddonInitFn = () => {
|
||||
@ -534,6 +639,12 @@ const downEventHintFn = () => {
|
||||
const activeNameTabFn = (data: any) => {
|
||||
activeName.value = 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) {
|
||||
activeNameTabFn(route.query.id)
|
||||
@ -603,26 +714,7 @@ const buildInfo = (list: any[]) => {
|
||||
|
||||
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 name = search_name.value
|
||||
const type = search_type.value
|
||||
@ -689,6 +781,7 @@ const localListFn = () => {
|
||||
appLink.value[item.meta.app] = item.name
|
||||
}
|
||||
})
|
||||
activeNameTabFn(activeName.value)
|
||||
loading.value = false
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
@ -909,6 +1002,7 @@ const handleCloudInstall = () => {
|
||||
installStep.value = 1
|
||||
terminalRef.value.execute('clear')
|
||||
terminalRef.value.execute('开始安装插件')
|
||||
if (res.data.length) installAfterTips.value = res.data
|
||||
getInstallTask()
|
||||
cloudInstalling.value = false
|
||||
}).catch((res) => {
|
||||
@ -1155,23 +1249,55 @@ const versionJudge = (row: any) => {
|
||||
return false
|
||||
}
|
||||
|
||||
let batchUpgradeApp = []
|
||||
const handleSelectionChange = (e: any) => {
|
||||
batchUpgradeApp = e.map(item => item.key)
|
||||
const batchUpgradeApp = ref<String[]>([])
|
||||
|
||||
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 = () => {
|
||||
if (!batchUpgradeApp.length) {
|
||||
ElMessage({ message: '请先勾选要升级的插件', type: 'error', duration: 5000 })
|
||||
const appKeys = batchUpgradeApp.value
|
||||
if (frameworkVersion.value != frameworkNewVersion.value) {
|
||||
appKeys.unshift('niucloud-admin')
|
||||
}
|
||||
if (!appKeys.length) {
|
||||
ElMessage({ message: localList.recentlyUpdated.length ? '请先勾选要升级的插件' : '当前已是最新版', type: 'error', duration: 5000 })
|
||||
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(() => {
|
||||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -1383,7 +1509,7 @@ html.dark .table-head-bg {
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
::v-deep .number-of-steps {
|
||||
:deep(.number-of-steps) {
|
||||
.el-step__line {
|
||||
margin: 0 25px;
|
||||
background: #dddddd;
|
||||
@ -1451,6 +1577,11 @@ html.dark .table-head-bg {
|
||||
}
|
||||
}
|
||||
|
||||
.app-card {
|
||||
width: calc((100% - 120px) / 5);
|
||||
min-width: 260px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
@ -147,7 +147,7 @@ const deleteEvent = (id: number) => {
|
||||
}
|
||||
).then(() => {
|
||||
deleteMemberLevel(id).then(() => {
|
||||
loadMemberLevelList()
|
||||
loadMemberLevelList(getTablePageStorage(memberLevelTableData.searchParam).page);
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
|
||||
@ -529,17 +529,20 @@ const save = (callback: any) => {
|
||||
|
||||
const api = posterStore.id ? editPoster : addPoster
|
||||
api(data).then((res: any) => {
|
||||
isRepeat.value = false
|
||||
// isRepeat.value = false
|
||||
if (res.code == 1) {
|
||||
if (posterStore.id) {
|
||||
isRepeat.value = false // 不刷新
|
||||
} else {
|
||||
// isRepeat.value = false
|
||||
location.href = `${location.origin}${backPath}`
|
||||
}
|
||||
if (callback) callback(res.data.id)
|
||||
}
|
||||
}).catch(() => {
|
||||
isRepeat.value = false
|
||||
}).finally(() => {
|
||||
// isRepeat.value = false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
<el-button type="primary" class="w-[100px]" @click="dialogVisible = true">{{ t('添加海报') }}</el-button>
|
||||
</div>
|
||||
<div class="mt-[20px]" v-if="!isImagick">
|
||||
<div class="mt-[20px]" v-if="!isImagick && !posterTableData.loading">
|
||||
<el-alert type="warning" show-icon :closable="false">
|
||||
<template #title>
|
||||
<span class="!text-[14px]">检测到PHP未安装ImageMagick扩展,需安装后才能使用海报功能</span>
|
||||
@ -225,7 +225,7 @@ const modifyPosterStatusFn = (id:any, status:any) => {
|
||||
modifyPosterStatus({
|
||||
id, status
|
||||
}).then((res) => {
|
||||
loadPosterPageList()
|
||||
loadPosterPageList(getTablePageStorage(posterTableData.searchParam).page)
|
||||
isRepeat.value = false
|
||||
})
|
||||
}
|
||||
@ -237,7 +237,7 @@ const modifyPosterDefaultFn = (id:any) => {
|
||||
modifyPosterDefault({
|
||||
id
|
||||
}).then((res) => {
|
||||
loadPosterPageList()
|
||||
loadPosterPageList(getTablePageStorage(posterTableData.searchParam).page)
|
||||
isRepeat.value = false
|
||||
})
|
||||
}
|
||||
@ -254,7 +254,7 @@ const deleteEvent = (id: number) => {
|
||||
if (isRepeat.value) return
|
||||
isRepeat.value = true
|
||||
deletePoster(id).then(() => {
|
||||
loadPosterPageList()
|
||||
loadPosterPageList(getTablePageStorage(posterTableData.searchParam).page)
|
||||
isRepeat.value = false
|
||||
}).catch(() => {
|
||||
isRepeat.value = false
|
||||
@ -287,11 +287,10 @@ const resetForm = (formEl: FormInstance | undefined) => {
|
||||
formEl.resetFields()
|
||||
loadPosterPageList()
|
||||
}
|
||||
const isImagick = ref(false)
|
||||
const isImagick = ref(true)
|
||||
// 判断是否安装imagemagick扩展
|
||||
const checkImagickFn = () => {
|
||||
checkImagick().then((res:any) => {
|
||||
console.log(res)
|
||||
isImagick.value = res.data
|
||||
|
||||
})
|
||||
|
||||
@ -129,7 +129,7 @@ const modifyPrinterStatusEvent = (printer_id: any, status: any) => {
|
||||
printer_id,
|
||||
status
|
||||
}).then((res) => {
|
||||
loadPrinterList()
|
||||
loadPrinterList(getTablePageStorage(printerTable.searchParam).page)
|
||||
isRepeat.value = false
|
||||
})
|
||||
}
|
||||
@ -163,7 +163,7 @@ const deleteEvent = (id: number) => {
|
||||
if (repeat.value) return
|
||||
repeat.value = true
|
||||
deletePrinter(id).then(() => {
|
||||
loadPrinterList()
|
||||
loadPrinterList(getTablePageStorage(printerTable.searchParam).page)
|
||||
repeat.value = false
|
||||
}).catch(() => {
|
||||
repeat.value = false
|
||||
@ -214,7 +214,7 @@ const refreshTokenEvent = (printer_id: any) => {
|
||||
if (repeat.value) return
|
||||
repeat.value = true
|
||||
refreshPrinterToken(printer_id).then((res: any) => {
|
||||
loadPrinterList()
|
||||
loadPrinterList(getTablePageStorage(printerTable.searchParam).page)
|
||||
repeat.value = false
|
||||
}).catch(() => {
|
||||
repeat.value = false
|
||||
|
||||
@ -148,7 +148,7 @@ const deleteEvent = (id: number) => {
|
||||
}
|
||||
).then(() => {
|
||||
deletePrinterTemplate(id).then(() => {
|
||||
loadPrinterTemplateList()
|
||||
loadPrinterTemplateList(getTablePageStorage(printerTemplateTable.searchParam).page)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -22,19 +22,19 @@
|
||||
<h3 class="panel-title !text-[14px] bg-[#F4F5F7] p-3 border-[#E6E6E6] border-solid border-b-[1px]">{{ t('putOnRecordEdit') }}</h3>
|
||||
<el-form-item :label="t('icp')" prop="icp">
|
||||
<el-input v-model.trim="formData.icp" :placeholder="t('icpPlaceholder')" class="input-width" clearable maxlength="20"/>
|
||||
<div class="form-tip">{{ t('网站的ICP备案号,显示在H5和PC端底部') }}</div>
|
||||
<div class="form-tip">{{ t('网站的ICP备案号,显示在PC端底部') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('govRecord')" >
|
||||
<el-input v-model.trim="formData.gov_record" :placeholder="t('govRecordPlaceholder')" class="input-width" clearable maxlength="50"/>
|
||||
<div class="form-tip">{{ t('公安部门登记的备案信息,显示在pc底部') }}</div>
|
||||
<div class="form-tip">{{ t('公安部门登记的备案信息,显示在PC底部') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('govUrl')" >
|
||||
<el-input v-model.trim="formData.gov_url" :placeholder="t('govUrlPlaceholder')" class="input-width" clearable />
|
||||
<div class="form-tip">{{ t('H5和PC底部显示的网站公安点击跳转的链接') }}</div>
|
||||
<div class="form-tip">{{ t('PC底部显示的网站公安点击跳转的链接') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('marketSupervisionUrl')" >
|
||||
<el-input v-model.trim="formData.market_supervision_url" rows="4" clearable :placeholder="t('marketSupervisionUrlPlaceholder')" class="input-width" />
|
||||
<div class="form-tip">{{ t('H5和PC底部显示的市场监督管理局点击跳转的链接') }}</div>
|
||||
<div class="form-tip">{{ t('PC底部显示的市场监督管理局点击跳转的链接') }}</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@ -10,7 +10,11 @@
|
||||
<el-form-item :label="t('mapKey')" prop="key">
|
||||
<el-input v-model.trim="formData.key" class="input-width" clearable />
|
||||
<span class="ml-2 cursor-pointer tutorial-btn" @click="tutorialFn">{{ t('clickTutorial') }}</span>
|
||||
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn">{{ t('clickSecretKey') }}</span>
|
||||
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn('https://lbs.qq.com/dev/console/key/manage')">{{ t('clickSecretKey') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aMapKey')" prop="key">
|
||||
<el-input v-model.trim="formData.amap_key" class="input-width" clearable />
|
||||
<span class="ml-2 cursor-pointer secret-btn" @click="secretFn('https://lbs.amap.com/')">{{ t('clickSecretKey') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('isOpen')" prop="is_open">
|
||||
<el-switch v-model="formData.is_open" :active-value="1" :inactive-value="0" />
|
||||
@ -42,6 +46,7 @@ const loading = ref(true)
|
||||
const formRef = ref<FormInstance>()
|
||||
const formData = reactive({
|
||||
key: '',
|
||||
amap_key: '',
|
||||
is_open: 0,
|
||||
valid_time: 0
|
||||
})
|
||||
@ -71,9 +76,7 @@ const formRules = computed(() => {
|
||||
const setFormData = async () => {
|
||||
loading.value = true
|
||||
const service_data = await (await getMap()).data
|
||||
formData.key = service_data.key
|
||||
formData.is_open = service_data.is_open
|
||||
formData.valid_time = service_data.valid_time
|
||||
Object.assign(formData, service_data)
|
||||
loading.value = false
|
||||
}
|
||||
setFormData()
|
||||
@ -107,8 +110,8 @@ const tutorialFn = () => {
|
||||
/**
|
||||
* 点击访问腾讯地图
|
||||
*/
|
||||
const secretFn = () => {
|
||||
window.open('https://lbs.qq.com/dev/console/key/manage')
|
||||
const secretFn = (url: string) => {
|
||||
window.open(url)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -858,7 +858,7 @@ const batchDelete = () => {
|
||||
background-color: var(--el-table-header-bg-color);
|
||||
}
|
||||
|
||||
::v-deep .number-of-steps {
|
||||
:deep(.number-of-steps) {
|
||||
.el-step__line {
|
||||
margin: 0 25px;
|
||||
background: #dddddd;
|
||||
|
||||
@ -126,7 +126,9 @@ const prop = defineProps({
|
||||
},
|
||||
ignore: {
|
||||
type: Array,
|
||||
default: []
|
||||
default: () => {
|
||||
return [] // 指定需要忽略的自定义链接,例如:['DIY_MAKE_PHONE_CALL'],表示隐藏拨打电话
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -47,12 +47,13 @@ let editorEl = null
|
||||
|
||||
const serverHeaders = {}
|
||||
serverHeaders[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`
|
||||
|
||||
const editorConfig = ref({
|
||||
debug: false,
|
||||
UEDITOR_HOME_URL: import.meta.env.MODE == 'development' ? '/public/ueditor/' : '/admin/ueditor/',
|
||||
UEDITOR_CORS_URL: import.meta.env.MODE == 'development' ? location.origin + '/ueditor/' : location.origin + '/admin/ueditor/',
|
||||
serverUrl: `${import.meta.env.VITE_APP_BASE_URL}sys/ueditor`,
|
||||
serverUrl: `${baseUrl}sys/ueditor`,
|
||||
serverHeaders,
|
||||
// 编辑器不自动被内容撑高
|
||||
autoHeightEnabled: false,
|
||||
@ -89,6 +90,17 @@ const handleEditorReady = (editor) => {
|
||||
emit('handleBlur', editor.getContent()) // 把内容传出去
|
||||
})
|
||||
|
||||
// 全屏切换监听
|
||||
editor.addListener('fullscreenchanged', (type, fullscreen) =>{
|
||||
const editorDom = editor.ui.getDom()
|
||||
if (fullscreen) {
|
||||
editorDom.classList.add('edui-fullscreen')
|
||||
} else {
|
||||
editorDom.classList.remove('edui-fullscreen')
|
||||
}
|
||||
console.log('全屏切换', fullscreen)
|
||||
})
|
||||
|
||||
// 方案二:原型链扩展(如果编辑器版本支持)
|
||||
const originalCount = editor.getContentLength; // 原生统计方法
|
||||
|
||||
|
||||
@ -23,6 +23,10 @@ const prop = defineProps({
|
||||
api: {
|
||||
type: String,
|
||||
default: 'sys/document/document'
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: '.doc,.docx,.xml,.txt,.pem,.zip,.rar,.7z,.crt,.key,.xls,.xlsx'
|
||||
}
|
||||
})
|
||||
|
||||
@ -37,11 +41,16 @@ const value = computed({
|
||||
}
|
||||
})
|
||||
|
||||
const upload: Record<string, any> = {
|
||||
action: `${import.meta.env.VITE_APP_BASE_URL}/${prop.api}`,
|
||||
const upload = computed(() => {
|
||||
const headers: Record<string, any> = {}
|
||||
headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||
const baseURL = import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`
|
||||
|
||||
return {
|
||||
action: `${baseURL}${prop.api}`,
|
||||
showFileList: false,
|
||||
headers: {},
|
||||
accept: '.doc,.docx,.xml,.txt,.pem,.zip,.rar,.7z,.crt,.key,.xls,.xlsx',
|
||||
accept: prop.accept,
|
||||
headers,
|
||||
onSuccess: (response: any, uploadFile: UploadFile) => {
|
||||
if (response.code != undefined && response.code != 1) {
|
||||
ElMessage({ message: response.msg, type: 'error' })
|
||||
@ -51,8 +60,7 @@ const upload: Record<string, any> = {
|
||||
ElMessage({ message: t('upload.success'), type: 'success' })
|
||||
}
|
||||
}
|
||||
upload.headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@ -103,7 +103,7 @@
|
||||
"405": "请求方法未允许",
|
||||
"408": "请求超时",
|
||||
"409": "请求跨域",
|
||||
"500": "服务器端出错,错误原因:",
|
||||
"500": "服务器内部错误",
|
||||
"501": "网络未实现",
|
||||
"502": "网络错误",
|
||||
"503": "服务不可用",
|
||||
@ -219,5 +219,10 @@
|
||||
"cloudBuildTips": "是否要进行云编译该操作可能会影响到正在访问的客户是否要继续操作?",
|
||||
"promoteUrl": "推广链接",
|
||||
"downLoadQRCode": "下载二维码",
|
||||
"configureFailed": "配置失败"
|
||||
"configureFailed": "配置失败",
|
||||
"lefttitle": "左侧标题",
|
||||
"righttitle": "右侧标题",
|
||||
"leftDesc": "左侧简介",
|
||||
"rightDesc": "右侧简介",
|
||||
"descPlaceholder": "请输入简介内容"
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<template v-if="meta.show">
|
||||
<el-sub-menu v-if="routes.children" :index="String(routes.name)">
|
||||
<el-sub-menu v-if="hasVisibleChild" :index="String(routes.name)">
|
||||
<template #title>
|
||||
<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" />
|
||||
@ -15,7 +15,7 @@
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
<template v-else>
|
||||
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
|
||||
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
|
||||
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
|
||||
@ -23,7 +23,7 @@
|
||||
<span class="ml-[10px]">{{ meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else>
|
||||
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-else>
|
||||
<template #title>
|
||||
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
|
||||
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
|
||||
@ -39,12 +39,13 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, computed, watch , onMounted, onUnmounted} from 'vue'
|
||||
import menuItem from './menu-item.vue'
|
||||
import useSystemStore from '@/stores/modules/system'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
import { findFirstValidRoute ,formatRouters} from '@/router/routers'
|
||||
import { getShowApp,getShowSpecialMenu } from '@/app/api/addon'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const routers = useUserStore().routers
|
||||
@ -61,6 +62,9 @@ const props = defineProps({
|
||||
const systemStore = useSystemStore()
|
||||
const meta = computed(() => props.routes.meta)
|
||||
|
||||
// 存储所有特殊菜单的name
|
||||
const specialMenuNames = ref<string[]>([])
|
||||
const specialMenuNamesLevel1 = ref<string[]>([])
|
||||
const addons = computed(() => {
|
||||
const addons:Record<string, any> = {}
|
||||
systemStore?.apps.forEach((item: any) => { addons[item.key] = item })
|
||||
@ -72,6 +76,13 @@ const systemAddonKeys = computed(() => {
|
||||
return systemStore?.addons.map((item: any) => item.key)
|
||||
})
|
||||
|
||||
const hasVisibleChild = computed(() => {
|
||||
if (!props.routes.children || !Array.isArray(props.routes.children)) {
|
||||
return false
|
||||
}
|
||||
return props.routes.children.some(child => child.meta?.show === 1)
|
||||
})
|
||||
|
||||
const addonRouters: Record<string, any> = {}
|
||||
routers.forEach(item => {
|
||||
item.original_name = item.name
|
||||
@ -85,8 +96,132 @@ routers.forEach(item => {
|
||||
|
||||
const addonsMenus = ref(null)
|
||||
|
||||
// 提取所有特殊菜单的name
|
||||
const collectSpecialMenuNames = (menus: any[]) => {
|
||||
const names: string[] = []
|
||||
const traverse = (children: any[]) => {
|
||||
children.forEach(child => {
|
||||
if (child.name) {
|
||||
names.push(child.name)
|
||||
}
|
||||
// 递归处理子菜单
|
||||
if (child.children && Array.isArray(child.children)) {
|
||||
traverse(child.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
menus.forEach(menu => {
|
||||
if (menu.children && Array.isArray(menu.children)) {
|
||||
traverse(menu.children)
|
||||
}
|
||||
})
|
||||
return names
|
||||
}
|
||||
// 提取所有一级特殊菜单的name
|
||||
const collectSpecialMenuNamesLevel1 = (menus: any[]) =>{
|
||||
const names: string[] = []
|
||||
menus.forEach(menu => {
|
||||
if (menu.name) {
|
||||
names.push(menu.name)
|
||||
}
|
||||
})
|
||||
return names
|
||||
}
|
||||
|
||||
// 从 addonKeys 中提取所有需要匹配的 key
|
||||
const getAddonAllKeys = (addonData) => {
|
||||
if (!addonData || typeof addonData !== 'object') return [];
|
||||
const allKeys = [];
|
||||
Object.values(addonData).forEach(category => {
|
||||
if (Array.isArray(category.list)) {
|
||||
category.list.forEach(item => {
|
||||
if (item.key) allKeys.push(item.key);
|
||||
});
|
||||
}
|
||||
});
|
||||
return allKeys;
|
||||
};
|
||||
|
||||
// 处理 specialMenusKeys 子菜单 show 的方法
|
||||
const handleSpecialMenus = () => {
|
||||
const specialMenusKeys = storage.get('specialAppList')
|
||||
if (Array.isArray(specialMenusKeys) && specialMenusKeys.length) {
|
||||
const processedSpecialMenus = JSON.parse(JSON.stringify(specialMenusKeys));
|
||||
const activeAppKey = storage.get('activeAppKey');
|
||||
|
||||
// 收集所有特殊菜单的name
|
||||
processedSpecialMenus.forEach(menu => {
|
||||
if (menu.children && Array.isArray(menu.children)) {
|
||||
const traverseChildren = (children) => {
|
||||
children.forEach(child => {
|
||||
if (child && child.is_show !== undefined) {
|
||||
child.is_show = (child.menu_key === activeAppKey) ? 1 : 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
traverseChildren(menu.children);
|
||||
}
|
||||
});
|
||||
// 过滤掉 children 为空的特殊菜单
|
||||
const filteredSpecialMenus = processedSpecialMenus.filter(menu => {
|
||||
return menu.children && menu.children.length > 0;
|
||||
});
|
||||
return formatRouters(filteredSpecialMenus);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
// 统一处理跳转逻辑
|
||||
const handleJump = (routeName: string) => {
|
||||
// 检查目标路由是否在特殊菜单列表中
|
||||
const isInSpecialMenus = specialMenuNames.value.includes(routeName)
|
||||
// 核心逻辑:如果不在特殊菜单中,就删除activeAppKey
|
||||
if (!isInSpecialMenus) {
|
||||
storage.remove('activeAppKey')
|
||||
} else {
|
||||
}
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
specialMenuNamesLevel1.value = collectSpecialMenuNamesLevel1(processedSpecialMenus)
|
||||
|
||||
// 点击特殊菜单的一级,跳转应用列表
|
||||
if (specialMenuNamesLevel1.value.includes(routeName)) {
|
||||
routeName = 'addon_list'
|
||||
}
|
||||
// 跳转时添加随机查询参数(用于触发页面感知)
|
||||
const query = route.name === routeName
|
||||
? { refresh: Date.now() } // 相同路由时添加随机参数
|
||||
: {};
|
||||
|
||||
// 执行跳转
|
||||
router.push({ name: routeName, query });
|
||||
}
|
||||
|
||||
watch(route, () => {
|
||||
const addonKeys = storage.get('defaultAppList')
|
||||
if (props.routes.name == 'addon_list') {
|
||||
const addonAllKeys = getAddonAllKeys(addonKeys);
|
||||
if (props.routes.children) {
|
||||
// 过滤掉不需要显示的子菜单
|
||||
props.routes.children = props.routes.children.filter(child => {
|
||||
return !child.name || !addonAllKeys.includes(child.name);
|
||||
});
|
||||
// 处理特殊菜单
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
if (processedSpecialMenus.length) {
|
||||
const newChildren = [...(props.routes.children || [])];
|
||||
processedSpecialMenus.forEach(special => {
|
||||
const index = newChildren.findIndex(child => child.name === special.name);
|
||||
if (index !== -1) {
|
||||
newChildren[index] = special;
|
||||
} else {
|
||||
newChildren.push(special);
|
||||
}
|
||||
});
|
||||
props.routes.children = newChildren;
|
||||
}
|
||||
}
|
||||
|
||||
if (systemAddonKeys.value.includes(route.meta.addon) && addonRouters[route.meta.addon]) {
|
||||
addonsMenus.value = addonRouters[route.meta.addon]
|
||||
} else if (route.meta.attr && addonRouters[route.meta.attr]) {
|
||||
@ -106,7 +241,73 @@ watch(route, () => {
|
||||
addonsMenus.value = null
|
||||
}
|
||||
}
|
||||
// console.log('addonsMenus', props.routes)
|
||||
}, { immediate: true })
|
||||
const getAppList = async () => {
|
||||
const res = await getShowApp()
|
||||
const res2 = await getShowSpecialMenu()
|
||||
storage.set({ key: 'specialAppList', data: res2.data.list })
|
||||
storage.set({ key: 'defaultAppList', data: res.data })
|
||||
if (props.routes.name == 'addon_list') {
|
||||
const addonAllKeys = getAddonAllKeys(res.data);
|
||||
if (props.routes.children) {
|
||||
// 过滤掉不需要显示的子菜单
|
||||
props.routes.children = props.routes.children.filter(child => {
|
||||
return !child.name || !addonAllKeys.includes(child.name);
|
||||
});
|
||||
// 处理特殊菜单
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
if (processedSpecialMenus.length) {
|
||||
const newChildren = [...(props.routes.children || [])];
|
||||
processedSpecialMenus.forEach(special => {
|
||||
const index = newChildren.findIndex(child => child.name === special.name);
|
||||
if (index !== -1) {
|
||||
newChildren[index] = special;
|
||||
} else {
|
||||
newChildren.push(special);
|
||||
}
|
||||
});
|
||||
props.routes.children = newChildren;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 监听 localStorage 中 activeAppKey 的变化
|
||||
onMounted(() => {
|
||||
// 暂时这么处理
|
||||
if(props.routes.name == 'addon_list'){
|
||||
getAppList();
|
||||
}
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
specialMenuNames.value = collectSpecialMenuNames(processedSpecialMenus)
|
||||
specialMenuNamesLevel1.value = collectSpecialMenuNamesLevel1(processedSpecialMenus)
|
||||
const handleStorageChange = (event: StorageEvent) => {
|
||||
if (event.key === 'activeAppKey') {
|
||||
if (props.routes.name == 'addon_list') {
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
if (processedSpecialMenus.length && props.routes.children) {
|
||||
const newChildren = [...(props.routes.children || [])];
|
||||
processedSpecialMenus.forEach(special => {
|
||||
const index = newChildren.findIndex(child => child.name === special.name);
|
||||
if (index !== -1) {
|
||||
newChildren[index] = special;
|
||||
} else {
|
||||
newChildren.push(special);
|
||||
}
|
||||
});
|
||||
props.routes.children = newChildren;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('storage', handleStorageChange);
|
||||
|
||||
// 组件卸载时移除事件监听
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('storage', handleStorageChange);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@ -31,7 +31,7 @@ import menuItem from './menu-item.vue'
|
||||
import { img } from '@/utils/common'
|
||||
import { findFirstValidRoute } from '@/router/routers'
|
||||
import { getWebConfig } from "@/app/api/sys"
|
||||
import { getShowMarketing } from '@/app/api/addon'
|
||||
// import { getShowApp,getShowSpecialMenu } from '@/app/api/addon'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const systemStore = useSystemStore()
|
||||
@ -43,23 +43,30 @@ const addonIndexRoute = userStore.addonIndexRoute
|
||||
const menuData = ref<Record<string, any>[]>([])
|
||||
const addonRouters: Record<string, any> = {}
|
||||
|
||||
const getMarketingList = async () => {
|
||||
const res = await getShowMarketing()
|
||||
const marketingList = res.data
|
||||
const marketingKeys = marketingList?.marketing?.list?.map(item => item.key) ?? []
|
||||
// menuData.value.forEach((item, index, arr) => {
|
||||
// if (marketingKeys.includes(item.name)) {
|
||||
// arr.splice(index, 1)
|
||||
// const appList = ref<Record<string, any>[]>([])
|
||||
|
||||
// const getAppList = async () => {
|
||||
// const res = await getShowApp()
|
||||
// appList.value = res.data
|
||||
|
||||
// storage.set({ key: 'defaultAppList', data: appList.value })
|
||||
// }
|
||||
// })
|
||||
storage.set({ key: 'defaultMarketingKeys', data: marketingKeys })
|
||||
}
|
||||
// const specialList = ref<Record<string, any>[]>([])
|
||||
// const getShowSpecialMenuList = async () => {
|
||||
// const res = await getShowSpecialMenu()
|
||||
// // specialList.value = formatRouters(res.data.list)
|
||||
// specialList.value = res.data.list
|
||||
|
||||
// storage.set({ key: 'specialAppList', data: specialList.value })
|
||||
// }
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getWebConfig().then(({ data }) => {
|
||||
webSite.value = data
|
||||
});
|
||||
getMarketingList()
|
||||
// getAppList()
|
||||
// getShowSpecialMenuList()
|
||||
})
|
||||
|
||||
routers.forEach(item => {
|
||||
|
||||
@ -114,15 +114,24 @@ const getParentTitleChain=(meta:any) =>{
|
||||
|
||||
return titles.join(' - ')
|
||||
}
|
||||
const flattenRoutes = (routes:any, parent = null)=> {
|
||||
// 2. 改造 flattenRoutes:增加 parentShow 参数,传递父级 show 状态
|
||||
const flattenRoutes = (routes: any, parent = null, parentShow = 1) => {
|
||||
let flat = [];
|
||||
|
||||
routes.forEach(route => {
|
||||
const { path, name, meta = {}, short_title, children } = route
|
||||
const isLeaf = meta.type ==1 && meta.show==1
|
||||
const { path, name, meta = {}, short_title, children } = route;
|
||||
// 关键:当前菜单的最终 show 状态 = 自身 show(默认1) && 父级 show(默认1)
|
||||
// 若父级 show 不是1,当前菜单直接隐藏,不加入列表
|
||||
const currentShow = meta.show === undefined ? 1 : meta.show;
|
||||
const finalShow = currentShow && parentShow; // 父级隐藏则子级必隐藏
|
||||
|
||||
// 叶子节点判断:type=1 + 最终 show=1(父级+自身都显示)
|
||||
const isLeaf = meta.type === 1 && finalShow === 1;
|
||||
|
||||
if (isLeaf) {
|
||||
const title = meta.title || short_title || ''
|
||||
const parentTitleChain = getParentTitleChain(meta)
|
||||
const fullTitle = parentTitleChain ? `${parentTitleChain} - ${title}` : title
|
||||
const title = meta.title || short_title || '';
|
||||
const parentTitleChain = getParentTitleChain(meta);
|
||||
const fullTitle = parentTitleChain ? `${parentTitleChain} - ${title}` : title;
|
||||
const item = {
|
||||
path,
|
||||
name,
|
||||
@ -130,16 +139,18 @@ const flattenRoutes = (routes:any, parent = null)=> {
|
||||
parent_title: parentTitleChain,
|
||||
full_title: fullTitle
|
||||
};
|
||||
|
||||
flat.push(item);
|
||||
}
|
||||
|
||||
// 递归处理子菜单:传递当前菜单的 finalShow 作为子级的 parentShow
|
||||
if (children && children.length > 0) {
|
||||
flat = flat.concat(flattenRoutes(children, route))
|
||||
flat = flat.concat(flattenRoutes(children, route, finalShow));
|
||||
}
|
||||
});
|
||||
|
||||
return flat;
|
||||
}
|
||||
};
|
||||
|
||||
const flatRoutes = flattenRoutes(routers)
|
||||
const selectedRoute = ref('')
|
||||
const handleRouteSelect = (name:any) => {
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<template v-if="meta.show">
|
||||
<el-sub-menu v-if="routes.children" :index="String(routes.name)">
|
||||
<el-sub-menu v-if="hasVisibleChild" :index="String(routes.name)">
|
||||
<template #title>
|
||||
<span :class="['ml-[10px]']">{{ meta.title }}</span>
|
||||
</template>
|
||||
<menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" />
|
||||
</el-sub-menu>
|
||||
<template v-else>
|
||||
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
|
||||
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
|
||||
<template #title>
|
||||
<span :class="[{'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}, {'ml-[10px]': routes.meta.class == 2, 'ml-[15px]': routes.meta.class == 3}]">{{ meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else>
|
||||
<el-menu-item :index="String(routes.name)" @click="handleJump(routes.name)" v-else>
|
||||
<template #title>
|
||||
<span :class="[{'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}, {'ml-[10px]': routes.meta.class == 2, 'ml-[15px]': routes.meta.class == 3}]">{{ meta.title }}</span>
|
||||
</template>
|
||||
@ -24,12 +24,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRouter , useRoute} from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import menuItem from './menu-item.vue'
|
||||
import useSystemStore from "@/stores/modules/system";
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const props = defineProps({
|
||||
routes: {
|
||||
type: Object,
|
||||
@ -39,13 +41,43 @@ const props = defineProps({
|
||||
const systemStore = useSystemStore()
|
||||
const meta = computed(() => props.routes.meta)
|
||||
|
||||
const hasVisibleChild = computed(() => {
|
||||
if (!props.routes.children || !Array.isArray(props.routes.children)) {
|
||||
return false
|
||||
}
|
||||
return props.routes.children.some(child => child.meta?.show === 1)
|
||||
})
|
||||
|
||||
|
||||
const addons = computed(() => {
|
||||
const addons:Record<string, any> = {}
|
||||
systemStore?.apps.forEach((item: any) => { addons[item.key] = item })
|
||||
systemStore?.addons.forEach((item: any) => { addons[item.key] = item })
|
||||
return addons
|
||||
})
|
||||
// 统一处理跳转逻辑
|
||||
const handleJump = (routeName: string) => {
|
||||
// 检查目标路由是否在特殊菜单列表中
|
||||
const specialMenuNames = storage.get('specialMenuNames')
|
||||
const specialMenuNamesLevel1 = storage.get('specialMenuNamesLevel1')
|
||||
const isInSpecialMenus = specialMenuNames.includes(routeName)
|
||||
// 核心逻辑:如果不在特殊菜单中,就删除activeAppKey
|
||||
if (!isInSpecialMenus) {
|
||||
storage.remove('activeAppKey')
|
||||
} else {
|
||||
}
|
||||
// 点击特殊菜单的一级,跳转应用列表
|
||||
if (specialMenuNamesLevel1.includes(routeName)) {
|
||||
routeName = 'addon_list'
|
||||
}
|
||||
// 跳转时添加随机查询参数(用于触发页面感知)
|
||||
const query = route.name === routeName
|
||||
? { refresh: Date.now() } // 相同路由时添加随机参数
|
||||
: {};
|
||||
|
||||
// 执行跳转
|
||||
router.push({ name: routeName, query });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<el-scrollbar class="h-[calc( 100vh - 64px )]">
|
||||
<el-menu :default-active="oneMenuActive" :router="true" class="aside-menu" :unique-opened="true" :collapse="systemStore.menuIsCollapse">
|
||||
<template v-for="(item, index) in oneMenuData" :key="index">
|
||||
<el-menu-item :index="item.original_name" @click="router.push({ name: item.name })" v-if="item.meta.show">
|
||||
<el-menu-item :index="item.original_name" @click="handleJump(item.name)" v-if="item.meta.show">
|
||||
<div v-if="item.meta.icon" class="w-[16px] h-[16px] relative flex justify-center">
|
||||
<el-image class="w-[16px] h-[16px] rounded-[50%] overflow-hidden" :src="item.meta.icon" fit="fill" v-if="isUrl(item.meta.icon)"/>
|
||||
<icon :name="item.meta.icon" class="absolute top-[50%] -translate-y-[50%]" v-else />
|
||||
@ -32,8 +32,8 @@
|
||||
<div class="h-[48px]"></div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<el-scrollbar v-if="twoMenuData.length" class="two-menu w-[140px]">
|
||||
<div class="w-[140px] h-[64px] flex items-center justify-center text-[16px] border-b-[1px] border-solid border-[var(--el-border-color-lighter)]">
|
||||
<el-scrollbar v-if="twoMenuData.length" class="two-menu w-[152px]">
|
||||
<div class="w-[152px] h-[64px] flex items-center justify-center text-[16px] border-b-[1px] border-solid border-[var(--el-border-color-lighter)]">
|
||||
{{ route.matched[1].meta.title }}
|
||||
</div>
|
||||
|
||||
@ -51,10 +51,11 @@ import { ref, watch, computed, onMounted, watchEffect } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import useSystemStore from '@/stores/modules/system'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import { getShowApp,getShowMarketing } from '@/app/api/addon'
|
||||
import { getShowApp,getShowSpecialMenu } from '@/app/api/addon'
|
||||
import menuItem from './menu-item.vue'
|
||||
import { img, isUrl } from '@/utils/common'
|
||||
import { findFirstValidRoute } from '@/router/routers'
|
||||
import { findFirstValidRoute,formatRouters } from '@/router/routers'
|
||||
import storage from '@/utils/storage'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
const systemStore = useSystemStore()
|
||||
@ -111,98 +112,228 @@ const marketingList = ref(null);
|
||||
const oneMenuActive = ref(route.matched[1].name)
|
||||
|
||||
const getAppList = async () => {
|
||||
const res = await getShowApp();
|
||||
appList.value = res.data;
|
||||
// loading.value = false;
|
||||
};
|
||||
const getMarketingList = async () => {
|
||||
const res = await getShowMarketing();
|
||||
marketingList.value = res.data
|
||||
const res = await getShowApp()
|
||||
appList.value = res.data
|
||||
|
||||
storage.set({ key: 'defaultAppList', data: appList.value })
|
||||
}
|
||||
const specialList = ref<Record<string, any>[]>([])
|
||||
const getShowSpecialMenuList = async () => {
|
||||
const res = await getShowSpecialMenu()
|
||||
// specialList.value = formatRouters(res.data.list)
|
||||
specialList.value = res.data.list
|
||||
storage.set({ key: 'specialAppList', data: specialList.value })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getAppList() // 确保数据先加载
|
||||
await getMarketingList()
|
||||
const specialMenuNames = ref<string[]>([])
|
||||
const specialMenuNamesLevel1 = ref<string[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
getAppList()
|
||||
getShowSpecialMenuList()
|
||||
const processedSpecialMenus = handleSpecialMenus();
|
||||
specialMenuNames.value = collectSpecialMenuNames(processedSpecialMenus)
|
||||
specialMenuNamesLevel1.value = collectSpecialMenuNamesLevel1(processedSpecialMenus)
|
||||
storage.set({ key: 'specialMenuNames', data: specialMenuNames.value })
|
||||
storage.set({ key: 'specialMenuNamesLevel1', data: specialMenuNamesLevel1.value })
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
// if (!appList.value || loading.value) return; // 确保数据加载完毕
|
||||
const addonKeys = appList.value?.addon?.list?.map(item => item.key) ?? [];
|
||||
const toolKeys = appList.value?.tool?.list?.map(item => item.key) ?? [];
|
||||
const allKeys = [...addonKeys, ...toolKeys];
|
||||
const marketingKeys = marketingList.value?.marketing?.list?.map(item => item.key) ?? [];
|
||||
const matchedName = route.matched[1]?.name;
|
||||
if (allKeys.includes(matchedName)) {
|
||||
oneMenuActive.value = "addon";
|
||||
twoMenuData.value = route.matched[1]?.children ?? [];
|
||||
} else if (marketingKeys.includes(matchedName)) {
|
||||
oneMenuActive.value = "active";
|
||||
twoMenuData.value = route.matched[1]?.children ?? [];
|
||||
} else if (route.meta.attr !== "") {
|
||||
oneMenuActive.value = route.matched[2]?.name;
|
||||
twoMenuData.value = route.matched[1]?.children ?? [];
|
||||
} else {
|
||||
// 多应用
|
||||
if (systemStore?.apps.length > 1) {
|
||||
twoMenuData.value = route.matched[1]?.children;
|
||||
oneMenuActive.value = route.matched[1]?.name;
|
||||
} else {
|
||||
// 单应用
|
||||
const oneMenu = route.matched[1];
|
||||
if (oneMenu.meta.addon === "") {
|
||||
oneMenuActive.value = route.matched[1]?.name;
|
||||
twoMenuData.value = route.matched[1]?.children ?? [];
|
||||
} else {
|
||||
if (oneMenu.meta.addon === systemStore?.apps[0]?.key) {
|
||||
oneMenuActive.value = route.matched[2]?.name;
|
||||
twoMenuData.value = route.matched[2]?.children ?? [];
|
||||
} else {
|
||||
oneMenuActive.value = route.matched[1]?.name;
|
||||
twoMenuData.value = route.matched[1]?.children ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 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([])
|
||||
watch(twoMenuData.value, () => {
|
||||
menuOption.value = [];
|
||||
if(twoMenuData.value && Object.values(twoMenuData.value).length){
|
||||
let data = cloneDeep(twoMenuData.value);
|
||||
for(let key in data){
|
||||
menuOption.value.push(data[key].name);
|
||||
const secondMenuShowWayFn = () => {
|
||||
menuOption.value = []
|
||||
if (oneMenuActive.value !== 'active' && oneMenuActive.value !== 'addon' && twoMenuData.value && Object.values(twoMenuData.value).length) {
|
||||
const data = cloneDeep(twoMenuData.value)
|
||||
for (const key in data) {
|
||||
menuOption.value.push(data[key].name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 })
|
||||
</script>
|
||||
|
||||
@ -240,13 +371,13 @@ watch(twoMenuData.value, () => {
|
||||
}
|
||||
.two-menu{
|
||||
.aside-menu:not(.el-menu--collapse) {
|
||||
width: 140px;
|
||||
width: 152px;
|
||||
border: 0;
|
||||
padding-top: 16px;
|
||||
.el-menu-item{
|
||||
height: 36px;
|
||||
margin: 0 8px 4px;
|
||||
padding: 0 8px !important;
|
||||
margin: 0 12px 4px;
|
||||
padding: 0 !important;
|
||||
border-radius: 2px;
|
||||
span{
|
||||
margin-left: 8px;
|
||||
@ -265,7 +396,7 @@ watch(twoMenuData.value, () => {
|
||||
.el-sub-menu__title{
|
||||
margin: 0 8px 4px;
|
||||
height: 36px;
|
||||
padding-left: 8px;
|
||||
padding-left:0px;
|
||||
border-radius: 2px;
|
||||
span{
|
||||
height: 36px;
|
||||
@ -282,7 +413,35 @@ watch(twoMenuData.value, () => {
|
||||
}
|
||||
}
|
||||
.el-menu-item{
|
||||
padding-left: 20px !important;
|
||||
padding-left: 0px !important;
|
||||
}
|
||||
.el-sub-menu{
|
||||
margin-bottom: 8px;
|
||||
.el-sub-menu__title{
|
||||
margin: 0 8px 4px;
|
||||
height: 36px;
|
||||
padding-left: 18px;
|
||||
border-radius: 2px;
|
||||
span{
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
&:hover{
|
||||
background-color: #f7f7f7;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.el-icon.el-sub-menu__icon-arrow{
|
||||
right: 5px;
|
||||
}
|
||||
}
|
||||
.el-menu-item{
|
||||
padding-left: 40px !important;
|
||||
span{
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,22 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="right-panel h-full flex items-center justify-end">
|
||||
<div class="flex items-center flex-shrink-0 hidden-xs-only">
|
||||
<el-popover placement="bottom" :width="330" trigger="click" v-model:visible="isMenuSearch" >
|
||||
<template #reference>
|
||||
<i class="iconfont icona-sousuoV6xx-36 cursor-pointer px-[8px] !text-[14px]"></i>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="flex items-center">
|
||||
<el-select v-model="selectedRoute" filterable class="!w-[250px] mr-[20px] menu-select" :teleported="false" clearable @change="handleRouteSelect">
|
||||
<el-option v-for="item in flatRoutes" :key="item.name" :label="item.full_title" :value="item.name" >
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" link @click="isMenuSearch = false">{{t('取消')}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
<!-- 预览-->
|
||||
<i class="iconfont iconicon_huojian1 cursor-pointer px-[8px]" :title="t('visitWap')" @click="toPreview"></i>
|
||||
<!-- 预览 只有站点时展示-->
|
||||
@ -66,6 +82,7 @@ import switchLang from './switch-lang.vue'
|
||||
import userInfo from './user-info.vue'
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
import useSystemStore from '@/stores/modules/system'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
import { useRoute,useRouter } from 'vue-router'
|
||||
import { t } from '@/lang'
|
||||
@ -79,10 +96,61 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
|
||||
|
||||
const userStore = useUserStore()
|
||||
const dark = computed(() => {
|
||||
return systemStore.dark
|
||||
})
|
||||
|
||||
const isMenuSearch = ref(false)
|
||||
const routers = userStore.routers
|
||||
const getParentTitleChain=(meta:any) =>{
|
||||
let titles = []
|
||||
let current = meta?.parent_route
|
||||
|
||||
while (current) {
|
||||
if (current.short_title) {
|
||||
titles.unshift(current.short_title)
|
||||
}
|
||||
current = current.parent_route
|
||||
}
|
||||
|
||||
return titles.join(' - ')
|
||||
}
|
||||
const flattenRoutes = (routes:any, parent = null)=> {
|
||||
let flat = [];
|
||||
routes.forEach(route => {
|
||||
const { path, name, meta = {}, short_title, children } = route
|
||||
const isLeaf = meta.type ==1 && meta.show==1
|
||||
if(isLeaf){
|
||||
const title = meta.title || short_title || ''
|
||||
const parentTitleChain = getParentTitleChain(meta)
|
||||
const fullTitle = parentTitleChain ? `${parentTitleChain} - ${title}` : title
|
||||
const item = {
|
||||
path,
|
||||
name,
|
||||
title,
|
||||
parent_title: parentTitleChain,
|
||||
full_title: fullTitle
|
||||
};
|
||||
|
||||
flat.push(item);
|
||||
}
|
||||
if (children && children.length > 0) {
|
||||
flat = flat.concat(flattenRoutes(children, route))
|
||||
}
|
||||
});
|
||||
|
||||
return flat;
|
||||
}
|
||||
const flatRoutes = flattenRoutes(routers)
|
||||
const selectedRoute = ref('')
|
||||
const handleRouteSelect = (name:any) => {
|
||||
if (name) {
|
||||
router.push({ name })
|
||||
isMenuSearch.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 检测登录 start
|
||||
const detectionLoginDialog = ref(false)
|
||||
const comparisonToken = ref('')
|
||||
|
||||
@ -77,8 +77,18 @@ const useDiyStore = defineStore('diy', {
|
||||
bottomTabBar: {
|
||||
control: true, // 是否允许展示编辑
|
||||
isShow: true, // 是否显示
|
||||
designNav:{ //类型
|
||||
title: "", // 标题
|
||||
key: "", // 组件标识
|
||||
}
|
||||
},
|
||||
|
||||
// 版权信息
|
||||
copyright: {
|
||||
control: true, // 是否允许展示编辑
|
||||
isShow: false, // 是否显示
|
||||
textColor : "#ccc", // 文字颜色
|
||||
},
|
||||
// 弹框 count:不弹出 -1,首次弹出 1,每次弹出 0
|
||||
popWindow: {
|
||||
imgUrl: "",
|
||||
@ -171,6 +181,17 @@ const useDiyStore = defineStore('diy', {
|
||||
bottomTabBar: {
|
||||
control: true, // 是否允许展示编辑
|
||||
isShow: true, // 是否显示
|
||||
designNav:{ //类型
|
||||
title: "", // 标题
|
||||
key: "", // 组件标识
|
||||
}
|
||||
},
|
||||
|
||||
// 版权信息
|
||||
copyright: {
|
||||
control: true, // 是否允许展示编辑
|
||||
isShow: true, // 是否显示
|
||||
textColor : "#ccc", // 文字颜色
|
||||
},
|
||||
|
||||
// 弹框 count:不弹出 -1,首次弹出 1,每次弹出 0
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
@font-face {
|
||||
font-family: "iconfont";
|
||||
/* Project id 3883393 */
|
||||
src: url('//at.alicdn.com/t/c/font_3883393_6d60cyygl4.woff2?t=1755603992297') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_3883393_6d60cyygl4.woff?t=1755603992297') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_3883393_6d60cyygl4.ttf?t=1755603992297') format('truetype');
|
||||
src: url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.woff2?t=1762651161569') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.woff?t=1762651161569') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_3883393_0604cbkh5j03.ttf?t=1762651161569') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@ -14,6 +14,78 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icona-zhulixiangqingpc30:before {
|
||||
content: "\e914";
|
||||
}
|
||||
|
||||
.icona-zuidijiapc30:before {
|
||||
content: "\e915";
|
||||
}
|
||||
|
||||
.icona-zhulipc30:before {
|
||||
content: "\e916";
|
||||
}
|
||||
|
||||
.icona-zhulijiapc30:before {
|
||||
content: "\e917";
|
||||
}
|
||||
|
||||
.icona-kanhoujiapc30:before {
|
||||
content: "\e918";
|
||||
}
|
||||
|
||||
.icona-jiagepc30:before {
|
||||
content: "\e919";
|
||||
}
|
||||
|
||||
.icona-zhuliwanfapc30:before {
|
||||
content: "\e91a";
|
||||
}
|
||||
|
||||
.icona-zhulipc301:before {
|
||||
content: "\e91b";
|
||||
}
|
||||
|
||||
.icona-zhulilunbopc30:before {
|
||||
content: "\e91c";
|
||||
}
|
||||
|
||||
.icona-canyuxinxipc30:before {
|
||||
content: "\e91d";
|
||||
}
|
||||
|
||||
.icona-zhulixiangqingpc301:before {
|
||||
content: "\e91e";
|
||||
}
|
||||
|
||||
.iconyoujiantou:before {
|
||||
content: "\e913";
|
||||
}
|
||||
|
||||
.iconanzhuang1:before {
|
||||
content: "\e90d";
|
||||
}
|
||||
|
||||
.icongengxin:before {
|
||||
content: "\e90e";
|
||||
}
|
||||
|
||||
.iconliebiao:before {
|
||||
content: "\e90f";
|
||||
}
|
||||
|
||||
.iconyijianshengji:before {
|
||||
content: "\e910";
|
||||
}
|
||||
|
||||
.iconliebiaoqiehuan:before {
|
||||
content: "\e911";
|
||||
}
|
||||
|
||||
.iconyijianxiufu:before {
|
||||
content: "\e912";
|
||||
}
|
||||
|
||||
.icona-bijiPC30:before {
|
||||
content: "\e90b";
|
||||
}
|
||||
|
||||
@ -131,9 +131,10 @@ export function img(path: string): string {
|
||||
|
||||
if (typeof path == 'string' && path.startsWith('/')) path = path.replace(/^\//, '')
|
||||
if (typeof imgDomain == 'string' && imgDomain.endsWith('/')) imgDomain = imgDomain.slice(0, -1)
|
||||
|
||||
if(path){
|
||||
return isUrl(path) ? path : `${imgDomain}/${path}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出asset img
|
||||
|
||||
15577
uni-app/package-lock.json
generated
15577
uni-app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -53,6 +53,7 @@
|
||||
"@dcloudio/uni-mp-toutiao": "3.0.0-3080720230703001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-3080720230703001",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-3080720230703001",
|
||||
"@uni-helper/vite-plugin-uni-layouts": "^0.1.11",
|
||||
"html2canvas": "^1.4.1",
|
||||
"image-tools": "^1.4.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
@ -77,6 +78,7 @@
|
||||
"@types/sortablejs": "^1.15.0",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"sass": "^1.54.5",
|
||||
"sass-loader": "10.4.1",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "4.0.4",
|
||||
"vite-plugin-windicss": "^1.8.10",
|
||||
|
||||
@ -9,7 +9,6 @@ import { useLogin } from '@/hooks/useLogin'
|
||||
import { useShare } from '@/hooks/useShare'
|
||||
|
||||
onLaunch((data: any) => {
|
||||
|
||||
// 添加初始化拦截器
|
||||
launchInterceptor()
|
||||
const systemStore = useSystemStore()
|
||||
@ -55,7 +54,6 @@ onLaunch((data: any) => {
|
||||
}
|
||||
}, false);
|
||||
|
||||
|
||||
try {
|
||||
uni.hideTabBar() // 隐藏tabbar
|
||||
} catch (e) {
|
||||
@ -207,6 +205,10 @@ onLaunch((data: any) => {
|
||||
|
||||
// 控制弹窗展示
|
||||
uni.setStorageSync('isOnLoad', true); // 存储是页面是否加载完成的状态
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
systemStore.getVersionInfoFn()
|
||||
// #endif
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
|
||||
@ -1,140 +1,146 @@
|
||||
<template>
|
||||
<view class="diy-group" id="componentList">
|
||||
<top-tabbar v-if="data.global && Object.keys(data.global).length && data.global.topStatusBar && data.global.topStatusBar.isShow"
|
||||
:scrollBool="diyGroup.componentsScrollBool.TopTabbar" ref="topTabbarRef" :data="data.global" />
|
||||
<top-tabbar :scrollBool="diyGroup.componentsScrollBool.TopTabbar" v-if="data.global && Object.keys(data.global).length && data.global.topStatusBar && data.global.topStatusBar.isShow" ref="topTabbarRef" :data="data.global" />
|
||||
<pop-ads v-if="data.global && Object.keys(data.global).length && data.global.popWindow && data.global.popWindow.show" ref="popAbsRef" :data="data.global" />
|
||||
<template v-for="(component, index) in data.value" :key="component.id">
|
||||
<view
|
||||
v-show="component.componentIsShow"
|
||||
<view v-show="component.componentIsShow"
|
||||
@click="diyStore.changeCurrentIndex(index, component)"
|
||||
:class="diyGroup.getComponentClass(index,component)" :style="component.pageStyle"
|
||||
>
|
||||
<view class="relative" :style="{ marginTop : component.margin.top < 0 ? (component.margin.top * 2) + 'rpx' : '0' }">
|
||||
|
||||
:class="diyGroup.getComponentClass(index,component)" :style="component.pageStyle">
|
||||
<view class="relative" :style="{ marginTop : component.margin.top < 0 ? (component.margin.top * 2) + 'rpx' : '0', marginBottom : component.margin.bottom < 0 ? (component.margin.bottom * 2) + 'rpx' : '0' }">
|
||||
<!-- 装修模式下,设置负上边距后超出的内容,禁止选中设置 -->
|
||||
<view v-if="diyGroup.isShowPlaceHolder(index,component)" class="absolute w-full z-1"
|
||||
:style="{ height : (component.margin.top * 2 * -1) + 'rpx' }"
|
||||
@click.stop="diyGroup.placeholderEvent"></view>
|
||||
|
||||
<template v-if="component.componentName == '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>
|
||||
<view v-if="diyGroup.isShowPlaceHolder(index,component)" class="absolute w-full z-1" :style="{ height : (component.margin.top * 2 * -1) + 'rpx' }" @click.stop="diyGroup.placeholderEvent"></view>
|
||||
<template v-if="component.componentName == 'ActiveCube'">
|
||||
<diy-active-cube :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FloatBtn'">
|
||||
<diy-float-btn :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 == '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 v-if="component.componentName == 'PictureShow'">
|
||||
<diy-picture-show :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormSubmit'">
|
||||
<diy-form-submit :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormInput'">
|
||||
<diy-form-input ref="diyFormInputRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormTextarea'">
|
||||
<diy-form-textarea ref="diyFormTextareaRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormIdentity'">
|
||||
<diy-form-identity ref="diyFormIdentityRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormEmail'">
|
||||
<diy-form-email ref="diyFormEmailRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormMobile'">
|
||||
<diy-form-mobile ref="diyFormMobileRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormWechatName'">
|
||||
<diy-form-wechat-name ref="diyFormWechatNameRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormNumber'">
|
||||
<diy-form-number ref="diyFormNumberRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormRadio'">
|
||||
<diy-form-radio ref="diyFormRadioRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormCheckbox'">
|
||||
<diy-form-checkbox ref="diyFormCheckboxRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormTable'">
|
||||
<diy-form-table ref="diyFormTableRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormDate'">
|
||||
<diy-form-date ref="diyFormDateRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormDateScope'">
|
||||
<diy-form-date-scope ref="diyFormDateScopeRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormTime'">
|
||||
<diy-form-time ref="diyFormTimeRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormTimeScope'">
|
||||
<diy-form-time-scope ref="diyFormTimeScopeRef" :component="component" :global="data.global" :index="index" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormLocation'">
|
||||
<diy-form-location ref="diyFormLocationRef" :component="component" :global="data.global" :index="index" />
|
||||
<template v-if="component.componentName == 'FloatBtn'">
|
||||
<diy-float-btn ref="diyFloatBtnRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FloatBtn" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormAddress'">
|
||||
<diy-form-address ref="diyFormAddressRef" :component="component" :global="data.global" :index="index" />
|
||||
<diy-form-address ref="diyFormAddressRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormAddress" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormImage'">
|
||||
<diy-form-image ref="diyFormImageRef" :component="component" :global="data.global" :index="index" />
|
||||
<template v-if="component.componentName == 'FormCheckbox'">
|
||||
<diy-form-checkbox ref="diyFormCheckboxRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormCheckbox" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormVideo'">
|
||||
<diy-form-video ref="diyFormVideoRef" :component="component" :global="data.global" :index="index" />
|
||||
<template v-if="component.componentName == 'FormDate'">
|
||||
<diy-form-date ref="diyFormDateRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormDate" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormDateScope'">
|
||||
<diy-form-date-scope ref="diyFormDateScopeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormDateScope" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormEmail'">
|
||||
<diy-form-email ref="diyFormEmailRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormEmail" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormFile'">
|
||||
<diy-form-file ref="diyFormFileRef" :component="component" :global="data.global" :index="index" />
|
||||
<diy-form-file ref="diyFormFileRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormFile" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormIdentity'">
|
||||
<diy-form-identity ref="diyFormIdentityRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormIdentity" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormIdentityPrivacy'">
|
||||
<diy-form-identity-privacy ref="diyFormIdentityPrivacyRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormIdentityPrivacy" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormImage'">
|
||||
<diy-form-image ref="diyFormImageRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormImage" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormInput'">
|
||||
<diy-form-input ref="diyFormInputRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormInput" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormLocation'">
|
||||
<diy-form-location ref="diyFormLocationRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormLocation" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormMobile'">
|
||||
<diy-form-mobile ref="diyFormMobileRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormMobile" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormNumber'">
|
||||
<diy-form-number ref="diyFormNumberRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormNumber" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormPrivacy'">
|
||||
<diy-form-privacy ref="diyFormPrivacyRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormPrivacy" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormPrivacyPop'">
|
||||
<diy-form-privacy-pop ref="diyFormPrivacyPopRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormPrivacyPop" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormRadio'">
|
||||
<diy-form-radio ref="diyFormRadioRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormRadio" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormSubmit'">
|
||||
<diy-form-submit ref="diyFormSubmitRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormSubmit" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormTable'">
|
||||
<diy-form-table ref="diyFormTableRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTable" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormTextarea'">
|
||||
<diy-form-textarea ref="diyFormTextareaRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTextarea" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormTime'">
|
||||
<diy-form-time ref="diyFormTimeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTime" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormTimeScope'">
|
||||
<diy-form-time-scope ref="diyFormTimeScopeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTimeScope" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormVideo'">
|
||||
<diy-form-video ref="diyFormVideoRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormVideo" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'FormWechatName'">
|
||||
<diy-form-wechat-name ref="diyFormWechatNameRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormWechatName" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'GraphicNav'">
|
||||
<diy-graphic-nav ref="diyGraphicNavRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.GraphicNav" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'HorzBlank'">
|
||||
<diy-horz-blank ref="diyHorzBlankRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.HorzBlank" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'HorzLine'">
|
||||
<diy-horz-line ref="diyHorzLineRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.HorzLine" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'HotArea'">
|
||||
<diy-hot-area ref="diyHotAreaRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.HotArea" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'ImageAds'">
|
||||
<diy-image-ads ref="diyImageAdsRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ImageAds" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'MemberInfo'">
|
||||
<diy-member-info ref="diyMemberInfoRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.MemberInfo" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'MemberLevel'">
|
||||
<diy-member-level ref="diyMemberLevelRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.MemberLevel" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'Notice'">
|
||||
<diy-notice ref="diyNoticeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.Notice" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'PictureShow'">
|
||||
<diy-picture-show ref="diyPictureShowRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.PictureShow" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'RichText'">
|
||||
<diy-rich-text ref="diyRichTextRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.RichText" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'RubikCube'">
|
||||
<diy-rubik-cube ref="diyRubikCubeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.RubikCube" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
<template v-if="component.componentName == 'Text'">
|
||||
<diy-text ref="diyTextRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.Text" @update:componentIsShow="component.componentIsShow = $event" />
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-if="diyStore.mode == '' && data.global && diyGroup.showCopyright.value && data.global.copyright && data.global.copyright.isShow">
|
||||
<copy-right :textColor="data.global.copyright.textColor" />
|
||||
</template>
|
||||
|
||||
<template v-if="diyStore.mode == '' && data.global && data.global.bottomTabBar && data.global.bottomTabBar.isShow">
|
||||
<view class="pt-[20rpx]"></view>
|
||||
<tabbar />
|
||||
<tabbar :addon="data.global.bottomTabBar.designNav.key" />
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import topTabbar from '@/components/top-tabbar/top-tabbar.vue'
|
||||
import popAds from '@/components/pop-ads/pop-ads.vue'
|
||||
import { useDiyGroup } from './useDiyGroup'
|
||||
import useDiyStore from '@/app/stores/diy';
|
||||
import { useDiyGroup } from './useDiyGroup';
|
||||
import { ref,getCurrentInstance } from 'vue';
|
||||
|
||||
const props = defineProps(['data']);
|
||||
@ -144,22 +150,18 @@ const getFormRef = () => {
|
||||
componentRefs: instance.refs
|
||||
}
|
||||
}
|
||||
|
||||
const diyStore = useDiyStore();
|
||||
|
||||
const diyGroup = useDiyGroup({
|
||||
...props,
|
||||
getFormRef
|
||||
});
|
||||
|
||||
const data = ref(diyGroup.data)
|
||||
const data = ref(diyGroup.data);
|
||||
|
||||
// 监听页面加载完成
|
||||
diyGroup.onMounted()
|
||||
diyGroup.onMounted();
|
||||
|
||||
// 监听滚动事件
|
||||
diyGroup.onPageScroll()
|
||||
|
||||
diyGroup.onPageScroll();
|
||||
defineExpose({
|
||||
refresh: diyGroup.refresh,
|
||||
getFormRef
|
||||
|
||||
@ -5,6 +5,8 @@ import { onPageScroll, onHide, onShow } from '@dcloudio/uni-app';
|
||||
import useDiyStore from '@/app/stores/diy';
|
||||
|
||||
export function useDiyGroup(params: any = {}) {
|
||||
// 新增:控制版权显示的状态,默认隐藏
|
||||
const showCopyright = ref(false);
|
||||
|
||||
let scrollVal: any = ""; //组件滚动值集合
|
||||
const componentsScrollBool: any = ref({}); //组件是否根据滚动进行相应改变
|
||||
@ -98,6 +100,9 @@ export function useDiyGroup(params: any = {}) {
|
||||
componentsScrollBool.value[key] = -1;
|
||||
}
|
||||
}
|
||||
// 新增:页面加载完成后,显示版权
|
||||
showCopyright.value = true;
|
||||
|
||||
}, 500)
|
||||
});
|
||||
});
|
||||
@ -174,11 +179,13 @@ export function useDiyGroup(params: any = {}) {
|
||||
scrollV: scrollValStr.value,
|
||||
data: data.value,
|
||||
componentsScrollBool: componentsScrollBool.value,
|
||||
showCopyright, // 新增:暴露版权显示状态
|
||||
placeholderEvent,
|
||||
refresh,
|
||||
isShowPlaceHolder,
|
||||
getComponentClass,
|
||||
onPageScroll: onPageScrollLifeCycle,
|
||||
onMounted: onMountedLifeCycle
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
3
uni-app/src/androidPrivacy.json
Normal file
3
uni-app/src/androidPrivacy.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"prompt": "template"
|
||||
}
|
||||
@ -131,3 +131,13 @@ export function bind(data: AnyObject) {
|
||||
export function memberLog(data: AnyObject) {
|
||||
return request.post('member/log', data, { showErrorMessage: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信app授权登录
|
||||
*/
|
||||
export function wxappLogin(data: AnyObject) {
|
||||
if (uni.getStorageSync('pid')) {
|
||||
data.pid = uni.getStorageSync('pid');
|
||||
}
|
||||
return request.post('wxapp/login', data, { showErrorMessage: false })
|
||||
}
|
||||
@ -158,3 +158,10 @@ export function getInitInfo(params: Record<string, any>) {
|
||||
export function getMemberMobileExist(params: Record<string, any>) {
|
||||
return request.get('member_mobile_exist', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新版本信息
|
||||
*/
|
||||
export function getNewVersion(params: Record<string, any>) {
|
||||
return request.get('app/newversion', params)
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
<!-- <view class="line" :style="{'background-color': getTabColor(currTabIndex == -1)}" v-if="currTabIndex == -1"></view> -->
|
||||
</view>
|
||||
<view v-for="(item, index) in diyComponent.tab.list" class="scroll-item" :class="[{ active: index == currTabIndex }]" @click="changeData(item,index)" :id="'a' + index" :key="index">
|
||||
<view class="name" :style="{'color': getTabColor(index == currTabIndex)}">{{ item.text }}</view>
|
||||
<view class="name" :style="{'color': getTabColor(index == currTabIndex)}">{{ item ? item.text : '' }}</view>
|
||||
<!-- <view class="line" :style="{'background-color': getTabColor(index == currTabIndex)}" v-if="index == currTabIndex"></view> -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
@ -148,8 +148,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
// 轮播搜索
|
||||
import { ref, reactive, computed, watch, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||
import { img } from '@/utils/common';
|
||||
import { ref, reactive, computed, watch, onMounted, nextTick, getCurrentInstance, onUnmounted } from 'vue';
|
||||
import { img, getTopFixedStatusName } from '@/utils/common';
|
||||
import useDiyStore from '@/app/stores/diy';
|
||||
import diyGroup from '@/addon/components/diy/group/index.vue';
|
||||
import { getDiyInfo } from '@/app/api/diy';
|
||||
@ -164,6 +164,12 @@ const diyComponent = computed(() => {
|
||||
if (diyStore.mode == 'decorate') {
|
||||
return diyStore.value[props.index];
|
||||
} else {
|
||||
// 解决在电脑端,切换微页面时,装修页与h5页面之间的相互影响
|
||||
if(uni.getStorageSync(getTopFixedStatusName(props.global))){
|
||||
diyStore.topFixedStatus = uni.getStorageSync(getTopFixedStatusName(props.global));
|
||||
}else{
|
||||
diyStore.topFixedStatus = 'home'
|
||||
}
|
||||
return props.component;
|
||||
}
|
||||
})
|
||||
@ -376,8 +382,8 @@ const changeData = (item: any, index: any) => {
|
||||
// 查询微页面数据
|
||||
diyStore.topFixedStatus = 'diy'
|
||||
getDiyInfoFn(item.diy_id);
|
||||
|
||||
}
|
||||
uni.setStorageSync(getTopFixedStatusName(props.global), diyStore.topFixedStatus);
|
||||
}
|
||||
|
||||
const tabAllPopup = ref(false);
|
||||
@ -389,6 +395,11 @@ const navbarInnerStyle = ref('')
|
||||
// 如果是各家小程序,导航栏内部的宽度需要减少右边胶囊的宽度
|
||||
navbarInnerStyle.value += 'padding-top:' + systemStore.menuButtonInfo.top + 'px;';
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
navbarInnerStyle.value += 'padding-top:' + systemStore.systemInfo.statusBarHeight + 'px;';
|
||||
// #endif
|
||||
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
// 装修模式下刷新
|
||||
@ -397,6 +408,7 @@ onMounted(() => {
|
||||
() => diyComponent.value,
|
||||
(newValue, oldValue) => {
|
||||
if (newValue && newValue.componentName == 'CarouselSearch') {
|
||||
diyStore.topFixedStatus = 'home'; // 装修页的微页面始终展示home
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
@ -427,11 +439,14 @@ onMounted(() => {
|
||||
// #endif
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.removeStorageSync(getTopFixedStatusName(props.global))
|
||||
})
|
||||
|
||||
const refresh = () => {
|
||||
setModuleLocation();
|
||||
// 刷新定位
|
||||
locationVal.refresh();
|
||||
|
||||
changeData({ source: 'home' }, -1)
|
||||
diyComponent.value.swiper.list.forEach((item: any) => {
|
||||
if (item.imageUrl == '') {
|
||||
@ -471,6 +486,7 @@ const getDiyInfoFn = (id: any) => {
|
||||
diyPageData.value = sources.value;
|
||||
|
||||
diyPageData.value.forEach((item: any, index) => {
|
||||
item.componentIsShow = true // 是否显示
|
||||
item.pageStyle = '';
|
||||
if (item.pageStartBgColor) {
|
||||
if (item.pageStartBgColor && item.pageEndBgColor) item.pageStyle += `background:linear-gradient(${ item.pageGradientAngle },${ item.pageStartBgColor },${ item.pageEndBgColor });`;
|
||||
|
||||
@ -321,8 +321,9 @@ const calcFourSquare = () => {
|
||||
}
|
||||
});
|
||||
diyComponent.value.list.forEach((item: any, index: any) => {
|
||||
item.imgWidth = 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 2) + 'px) / 2)';
|
||||
item.widthStyle = item.imgWidth;
|
||||
//影响下次计算ratio
|
||||
// item.imgWidth = 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 2) + 'px) / 2)';
|
||||
item.widthStyle = 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 2) + 'px) / 2)';
|
||||
if (index <= 1) {
|
||||
item.imgHeight = maxHeightFirst;
|
||||
} else if (index > 1) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="w-screen h-screen flex flex-col" :style="themeColor()">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
|
||||
<view :style="{'height':headerHeight}">
|
||||
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
|
||||
</view>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view class="w-screen h-screen" :style="themeColor()">
|
||||
<view class="w-screen h-screen" :style="warpStyle">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
|
||||
<view :style="{'height':headerHeight}">
|
||||
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
|
||||
</view>
|
||||
@ -52,11 +52,11 @@
|
||||
|
||||
<!-- 手机号登录 -->
|
||||
<view v-if="loginConfig.is_mobile" class="mb-[40rpx] w-full flex items-center justify-center">
|
||||
<!-- #ifdef H5 -->
|
||||
<!-- #ifndef MP -->
|
||||
<button class="w-[630rpx] h-[88rpx] !mx-[0] !bg-[#fff] border-[var(--primary-color)] border-solid border-[2rpx] text-[26rpx] rounded-[44rpx] leading-[84rpx] !text-[var(--primary-color)]" @click="redirect({ url: '/app/pages/auth/login',param:{type:'mobile'}})">{{ t('mobileLogin') }}</button>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP -->
|
||||
<button v-if="authRegisterLogin && loginConfig.is_mobile"
|
||||
class="w-[630rpx] h-[88rpx] !mx-[0] !bg-[#fff] border-[var(--primary-color)] border-solid border-[2rpx] text-[26rpx] rounded-[44rpx] leading-[84rpx] !text-[var(--primary-color)]"
|
||||
@click="redirect({ url: '/app/pages/auth/login',param:{type:'mobile'}})">
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="w-screen h-screen flex flex-col " :style="themeColor()" v-if="type">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
|
||||
<view :style="{'height':headerHeight}">
|
||||
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
|
||||
</view>
|
||||
@ -28,9 +28,12 @@
|
||||
class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx"
|
||||
placeholderClass="!text-[var(--text-color-light9)] text-[26rpx]">
|
||||
<template #suffix>
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<view @click="changePassword" v-if="formData.password">
|
||||
<u-icon :name="isPassword?'eye-off':'eye-fill'" color="#b9b9b9" size="20"></u-icon>
|
||||
<u-icon name="eye-off" color="#b9b9b9" size="20" v-if="isPassword"></u-icon>
|
||||
<u-icon name="eye-fill" color="#b9b9b9" size="20" v-else></u-icon>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
</u-input>
|
||||
</u-form-item>
|
||||
@ -205,6 +208,12 @@ onLoad(async(option: any) => {
|
||||
isShowQuickLogin.value = false;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
if (systemStore.appConfig.wechat_app_id) {
|
||||
isShowQuickLogin.value = true;
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
|
||||
const formData = reactive({
|
||||
@ -285,8 +294,8 @@ const handleLogin = () => {
|
||||
loading.value = true
|
||||
|
||||
const login = type.value == 'username' ? usernameLogin : mobileLogin
|
||||
|
||||
login(formData).then((res: any) => {
|
||||
|
||||
memberStore.setToken(res.data.token)
|
||||
// todo 已注册的会员不受影响
|
||||
// if (configStore.login.is_bind_mobile && !res.data.mobile) {
|
||||
@ -300,6 +309,7 @@ const handleLogin = () => {
|
||||
}
|
||||
|
||||
const toLink = () => {
|
||||
// #ifndef APP-PLUS
|
||||
const pages = getCurrentPages(); // 获取页面栈
|
||||
if (pages.length > 1) {
|
||||
const currentPage = pages[pages.length - 2].route;
|
||||
@ -314,6 +324,17 @@ const toLink = () => {
|
||||
} else {
|
||||
redirect({ url: '/app/pages/auth/index', mode: 'redirectTo' })
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
useLogin().getAuthCode({
|
||||
successCallback: () => {
|
||||
if (!getToken()) {
|
||||
redirect({ url: '/app/pages/auth/bind', param: { 'register_type': 'wechat' }, mode: 'redirectTo' })
|
||||
}
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
const toResetpwd = () =>{
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="w-screen h-screen flex flex-col" :style="themeColor()" v-if="type">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
|
||||
<view :style="{'height':headerHeight}">
|
||||
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
|
||||
</view>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="w-screen h-screen flex flex-col" :style="themeColor()">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
|
||||
<view :style="{'height':headerHeight}">
|
||||
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
|
||||
</view>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<view :style="themeColor()">
|
||||
<view class="bg-[var(--page-bg-color)] min-h-screen overflow-hidden" v-if="Object.keys(friendsInfo).length && !loading">
|
||||
<view :style="{background: 'url(' + img('static/resource/images/app/friendpay_money.png') + ') left bottom / cover no-repeat'}" class="pb-[194rpx] overflow-hidden">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
|
||||
<view class="sticky top-0 left-0 right-0 z-100">
|
||||
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
|
||||
</view>
|
||||
@ -52,11 +52,11 @@
|
||||
<view class="border-0 border-solid border-b-[1rpx] border-[#f6f6f6] mb-[20rpx]">
|
||||
<view v-for="(item, index) in friendsInfo.trade_info.item_list" class="flex justify-between" :class="{' mb-[34rpx]': (index + 1) != friendsInfo.trade_info.length }">
|
||||
<view class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden flex-shrink-0">
|
||||
<u--image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
|
||||
<up-image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
|
||||
<template #error>
|
||||
<image class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill" />
|
||||
</template>
|
||||
</u--image>
|
||||
</up-image>
|
||||
</view>
|
||||
<view class="ml-[20rpx] flex flex-1 flex-col justify-between">
|
||||
<view>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<view :style="themeColor()">
|
||||
<view class="bg-[var(--page-bg-color)] min-h-screen overflow-hidden" v-if="Object.keys(friendsInfo).length && !loading">
|
||||
<view :style="{background: 'url(' + img('static/resource/images/app/friendpay_bg.png') + ') left bottom /100% no-repeat'}" class="pb-[168rpx] overflow-hidden">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
|
||||
<view class="sticky top-0 left-0 right-0 z-100">
|
||||
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
|
||||
</view>
|
||||
@ -45,11 +45,11 @@
|
||||
<view class="border-0 border-solid border-b-[1rpx] border-[#f6f6f6] mb-[20rpx]">
|
||||
<view v-for="(item, index) in friendsInfo.trade_info.item_list" class="flex justify-between mb-[30rpx]">
|
||||
<view class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden flex-shrink-0">
|
||||
<u--image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
|
||||
<up-image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
|
||||
<template #error>
|
||||
<image class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill" />
|
||||
</template>
|
||||
</u--image>
|
||||
</up-image>
|
||||
</view>
|
||||
<view class="ml-[20rpx] flex flex-1 flex-col justify-between">
|
||||
<view>
|
||||
@ -201,7 +201,8 @@ const getFriendspayInfoFn = (tradeType : string, tradeId : number) => {
|
||||
},
|
||||
weapp: {
|
||||
...share
|
||||
}
|
||||
},
|
||||
isStop:true
|
||||
});
|
||||
// 分享 - end
|
||||
copyUrlFn();
|
||||
|
||||
@ -19,7 +19,10 @@
|
||||
<!-- 小程序隐私协议 -->
|
||||
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 版本更新弹窗 -->
|
||||
<!-- #ifdef APP -->
|
||||
<update-version ref="updateVersionRef"></update-version>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -29,6 +32,8 @@ import { useDiy } from '@/hooks/useDiy'
|
||||
import { redirect } from '@/utils/common';
|
||||
import { useShare } from '@/hooks/useShare'
|
||||
import diyGroup from '@/addon/components/diy/group/index.vue'
|
||||
import updateVersion from '@/components/update-version/update-version.vue'
|
||||
import useSystemStore from '@/stores/system';
|
||||
|
||||
const { setShare } = useShare()
|
||||
|
||||
@ -42,6 +47,11 @@ const diyGroupRef = ref(null)
|
||||
|
||||
const wxPrivacyPopupRef: any = ref(null)
|
||||
const collectTipRef: any = ref(null)
|
||||
const updateVersionRef: any = ref(null)
|
||||
|
||||
const systemStore = useSystemStore();
|
||||
const versionInfo = systemStore.versionInfo
|
||||
|
||||
// 监听页面加载
|
||||
diy.onLoad();
|
||||
|
||||
@ -52,9 +62,18 @@ diy.onShow((data: any) => {
|
||||
// title: diyData.title
|
||||
// })
|
||||
} 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' })
|
||||
}
|
||||
}
|
||||
let share = data.share ? JSON.parse(data.share) : null;
|
||||
setShare(share);
|
||||
diyGroupRef.value?.refresh();
|
||||
|
||||
@ -80,8 +80,10 @@ import { redirect } from '@/utils/common'
|
||||
import { t } from '@/locale'
|
||||
import { addAddress, editAddress, getAddressInfo } from '@/app/api/member'
|
||||
import manifestJson from '@/manifest.json'
|
||||
import { getAddressByLatlng } from '@/app/api/system'
|
||||
import { getAddressByLatlng,getAreatree } from '@/app/api/system'
|
||||
import useSystemStore from '@/stores/system';
|
||||
|
||||
const systemStore = useSystemStore();
|
||||
const formData: any = ref({
|
||||
id: 0,
|
||||
name: '',
|
||||
@ -123,7 +125,7 @@ onLoad((data: any) => {
|
||||
if (uni.getStorageSync('addressInfo')) {
|
||||
Object.assign(formData.value, uni.getStorageSync('addressInfo'))
|
||||
}
|
||||
formData.value.address = data.name;
|
||||
// formData.value.address = data.name;
|
||||
getAddress(data.latng);
|
||||
const tempArr = getQueryVariable('latng').split(',');
|
||||
formData.value.lat = tempArr[0];
|
||||
@ -133,6 +135,7 @@ onLoad((data: any) => {
|
||||
if (selectAddress) {
|
||||
addressType.value = selectAddress.delivery == 'express' ? 'address' : 'locationAddress';
|
||||
}
|
||||
getAreatreeFn()
|
||||
// #ifdef MP
|
||||
nextTick(() => {
|
||||
if (wxPrivacyPopupRef.value) wxPrivacyPopupRef.value.proactive();
|
||||
@ -256,8 +259,13 @@ const save = () => {
|
||||
|
||||
// 选择地址
|
||||
const chooseLocation = () => {
|
||||
// #ifdef MP
|
||||
let latitude = systemStore.diyAddressInfo ? systemStore.diyAddressInfo.latitude : '';
|
||||
let longitude = systemStore.diyAddressInfo ? systemStore.diyAddressInfo.longitude : '';
|
||||
|
||||
// #ifndef H5
|
||||
uni.chooseLocation({
|
||||
latitude,
|
||||
longitude,
|
||||
success: (res) => {
|
||||
res.latitude && (formData.value.lat = res.latitude)
|
||||
res.longitude && (formData.value.lng = res.longitude)
|
||||
@ -301,6 +309,9 @@ const chooseLocation = () => {
|
||||
const getAddress = (latlng: any) => {
|
||||
getAddressByLatlng({ latlng }).then((res: any) => {
|
||||
if (res.data) {
|
||||
// #ifdef H5
|
||||
formData.value.address = res.data.formatted_addresses.recommend;
|
||||
// #endif
|
||||
formData.value.full_address = '';
|
||||
formData.value.full_address += res.data.province != undefined ? res.data.province : '';
|
||||
formData.value.full_address += res.data.city != undefined ? '' + res.data.city : '';
|
||||
@ -308,7 +319,6 @@ const getAddress = (latlng: any) => {
|
||||
|
||||
formData.value.address_name = formData.value.full_address.replace(/-/g, '');
|
||||
formData.value.area = (res.data.province + res.data.city + res.data.district) || res.data.full_address;
|
||||
|
||||
formData.value.province_id = res.data.province_id != undefined ? res.data.province_id : 0;
|
||||
formData.value.city_id = res.data.city_id != undefined ? res.data.city_id : 0;
|
||||
formData.value.district_id = res.data.district_id != undefined ? res.data.district_id : 0;
|
||||
@ -330,20 +340,46 @@ const getQueryVariable = (variable: any) => {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const areaTree = ref([]); // 存储地址树数据
|
||||
const getAreatreeFn = () => {
|
||||
getAreatree(3).then(res => {
|
||||
areaTree.value = res.data
|
||||
}).catch()
|
||||
|
||||
}
|
||||
// 递归函数:根据名称和父id,查找对应id
|
||||
const findIdByNameAndParentId = (list, name, parentId = 0) => {
|
||||
for (const item of list) {
|
||||
if (item.name === name && item.pid === parentId) {
|
||||
return item.id;
|
||||
}
|
||||
if (item.child && item.child.length > 0) {
|
||||
const childId = findIdByNameAndParentId(item.child, name, item.id);
|
||||
if (childId) {
|
||||
return childId;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
const loadingchoosegAddress = ref(false)
|
||||
const choosegAddress = () =>{
|
||||
loadingchoosegAddress.value = true
|
||||
uni.chooseAddress({
|
||||
success(res) {
|
||||
console.log(7744)
|
||||
loadingchoosegAddress.value = false
|
||||
// 将地址信息格式化为“名字-手机号-地址(详细地址)”
|
||||
// const formattedAddress = `${res.userName}-${res.telNumber}-${res.provinceName}${res.cityName}${res.countyName}${res.detailInfoNew || res.detailInfo}`;
|
||||
console.log(res)
|
||||
formData.value.name = res.userName
|
||||
formData.value.mobile = res.telNumber
|
||||
formData.value.area = res.provinceName + res.cityName + res.countyName
|
||||
formData.value.address = res.detailInfo
|
||||
// 匹配省id
|
||||
formData.value.province_id = findIdByNameAndParentId(areaTree.value, res.provinceName);
|
||||
// 匹配市id(基于省id)
|
||||
formData.value.city_id = findIdByNameAndParentId(areaTree.value, res.cityName, formData.value.province_id);
|
||||
// 匹配区id(基于市id)
|
||||
formData.value.district_id = findIdByNameAndParentId(areaTree.value, res.countyName, formData.value.cityId);
|
||||
},
|
||||
fail(err) {
|
||||
loadingchoosegAddress.value = false
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<view class="min-h-[100vh] !bg-[var(--page-bg-color)]" :style="themeColor()" v-if="memberStore.info">
|
||||
<view class="fixed w-full z-2 !bg-[var(--page-bg-color)]">
|
||||
<view class="pb-[190rpx] text-[#fff] w-full" :style="headerStyle">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
|
||||
<top-tabbar :data="param" class="top-header" />
|
||||
<!-- #endif -->
|
||||
<view class="leading-[38rpx] text-[28rpx] pl-[60rpx] pt-[100rpx]">{{ t('accountBalance') }}</view>
|
||||
@ -153,6 +153,9 @@ const mescrollTop = computed(() => {
|
||||
return '718rpx'
|
||||
}
|
||||
} else {
|
||||
// #ifdef APP-PLUS
|
||||
return (pxToRpx(Number(systemStore.systemInfo.statusBarHeight)) + pxToRpx(8) + 632) + 'rpx'
|
||||
// #endif
|
||||
if (Object.keys(systemStore.menuButtonInfo).length) {
|
||||
return (pxToRpx(Number(systemStore.menuButtonInfo.height)) + pxToRpx(systemStore.menuButtonInfo.top) + pxToRpx(8) + 632) + 'rpx'
|
||||
} else {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<view class="bg-[var(--page-bg-color)] min-h-[100vh] w-full" :style="themeColor()" v-if="memberStore.info">
|
||||
<view class="fixed w-full z-2 !bg-[var(--page-bg-color)]">
|
||||
<view class="pb-[272rpx]" :style="headerStyle">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
|
||||
<top-tabbar :data="param" class="top-header" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
@ -17,12 +17,19 @@
|
||||
import { t } from '@/locale'
|
||||
import { img } from '@/utils/common';
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
const sendMessageTitle = ref('')
|
||||
const sendMessagePath = ref('')
|
||||
const sendMessageImg = ref('')
|
||||
|
||||
sendMessageImg.value = img('static/resource/images/member/contact_service.png')
|
||||
|
||||
onLoad((data: any) => {
|
||||
data.send_title && (sendMessageTitle.value = data.send_title)
|
||||
data.send_path && (sendMessagePath.value = decodeURIComponent(data.send_path))
|
||||
data.send_img && (sendMessageImg.value = decodeURIComponent(data.send_img))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<view class="min-h-[100vh] bg-[var(--page-bg-color)] overflow-hidden" :style="themeColor()">
|
||||
<view class="fixed left-0 right-0 top-0 z-99">
|
||||
<view class="bg-[#fff] px-[var(--sidebar-m)] py-[14rpx] flex items-center">
|
||||
<view class="search-input">
|
||||
<view class="bg-[#f6f6f6] px-[var(--sidebar-m)] py-[14rpx] flex items-center">
|
||||
<view class="search-input bg-[#fff]">
|
||||
<text class="nc-iconfont nc-icon-sousuo-duanV6xx1 btn" @click="searchTypeFn()"></text>
|
||||
<input class="input" maxlength="50" type="text" v-model="keyword" placeholder="请输入搜索关键词" placeholderClass="text-[var(--text-color-light9)] text-[24rpx]" confirm-type="search" @confirm="searchTypeFn()">
|
||||
<text v-if="keyword" class="nc-iconfont nc-icon-cuohaoV6xx1 clear" @click="keyword=''"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tab-style-1 pt-[4rpx] bg-[#fff]">
|
||||
<view class="tab-style-1 pt-[4rpx] bg-[#f6f6f6]">
|
||||
<view class="tab-left">
|
||||
<view class="tab-left-item"
|
||||
:class="{'!text-primary class-select':fromType.from_type === item.from_type && fromType.account_data_gt === item.account_data_gt}"
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<view :style="themeColor()" class="bg-[var(--page-bg-color)] min-h-[100vh] overflow-hidden">
|
||||
<loading-page :loading="loading && memberInfo"></loading-page>
|
||||
<view v-if="!loading && memberInfo && list && list.length" class="min-h-[100vh] overflow-hidden flex flex-col" :style="{backgroundColor: currLevelInfo.level_style.bg_color }">
|
||||
<!-- #ifdef MP -->
|
||||
<!-- #ifdef MP || APP-PLUS -->
|
||||
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
|
||||
<!-- #endif -->
|
||||
<view>
|
||||
@ -111,7 +111,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!loading && (!list || !list.length)">
|
||||
<!-- #ifdef MP -->
|
||||
<!-- #ifdef MP || APP-PLUS -->
|
||||
<top-tabbar :data="topTabbarDataEmpty" />
|
||||
<!-- #endif -->
|
||||
<view class="empty-page" >
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<template v-if="!loading">
|
||||
<view class="w-full bg-[var(--page-bg-color)]">
|
||||
<view class="pb-[210rpx] relative" :style="headerStyle">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
|
||||
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
|
||||
<!-- #endif -->
|
||||
<view class="text-[70rpx] leading-[90rpx] text-[#fff] pl-[60rpx] font-500 pt-[77rpx] price-font">{{ pointInfo.point || 0 }}</view>
|
||||
@ -124,7 +124,10 @@ const headerStyle = computed(() => {
|
||||
}
|
||||
})
|
||||
const topStyle = computed(() => {
|
||||
let style = Object.keys(systemStore.menuButtonInfo).length ? (pxToRpx(Number(systemStore.menuButtonInfo.height)) + pxToRpx(systemStore.menuButtonInfo.top) + 50) + 'rpx;' : '50rpx'
|
||||
let style = Object.keys(systemStore.menuButtonInfo).length ? (pxToRpx(Number(systemStore.menuButtonInfo.height)) + pxToRpx(systemStore.menuButtonInfo.top) + 50) + 'rpx' : '50rpx'
|
||||
// #ifdef APP-PLUS
|
||||
style = '120rpx'
|
||||
// #endif
|
||||
return style
|
||||
})
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<view class="bg-[var(--page-bg-color)] min-h-[100vh]" :style="themeColor()">
|
||||
<view class="fixed left-0 right-0 top-0 z-10085">
|
||||
<view class="bg-[#fff] px-[30rpx] h-[88rpx] flex-center relative z-10084">
|
||||
<view class="search-input">
|
||||
<view class="bg-[#f6f6f6] px-[30rpx] h-[88rpx] pt-[10rpx] flex-center relative z-10084">
|
||||
<view class="search-input bg-[#fff]">
|
||||
<view class="flex-1 text-[24rpx] leading-[60rpx] text-[var(--text-color-light9)]" :class="{'!text-[#333]':from_type}" @click="typePopup = true">{{ from_type_name || '请选择来源用途' }}</view>
|
||||
<text class="nc-iconfont nc-icon-shangV6xx-1 !text-[26rpx] ml-[18rpx] !text-[var(--text-color-light6)]" v-if="typePopup" @click="typePopup = false"></text>
|
||||
<text class="nc-iconfont nc-icon-xiaV6xx !text-[26rpx] ml-[18rpx] !text-[var(--text-color-light6)]" v-else @click="typePopup = true"></text>
|
||||
@ -19,7 +19,7 @@
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
<view class="px-[var(--sidebar-m)] py-[30rpx] flex items-center justify-between">
|
||||
<view class="px-[var(--sidebar-m)] pb-[20rpx] pt-[20rpx] bg-[#f6f6f6] flex items-center justify-between">
|
||||
<view class="flex items-center">
|
||||
<view class="px-[30rpx] bg-[#fff] rounded-[30rpx] text-[24rpx] leading-[54rpx] mr-[20rpx] text-[#333]"
|
||||
:class="{'!text-[var(--primary-color)] font-500':amount_type == item.status}"
|
||||
@ -31,9 +31,9 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<mescroll-body ref="mescrollRef" @init="mescrollInit" :down="{ use: false }" @up="getPointListFn" top="202rpx">
|
||||
<mescroll-body ref="mescrollRef" @init="mescrollInit" :down="{ use: false }" @up="getPointListFn" top="185rpx">
|
||||
<view v-for="(item,index) in pointList" :key="index"
|
||||
class="sidebar-margin card-template mb-[var(--top-m)]">
|
||||
class="sidebar-margin card-template mt-[var(--top-m)]">
|
||||
<view class="flex justify-between items-center">
|
||||
<view class="text-[#333]">
|
||||
<text class="text-[26rpx]">{{ item.month_info.year }}年</text>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<view class="min-h-screen overflow-hidden" v-if="Object.values(info).length" :class="{ 'bg-[#F6F6F6]' : info && info.is_use }">
|
||||
<view v-if="info.is_use">
|
||||
<view class="sigin-header">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifndef H5 -->
|
||||
<view v-if="info.rule_explain" class="side-tab" :style="{top: topStyle}" @click="signPopup = true">
|
||||
<text class="nc-iconfont nc-icon-a-meiriqiandaoV6xx-36 icon"></text>
|
||||
<text class="desc">签到规则</text>
|
||||
@ -154,7 +154,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="h-[100vh] w-[100vw] flex justify-center items-center" v-else>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
|
||||
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
|
||||
<!-- #endif -->
|
||||
<u-empty text="签到未开启" width="347rpx" height="265rpx" :icon="img('static/resource/images/system/empty.png')" />
|
||||
@ -470,11 +470,22 @@ let topTabbarData = topTabarObj.setTopTabbarParam({ title: '我的签到' })
|
||||
/********* 自定义头部 - end ***********/
|
||||
|
||||
const headStyle = computed(() => {
|
||||
// #ifdef MP
|
||||
let style = pxToRpx(Number(systemStore.menuButtonInfo.height) + systemStore.menuButtonInfo.top + 8) + 382 + 'rpx;'
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
let style = pxToRpx(systemStore.systemInfo.statusBarHeight + 8) + 382 + 'rpx'
|
||||
// #endif
|
||||
return style
|
||||
})
|
||||
const topStyle = computed(() => {
|
||||
// #ifdef MP
|
||||
let style = pxToRpx(Number(systemStore.menuButtonInfo.height) + systemStore.menuButtonInfo.top + 8) + 50 + 'rpx;'
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
let style = pxToRpx(systemStore.systemInfo.statusBarHeight + 8) + 50 + 'rpx'
|
||||
// #endif
|
||||
console.log(style)
|
||||
return style
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -5,6 +5,14 @@
|
||||
<u-cell :title="t('personalSettings')" :is-link="true" url="/app/pages/member/personal"></u-cell>
|
||||
<u-cell :title="t('switchLang')" :is-link="true" :value="lang" @click="langSheetShow = true"></u-cell>
|
||||
<u-cell :title="t('version')" :value="version"></u-cell>
|
||||
<!-- #ifdef APP -->
|
||||
<u-cell :title="'检测到新版本' + versionInfo.version_name" :is-link="true" @click="checkUpdate" v-if="versionInfo">
|
||||
<template #value>
|
||||
<text class="u-slot-value flex items-center">立即升级<text class="ml-[5px] w-[10rpx] h-[10rpx] bg-[red] rounded-[10rpx]"></text></text>
|
||||
</template>
|
||||
</u-cell>
|
||||
<u-cell title="版本更新" :is-link="true" value="检查版本更新" @click="checkUpdate" v-else/>
|
||||
<!-- #endif -->
|
||||
</u-cell-group>
|
||||
</view>
|
||||
<view class="mb-[var(--top-m)] sidebar-margin card-template !py-[20rpx]">
|
||||
@ -19,6 +27,10 @@
|
||||
<u-action-sheet :actions="langList" :show="langSheetShow" :closeOnClickOverlay="true"
|
||||
:safeAreaInsetBottom="true"
|
||||
@close="langSheetShow = false" @select="switchLang"></u-action-sheet>
|
||||
<!-- 版本更新弹窗 -->
|
||||
<!-- #ifdef APP -->
|
||||
<!-- <update-version ref="updateVersionRef"></update-version> -->
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -26,10 +38,30 @@
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import { t, language } from '@/locale'
|
||||
import updateVersion from '@/components/update-version/update-version.vue'
|
||||
import useSystemStore from '@/stores/system';
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
|
||||
const systemStore = useSystemStore();
|
||||
const versionInfo = computed(() => {
|
||||
return systemStore.versionInfo
|
||||
})
|
||||
|
||||
const version = ref(import.meta.env.VITE_APP_VERSION)
|
||||
// #ifdef APP
|
||||
plus.runtime.getProperty(plus.runtime.appid, (inf) => {
|
||||
// 获取版本号
|
||||
version.value = inf.version
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP
|
||||
//获取当前app的版本
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
version.value = systemInfo.appVersion;
|
||||
// #endif
|
||||
|
||||
const updateVersionRef: any = ref(null)
|
||||
|
||||
/**
|
||||
* 支持的语言列表
|
||||
@ -52,6 +84,18 @@ const lang = computed(() => {
|
||||
const switchLang = (lang) => {
|
||||
language.loadAllLocaleMessages('app', lang.value)
|
||||
}
|
||||
|
||||
const checkUpdate = ()=>{
|
||||
if(versionInfo.value && versionInfo.value.version_name){
|
||||
systemStore.showUpdateVersion()
|
||||
// uni.$emit('showUpdateVersion',{})
|
||||
}else{
|
||||
uni.showToast({
|
||||
title: '当前版本已是最新版本!',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -124,7 +124,7 @@ const checkIsVerifier = () => {
|
||||
}
|
||||
|
||||
const scanCode = () => {
|
||||
// #ifdef MP
|
||||
// #ifndef H5
|
||||
uni.scanCode({
|
||||
onlyFromCamera: true,
|
||||
success: res => {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<view class="bg-[var(--page-bg-color)] min-h-screen overflow-hidden" :style="themeColor()">
|
||||
<view class="fixed left-0 right-0 top-0 z-99 bg-[#fff]">
|
||||
<view class="fixed left-0 right-0 top-0 z-99 bg-[#f6f6f6]">
|
||||
<view class="py-[14rpx] flex items-center justify-between px-[20rpx]">
|
||||
<view class="flex-1 search-input mr-[20rpx]">
|
||||
<view class="flex-1 search-input bg-[#fff] mr-[20rpx]">
|
||||
<text @click.stop="searchTypeFn()" class="nc-iconfont nc-icon-sousuo-duanV6xx1 btn"></text>
|
||||
<input class="input" maxlength="50" type="text" v-model="keyword" placeholder="请输入搜索关键词" placeholderClass="text-[var(--text-color-light9)] text-[24rpx]" confirm-type="search" @confirm="searchTypeFn()">
|
||||
<text v-if="keyword" class="nc-iconfont nc-icon-cuohaoV6xx1 clear" @click="keyword=''"></text>
|
||||
@ -21,11 +21,11 @@
|
||||
<text class="text-[#303133] text-[26rpx] font-400 nc-iconfont nc-icon-fuzhiV6xx1 ml-[11rpx]" @click.stop="copy(item.code)"></text>
|
||||
</view>
|
||||
<view class="flex flex-1 mb-2" v-for="(dataItem,dataIndex) in item.value.list" :key="dataIndex">
|
||||
<u--image width="130rpx" height="130rpx" :radius="'var(--goods-rounded-big)'" :src="img(dataItem.cover ? dataItem.cover : '')" mode="aspectFill">
|
||||
<up-image width="130rpx" height="130rpx" :radius="'var(--goods-rounded-big)'" :src="img(dataItem.cover ? dataItem.cover : '')" mode="aspectFill">
|
||||
<template #error>
|
||||
<image class="w-[130rpx] h-[130rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"/>
|
||||
</template>
|
||||
</u--image>
|
||||
</up-image>
|
||||
<view class="flex flex-col flex-1 ml-[20rpx] py-[4rpx]">
|
||||
<view class="max-w-[490rpx] leading-[1.3] truncate text-[28rpx] text-[#303133]">{{ dataItem.name }}</view>
|
||||
<view class="mt-[14rpx] truncate text-[24rpx] text-[var(--text-color-light9)] max-w-[490rpx] " v-if="item.sub_name">{{ item.sub_name }}</view>
|
||||
|
||||
@ -13,6 +13,7 @@ interface Diy {
|
||||
pageStartBgColor: string, // 页面背景颜色(开始)
|
||||
pageEndBgColor: string, // 页面背景颜色(结束)
|
||||
bottomTabBar: any, // 底部导航
|
||||
copyright: any, // 版权信息
|
||||
bgUrl: string
|
||||
},
|
||||
// 组件集合
|
||||
@ -38,6 +39,15 @@ const useDiyStore = defineStore('diy', {
|
||||
bottomTabBar: {
|
||||
control: true, // 是否允许展示编辑
|
||||
isShow: true, // 是否显示
|
||||
designNav:{
|
||||
title:'',
|
||||
key:''
|
||||
}
|
||||
},
|
||||
copyright:{
|
||||
control: true, // 是否允许展示编辑
|
||||
isShow: false, // 是否显示
|
||||
textColor: '#ccc',
|
||||
},
|
||||
bgUrl: ''
|
||||
},
|
||||
|
||||
@ -17,8 +17,8 @@
|
||||
</view>
|
||||
<view class="flex-1 pr-[10rpx]" v-else></view>
|
||||
</view>
|
||||
<scroll-view scroll-y="true" class="h-[50vh]" :scroll-top="scrollTop" scroll-with-animation @touchmove.stop>
|
||||
<view class="flex p-[30rpx] pt-[0] text-sm font-500 h-[50vh]">
|
||||
<scroll-view scroll-y="true" class="h-[700rpx] overflow-y-auto" :scroll-top="scrollTop" scroll-with-animation @touchmove.stop>
|
||||
<view class="flex p-[30rpx] pt-[0] text-sm font-500">
|
||||
<view v-if="areaList.province.length" class="flex-1 pr-[10rpx]" :style="{ opacity: currSelect == 'province' ? 1 : 0, pointerEvents: currSelect == 'province' ? 'auto' : 'none',height: currSelect == 'province' ? 'auto' : '0',overflow: currSelect == 'province' ? 'auto' : 'hidden' }">
|
||||
<view v-for="(item, index) in areaList.province" :key="item.id" class="h-[80rpx] flex items-center" :class="{'text-[var(--primary-color)]': selected.province && selected.province.id == item.id }" @click="handleProvinceClick(item)">{{ item.name }}</view>
|
||||
</view>
|
||||
@ -138,6 +138,7 @@ watch(() => selected.city, (nval) => {
|
||||
}
|
||||
}
|
||||
if (!data.length) {
|
||||
currSelect.value = 'city'
|
||||
emits('complete', selected)
|
||||
show.value = false
|
||||
}
|
||||
@ -155,40 +156,40 @@ const emits = defineEmits(['complete'])
|
||||
// 滚动到选中项位置
|
||||
const scrollToSelected = (type: string, selectedIndex: number) => {
|
||||
// 计算目标位置,让选中项显示在列表的下半部分
|
||||
const itemHeight = 80 // 每项高度 80rpx
|
||||
const itemHeight = 40 // 每项高度 80rpx
|
||||
const targetScrollTop = Math.max(0, (selectedIndex - 2) * itemHeight) // 让选中项显示在第3个位置左右
|
||||
scrollTop.value = targetScrollTop
|
||||
}
|
||||
|
||||
// 重置滚动位置
|
||||
const resetScrollTop = () => {
|
||||
scrollTop.value = 0
|
||||
scrollTop.value = 1;
|
||||
nextTick(() => {
|
||||
scrollTop.value = 0;
|
||||
});
|
||||
}
|
||||
|
||||
// 监听当前选择标签变化,重置滚动位置并滚动到选中项
|
||||
// 监听currSelect变化,触发滚动(改用nextTick确保DOM更新)
|
||||
watch(() => currSelect.value, (newVal) => {
|
||||
// 先重置滚动位置
|
||||
resetScrollTop()
|
||||
|
||||
setTimeout(() => {
|
||||
nextTick(() => { // 等待DOM更新后再执行滚动
|
||||
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) {
|
||||
scrollToSelected('province', index)
|
||||
scrollToSelected('province', index); // 调用滚动函数
|
||||
}
|
||||
} 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) {
|
||||
scrollToSelected('city', index)
|
||||
scrollToSelected('city', index); // 调用滚动函数
|
||||
}
|
||||
} 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) {
|
||||
scrollToSelected('district', index)
|
||||
scrollToSelected('district', index); // 调用滚动函数
|
||||
}
|
||||
}
|
||||
}, 150)
|
||||
})
|
||||
});
|
||||
}, { immediate: true }); // 增加immediate,初始化时也触发
|
||||
|
||||
/**
|
||||
* 监听区县变更
|
||||
@ -204,7 +205,6 @@ watch(() => selected.district, (nval) => {
|
||||
// 处理省份点击
|
||||
const handleProvinceClick = (item: any) => {
|
||||
selected.province = item
|
||||
// 立即滚动到顶部,确保新数据可见
|
||||
nextTick(() => {
|
||||
resetScrollTop()
|
||||
})
|
||||
@ -213,7 +213,6 @@ const handleProvinceClick = (item: any) => {
|
||||
// 处理城市点击
|
||||
const handleCityClick = (item: any) => {
|
||||
selected.city = item
|
||||
// 立即滚动到顶部,确保新数据可见
|
||||
nextTick(() => {
|
||||
resetScrollTop()
|
||||
})
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
:style="itemStyle(index)"
|
||||
:class="{ 'scale-in': item.isNew }"
|
||||
>
|
||||
<u--image width="30rpx" height="30rpx" radius="15rpx" :src="img(item.src)" mode="aspectFill">
|
||||
<up-image width="30rpx" height="30rpx" radius="15rpx" :src="img(item.src)" mode="aspectFill">
|
||||
<template #error>
|
||||
<image
|
||||
class="w-[30rpx] h-[30rpx] rounded-full"
|
||||
@ -16,7 +16,7 @@
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</template>
|
||||
</u--image>
|
||||
</up-image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
46
uni-app/src/components/copy-right/copy-right.vue
Normal file
46
uni-app/src/components/copy-right/copy-right.vue
Normal 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>
|
||||
@ -10,7 +10,9 @@
|
||||
:send-message-title="props.sendMessageTitle"
|
||||
:send-message-path="props.sendMessagePath"
|
||||
:send-message-img="props.sendMessageImg"
|
||||
:show-message-card="true">
|
||||
:show-message-card="true"
|
||||
@contact="handleContact"
|
||||
>
|
||||
</button>
|
||||
|
||||
<u-popup :show="popupShow" @close="popupShow = false" mode="center" :round="5" :safeAreaInsetBottom="false">
|
||||
@ -33,6 +35,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import useSystemStore from '@/stores/system'
|
||||
import { redirect } from '@/utils/common';
|
||||
|
||||
const props = defineProps({
|
||||
sendMessageTitle: {
|
||||
@ -58,7 +61,7 @@ const siteInfo: any = computed(() => {
|
||||
const popupShow = ref(false);
|
||||
|
||||
const contactService = () => {
|
||||
// #ifdef H5
|
||||
// #ifndef MP-WEIXIN
|
||||
popupShow.value = true;
|
||||
// #endif
|
||||
}
|
||||
@ -70,6 +73,12 @@ const callPhone = () => {
|
||||
});
|
||||
}
|
||||
|
||||
const handleContact = (e) => {
|
||||
if (e.detail && e.detail.path) {
|
||||
redirect({ mode: 'redirectTo', url: e.detail.path, param: e.detail.query || {} })
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
</script>
|
||||
|
||||
|
||||
@ -11,12 +11,12 @@
|
||||
</view>
|
||||
<view v-if="data.customer_phone" class="w-full h-[1rpx] bg-dashed-style my-[40rpx]"></view>
|
||||
<view class="px-[24rpx] flex flex-col items-center justify-center" v-if="data.customer_qrcode">
|
||||
<view class="border border-[#eee] border-solid rounded-[10rpx] border-[1rpx] w-[240rpx] h-[240rpx] flex items-center justify-center">
|
||||
<view class="border border-[#eee] border-solid rounded-[10rpx] border-[1rpx] p-[10rpx] flex items-center justify-center">
|
||||
<image class="w-[226rpx] h-[226rpx]" :src="img(data.customer_qrcode)" mode="widthFix" />
|
||||
</view>
|
||||
<view class="text-[26rpx] text-[#333] mt-[20rpx]">扫描二维码添加客服</view>
|
||||
</view>
|
||||
<view class="px-[50rpx] mt-[16rpx] text-[22rpx] text-[#999] leading-[1.5]" style="text-indent: 2em;">{{ data.customer_guided_words }}</view>
|
||||
<view class="px-[50rpx] mt-[16rpx] text-center text-[22rpx] text-[#999] leading-[1.5]">{{ data.customer_guided_words }}</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<!-- <view @click="servicesDataShow = false" class="mt-[50rpx] nc-iconfont nc-icon-cuohaoV6xx1 !text-[50rpx] text-[#fff]"></view> -->
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="btn-wrap">
|
||||
<button class="primary-btn-bg btn" hover-class="none" :loading="loading" @click="confirmPay">{{ t('pay.confirmPay') }}</button>
|
||||
<button class="primary-btn-bg bg-[var(--primary-color)] btn" hover-class="none" :loading="loading" @click="confirmPay">{{ t('pay.confirmPay') }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
@ -145,6 +145,18 @@ const confirmPay = () => {
|
||||
location.href = res.data.url
|
||||
}
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
uni.requestPayment({
|
||||
provider: 'alipay',
|
||||
...res.data,
|
||||
success: (res: any) => {
|
||||
toPayResult()
|
||||
},
|
||||
fail: (res: any) => {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
break;
|
||||
default:
|
||||
if (res.data.url) {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<u-popup :show="sharePopupShow" type="bottom" @close="sharePopupClose" overlayOpacity="0.8">
|
||||
<view @touchmove.prevent.stop>
|
||||
<view class="share-content">
|
||||
<!-- #ifdef MP || APP-PLUS -->
|
||||
<!-- #ifdef MP -->
|
||||
<view class="share-box">
|
||||
<button class="share-btn" :plain="true" open-type="share">
|
||||
<view class="text-[#07c160] iconfont iconweixin11"></view>
|
||||
@ -21,6 +21,22 @@
|
||||
</button>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view class="share-box">
|
||||
<button class="share-btn" :plain="true" @click="shareSession">
|
||||
<view class="text-[#07c160] iconfont iconweixin11"></view>
|
||||
<text>分享给好友</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="share-box">
|
||||
<button class="share-btn" :plain="true" @click="shareWechatMoments">
|
||||
<image :src="img('static/resource/images/app/wechat_moments.png')"></image>
|
||||
<text>分享到朋友圈</text>
|
||||
</button>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="share-footer" @click="sharePopupClose">
|
||||
<text class="text-[#333]">取消分享</text>
|
||||
@ -46,6 +62,7 @@
|
||||
import { ref } from 'vue';
|
||||
import { img, copy } from '@/utils/common';
|
||||
import useSystemStore from "@/stores/system";
|
||||
import { useShare } from '@/hooks/useShare'
|
||||
|
||||
const props = defineProps({
|
||||
copyUrl: { // 例 "/wap/addon/shop_fenxiao/pages/goods"
|
||||
@ -106,6 +123,16 @@ const sharePopupClose = () => {
|
||||
emits('close');
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const shareSession = () => {
|
||||
useShare().onShareAppMessage();
|
||||
}
|
||||
|
||||
const shareWechatMoments = () => {
|
||||
useShare().onShareTimeline();
|
||||
}
|
||||
// #endif
|
||||
|
||||
defineExpose({
|
||||
openShare
|
||||
})
|
||||
|
||||
@ -113,6 +113,7 @@ const props = defineProps({
|
||||
const emits = defineEmits(['close'])
|
||||
|
||||
const sharePopupShow = ref(false);
|
||||
const posterType = ref(props.posterType)
|
||||
|
||||
// 复制
|
||||
const copyUrl = () => {
|
||||
@ -134,7 +135,8 @@ const copyUrl = () => {
|
||||
});
|
||||
}
|
||||
|
||||
const openShare = () => {
|
||||
const openShare = (data: any) => {
|
||||
posterType.value = data?.type || posterType.value || ''
|
||||
sharePopupShow.value = true
|
||||
loadPoster();
|
||||
}
|
||||
@ -154,7 +156,7 @@ const loadPoster = () => {
|
||||
isPosterImg.value = false;
|
||||
let obj = {
|
||||
id: props.posterId,
|
||||
type: props.posterType,
|
||||
type: posterType.value,
|
||||
param: props.posterParam
|
||||
}
|
||||
let startTime = Date.parse(new Date());
|
||||
|
||||
@ -46,7 +46,6 @@ if (!addon && configStore.addon) {
|
||||
}
|
||||
|
||||
const tabbar: any = reactive({})
|
||||
|
||||
const setTabbar = () => {
|
||||
let list = cloneDeep(useConfigStore().tabbarList);
|
||||
if (list.length == 1) {
|
||||
@ -96,6 +95,19 @@ const setTabbar = () => {
|
||||
|
||||
setTabbar()
|
||||
|
||||
// 移除原有的 "if (!props.addon)" 条件限制,直接监听 configStore.addon
|
||||
watch(
|
||||
() => useConfigStore().addon,
|
||||
(newAddon, oldAddon) => {
|
||||
// 当 props.addon 为空时,才响应 configStore.addon 的变化
|
||||
if (!props.addon && newAddon !== oldAddon) {
|
||||
addon = newAddon; // 同步更新本地 addon 变量
|
||||
setTabbar(); // 重新设置 TabBar
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true } // 立即执行 + 深度监听
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.addon,
|
||||
(newValue, oldValue) => {
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<view class="u-navbar z-100" :class="{'fixed': props.scrollBool != -1, 'absolute': props.scrollBool == -1}" :style="{ backgroundColor: bgColor}">
|
||||
<view class="navbar-inner" :style="{ width: '100%', height: placeholderHeight + 'px' }">
|
||||
<view v-if="topStatusBarData.style == 'style-1'" class="content-wrap" :class="[topStatusBarData.textAlign]" :style="navbarInnerStyle">
|
||||
<view class="back-wrap -ml-[16rpx] text-[26px] nc-iconfont nc-icon-zuoV6xx" :class="{'!text-transparent': !isBackShow}" :style="{ color: titleTextColor }" @tap="goBack"></view>
|
||||
<view v-if="isBackShow" class="back-wrap -ml-[16rpx] text-[26px] nc-iconfont nc-icon-zuoV6xx" :class="{'!text-transparent': !isBackShow}" :style="{ color: titleTextColor }" @tap="goBack"></view>
|
||||
<view class="title-wrap" :style="styleOneFontSize">{{ data.title }}</view>
|
||||
</view>
|
||||
<view v-if="topStatusBarData.style == 'style-2'" class="content-wrap" :style="navbarInnerStyle" @click="diyStore.toRedirect(topStatusBarData.link)">
|
||||
@ -45,7 +45,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, getCurrentInstance, nextTick } from 'vue';
|
||||
import { redirect, img } from '@/utils/common';
|
||||
import { redirect, img ,currRoute} from '@/utils/common';
|
||||
import useSystemStore from '@/stores/system';
|
||||
import useDiyStore from '@/app/stores/diy';
|
||||
import { useLocation } from '@/hooks/useLocation'
|
||||
@ -82,20 +82,13 @@ const props = defineProps({
|
||||
isFill: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 临时处理方法,强制显示
|
||||
mustFill: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 控制-定位后是否对导航栏进行填充
|
||||
const isFill = computed(() => {
|
||||
let bool = true;
|
||||
if(props.mustFill){
|
||||
return props.mustFill
|
||||
}else if (diyStore.imageAdsSameScreen) {
|
||||
if (diyStore.imageAdsSameScreen) {
|
||||
bool = false;
|
||||
} else {
|
||||
bool = props.isFill;
|
||||
@ -201,6 +194,8 @@ const isBackShow = computed(() => {
|
||||
let bool = false;
|
||||
if (props.isBack && pages.length > 1) {
|
||||
bool = true;
|
||||
} else if (currRoute() == 'app/pages/auth/index') {
|
||||
bool = true;
|
||||
}
|
||||
return bool;
|
||||
})
|
||||
@ -212,7 +207,14 @@ const goBack = () => {
|
||||
if (typeof props.customBack === 'function') {
|
||||
props.customBack();
|
||||
} else {
|
||||
uni.navigateBack();
|
||||
if (getCurrentPages().length > 1) {
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
redirect({
|
||||
url: '/addon/shop/pages/index',
|
||||
mode: 'reLaunch'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
/******************************* 返回按钮-end ***********************/
|
||||
|
||||
287
uni-app/src/components/update-version/update-version.vue
Normal file
287
uni-app/src/components/update-version/update-version.vue
Normal 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 //true表示强制安装,不进行版本号的校验;false则需要版本号校验,
|
||||
},
|
||||
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 //true表示强制安装,不进行版本号的校验;false则需要版本号校验,
|
||||
},
|
||||
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>
|
||||
@ -5,8 +5,8 @@ import {
|
||||
updateWechatOpenid,
|
||||
wechatUser,
|
||||
wechatUserLogin,
|
||||
updateWechatOpenidByH5
|
||||
|
||||
updateWechatOpenidByH5,
|
||||
wxappLogin
|
||||
} from '@/app/api/auth'
|
||||
import { getWechatAuthCode } from '@/app/api/system'
|
||||
import useMemberStore from '@/stores/member'
|
||||
@ -23,8 +23,7 @@ export function useLogin() {
|
||||
const config = useConfigStore()
|
||||
const systemStore = useSystemStore()
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
|
||||
// #ifdef MP
|
||||
if (!uni.getStorageSync('autoLoginLock') && config.login.is_bind_mobile) {
|
||||
uni.setStorageSync('isBindMobile', true) // 强制绑定手机号标识
|
||||
}
|
||||
@ -38,7 +37,7 @@ export function useLogin() {
|
||||
|
||||
// 如果只开启了账号密码登录,就不需要跳转到登录中间页了,直接进入普通账号密码登录页面
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// #ifdef MP
|
||||
if (config.login.is_username && !config.login.is_mobile && !config.login.is_auth_register) {
|
||||
redirect({ url: '/app/pages/auth/login', param: { type: 'username' } })
|
||||
} else if (systemStore.initStatus == 'finish' && !config.login.is_username && !config.login.is_mobile && !config.login.is_auth_register) {
|
||||
@ -69,6 +68,16 @@ export function useLogin() {
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
if (config.login.is_username && !config.login.is_mobile) {
|
||||
redirect({ url: '/app/pages/auth/login', param: { type: 'username' } })
|
||||
} else if (systemStore.initStatus == 'finish' && !config.login.is_username && !config.login.is_mobile) {
|
||||
uni.showToast({ title: '商家未开启登录注册', icon: 'none' })
|
||||
} else {
|
||||
redirect({ url: '/app/pages/auth/index' })
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
@ -223,6 +232,24 @@ export function useLogin() {
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
wxappLogin(obj).then((res) => {
|
||||
if (res.data.token) {
|
||||
useMemberStore().setToken(res.data.token, () => {
|
||||
handleLoginBack();
|
||||
})
|
||||
} else {
|
||||
uni.setStorageSync('openid', res.data.openid)
|
||||
uni.setStorageSync('unionid', res.data.unionid)
|
||||
if(res.data.nickname) uni.setStorageSync('nickname', res.data.nickname)
|
||||
if(res.data.avatar) uni.setStorageSync('avatar', res.data.avatar)
|
||||
}
|
||||
|
||||
if (params.successCallback) params.successCallback(res.data)
|
||||
}).catch((err) => {
|
||||
uni.showToast({ title: err.msg, icon: 'none' })
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
@ -341,6 +368,19 @@ export function useLogin() {
|
||||
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
uni.login({
|
||||
"provider": "weixin",
|
||||
"onlyAuthorize": true, // 微信登录仅请求授权认证
|
||||
success: function(event){
|
||||
const { code } = event
|
||||
authLogin({ code })
|
||||
},
|
||||
fail: function (err) {
|
||||
uni.showToast({ title: err.msg, icon: 'none' })
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
const updateWechatOpenidForH5 = (wx_openid: any) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user