fix: 首页和我的应用排序按 seq 排列
This commit is contained in:
61
.hermes/note-backend-jwt-changes.md
Normal file
61
.hermes/note-backend-jwt-changes.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# 后端 JWT Token 签发 — 需要添加的修改
|
||||
|
||||
以 CRMEB 后端 PHP 为例,找到签发 token 的方法(通常在类似以下位置):
|
||||
|
||||
## 1. 查找签发位置
|
||||
|
||||
CRMEB 后端搜索 `JWT::encode` 或 `makeToken` / `createToken`:
|
||||
- `crmeb/app/services/system/SystemAuthService.php`
|
||||
- `crmeb/app/services/other/CacheTokenService.php`
|
||||
- 或 `crmeb/app/services/wechat/...`
|
||||
|
||||
## 2. 修改 Payload
|
||||
|
||||
原始代码可能类似:
|
||||
|
||||
```php
|
||||
$payload = [
|
||||
'iss' => 'crmeb',
|
||||
'iat' => time(),
|
||||
'exp' => time() + 86400 * 7,
|
||||
'id' => $user->id,
|
||||
];
|
||||
$token = JWT::encode($payload, $key, 'HS256');
|
||||
```
|
||||
|
||||
改为(添加 uid、phone、openid):
|
||||
|
||||
```php
|
||||
$payload = [
|
||||
'iss' => 'crmeb',
|
||||
'iat' => time(),
|
||||
'exp' => time() + 86400 * 7,
|
||||
'uid' => $user->id, // ← 添加:用户 ID
|
||||
'phone' => $user->phone ?? '', // ← 添加:手机号
|
||||
'openid' => $user->openid ?? '', // ← 添加:微信 openid
|
||||
];
|
||||
$token = JWT::encode($payload, $key, 'HS256');
|
||||
```
|
||||
|
||||
## 3. 验证
|
||||
|
||||
修改后登录任意用户,token 形如:
|
||||
|
||||
```
|
||||
eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEyMywicGhvbmUiOiIxMzgwMDAwMDAwMCIsIm9wZW5pZCI6Im8xMjNfIn0.xxxx
|
||||
```
|
||||
|
||||
在浏览器 DevTools 或 https://jwt.io 解码 payload 应看到:
|
||||
|
||||
```json
|
||||
{
|
||||
"uid": 123,
|
||||
"phone": "13800000000",
|
||||
"openid": "o123_xxx",
|
||||
"exp": 1748000000,
|
||||
"iss": "crmeb",
|
||||
"iat": 1747395200
|
||||
}
|
||||
```
|
||||
|
||||
前端 `parseJwt()` 就能正确读取这些字段。
|
||||
77
.trae/documents/mp_to_h5_token_design.md
Normal file
77
.trae/documents/mp_to_h5_token_design.md
Normal file
@@ -0,0 +1,77 @@
|
||||
## 方案:小程序跳转到 H5 页面携带 token
|
||||
|
||||
### Summary
|
||||
- 目标:在微信小程序内通过 web-view 打开指定 H5 地址时,让 H5 自动获得与小程序一致的登录态(token),并在 H5 内完成 Vuex/缓存写入与用户信息同步。
|
||||
- 约束(来自现状与确认):打开方式为 web-view;当前不方便改后端接口;倾向直接携带 Bearer token。
|
||||
- 推荐策略:优先把 token 放到 URL 的 fragment(`#` 之后)以避免出现在 HTTP 请求与 Referer/服务端日志里;H5 启动时从 fragment 解析 token,写入 `LOGIN_STATUS` 缓存并拉取用户信息,随后清理 URL 中的 token。
|
||||
|
||||
### Current State Analysis
|
||||
- 小程序打开 H5 的通用入口:`utils/util.js: JumpPath`,当 url 包含 `http` 时在 MP/APP-PLUS 走 web-view 页面:
|
||||
- [util.js](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/utils/util.js#L162-L201)
|
||||
- web-view 页面接收 `option.url` 并 `decodeURIComponent` 后赋给 `<web-view :src="url">`:
|
||||
- [pages/annex/web_view/index.vue](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/pages/annex/web_view/index.vue#L1-L28)
|
||||
- H5 端已有“从 URL query 自动登录”的机制,但目前只处理 `remote_token`:
|
||||
- H5 `App.vue`:`if (!isLogin && option.query.hasOwnProperty('remote_token')) this.remoteRegister(...)`
|
||||
- [App.vue](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/App.vue#L88-L92)
|
||||
- `remoteRegister` 调用 `GET /api/remote_register`(noAuth)换取 token 或跳转 URL:
|
||||
- [api/public.js](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/api/public.js#L231-L239)
|
||||
- token 使用方式:请求层统一在 `utils/request.js` 注入 header `Authori-zation: Bearer <token>`:
|
||||
- [request.js](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/utils/request.js#L53-L63)
|
||||
- 登录态落地:`store/modules/app.js: LOGIN` 将 token 写入 `Cache.set(LOGIN_STATUS, token, time)`;并可配合 `getUserInfo()` 写入 uid/userInfo:
|
||||
- [app.js](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/store/modules/app.js#L89-L117)
|
||||
|
||||
### Proposed Changes
|
||||
|
||||
#### 1) 小程序侧:构造带 token 的 H5 URL(尽量放在 fragment)
|
||||
- 修改文件:`utils/util.js`
|
||||
- 修改点:在 `JumpPath` 的 `url.indexOf("http") != -1` 分支(MP/APP-PLUS)中,在跳转到 `/pages/annex/web_view/index` 之前对目标 url 做“携带 token”的包装。
|
||||
- 行为设计:
|
||||
- 若用户未登录(`store.getters.isLogin` 为 false 或 `store.state.app.token` 为空),直接走现有登录流程(可复用 `libs/login.js: toLogin`),不打开 H5。
|
||||
- 若已登录:
|
||||
- 优先把 token 附加在 hash 内:`https://h5.xxx.com/#/some/path?token=<encoded>`
|
||||
- 若目标 url 不含 `#`,则退化为 query:`https://h5.xxx.com/some/path?token=<encoded>`(存在服务端日志/Referer 风险,属于“不得不”的兼容路径)。
|
||||
- 在把 url 作为参数传给 web_view 页面时,对整段 url 做 `encodeURIComponent`,避免 token 中的特殊字符以及 `&` 被小程序路由二次解析。
|
||||
- 预计新增/调整函数(放在 util.js 内部):
|
||||
- `appendTokenToUrl(rawUrl, token): string`(只做字符串处理,不引入新依赖)
|
||||
- `encodeForWebView(url): string`(统一 `encodeURIComponent`)
|
||||
- 影响面:
|
||||
- 任何通过 `this.$util.JumpPath('https://...')` 打开的 H5 链接,都将自动带上 token(仅在 MP/APP-PLUS 生效)。
|
||||
|
||||
#### 2) web-view 容器页:强制 decode 前提下保证传参完整
|
||||
- 修改文件:`pages/annex/web_view/index.vue`
|
||||
- 修改点:保持现有 `decodeURIComponent(option.url)`,但要求所有调用方都传入 encode 后的 url。
|
||||
- 如果项目内存在“未 encode 直接拼接 url”的调用点(当前 `utils/util.js` 即如此),统一改为 encode 后再传。
|
||||
|
||||
#### 3) H5 侧:解析 token → 写入登录态 → 拉取用户信息 → 清理 URL
|
||||
- 修改文件:`App.vue`
|
||||
- 修改点:H5 `onLaunch` 内(`// #ifdef H5` 分支)补充 token 自动登录逻辑,优先级建议:
|
||||
1. 若当前已登录(`this.$store.getters.isLogin` 为 true),不处理外部 token(避免覆盖用户主动登录态)。
|
||||
2. 仍保留现有 `remote_token` 分支逻辑(兼容已有能力)。
|
||||
3. 新增 `token` 分支逻辑:
|
||||
- 从 `option.query.token` 读取(query 方案)
|
||||
- 若取不到,再从 `location.hash` 里解析 `token`(fragment 方案)
|
||||
- 拿到 token 后:
|
||||
- `this.$store.commit("LOGIN", { token, time: 0 })`(time=0 表示缓存不主动过期,后续由服务端 410xxx 兜底踢下线)
|
||||
- 调用 `getUserInfo()`,成功后 `SETUID/UPDATE_USERINFO`
|
||||
- 最后用 `history.replaceState` 把 URL 中的 token 移除(避免用户复制链接时携带 token)
|
||||
- 需要补一个轻量解析方法(不引入库):
|
||||
- `parseTokenFromHash(hash): string | ''`:兼容 `#/path?token=...` 和 `#/?token=...` 两种。
|
||||
|
||||
### Assumptions & Decisions
|
||||
- H5 地址可能是 uni-app 默认 hash 路由(`/#/`);因此默认把 token 放在 fragment 内最安全。
|
||||
- 不改后端时,“直接 token”不可避免有泄露风险;通过 fragment + 清理 URL 将风险降到相对可接受:
|
||||
- fragment 不会出现在 HTTP 请求行与一般服务端 access log
|
||||
- Referer 通常不包含 fragment
|
||||
- token 仍可能在以下场景泄露:用户截屏、设备调试、前端错误上报采集 `location.href` 等;因此上线前需确认前端埋点/监控不会上报完整 URL。
|
||||
|
||||
### Verification
|
||||
- 本地/测试环境:
|
||||
- 小程序端登录后,通过任意入口打开一个 H5 链接(走 `JumpPath`):
|
||||
- web-view 打开的 H5 页面无需再次登录
|
||||
- H5 内 `store.getters.isLogin` 为 true,且请求头带 `Authori-zation: Bearer ...`
|
||||
- `getUserInfo()` 可正常返回并写入 uid/userInfo
|
||||
- 打开后检查 H5 当前地址:token 参数已被移除(history.replaceState 生效)
|
||||
- 回归:
|
||||
- 未登录时打开 H5 链接:应先跳登录,不应打开带 token 的 H5
|
||||
- H5 原有 `remote_token` 登录不受影响
|
||||
|
||||
742
App.vue
742
App.vue
@@ -1,333 +1,409 @@
|
||||
<script>
|
||||
import {checkLogin} from './libs/login';
|
||||
import {HTTP_REQUEST_URL} from './config/app';
|
||||
import { LOGIN_STATUS, SUBSCRIBE_MESSAGE } from "./config/cache.js"
|
||||
import { getShopConfig, silenceAuth, getLogo, remoteRegister } from '@/api/public';
|
||||
import { silenceBindingSpread } from '@/utils';
|
||||
import Auth from '@/libs/wechat.js';
|
||||
import Routine from './libs/routine.js';
|
||||
import { colorChange, copyRight } from '@/api/api.js';
|
||||
import { getUserInfo } from "@/api/user.js"
|
||||
import { mapGetters } from "vuex"
|
||||
// #ifdef MP
|
||||
// let livePlayer = requirePlugin('live-player-plugin')
|
||||
// #endif
|
||||
export default {
|
||||
globalData: {
|
||||
spid: 0,
|
||||
code: 0,
|
||||
isLogin: false,
|
||||
userInfo: {},
|
||||
globalData: false,
|
||||
windowHeight: uni.getWindowInfo().windowHeight + 'px',
|
||||
sysHeight:uni.getWindowInfo().statusBarHeight,
|
||||
},
|
||||
computed: mapGetters(['isLogin', 'cartNum']),
|
||||
watch: {
|
||||
cartNum(newCart, b) {
|
||||
this.$store.commit('indexData/setCartNum', newCart + '')
|
||||
if (newCart > 0) {
|
||||
uni.setTabBarBadge({
|
||||
index: 3,
|
||||
text: newCart>99?'99+':newCart+''
|
||||
})
|
||||
} else {
|
||||
uni.hideTabBarRedDot({
|
||||
index: 3
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
onShow(options) {
|
||||
const queryData = uni.getEnterOptionsSync(); // uni-app版本 3.5.1+ 支持
|
||||
if (queryData.query.spid) {
|
||||
this.$Cache.set('spread', queryData.query.spid);
|
||||
this.globalData.spid = queryData.query.spid;
|
||||
this.globalData.pid = queryData.query.spid;
|
||||
silenceBindingSpread(this.globalData);
|
||||
}
|
||||
// #ifdef MP
|
||||
if (queryData.query.scene) {
|
||||
let param = this.$util.getUrlParams(decodeURIComponent(queryData.query.scene));
|
||||
if (param.spid) {
|
||||
this.$Cache.set('spread', param.spid);
|
||||
this.globalData.spid = param.spid;
|
||||
}
|
||||
/** 直播间分享**/
|
||||
// const sceneList = [1007, 1008, 1014, 1044, 1045, 1046, 1047, 1048, 1049, 1073, 1154, 1155];
|
||||
// if (sceneList.includes(queryData.query.scene)) {
|
||||
// livePlayer.getShareParams().then(res => {
|
||||
// //记录推广人uid
|
||||
// if(res.custom_params.pid){
|
||||
// this.$Cache.set('spread', res.custom_params.pid);
|
||||
// this.globalData.spid = res.custom_params.pid;
|
||||
// }
|
||||
// }).catch(err => {
|
||||
// })
|
||||
// }
|
||||
silenceBindingSpread(this.globalData);
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
onLaunch(option) {
|
||||
uni.hideTabBar()
|
||||
//#ifdef APP
|
||||
plus.screen.lockOrientation("portrait-primary");
|
||||
//#endif
|
||||
let that = this;
|
||||
//获取并存储配置
|
||||
this.setConfig();
|
||||
//主题换色
|
||||
this.setTheme();
|
||||
// 通过vuex获取并储存公共配置
|
||||
this.$store.dispatch("getBasicConfig");
|
||||
// 通过vuex获取并储存底部菜单的数据
|
||||
this.$store.dispatch("getPageFooter");
|
||||
// 通过vuex获取并储存活动弹窗的数据
|
||||
this.$store.dispatch("getActivityModal");
|
||||
// #ifdef H5
|
||||
if (!this.isLogin && option.query.hasOwnProperty('remote_token')) {
|
||||
this.remoteRegister(option.query.remote_token);
|
||||
}
|
||||
this.setScript();
|
||||
const queryData = uni.getEnterOptionsSync();
|
||||
uni.getSystemInfo({
|
||||
success(e) {
|
||||
/* 窗口宽度大于420px且不在PC页面且不在移动设备时跳转至 PC.html 页面 */
|
||||
if (e.windowWidth > 420 && !window.top.isPC && !/iOS|Android/i.test(e.system)) {
|
||||
window.location.pathname = '/static/html/pc.html';
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
// 小程序静默授权
|
||||
if (!this.$store.getters.isLogin) {
|
||||
Routine.getCode().then(code => {
|
||||
this.silenceAuth(code);
|
||||
}).catch(res => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
}else if(this.$store.getters.isLogin && this.getExpired()){
|
||||
//处理小程序有token情况下但是静默授权失败
|
||||
this.$Cache.clear(LOGIN_STATUS);
|
||||
Routine.getCode().then(code => {
|
||||
this.silenceAuth(code);
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
uni.setStorageSync('DEBUG_DIY_RENDER', true)
|
||||
},
|
||||
methods: {
|
||||
remoteRegister(remote_token) {
|
||||
remoteRegister({ remote_token }).then((res) => {
|
||||
let data = res.data;
|
||||
if (data.get_remote_login_url) {
|
||||
location.href = data.get_remote_login_url
|
||||
} else {
|
||||
this.$store.commit("LOGIN", {
|
||||
'token': data.token,
|
||||
'time': data.expires_time - this.$Cache.time()
|
||||
});
|
||||
getUserInfo().then(res => {
|
||||
this.$store.commit("SETUID", res.data.uid);
|
||||
this.$store.commit("UPDATE_USERINFO", res.data);
|
||||
location.reload()
|
||||
}).catch(error => {
|
||||
return this.$util.Tips({
|
||||
title:err
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
// 小程序静默授权
|
||||
silenceAuth(code) {
|
||||
let that = this;
|
||||
let spid = that.globalData.spid ? that.globalData.spid : '';
|
||||
silenceAuth({
|
||||
code: code,
|
||||
spread_spid: spid,
|
||||
spread_code: that.globalData.code
|
||||
})
|
||||
.then(res => {
|
||||
if (res.data.token !== undefined && res.data.token) {
|
||||
uni.hideLoading();
|
||||
let time = res.data.expires_time - this.$Cache.time();
|
||||
that.$store.commit('LOGIN', {
|
||||
token: res.data.token,
|
||||
time: time
|
||||
});
|
||||
that.$store.commit('SETUID', res.data.userInfo.uid);
|
||||
that.$store.commit('UPDATE_USERINFO', res.data.userInfo);
|
||||
}
|
||||
}).catch(err => {
|
||||
return that.$util.Tips({
|
||||
title:err
|
||||
})
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 检测当前的小程序
|
||||
* 是否是最新版本,是否需要下载、更新
|
||||
*/
|
||||
checkUpdateVersion() {
|
||||
//判断微信版本是否 兼容小程序更新机制API的使用
|
||||
if (wx.canIUse('getUpdateManager')) {
|
||||
const updateManager = wx.getUpdateManager();
|
||||
//检测版本更新
|
||||
updateManager.onCheckForUpdate(function(res) {
|
||||
if (res.hasUpdate) {
|
||||
updateManager.onUpdateReady(function() {
|
||||
wx.showModal({
|
||||
title: '温馨提示',
|
||||
content: '检测到新版本,是否重启小程序?',
|
||||
showCancel: false,
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
|
||||
updateManager.applyUpdate()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
updateManager.onUpdateFailed(function() {
|
||||
// 新版本下载失败
|
||||
wx.showModal({
|
||||
title: '已有新版本',
|
||||
content: '请您删除小程序,重新搜索进入',
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showModal({
|
||||
title: '溫馨提示',
|
||||
content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
|
||||
})
|
||||
}
|
||||
},
|
||||
async getExpired(){
|
||||
// 通过一个个人中心的接口判断token是否生效,catch捕获到就证明token过期,返回true
|
||||
try {
|
||||
await getUserInfo();
|
||||
return false; // token有效
|
||||
} catch (err) {
|
||||
return true; // token过期
|
||||
}
|
||||
},
|
||||
setConfig(){
|
||||
uni.removeStorageSync(SUBSCRIBE_MESSAGE);
|
||||
this.$Cache.clear('homeTop');
|
||||
let that = this;
|
||||
// #ifdef MP
|
||||
if (HTTP_REQUEST_URL == '') {
|
||||
uni.showToast({
|
||||
title: "请配置根目录下的config/app.js文件中的HTTP_REQUEST_URL",
|
||||
icon:'error'
|
||||
})
|
||||
}
|
||||
this.checkUpdateVersion();
|
||||
let menuButtonInfo = uni.getMenuButtonBoundingClientRect();
|
||||
that.globalData.navH = menuButtonInfo.top * 2 + menuButtonInfo.height / 2;
|
||||
const version = wx.getAppBaseInfo().SDKVersion;
|
||||
if (Routine.compareVersion(version, '2.21.2') >= 0) {
|
||||
that.$Cache.set('MP_VERSION_ISNEW', true)
|
||||
} else {
|
||||
that.$Cache.set('MP_VERSION_ISNEW', false)
|
||||
}
|
||||
// #endif
|
||||
getLogo().then(res => {
|
||||
uni.setStorageSync('BASIC_CONFIG', res.data)
|
||||
});
|
||||
},
|
||||
setTheme(){
|
||||
let green =
|
||||
'--view-theme: #42CA4D;--view-minorColorT:rgba(66, 202, 77, 0.1);--view-bntColor:#FE960F;--view-gradient:#4DEA4D'
|
||||
let red =
|
||||
'--view-theme: #e93323;--view-minorColorT:rgba(233, 51, 35, 0.1);--view-bntColor:#FE960F;--view-gradient:#FF7931'
|
||||
let blue =
|
||||
'--view-theme: #1DB0FC;--view-minorColorT:rgba(9, 139, 243, 0.1);--view-bntColor:#22CAFD;--view-gradient:#5ACBFF'
|
||||
let pink =
|
||||
'--view-theme: #FF448F;--view-minorColorT:rgba(255, 68, 143, 0.1);--view-bntColor:#282828;--view-gradient:#FF67AD'
|
||||
let orange =
|
||||
'--view-theme: #FE5C2D;--view-minorColorT:rgba(254, 92, 45, 0.1);--view-bntColor:#FDB000;--view-gradient:#FF9451'
|
||||
let gold =
|
||||
'--view-theme: #E0A558;--view-minorColorT:rgba(224, 165, 88, 0.1);--view-bntColor:#1A1A1A;--view-gradient:#FFCD8C'
|
||||
colorChange('color_change').then(res => {
|
||||
let statusColor = res.data.status; //判断显示啥颜色
|
||||
let themeType = res.data.color_data?.theme_type || 1;
|
||||
if(themeType == 1){
|
||||
uni.setStorageSync('statusColor', statusColor);
|
||||
uni.$emit('colorOk', statusColor);
|
||||
let themeList = {
|
||||
1:blue, 2:green, 3:red, 4:pink, 5:orange, 6:gold
|
||||
};
|
||||
Object.entries(themeList).forEach(([key, themeSelect]) => {
|
||||
if (key == statusColor) {
|
||||
uni.setStorageSync('viewColor', themeSelect);
|
||||
uni.$emit('ok', themeSelect);
|
||||
}
|
||||
});
|
||||
}else{
|
||||
let colorData = res.data.color_data;
|
||||
let themeStr = `--view-theme: ${colorData.theme_color};--view-minorColorT:${colorData.light_color};--view-bntColor:${colorData.aux_color};--view-gradient:${colorData.gradient_color}`;
|
||||
uni.setStorageSync('viewColor', themeStr);
|
||||
uni.$emit('ok', themeStr);
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
setScript(){
|
||||
// 添加crmeb chat 统计
|
||||
var __s = document.createElement('script');
|
||||
__s.src = `${HTTP_REQUEST_URL}/api/get_script`;
|
||||
document.head.appendChild(__s);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* #ifndef APP-PLUS-NVUE || APP-NVUE */
|
||||
@import url('@/plugin/emoji-awesome/css/tuoluojiang.css');
|
||||
@import 'static/css/base.css';
|
||||
@import 'static/iconfont/iconfont.css';
|
||||
@import 'static/css/unocss.css';
|
||||
@import 'static/fonts/font.scss';
|
||||
|
||||
view {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
page {
|
||||
font-family: PingFang SC;
|
||||
}
|
||||
|
||||
.placeholder{
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.mer-bg{
|
||||
background-color: $primary-merchant;
|
||||
}
|
||||
|
||||
.uni-scroll-view::-webkit-scrollbar {
|
||||
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.uni-system-open-location .map-content.fix-position {
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
<script>
|
||||
import {checkLogin} from './libs/login';
|
||||
import {HTTP_REQUEST_URL} from './config/app';
|
||||
import { LOGIN_STATUS, SUBSCRIBE_MESSAGE } from "./config/cache.js"
|
||||
import { getShopConfig, silenceAuth, getLogo, remoteRegister } from '@/api/public';
|
||||
import { silenceBindingSpread } from '@/utils';
|
||||
import Auth from '@/libs/wechat.js';
|
||||
import Routine from './libs/routine.js';
|
||||
import { colorChange, copyRight } from '@/api/api.js';
|
||||
import { getUserInfo } from "@/api/user.js"
|
||||
import { mapGetters } from "vuex"
|
||||
// #ifdef MP
|
||||
// let livePlayer = requirePlugin('live-player-plugin')
|
||||
// #endif
|
||||
export default {
|
||||
globalData: {
|
||||
spid: 0,
|
||||
code: 0,
|
||||
isLogin: false,
|
||||
userInfo: {},
|
||||
globalData: false,
|
||||
windowHeight: uni.getWindowInfo().windowHeight + 'px',
|
||||
sysHeight:uni.getWindowInfo().statusBarHeight,
|
||||
},
|
||||
computed: mapGetters(['isLogin', 'cartNum']),
|
||||
watch: {
|
||||
cartNum(newCart, b) {
|
||||
this.$store.commit('indexData/setCartNum', newCart + '')
|
||||
if (newCart > 0) {
|
||||
uni.setTabBarBadge({
|
||||
index: 3,
|
||||
text: newCart>99?'99+':newCart+''
|
||||
})
|
||||
} else {
|
||||
uni.hideTabBarRedDot({
|
||||
index: 3
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
onShow(options) {
|
||||
const queryData = uni.getEnterOptionsSync(); // uni-app版本 3.5.1+ 支持
|
||||
if (queryData.query.spid) {
|
||||
this.$Cache.set('spread', queryData.query.spid);
|
||||
this.globalData.spid = queryData.query.spid;
|
||||
this.globalData.pid = queryData.query.spid;
|
||||
silenceBindingSpread(this.globalData);
|
||||
}
|
||||
// #ifdef MP
|
||||
if (queryData.query.scene) {
|
||||
let param = this.$util.getUrlParams(decodeURIComponent(queryData.query.scene));
|
||||
if (param.spid) {
|
||||
this.$Cache.set('spread', param.spid);
|
||||
this.globalData.spid = param.spid;
|
||||
}
|
||||
/** 直播间分享**/
|
||||
// const sceneList = [1007, 1008, 1014, 1044, 1045, 1046, 1047, 1048, 1049, 1073, 1154, 1155];
|
||||
// if (sceneList.includes(queryData.query.scene)) {
|
||||
// livePlayer.getShareParams().then(res => {
|
||||
// //记录推广人uid
|
||||
// if(res.custom_params.pid){
|
||||
// this.$Cache.set('spread', res.custom_params.pid);
|
||||
// this.globalData.spid = res.custom_params.pid;
|
||||
// }
|
||||
// }).catch(err => {
|
||||
// })
|
||||
// }
|
||||
silenceBindingSpread(this.globalData);
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
onLaunch(option) {
|
||||
uni.hideTabBar()
|
||||
//#ifdef APP
|
||||
plus.screen.lockOrientation("portrait-primary");
|
||||
//#endif
|
||||
let that = this;
|
||||
//获取并存储配置
|
||||
this.setConfig();
|
||||
//主题换色
|
||||
this.setTheme();
|
||||
// 通过vuex获取并储存公共配置
|
||||
this.$store.dispatch("getBasicConfig");
|
||||
// 通过vuex获取并储存底部菜单的数据
|
||||
this.$store.dispatch("getPageFooter");
|
||||
// 通过vuex获取并储存活动弹窗的数据
|
||||
this.$store.dispatch("getActivityModal");
|
||||
// #ifdef H5
|
||||
if (!this.isLogin) {
|
||||
if (option.query && option.query.hasOwnProperty('remote_token')) {
|
||||
this.remoteRegister(option.query.remote_token);
|
||||
} else {
|
||||
let token = option.query && option.query.token ? option.query.token : '';
|
||||
if (!token) token = this.getTokenFromHash();
|
||||
if (token) this.loginByToken(token);
|
||||
}
|
||||
}
|
||||
this.setScript();
|
||||
const queryData = uni.getEnterOptionsSync();
|
||||
uni.getSystemInfo({
|
||||
success(e) {
|
||||
/* 窗口宽度大于420px且不在PC页面且不在移动设备时跳转至 PC.html 页面 */
|
||||
if (e.windowWidth > 420 && !window.top.isPC && !/iOS|Android/i.test(e.system)) {
|
||||
window.location.pathname = '/static/html/pc.html';
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
// 小程序静默授权
|
||||
if (!this.$store.getters.isLogin) {
|
||||
Routine.getCode().then(code => {
|
||||
this.silenceAuth(code);
|
||||
}).catch(res => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
}else if(this.$store.getters.isLogin && this.getExpired()){
|
||||
//处理小程序有token情况下但是静默授权失败
|
||||
this.$Cache.clear(LOGIN_STATUS);
|
||||
Routine.getCode().then(code => {
|
||||
this.silenceAuth(code);
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
uni.setStorageSync('DEBUG_DIY_RENDER', true)
|
||||
},
|
||||
methods: {
|
||||
getTokenFromHash() {
|
||||
try {
|
||||
let hash = window.location.hash || '';
|
||||
let qIndex = hash.indexOf('?');
|
||||
if (qIndex === -1) return '';
|
||||
let queryStr = hash.slice(qIndex + 1);
|
||||
let parts = queryStr.split('&');
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (!parts[i]) continue;
|
||||
let eqIndex = parts[i].indexOf('=');
|
||||
let k = eqIndex === -1 ? parts[i] : parts[i].slice(0, eqIndex);
|
||||
if (k === 'token') {
|
||||
let v = eqIndex === -1 ? '' : parts[i].slice(eqIndex + 1);
|
||||
return decodeURIComponent(v);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
cleanTokenFromUrl() {
|
||||
try {
|
||||
let href = window.location.href || '';
|
||||
if (href.indexOf('token=') === -1) return;
|
||||
let urlObj = new URL(href);
|
||||
if (urlObj.searchParams && urlObj.searchParams.has('token')) {
|
||||
urlObj.searchParams.delete('token');
|
||||
let search = urlObj.searchParams.toString();
|
||||
history.replaceState(null, '', urlObj.pathname + (search ? '?' + search : '') + urlObj.hash);
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
let hash = window.location.hash || '';
|
||||
let qIndex = hash.indexOf('?');
|
||||
if (qIndex === -1) return;
|
||||
let hashPath = hash.slice(1, qIndex);
|
||||
let queryStr = hash.slice(qIndex + 1);
|
||||
let parts = queryStr.split('&').filter(Boolean);
|
||||
let newParts = [];
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
let eqIndex = parts[i].indexOf('=');
|
||||
let k = eqIndex === -1 ? parts[i] : parts[i].slice(0, eqIndex);
|
||||
if (k !== 'token') newParts.push(parts[i]);
|
||||
}
|
||||
let newHash = '#' + hashPath + (newParts.length ? '?' + newParts.join('&') : '');
|
||||
history.replaceState(null, '', window.location.pathname + window.location.search + newHash);
|
||||
} catch (e) {}
|
||||
},
|
||||
loginByToken(token) {
|
||||
this.$store.commit("LOGIN", {
|
||||
'token': token,
|
||||
'time': 0
|
||||
});
|
||||
// 从 JWT payload 中立即提取 uid,无需等 getUserInfo
|
||||
const payload = this.$util.parseJwt(token);
|
||||
if (payload) {
|
||||
if (payload.uid) this.$store.commit("SETUID", payload.uid);
|
||||
// phone 和 openid 也可从 JWT 读取
|
||||
// 后端需在签发 JWT 时加入 uid/phone/openid claims
|
||||
}
|
||||
getUserInfo().then(res => {
|
||||
this.$store.commit("SETUID", res.data.uid);
|
||||
this.$store.commit("UPDATE_USERINFO", res.data);
|
||||
this.cleanTokenFromUrl();
|
||||
}).catch(() => {
|
||||
this.cleanTokenFromUrl();
|
||||
})
|
||||
},
|
||||
remoteRegister(remote_token) {
|
||||
remoteRegister({ remote_token }).then((res) => {
|
||||
let data = res.data;
|
||||
if (data.get_remote_login_url) {
|
||||
location.href = data.get_remote_login_url
|
||||
} else {
|
||||
this.$store.commit("LOGIN", {
|
||||
'token': data.token,
|
||||
'time': data.expires_time - this.$Cache.time()
|
||||
});
|
||||
getUserInfo().then(res => {
|
||||
this.$store.commit("SETUID", res.data.uid);
|
||||
this.$store.commit("UPDATE_USERINFO", res.data);
|
||||
location.reload()
|
||||
}).catch(error => {
|
||||
return this.$util.Tips({
|
||||
title:err
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
// 小程序静默授权
|
||||
silenceAuth(code) {
|
||||
let that = this;
|
||||
let spid = that.globalData.spid ? that.globalData.spid : '';
|
||||
silenceAuth({
|
||||
code: code,
|
||||
spread_spid: spid,
|
||||
spread_code: that.globalData.code
|
||||
})
|
||||
.then(res => {
|
||||
if (res.data.token !== undefined && res.data.token) {
|
||||
uni.hideLoading();
|
||||
let time = res.data.expires_time - this.$Cache.time();
|
||||
that.$store.commit('LOGIN', {
|
||||
token: res.data.token,
|
||||
time: time
|
||||
});
|
||||
that.$store.commit('SETUID', res.data.userInfo.uid);
|
||||
that.$store.commit('UPDATE_USERINFO', res.data.userInfo);
|
||||
}
|
||||
}).catch(err => {
|
||||
return that.$util.Tips({
|
||||
title:err
|
||||
})
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 检测当前的小程序
|
||||
* 是否是最新版本,是否需要下载、更新
|
||||
*/
|
||||
checkUpdateVersion() {
|
||||
//判断微信版本是否 兼容小程序更新机制API的使用
|
||||
if (wx.canIUse('getUpdateManager')) {
|
||||
const updateManager = wx.getUpdateManager();
|
||||
//检测版本更新
|
||||
updateManager.onCheckForUpdate(function(res) {
|
||||
if (res.hasUpdate) {
|
||||
updateManager.onUpdateReady(function() {
|
||||
wx.showModal({
|
||||
title: '温馨提示',
|
||||
content: '检测到新版本,是否重启小程序?',
|
||||
showCancel: false,
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
|
||||
updateManager.applyUpdate()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
updateManager.onUpdateFailed(function() {
|
||||
// 新版本下载失败
|
||||
wx.showModal({
|
||||
title: '已有新版本',
|
||||
content: '请您删除小程序,重新搜索进入',
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showModal({
|
||||
title: '溫馨提示',
|
||||
content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
|
||||
})
|
||||
}
|
||||
},
|
||||
async getExpired(){
|
||||
// 通过一个个人中心的接口判断token是否生效,catch捕获到就证明token过期,返回true
|
||||
try {
|
||||
await getUserInfo();
|
||||
return false; // token有效
|
||||
} catch (err) {
|
||||
return true; // token过期
|
||||
}
|
||||
},
|
||||
setConfig(){
|
||||
uni.removeStorageSync(SUBSCRIBE_MESSAGE);
|
||||
this.$Cache.clear('homeTop');
|
||||
let that = this;
|
||||
// #ifdef MP
|
||||
if (HTTP_REQUEST_URL == '') {
|
||||
uni.showToast({
|
||||
title: "请配置根目录下的config/app.js文件中的HTTP_REQUEST_URL",
|
||||
icon:'error'
|
||||
})
|
||||
}
|
||||
this.checkUpdateVersion();
|
||||
let menuButtonInfo = uni.getMenuButtonBoundingClientRect();
|
||||
that.globalData.navH = menuButtonInfo.top * 2 + menuButtonInfo.height / 2;
|
||||
const version = wx.getAppBaseInfo().SDKVersion;
|
||||
if (Routine.compareVersion(version, '2.21.2') >= 0) {
|
||||
that.$Cache.set('MP_VERSION_ISNEW', true)
|
||||
} else {
|
||||
that.$Cache.set('MP_VERSION_ISNEW', false)
|
||||
}
|
||||
// #endif
|
||||
getLogo().then(res => {
|
||||
uni.setStorageSync('BASIC_CONFIG', res.data)
|
||||
});
|
||||
},
|
||||
setTheme(){
|
||||
let green =
|
||||
'--view-theme: #42CA4D;--view-minorColorT:rgba(66, 202, 77, 0.1);--view-bntColor:#FE960F;--view-gradient:#4DEA4D'
|
||||
let red =
|
||||
'--view-theme: #e93323;--view-minorColorT:rgba(233, 51, 35, 0.1);--view-bntColor:#FE960F;--view-gradient:#FF7931'
|
||||
let blue =
|
||||
'--view-theme: #1DB0FC;--view-minorColorT:rgba(9, 139, 243, 0.1);--view-bntColor:#22CAFD;--view-gradient:#5ACBFF'
|
||||
let pink =
|
||||
'--view-theme: #FF448F;--view-minorColorT:rgba(255, 68, 143, 0.1);--view-bntColor:#282828;--view-gradient:#FF67AD'
|
||||
let orange =
|
||||
'--view-theme: #FE5C2D;--view-minorColorT:rgba(254, 92, 45, 0.1);--view-bntColor:#FDB000;--view-gradient:#FF9451'
|
||||
let gold =
|
||||
'--view-theme: #E0A558;--view-minorColorT:rgba(224, 165, 88, 0.1);--view-bntColor:#1A1A1A;--view-gradient:#FFCD8C'
|
||||
colorChange('color_change').then(res => {
|
||||
let statusColor = res.data.status; //判断显示啥颜色
|
||||
let themeType = res.data.color_data?.theme_type || 1;
|
||||
if(themeType == 1){
|
||||
uni.setStorageSync('statusColor', statusColor);
|
||||
uni.$emit('colorOk', statusColor);
|
||||
let themeList = {
|
||||
1:blue, 2:green, 3:red, 4:pink, 5:orange, 6:gold
|
||||
};
|
||||
Object.entries(themeList).forEach(([key, themeSelect]) => {
|
||||
if (key == statusColor) {
|
||||
uni.setStorageSync('viewColor', themeSelect);
|
||||
uni.$emit('ok', themeSelect);
|
||||
}
|
||||
});
|
||||
}else{
|
||||
let colorData = res.data.color_data;
|
||||
let themeStr = `--view-theme: ${colorData.theme_color};--view-minorColorT:${colorData.light_color};--view-bntColor:${colorData.aux_color};--view-gradient:${colorData.gradient_color}`;
|
||||
uni.setStorageSync('viewColor', themeStr);
|
||||
uni.$emit('ok', themeStr);
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
setScript(){
|
||||
// 添加crmeb chat 统计
|
||||
var __s = document.createElement('script');
|
||||
__s.src = `${HTTP_REQUEST_URL}/api/get_script`;
|
||||
document.head.appendChild(__s);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* #ifndef APP-PLUS-NVUE || APP-NVUE */
|
||||
@import url('@/plugin/emoji-awesome/css/tuoluojiang.css');
|
||||
@import 'static/css/base.css';
|
||||
@import 'static/iconfont/iconfont.css';
|
||||
@import 'static/css/unocss.css';
|
||||
@import 'static/fonts/font.scss';
|
||||
|
||||
view {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
page {
|
||||
font-family: PingFang SC;
|
||||
}
|
||||
|
||||
.placeholder{
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.mer-bg{
|
||||
background-color: $primary-merchant;
|
||||
}
|
||||
|
||||
.uni-scroll-view::-webkit-scrollbar {
|
||||
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.uni-system-open-location .map-content.fix-position {
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
|
||||
@@ -417,5 +417,5 @@ export function getActivityModalListApi() {
|
||||
* 活动弹窗观看上报
|
||||
*/
|
||||
export function getActivityModalRecordApi(data) {
|
||||
return request.post("marketing/holiday_gift/record",data);
|
||||
return request.post("marketing/holiday_gift/record",data, {noAuth: true});
|
||||
}
|
||||
358
api/public.js
358
api/public.js
@@ -1,28 +1,28 @@
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
import request from "@/utils/request.js";
|
||||
import wechat from "@/libs/wechat.js";
|
||||
|
||||
/**
|
||||
* 获取微信sdk配置
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getWechatConfig() {
|
||||
return request.get(
|
||||
"wechat/config", {
|
||||
url: wechat.signLink()
|
||||
}, {
|
||||
noAuth: true
|
||||
}
|
||||
);
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
import request from "@/utils/request.js";
|
||||
import wechat from "@/libs/wechat.js";
|
||||
|
||||
/**
|
||||
* 获取微信sdk配置
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getWechatConfig() {
|
||||
return request.get(
|
||||
"wechat/config", {
|
||||
url: wechat.signLink()
|
||||
}, {
|
||||
noAuth: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,16 +56,16 @@ export function wechatBindingPhone(data) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* code生成用户
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export function authType(data) {
|
||||
return request.get("v2/routine/auth_type", data, {
|
||||
noAuth: true
|
||||
});
|
||||
|
||||
/**
|
||||
* code生成用户
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export function authType(data) {
|
||||
return request.get("v2/routine/auth_type", data, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,63 +77,63 @@ export function wechatAuthLogin(data) {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录授权login
|
||||
*
|
||||
*/
|
||||
export function getLogo() {
|
||||
return request.get('wechat/get_logo', {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序用户登录
|
||||
* @param data object 小程序用户登陆信息
|
||||
*/
|
||||
export function login(data) {
|
||||
return request.post("wechat/mp_auth", data, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 静默授权
|
||||
* @param {Object} data
|
||||
*/
|
||||
export function silenceAuth(data) {
|
||||
//#ifdef MP
|
||||
return request.get("v2/wechat/silence_auth", data, {
|
||||
noAuth: true
|
||||
});
|
||||
//#endif
|
||||
//#ifdef H5
|
||||
return request.get("v2/wechat/wx_silence_auth", data, {
|
||||
noAuth: true
|
||||
});
|
||||
//#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 分享
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getShare() {
|
||||
return request.get("share", {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关注海报
|
||||
* @returns {*}
|
||||
*/
|
||||
export function follow() {
|
||||
return request.get("wechat/follow", {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录授权login
|
||||
*
|
||||
*/
|
||||
export function getLogo() {
|
||||
return request.get('wechat/get_logo', {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序用户登录
|
||||
* @param data object 小程序用户登陆信息
|
||||
*/
|
||||
export function login(data) {
|
||||
return request.post("wechat/mp_auth", data, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 静默授权
|
||||
* @param {Object} data
|
||||
*/
|
||||
export function silenceAuth(data) {
|
||||
//#ifdef MP
|
||||
return request.get("v2/wechat/silence_auth", data, {
|
||||
noAuth: true
|
||||
});
|
||||
//#endif
|
||||
//#ifdef H5
|
||||
return request.get("v2/wechat/wx_silence_auth", data, {
|
||||
noAuth: true
|
||||
});
|
||||
//#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 分享
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getShare() {
|
||||
return request.get("share", {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关注海报
|
||||
* @returns {*}
|
||||
*/
|
||||
export function follow() {
|
||||
return request.get("wechat/follow", {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 授权登录
|
||||
* @returns {*}
|
||||
@@ -143,89 +143,89 @@ export function authLogin(data) {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片base64
|
||||
* @retins {*}
|
||||
* */
|
||||
export function imageBase64(image, code) {
|
||||
return request.post(
|
||||
"image_base64", {
|
||||
image: image,
|
||||
code: code
|
||||
}, {
|
||||
noAuth: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动复制口令功能
|
||||
* @returns {*}
|
||||
*/
|
||||
export function copyWords() {
|
||||
return request.get("copy_words", {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商城是否强制绑定手机号
|
||||
*/
|
||||
export function getShopConfig() {
|
||||
return request.get('v2/bind_status', {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序绑定手机号
|
||||
* @param {Object} data
|
||||
*/
|
||||
export function getUserPhone(data) {
|
||||
return request.post('v2/auth_bindind_phone', data, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序用户登录
|
||||
* @param data object 小程序用户登陆信息
|
||||
*/
|
||||
export function routineLogin(data) {
|
||||
return request.get("v2/wechat/routine_auth", data, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信sdk配置
|
||||
* @returns {*}
|
||||
*/
|
||||
export function wechatAuthV2(code, spid) {
|
||||
return request.get(
|
||||
"v2/wechat/auth", {
|
||||
code: code,
|
||||
spread_spid: spid
|
||||
}, {
|
||||
noAuth: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件底部菜单
|
||||
* @param data object 获取组件底部菜单
|
||||
*/
|
||||
export function getNavigation(data) {
|
||||
return request.get("navigation", data, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
export function getSubscribe() {
|
||||
return request.get("subscribe", {}, {
|
||||
noAuth: true
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取图片base64
|
||||
* @retins {*}
|
||||
* */
|
||||
export function imageBase64(image, code) {
|
||||
return request.post(
|
||||
"image_base64", {
|
||||
image: image,
|
||||
code: code
|
||||
}, {
|
||||
noAuth: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动复制口令功能
|
||||
* @returns {*}
|
||||
*/
|
||||
export function copyWords() {
|
||||
return request.get("copy_words", {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商城是否强制绑定手机号
|
||||
*/
|
||||
export function getShopConfig() {
|
||||
return request.get('v2/bind_status', {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序绑定手机号
|
||||
* @param {Object} data
|
||||
*/
|
||||
export function getUserPhone(data) {
|
||||
return request.post('v2/auth_bindind_phone', data, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序用户登录
|
||||
* @param data object 小程序用户登陆信息
|
||||
*/
|
||||
export function routineLogin(data) {
|
||||
return request.get("v2/wechat/routine_auth", data, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信sdk配置
|
||||
* @returns {*}
|
||||
*/
|
||||
export function wechatAuthV2(code, spid) {
|
||||
return request.get(
|
||||
"v2/wechat/auth", {
|
||||
code: code,
|
||||
spread_spid: spid
|
||||
}, {
|
||||
noAuth: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件底部菜单
|
||||
* @param data object 获取组件底部菜单
|
||||
*/
|
||||
export function getNavigation(data) {
|
||||
return request.get("navigation", data, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
export function getSubscribe() {
|
||||
return request.get("subscribe", {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,10 +242,10 @@ export function remoteRegister(data) {
|
||||
* 公共配置接口
|
||||
*
|
||||
*/
|
||||
export function systemConfigApi() {
|
||||
return request.get("system/config", {}, {
|
||||
noAuth: true
|
||||
});
|
||||
export function systemConfigApi() {
|
||||
return request.get("system/config", {}, {
|
||||
noAuth: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,414 +1,414 @@
|
||||
<template>
|
||||
<view class="t-wrap">
|
||||
<!-- 虚拟view用于计算,计算完成则消失 -->
|
||||
<view class="t-txt-hide" :id="hid" v-if="!isCompute" :style="[computeStyle(0),{'text-align':oneRowTextAlign}]">
|
||||
<text space="nbsp">{{testContent?testContent:content}}{{showSymbol?'...':''}}</text><text
|
||||
v-if="expandText && collapseText && showSymbol" class="t-button">
|
||||
{{expandText}}
|
||||
</text><text></text>
|
||||
</view>
|
||||
<!-- 真实显示的内容 -->
|
||||
<view class="t-ellipsis" :id="id"
|
||||
:style="[!isCompute?computeStyle(1):computeStyle(2),{'text-align':oneRowTextAlign}]" @click="contentClick">
|
||||
<text space="nbsp">{{(!isCompute || expand)?content:(actualContent+(showSymbol?'...':''))}}</text><text
|
||||
v-if="expandText && collapseText && showSymbol" class="t-button" @click.stop="changeCollapse"
|
||||
:style="{'color':actionFontColor}">{{!expand?expandText:collapseText}}</text><text></text>
|
||||
</view>
|
||||
<!-- 这里加入了自定义的动态骨架屏友好反馈 -->
|
||||
<view v-if="!isCompute && rows>0" class="t-skeleton">
|
||||
<view class="skeletons" v-for="(item,index) in rows" :key="index">
|
||||
<view class="empty"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//为了兼容部分老机型,增加扰动因子参数
|
||||
const factor = 5;
|
||||
export default {
|
||||
name: "KevyEllipsis",
|
||||
props: {
|
||||
/**
|
||||
* 文本唯一标识符,非必填
|
||||
*/
|
||||
textId: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 文本内容,默认''
|
||||
*/
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 字体大小,单位rpx,默认28
|
||||
*/
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: 28
|
||||
},
|
||||
/**
|
||||
* 字体颜色,默认#666666
|
||||
*/
|
||||
fontColor: {
|
||||
color: String,
|
||||
default: '#666666'
|
||||
},
|
||||
/**
|
||||
* 收起操作的文案,默认''
|
||||
*/
|
||||
collapseText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 展开操作的文案,默认''
|
||||
*/
|
||||
expandText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 收起、展开操作文字颜色,默认'#007aff'
|
||||
*/
|
||||
actionFontColor: {
|
||||
color: String,
|
||||
default: '#007aff'
|
||||
},
|
||||
/**
|
||||
* 展示行数,默认1
|
||||
*/
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
/**
|
||||
* 只有一行时文本对齐方式,支持left、right、justify
|
||||
*/
|
||||
oneRowTextAlign: {
|
||||
type: String,
|
||||
default: "justify"
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
//是否展开
|
||||
expand: false,
|
||||
//是否已计算
|
||||
isCompute: false,
|
||||
//内容高度
|
||||
h: undefined,
|
||||
//内容宽度
|
||||
w: undefined,
|
||||
//实际显示内容
|
||||
actualContent: '',
|
||||
//高度探测内容
|
||||
testContent: undefined,
|
||||
//是否显示省略号
|
||||
showSymbol: false,
|
||||
//hid和id,唯一标识符
|
||||
hid: 'hid' + Math.random().toString(36).substr(2),
|
||||
id: 'id' + Math.random().toString(36).substr(2),
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initEllipsis();
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
//动态计算组件样式
|
||||
computeStyle() {
|
||||
return b => {
|
||||
let lines = this.rows > 0 ? this.rows : 1;
|
||||
let obj = {};
|
||||
if (b == 1) {
|
||||
obj = {
|
||||
'-webkit-line-clamp': lines,
|
||||
'display': '-webkit-box',
|
||||
'text-overflow': 'ellipsis',
|
||||
'overflow': 'hidden',
|
||||
'-webkit-box-orient': 'vertical'
|
||||
};
|
||||
} else if (b == 2) {
|
||||
obj = {
|
||||
'position': 'relative',
|
||||
'left': '0rpx',
|
||||
...obj
|
||||
};
|
||||
}
|
||||
return {
|
||||
'font-size': this.fontSize + 'rpx',
|
||||
'color': this.fontColor,
|
||||
...obj
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
content(newVal, oldVal) {
|
||||
this.expand=false;
|
||||
this.isCompute=false;
|
||||
this.h=undefined;
|
||||
this.w=undefined;
|
||||
this.actualContent='';
|
||||
this.showSymbol=false;
|
||||
this.initEllipsis();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//初始化
|
||||
initEllipsis() {
|
||||
if (this.content?.length > 0) {
|
||||
this.$nextTick(() => {
|
||||
this.init(this, () => {
|
||||
this.compute(this);
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
//收起展开状态切换
|
||||
changeCollapse() {
|
||||
this.expand = !this.expand;
|
||||
},
|
||||
//文本点击事件
|
||||
contentClick() {
|
||||
this.$emit('contentClick', this.textId);
|
||||
},
|
||||
//组件参数初始化
|
||||
init($this, callback, isali) {
|
||||
if (isali) {
|
||||
uni.createSelectorQuery().in().select('#' + $this.id).boundingClientRect(d => {
|
||||
$this.h = Number(d.height.toFixed(1));
|
||||
$this.w = Number(d.width.toFixed(1));
|
||||
if (callback) {
|
||||
callback()
|
||||
}
|
||||
}).exec();
|
||||
} else {
|
||||
uni.createSelectorQuery().in($this).select('#' + $this.id).boundingClientRect(d => {
|
||||
$this.h = Number(d.height.toFixed(1));
|
||||
$this.w = Number(d.width.toFixed(1));
|
||||
if (callback) {
|
||||
callback()
|
||||
}
|
||||
}).exec();
|
||||
}
|
||||
},
|
||||
//动态计算组件内容
|
||||
computeContent($this, isali, dr) {
|
||||
$this.$nextTick(() => {
|
||||
$this.getH($this, isali, (ch) => {
|
||||
if (ch - factor > $this.h) {
|
||||
if (dr === -1) {
|
||||
$this.testContent = $this.content.substring(0, $this.testContent.length -
|
||||
1);
|
||||
$this.computeContent($this, isali, dr);
|
||||
} else {
|
||||
$this.actualContent = $this.content.substring(0, $this.testContent.length -
|
||||
1);
|
||||
$this.isCompute = true;
|
||||
}
|
||||
} else {
|
||||
if (dr === -1) {
|
||||
$this.actualContent = $this.testContent;
|
||||
$this.isCompute = true;
|
||||
} else {
|
||||
$this.testContent = $this.content.substring(0, $this.testContent.length +
|
||||
1);
|
||||
$this.computeContent($this, isali, dr);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
//计算工具方法
|
||||
compute($this, isali) {
|
||||
let {
|
||||
rows,
|
||||
fontSize,
|
||||
content,
|
||||
h,
|
||||
w
|
||||
} = $this;
|
||||
$this.testContent = content;
|
||||
$this.$nextTick(() => {
|
||||
$this.getH($this, isali, (ch) => {
|
||||
if (ch - factor > h) {
|
||||
let lh = h / rows;
|
||||
let fn = Math.floor(w / $this.rpx2px(fontSize));
|
||||
let sfn = fn * rows;
|
||||
let i = $this.fontNum(content, sfn * 2 - ($this.expandText ? $this.fontNum(
|
||||
$this.expandText) :
|
||||
0) - 3);
|
||||
$this.showSymbol = true;
|
||||
$this.testContent = content.substring(0, i);
|
||||
$this.$nextTick(() => {
|
||||
$this.getH($this, isali, (ch1) => {
|
||||
if (ch1 - factor > h) {
|
||||
$this.testContent = content.substring(0, $this
|
||||
.testContent.length - 1);
|
||||
$this.computeContent($this, isali, -1);
|
||||
} else {
|
||||
$this.testContent = content.substring(0, $this
|
||||
.testContent.length + 1);
|
||||
$this.computeContent($this, isali, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$this.isCompute = true;
|
||||
$this.actualContent = content;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
//动态计算字符数
|
||||
fontNum(val, limit) {
|
||||
let c = 0;
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
let a = val.charAt(i);
|
||||
if (a.match(/[^\x00-\xff]/ig) != null) {
|
||||
if (limit) {
|
||||
if (c + 2 > limit) {
|
||||
return i;
|
||||
} else {
|
||||
c += 2;
|
||||
}
|
||||
} else {
|
||||
c += 2;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (limit) {
|
||||
if (c + 1 > limit) {
|
||||
return i;
|
||||
} else {
|
||||
c += 1;
|
||||
}
|
||||
} else {
|
||||
c += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!limit) {
|
||||
return c;
|
||||
}
|
||||
},
|
||||
rpx2px(rpx) {
|
||||
return uni.getWindowInfo().windowWidth * Number(rpx) / 750;
|
||||
},
|
||||
getH($, isali, callback) {
|
||||
if (isali) {
|
||||
uni.createSelectorQuery().in().select('#' + $.hid).fields({
|
||||
size: true
|
||||
}, d => {
|
||||
if(d && d.height){
|
||||
callback(Number(d.height.toFixed(1)));
|
||||
}
|
||||
}).exec();
|
||||
} else {
|
||||
uni.createSelectorQuery().in($).select('#' + $.hid).fields({
|
||||
size: true
|
||||
}, d => {
|
||||
if(d && d.height){
|
||||
callback(Number(d.height.toFixed(1)));
|
||||
}
|
||||
}).exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.t-wrap {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.t-txt-hide {
|
||||
box-sizing: border-box;
|
||||
word-break: break-all;
|
||||
position: absolute;
|
||||
top: 999999px;
|
||||
left: 999999px;
|
||||
z-index: -1000;
|
||||
top: 0rpx;
|
||||
width: 100%;
|
||||
margin: 0rpx;
|
||||
text-align: justify;
|
||||
white-space: pre-line;
|
||||
line-height: 1.5 !important;
|
||||
.t-button {
|
||||
float: right;
|
||||
<template>
|
||||
<view class="t-wrap">
|
||||
<!-- 虚拟view用于计算,计算完成则消失 -->
|
||||
<view class="t-txt-hide" :id="hid" v-if="!isCompute" :style="[computeStyle(0),{'text-align':oneRowTextAlign}]">
|
||||
<text space="nbsp">{{testContent?testContent:content}}{{showSymbol?'...':''}}</text><text
|
||||
v-if="expandText && collapseText && showSymbol" class="t-button">
|
||||
{{expandText}}
|
||||
</text><text></text>
|
||||
</view>
|
||||
<!-- 真实显示的内容 -->
|
||||
<view class="t-ellipsis" :id="id"
|
||||
:style="[!isCompute?computeStyle(1):computeStyle(2),{'text-align':oneRowTextAlign}]" @click="contentClick">
|
||||
<text space="nbsp">{{(!isCompute || expand)?content:(actualContent+(showSymbol?'...':''))}}</text><text
|
||||
v-if="expandText && collapseText && showSymbol" class="t-button" @click.stop="changeCollapse"
|
||||
:style="{'color':actionFontColor}">{{!expand?expandText:collapseText}}</text><text></text>
|
||||
</view>
|
||||
<!-- 这里加入了自定义的动态骨架屏友好反馈 -->
|
||||
<view v-if="!isCompute && rows>0" class="t-skeleton">
|
||||
<view class="skeletons" v-for="(item,index) in rows" :key="index">
|
||||
<view class="empty"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//为了兼容部分老机型,增加扰动因子参数
|
||||
const factor = 5;
|
||||
export default {
|
||||
name: "KevyEllipsis",
|
||||
props: {
|
||||
/**
|
||||
* 文本唯一标识符,非必填
|
||||
*/
|
||||
textId: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 文本内容,默认''
|
||||
*/
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 字体大小,单位rpx,默认28
|
||||
*/
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: 28
|
||||
},
|
||||
/**
|
||||
* 字体颜色,默认#666666
|
||||
*/
|
||||
fontColor: {
|
||||
color: String,
|
||||
default: '#666666'
|
||||
},
|
||||
/**
|
||||
* 收起操作的文案,默认''
|
||||
*/
|
||||
collapseText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 展开操作的文案,默认''
|
||||
*/
|
||||
expandText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 收起、展开操作文字颜色,默认'#007aff'
|
||||
*/
|
||||
actionFontColor: {
|
||||
color: String,
|
||||
default: '#007aff'
|
||||
},
|
||||
/**
|
||||
* 展示行数,默认1
|
||||
*/
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
/**
|
||||
* 只有一行时文本对齐方式,支持left、right、justify
|
||||
*/
|
||||
oneRowTextAlign: {
|
||||
type: String,
|
||||
default: "justify"
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
//是否展开
|
||||
expand: false,
|
||||
//是否已计算
|
||||
isCompute: false,
|
||||
//内容高度
|
||||
h: undefined,
|
||||
//内容宽度
|
||||
w: undefined,
|
||||
//实际显示内容
|
||||
actualContent: '',
|
||||
//高度探测内容
|
||||
testContent: undefined,
|
||||
//是否显示省略号
|
||||
showSymbol: false,
|
||||
//hid和id,唯一标识符
|
||||
hid: 'hid' + Math.random().toString(36).substr(2),
|
||||
id: 'id' + Math.random().toString(36).substr(2),
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initEllipsis();
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
//动态计算组件样式
|
||||
computeStyle() {
|
||||
return b => {
|
||||
let lines = this.rows > 0 ? this.rows : 1;
|
||||
let obj = {};
|
||||
if (b == 1) {
|
||||
obj = {
|
||||
'-webkit-line-clamp': lines,
|
||||
'display': '-webkit-box',
|
||||
'text-overflow': 'ellipsis',
|
||||
'overflow': 'hidden',
|
||||
'-webkit-box-orient': 'vertical'
|
||||
};
|
||||
} else if (b == 2) {
|
||||
obj = {
|
||||
'position': 'relative',
|
||||
'left': '0rpx',
|
||||
...obj
|
||||
};
|
||||
}
|
||||
return {
|
||||
'font-size': this.fontSize + 'rpx',
|
||||
'color': this.fontColor,
|
||||
...obj
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
content(newVal, oldVal) {
|
||||
this.expand=false;
|
||||
this.isCompute=false;
|
||||
this.h=undefined;
|
||||
this.w=undefined;
|
||||
this.actualContent='';
|
||||
this.showSymbol=false;
|
||||
this.initEllipsis();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//初始化
|
||||
initEllipsis() {
|
||||
if (this.content?.length > 0) {
|
||||
this.$nextTick(() => {
|
||||
this.init(this, () => {
|
||||
this.compute(this);
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
//收起展开状态切换
|
||||
changeCollapse() {
|
||||
this.expand = !this.expand;
|
||||
},
|
||||
//文本点击事件
|
||||
contentClick() {
|
||||
this.$emit('contentClick', this.textId);
|
||||
},
|
||||
//组件参数初始化
|
||||
init($this, callback, isali) {
|
||||
if (isali) {
|
||||
uni.createSelectorQuery().in().select('#' + $this.id).boundingClientRect(d => {
|
||||
$this.h = Number(d.height.toFixed(1));
|
||||
$this.w = Number(d.width.toFixed(1));
|
||||
if (callback) {
|
||||
callback()
|
||||
}
|
||||
}).exec();
|
||||
} else {
|
||||
uni.createSelectorQuery().in($this).select('#' + $this.id).boundingClientRect(d => {
|
||||
$this.h = Number(d.height.toFixed(1));
|
||||
$this.w = Number(d.width.toFixed(1));
|
||||
if (callback) {
|
||||
callback()
|
||||
}
|
||||
}).exec();
|
||||
}
|
||||
},
|
||||
//动态计算组件内容
|
||||
computeContent($this, isali, dr) {
|
||||
$this.$nextTick(() => {
|
||||
$this.getH($this, isali, (ch) => {
|
||||
if (ch - factor > $this.h) {
|
||||
if (dr === -1) {
|
||||
$this.testContent = $this.content.substring(0, $this.testContent.length -
|
||||
1);
|
||||
$this.computeContent($this, isali, dr);
|
||||
} else {
|
||||
$this.actualContent = $this.content.substring(0, $this.testContent.length -
|
||||
1);
|
||||
$this.isCompute = true;
|
||||
}
|
||||
} else {
|
||||
if (dr === -1) {
|
||||
$this.actualContent = $this.testContent;
|
||||
$this.isCompute = true;
|
||||
} else {
|
||||
$this.testContent = $this.content.substring(0, $this.testContent.length +
|
||||
1);
|
||||
$this.computeContent($this, isali, dr);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
//计算工具方法
|
||||
compute($this, isali) {
|
||||
let {
|
||||
rows,
|
||||
fontSize,
|
||||
content,
|
||||
h,
|
||||
w
|
||||
} = $this;
|
||||
$this.testContent = content;
|
||||
$this.$nextTick(() => {
|
||||
$this.getH($this, isali, (ch) => {
|
||||
if (ch - factor > h) {
|
||||
let lh = h / rows;
|
||||
let fn = Math.floor(w / $this.rpx2px(fontSize));
|
||||
let sfn = fn * rows;
|
||||
let i = $this.fontNum(content, sfn * 2 - ($this.expandText ? $this.fontNum(
|
||||
$this.expandText) :
|
||||
0) - 3);
|
||||
$this.showSymbol = true;
|
||||
$this.testContent = content.substring(0, i);
|
||||
$this.$nextTick(() => {
|
||||
$this.getH($this, isali, (ch1) => {
|
||||
if (ch1 - factor > h) {
|
||||
$this.testContent = content.substring(0, $this
|
||||
.testContent.length - 1);
|
||||
$this.computeContent($this, isali, -1);
|
||||
} else {
|
||||
$this.testContent = content.substring(0, $this
|
||||
.testContent.length + 1);
|
||||
$this.computeContent($this, isali, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$this.isCompute = true;
|
||||
$this.actualContent = content;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
//动态计算字符数
|
||||
fontNum(val, limit) {
|
||||
let c = 0;
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
let a = val.charAt(i);
|
||||
if (a.match(/[^\x00-\xff]/ig) != null) {
|
||||
if (limit) {
|
||||
if (c + 2 > limit) {
|
||||
return i;
|
||||
} else {
|
||||
c += 2;
|
||||
}
|
||||
} else {
|
||||
c += 2;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (limit) {
|
||||
if (c + 1 > limit) {
|
||||
return i;
|
||||
} else {
|
||||
c += 1;
|
||||
}
|
||||
} else {
|
||||
c += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!limit) {
|
||||
return c;
|
||||
}
|
||||
},
|
||||
rpx2px(rpx) {
|
||||
return uni.getWindowInfo().windowWidth * Number(rpx) / 750;
|
||||
},
|
||||
getH($, isali, callback) {
|
||||
if (isali) {
|
||||
uni.createSelectorQuery().in().select('#' + $.hid).fields({
|
||||
size: true
|
||||
}, d => {
|
||||
if(d && d.height){
|
||||
callback(Number(d.height.toFixed(1)));
|
||||
}
|
||||
}).exec();
|
||||
} else {
|
||||
uni.createSelectorQuery().in($).select('#' + $.hid).fields({
|
||||
size: true
|
||||
}, d => {
|
||||
if(d && d.height){
|
||||
callback(Number(d.height.toFixed(1)));
|
||||
}
|
||||
}).exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.t-wrap {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.t-txt-hide {
|
||||
box-sizing: border-box;
|
||||
word-break: break-all;
|
||||
position: absolute;
|
||||
top: 999999px;
|
||||
left: 999999px;
|
||||
z-index: -1000;
|
||||
top: 0rpx;
|
||||
width: 100%;
|
||||
margin: 0rpx;
|
||||
text-align: justify;
|
||||
white-space: pre-line;
|
||||
line-height: 1.5 !important;
|
||||
.t-button {
|
||||
float: right;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.t-ellipsis {
|
||||
text-align: justify;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
position: relative;
|
||||
left: 99999px;
|
||||
white-space: pre-line;
|
||||
line-height: 1.5 !important;
|
||||
.t-button {
|
||||
float: right;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.t-ellipsis {
|
||||
text-align: justify;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
position: relative;
|
||||
left: 99999px;
|
||||
white-space: pre-line;
|
||||
line-height: 1.5 !important;
|
||||
.t-button {
|
||||
float: right;
|
||||
clear: both;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.t-skeleton {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 0rpx;
|
||||
left: 0rpx;
|
||||
}
|
||||
|
||||
.skeletons:first-child {
|
||||
margin-top: 0rpx !important;
|
||||
}
|
||||
|
||||
.skeletons {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 32rpx;
|
||||
margin-top: 12rpx;
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.skeletons .empty {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-webkit-transform: translateX(-100%);
|
||||
transform: translateX(-100%);
|
||||
background: linear-gradient(90deg, transparent, rgba(216, 216, 216, 0.753), transparent);
|
||||
-webkit-animation: loading .8s infinite;
|
||||
animation: loading .8s infinite;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@keyframes loading {
|
||||
100% {
|
||||
-webkit-transform: translateX(100%);
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.t-skeleton {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 0rpx;
|
||||
left: 0rpx;
|
||||
}
|
||||
|
||||
.skeletons:first-child {
|
||||
margin-top: 0rpx !important;
|
||||
}
|
||||
|
||||
.skeletons {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 32rpx;
|
||||
margin-top: 12rpx;
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.skeletons .empty {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-webkit-transform: translateX(-100%);
|
||||
transform: translateX(-100%);
|
||||
background: linear-gradient(90deg, transparent, rgba(216, 216, 216, 0.753), transparent);
|
||||
-webkit-animation: loading .8s infinite;
|
||||
animation: loading .8s infinite;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@keyframes loading {
|
||||
100% {
|
||||
-webkit-transform: translateX(100%);
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,218 +1,218 @@
|
||||
<template>
|
||||
<view :class="'wf-page wf-page'+type">
|
||||
<!-- left -->
|
||||
<view>
|
||||
<view id="left" v-if="leftList.length">
|
||||
<view v-for="(item,index) in leftList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem
|
||||
:item="item"
|
||||
:isStore="isStore"
|
||||
:type="type"
|
||||
:isMerchant="isMerchant"
|
||||
:recommend="recommend"
|
||||
:showCart="showCart"/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- right -->
|
||||
<view>
|
||||
<view id="right" v-if="rightList.length">
|
||||
<view v-for="(item,index) in rightList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem
|
||||
:item="item"
|
||||
:isStore="isStore"
|
||||
:type="type"
|
||||
:isMerchant="isMerchant"
|
||||
:recommend="recommend"
|
||||
:showCart="showCart"/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WaterfallsFlowItem from './WaterfallsFlowItem.vue'
|
||||
export default {
|
||||
components: {
|
||||
WaterfallsFlowItem
|
||||
},
|
||||
props: {
|
||||
// 瀑布流列表
|
||||
wfList: {
|
||||
type: Array,
|
||||
require: true
|
||||
},
|
||||
updateNum: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
isStore: {
|
||||
type: [String, Number],
|
||||
default: '1'
|
||||
},
|
||||
recommend:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showCart:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
isMerchant:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allList: [], // 全部列表
|
||||
leftList: [], // 左边列表
|
||||
rightList: [], // 右边列表
|
||||
mark: 0, // 列表标记
|
||||
boxHeight: [], // 下标0和1分别为左列和右列高度
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 监听列表数据变化
|
||||
wfList: {
|
||||
handler(nVal,oVal){
|
||||
// 如果数据为空或新的列表数据少于旧的列表数据(通常为下拉刷新或切换排序或使用筛选器),初始化变量
|
||||
|
||||
if (!this.wfList.length ||
|
||||
(this.wfList.length === this.updateNum && this.wfList.length <= this.allList.length)) {
|
||||
this.allList = [];
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.mark = 0;
|
||||
}
|
||||
|
||||
// 如果列表有值,调用waterfall方法
|
||||
|
||||
if (this.wfList.length) {
|
||||
this.allList = this.wfList;
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.allList.forEach((v, i) => {
|
||||
if(this.allList.length < 3 || (this.allList.length <= 7 && this.allList.length - i > 1) || (this.allList.length > 7 && this.allList.length - i > 2)) {
|
||||
if(i % 2){
|
||||
this.rightList.push(v);
|
||||
}else{
|
||||
this.leftList.push(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
if(this.allList.length < 3){
|
||||
this.mark = this.allList.length+1;
|
||||
}else if(this.allList.length <= 7){
|
||||
this.mark = this.allList.length - 1;
|
||||
}else{
|
||||
this.mark = this.allList.length - 2;
|
||||
}
|
||||
if(this.mark < this.allList.length){
|
||||
this.waterFall()
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep:true
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
|
||||
// 监听标记,当标记发生变化,则执行下一个item排序
|
||||
mark() {
|
||||
const len = this.allList.length;
|
||||
if (this.mark < len && this.mark !== 0 && this.boxHeight.length) {
|
||||
this.waterFall();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 瀑布流排序
|
||||
waterFall() {
|
||||
const i = this.mark;
|
||||
if (i == 0) {
|
||||
// 初始化,从左边开始插入
|
||||
this.leftList.push(this.allList[i]);
|
||||
// 更新左边列表高度
|
||||
this.getViewHeight(0);
|
||||
} else if (i == 1) {
|
||||
// 第二个item插入,默认为右边插入
|
||||
this.rightList.push(this.allList[i]);
|
||||
// 更新右边列表高度
|
||||
this.getViewHeight(1);
|
||||
} else {
|
||||
// 根据左右列表高度判断下一个item应该插入哪边
|
||||
if(!this.boxHeight.length){
|
||||
this.rightList.length < this.leftList.length
|
||||
? this.rightList.push(this.allList[i])
|
||||
: this.leftList.push(this.allList[i]);
|
||||
} else {
|
||||
const leftOrRight = this.boxHeight[0] > this.boxHeight[1] ? 1 : 0;
|
||||
if (leftOrRight) {
|
||||
this.rightList.push(this.allList[i])
|
||||
} else {
|
||||
this.leftList.push(this.allList[i])
|
||||
}
|
||||
}
|
||||
// 更新插入列表高度
|
||||
this.getViewHeight();
|
||||
}
|
||||
},
|
||||
// 获取列表高度
|
||||
getViewHeight() {
|
||||
// 使用nextTick,确保页面更新结束后,再请求高度
|
||||
this.$nextTick(() => {
|
||||
setTimeout(()=>{
|
||||
uni.createSelectorQuery().in(this).select('#right').boundingClientRect(res => {
|
||||
res ? this.boxHeight[1] = res.height : '';
|
||||
uni.createSelectorQuery().in(this).select('#left').boundingClientRect(res => {
|
||||
res ? this.boxHeight[0] = res.height : '';
|
||||
this.mark = this.mark + 1;
|
||||
}).exec();
|
||||
}).exec();
|
||||
},100)
|
||||
})
|
||||
},
|
||||
// item点击
|
||||
itemTap(item) {
|
||||
this.$emit('itemTap', item)
|
||||
},
|
||||
// item点击
|
||||
|
||||
goShop(item) {
|
||||
this.$emit('goShop', item)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$page-padding: 10px;
|
||||
$grid-gap: 10px;
|
||||
|
||||
.wf-page {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: $grid-gap;
|
||||
}
|
||||
.wf-item {
|
||||
width: calc((100vw - 2 * #{$page-padding} - #{$grid-gap}) / 2);
|
||||
padding-bottom: $grid-gap;
|
||||
}
|
||||
.wf-page1 .wf-item{
|
||||
margin-top: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
<template>
|
||||
<view :class="'wf-page wf-page'+type">
|
||||
<!-- left -->
|
||||
<view>
|
||||
<view id="left" v-if="leftList.length">
|
||||
<view v-for="(item,index) in leftList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem
|
||||
:item="item"
|
||||
:isStore="isStore"
|
||||
:type="type"
|
||||
:isMerchant="isMerchant"
|
||||
:recommend="recommend"
|
||||
:showCart="showCart"/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- right -->
|
||||
<view>
|
||||
<view id="right" v-if="rightList.length">
|
||||
<view v-for="(item,index) in rightList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem
|
||||
:item="item"
|
||||
:isStore="isStore"
|
||||
:type="type"
|
||||
:isMerchant="isMerchant"
|
||||
:recommend="recommend"
|
||||
:showCart="showCart"/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WaterfallsFlowItem from './WaterfallsFlowItem.vue'
|
||||
export default {
|
||||
components: {
|
||||
WaterfallsFlowItem
|
||||
},
|
||||
props: {
|
||||
// 瀑布流列表
|
||||
wfList: {
|
||||
type: Array,
|
||||
require: true
|
||||
},
|
||||
updateNum: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
isStore: {
|
||||
type: [String, Number],
|
||||
default: '1'
|
||||
},
|
||||
recommend:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showCart:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
isMerchant:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allList: [], // 全部列表
|
||||
leftList: [], // 左边列表
|
||||
rightList: [], // 右边列表
|
||||
mark: 0, // 列表标记
|
||||
boxHeight: [], // 下标0和1分别为左列和右列高度
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 监听列表数据变化
|
||||
wfList: {
|
||||
handler(nVal,oVal){
|
||||
// 如果数据为空或新的列表数据少于旧的列表数据(通常为下拉刷新或切换排序或使用筛选器),初始化变量
|
||||
|
||||
if (!this.wfList.length ||
|
||||
(this.wfList.length === this.updateNum && this.wfList.length <= this.allList.length)) {
|
||||
this.allList = [];
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.mark = 0;
|
||||
}
|
||||
|
||||
// 如果列表有值,调用waterfall方法
|
||||
|
||||
if (this.wfList.length) {
|
||||
this.allList = this.wfList;
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.allList.forEach((v, i) => {
|
||||
if(this.allList.length < 3 || (this.allList.length <= 7 && this.allList.length - i > 1) || (this.allList.length > 7 && this.allList.length - i > 2)) {
|
||||
if(i % 2){
|
||||
this.rightList.push(v);
|
||||
}else{
|
||||
this.leftList.push(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
if(this.allList.length < 3){
|
||||
this.mark = this.allList.length+1;
|
||||
}else if(this.allList.length <= 7){
|
||||
this.mark = this.allList.length - 1;
|
||||
}else{
|
||||
this.mark = this.allList.length - 2;
|
||||
}
|
||||
if(this.mark < this.allList.length){
|
||||
this.waterFall()
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep:true
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
|
||||
// 监听标记,当标记发生变化,则执行下一个item排序
|
||||
mark() {
|
||||
const len = this.allList.length;
|
||||
if (this.mark < len && this.mark !== 0 && this.boxHeight.length) {
|
||||
this.waterFall();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 瀑布流排序
|
||||
waterFall() {
|
||||
const i = this.mark;
|
||||
if (i == 0) {
|
||||
// 初始化,从左边开始插入
|
||||
this.leftList.push(this.allList[i]);
|
||||
// 更新左边列表高度
|
||||
this.getViewHeight(0);
|
||||
} else if (i == 1) {
|
||||
// 第二个item插入,默认为右边插入
|
||||
this.rightList.push(this.allList[i]);
|
||||
// 更新右边列表高度
|
||||
this.getViewHeight(1);
|
||||
} else {
|
||||
// 根据左右列表高度判断下一个item应该插入哪边
|
||||
if(!this.boxHeight.length){
|
||||
this.rightList.length < this.leftList.length
|
||||
? this.rightList.push(this.allList[i])
|
||||
: this.leftList.push(this.allList[i]);
|
||||
} else {
|
||||
const leftOrRight = this.boxHeight[0] > this.boxHeight[1] ? 1 : 0;
|
||||
if (leftOrRight) {
|
||||
this.rightList.push(this.allList[i])
|
||||
} else {
|
||||
this.leftList.push(this.allList[i])
|
||||
}
|
||||
}
|
||||
// 更新插入列表高度
|
||||
this.getViewHeight();
|
||||
}
|
||||
},
|
||||
// 获取列表高度
|
||||
getViewHeight() {
|
||||
// 使用nextTick,确保页面更新结束后,再请求高度
|
||||
this.$nextTick(() => {
|
||||
setTimeout(()=>{
|
||||
uni.createSelectorQuery().in(this).select('#right').boundingClientRect(res => {
|
||||
res ? this.boxHeight[1] = res.height : '';
|
||||
uni.createSelectorQuery().in(this).select('#left').boundingClientRect(res => {
|
||||
res ? this.boxHeight[0] = res.height : '';
|
||||
this.mark = this.mark + 1;
|
||||
}).exec();
|
||||
}).exec();
|
||||
},100)
|
||||
})
|
||||
},
|
||||
// item点击
|
||||
itemTap(item) {
|
||||
this.$emit('itemTap', item)
|
||||
},
|
||||
// item点击
|
||||
|
||||
goShop(item) {
|
||||
this.$emit('goShop', item)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$page-padding: 10px;
|
||||
$grid-gap: 10px;
|
||||
|
||||
.wf-page {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: $grid-gap;
|
||||
}
|
||||
.wf-item {
|
||||
width: calc((100vw - 2 * #{$page-padding} - #{$grid-gap}) / 2);
|
||||
padding-bottom: $grid-gap;
|
||||
}
|
||||
.wf-page1 .wf-item{
|
||||
margin-top: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,225 +1,225 @@
|
||||
<template>
|
||||
<view :class="'wf-page wf-page'+type">
|
||||
<!-- left -->
|
||||
<view>
|
||||
<view id="left" v-if="leftList.length">
|
||||
<view v-for="(item,index) in leftList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem :item="item" :border="border" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- right -->
|
||||
<view>
|
||||
<view id="right" v-if="rightList.length">
|
||||
<view v-for="(item,index) in rightList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem :item="item" :border="border" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WaterfallsFlowItem from './WaterfallsFlowItem.vue';
|
||||
import {LOGIN_STATUS} from '@/config/cache';
|
||||
import { HTTP_REQUEST_URL } from '@/config/app';
|
||||
export default {
|
||||
components: {
|
||||
WaterfallsFlowItem
|
||||
},
|
||||
props: {
|
||||
// 瀑布流列表
|
||||
wfList: {
|
||||
type: Array,
|
||||
require: true
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
updateNum: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
border:{
|
||||
type:Boolean,
|
||||
default: false
|
||||
},
|
||||
isSelf:{
|
||||
type:Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allList: [], // 全部列表
|
||||
leftList: [], // 左边列表
|
||||
rightList: [], // 右边列表
|
||||
mark: 0, // 列表标记
|
||||
boxHeight: [], // 下标0和1分别为左列和右列高度
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 监听列表数据变化
|
||||
wfList: {
|
||||
handler(nVal,oVal){
|
||||
// 如果数据为空或新的列表数据少于旧的列表数据(通常为下拉刷新或切换排序或使用筛选器),初始化变量
|
||||
|
||||
if (!this.wfList.length ||
|
||||
(this.wfList.length === this.updateNum && this.wfList.length <= this.allList.length)) {
|
||||
this.allList = [];
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.mark = 0;
|
||||
}
|
||||
|
||||
// 如果列表有值,调用waterfall方法
|
||||
|
||||
if (this.wfList.length) {
|
||||
this.allList = this.wfList;
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.allList.forEach((v, i) => {
|
||||
if(this.allList.length < 3 || (this.allList.length <= 7 && this.allList.length - i > 1) || (this.allList.length > 7 && this.allList.length - i > 2)) {
|
||||
if(i % 2){
|
||||
this.rightList.push(v);
|
||||
}else{
|
||||
this.leftList.push(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
if(this.allList.length < 3){
|
||||
this.mark = this.allList.length+1;
|
||||
}else if(this.allList.length <= 7){
|
||||
this.mark = this.allList.length - 1;
|
||||
}else{
|
||||
this.mark = this.allList.length - 2;
|
||||
}
|
||||
if(this.mark < this.allList.length){
|
||||
this.waterFall()
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep:true
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
|
||||
// 监听标记,当标记发生变化,则执行下一个item排序
|
||||
mark() {
|
||||
const len = this.allList.length;
|
||||
if (this.mark < len && this.mark !== 0 && this.boxHeight.length) {
|
||||
this.waterFall();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 瀑布流排序
|
||||
waterFall() {
|
||||
const i = this.mark;
|
||||
if (i == 0) {
|
||||
// 初始化,从左边开始插入
|
||||
this.leftList.push(this.allList[i]);
|
||||
// 更新左边列表高度
|
||||
this.getViewHeight(0);
|
||||
} else if (i == 1) {
|
||||
// 第二个item插入,默认为右边插入
|
||||
this.rightList.push(this.allList[i]);
|
||||
// 更新右边列表高度
|
||||
this.getViewHeight(1);
|
||||
} else {
|
||||
// 根据左右列表高度判断下一个item应该插入哪边
|
||||
if(!this.boxHeight.length){
|
||||
this.rightList.length < this.leftList.length
|
||||
? this.rightList.push(this.allList[i])
|
||||
: this.leftList.push(this.allList[i]);
|
||||
} else {
|
||||
const leftOrRight = this.boxHeight[0] > this.boxHeight[1] ? 1 : 0;
|
||||
if (leftOrRight) {
|
||||
this.rightList.push(this.allList[i])
|
||||
} else {
|
||||
this.leftList.push(this.allList[i])
|
||||
}
|
||||
}
|
||||
// 更新插入列表高度
|
||||
this.getViewHeight();
|
||||
}
|
||||
},
|
||||
// 获取列表高度
|
||||
getViewHeight() {
|
||||
// 使用nextTick,确保页面更新结束后,再请求高度
|
||||
this.$nextTick(() => {
|
||||
setTimeout(()=>{
|
||||
uni.createSelectorQuery().in(this).select('#right').boundingClientRect(res => {
|
||||
res ? this.boxHeight[1] = res.height : '';
|
||||
uni.createSelectorQuery().in(this).select('#left').boundingClientRect(res => {
|
||||
res ? this.boxHeight[0] = res.height : '';
|
||||
this.mark = this.mark + 1;
|
||||
}).exec();
|
||||
}).exec();
|
||||
},100)
|
||||
})
|
||||
},
|
||||
// item点击
|
||||
itemTap(item) {
|
||||
if(item.content_type == 1){
|
||||
uni.navigateTo({
|
||||
url: '/pages/discover/discoverDetails/index?id=' + item.id
|
||||
})
|
||||
}else{
|
||||
let that = this;
|
||||
if(this.isSelf){
|
||||
uni.navigateTo({
|
||||
// #ifdef MP || H5
|
||||
url: '/pages/discover/discoverVideo/index?id=' + item.id + '&relation_id=' + item.relation_id
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
url: `/pages/discover/discoverVideo/app?url=${HTTP_REQUEST_URL}/pages/discover/discoverVideo/index?content_type=2&token=${that.$Cache.get(LOGIN_STATUS)}&relation_id=${item.relation_id}`
|
||||
// #endif
|
||||
})
|
||||
}else{
|
||||
// #ifdef MP || H5
|
||||
uni.navigateTo({
|
||||
url: '/pages/discover/discoverVideo/index?id=' + item.id
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
let href = encodeURIComponent(`${HTTP_REQUEST_URL}/pages/discover/discoverVideo/index?content_type=2&id=${item.id}&token=${that.$Cache.get(LOGIN_STATUS)}`)
|
||||
uni.navigateTo({
|
||||
url: `/pages/discover/discoverVideo/app?url=${href}`
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$page-padding: 10px;
|
||||
$grid-gap: 10px;
|
||||
|
||||
.wf-page {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: $grid-gap;
|
||||
}
|
||||
.wf-item {
|
||||
width: calc((100vw - 2 * #{$page-padding} - #{$grid-gap}) / 2);
|
||||
padding-bottom: $grid-gap;
|
||||
}
|
||||
.wf-page1 .wf-item{
|
||||
margin-top: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
<template>
|
||||
<view :class="'wf-page wf-page'+type">
|
||||
<!-- left -->
|
||||
<view>
|
||||
<view id="left" v-if="leftList.length">
|
||||
<view v-for="(item,index) in leftList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem :item="item" :border="border" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- right -->
|
||||
<view>
|
||||
<view id="right" v-if="rightList.length">
|
||||
<view v-for="(item,index) in rightList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem :item="item" :border="border" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WaterfallsFlowItem from './WaterfallsFlowItem.vue';
|
||||
import {LOGIN_STATUS} from '@/config/cache';
|
||||
import { HTTP_REQUEST_URL } from '@/config/app';
|
||||
export default {
|
||||
components: {
|
||||
WaterfallsFlowItem
|
||||
},
|
||||
props: {
|
||||
// 瀑布流列表
|
||||
wfList: {
|
||||
type: Array,
|
||||
require: true
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
updateNum: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
border:{
|
||||
type:Boolean,
|
||||
default: false
|
||||
},
|
||||
isSelf:{
|
||||
type:Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allList: [], // 全部列表
|
||||
leftList: [], // 左边列表
|
||||
rightList: [], // 右边列表
|
||||
mark: 0, // 列表标记
|
||||
boxHeight: [], // 下标0和1分别为左列和右列高度
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 监听列表数据变化
|
||||
wfList: {
|
||||
handler(nVal,oVal){
|
||||
// 如果数据为空或新的列表数据少于旧的列表数据(通常为下拉刷新或切换排序或使用筛选器),初始化变量
|
||||
|
||||
if (!this.wfList.length ||
|
||||
(this.wfList.length === this.updateNum && this.wfList.length <= this.allList.length)) {
|
||||
this.allList = [];
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.mark = 0;
|
||||
}
|
||||
|
||||
// 如果列表有值,调用waterfall方法
|
||||
|
||||
if (this.wfList.length) {
|
||||
this.allList = this.wfList;
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.allList.forEach((v, i) => {
|
||||
if(this.allList.length < 3 || (this.allList.length <= 7 && this.allList.length - i > 1) || (this.allList.length > 7 && this.allList.length - i > 2)) {
|
||||
if(i % 2){
|
||||
this.rightList.push(v);
|
||||
}else{
|
||||
this.leftList.push(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
if(this.allList.length < 3){
|
||||
this.mark = this.allList.length+1;
|
||||
}else if(this.allList.length <= 7){
|
||||
this.mark = this.allList.length - 1;
|
||||
}else{
|
||||
this.mark = this.allList.length - 2;
|
||||
}
|
||||
if(this.mark < this.allList.length){
|
||||
this.waterFall()
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep:true
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
|
||||
// 监听标记,当标记发生变化,则执行下一个item排序
|
||||
mark() {
|
||||
const len = this.allList.length;
|
||||
if (this.mark < len && this.mark !== 0 && this.boxHeight.length) {
|
||||
this.waterFall();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 瀑布流排序
|
||||
waterFall() {
|
||||
const i = this.mark;
|
||||
if (i == 0) {
|
||||
// 初始化,从左边开始插入
|
||||
this.leftList.push(this.allList[i]);
|
||||
// 更新左边列表高度
|
||||
this.getViewHeight(0);
|
||||
} else if (i == 1) {
|
||||
// 第二个item插入,默认为右边插入
|
||||
this.rightList.push(this.allList[i]);
|
||||
// 更新右边列表高度
|
||||
this.getViewHeight(1);
|
||||
} else {
|
||||
// 根据左右列表高度判断下一个item应该插入哪边
|
||||
if(!this.boxHeight.length){
|
||||
this.rightList.length < this.leftList.length
|
||||
? this.rightList.push(this.allList[i])
|
||||
: this.leftList.push(this.allList[i]);
|
||||
} else {
|
||||
const leftOrRight = this.boxHeight[0] > this.boxHeight[1] ? 1 : 0;
|
||||
if (leftOrRight) {
|
||||
this.rightList.push(this.allList[i])
|
||||
} else {
|
||||
this.leftList.push(this.allList[i])
|
||||
}
|
||||
}
|
||||
// 更新插入列表高度
|
||||
this.getViewHeight();
|
||||
}
|
||||
},
|
||||
// 获取列表高度
|
||||
getViewHeight() {
|
||||
// 使用nextTick,确保页面更新结束后,再请求高度
|
||||
this.$nextTick(() => {
|
||||
setTimeout(()=>{
|
||||
uni.createSelectorQuery().in(this).select('#right').boundingClientRect(res => {
|
||||
res ? this.boxHeight[1] = res.height : '';
|
||||
uni.createSelectorQuery().in(this).select('#left').boundingClientRect(res => {
|
||||
res ? this.boxHeight[0] = res.height : '';
|
||||
this.mark = this.mark + 1;
|
||||
}).exec();
|
||||
}).exec();
|
||||
},100)
|
||||
})
|
||||
},
|
||||
// item点击
|
||||
itemTap(item) {
|
||||
if(item.content_type == 1){
|
||||
uni.navigateTo({
|
||||
url: '/pages/discover/discoverDetails/index?id=' + item.id
|
||||
})
|
||||
}else{
|
||||
let that = this;
|
||||
if(this.isSelf){
|
||||
uni.navigateTo({
|
||||
// #ifdef MP || H5
|
||||
url: '/pages/discover/discoverVideo/index?id=' + item.id + '&relation_id=' + item.relation_id
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
url: `/pages/discover/discoverVideo/app?url=${HTTP_REQUEST_URL}/pages/discover/discoverVideo/index?content_type=2&token=${that.$Cache.get(LOGIN_STATUS)}&relation_id=${item.relation_id}`
|
||||
// #endif
|
||||
})
|
||||
}else{
|
||||
// #ifdef MP || H5
|
||||
uni.navigateTo({
|
||||
url: '/pages/discover/discoverVideo/index?id=' + item.id
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
let href = encodeURIComponent(`${HTTP_REQUEST_URL}/pages/discover/discoverVideo/index?content_type=2&id=${item.id}&token=${that.$Cache.get(LOGIN_STATUS)}`)
|
||||
uni.navigateTo({
|
||||
url: `/pages/discover/discoverVideo/app?url=${href}`
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$page-padding: 10px;
|
||||
$grid-gap: 10px;
|
||||
|
||||
.wf-page {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: $grid-gap;
|
||||
}
|
||||
.wf-item {
|
||||
width: calc((100vw - 2 * #{$page-padding} - #{$grid-gap}) / 2);
|
||||
padding-bottom: $grid-gap;
|
||||
}
|
||||
.wf-page1 .wf-item{
|
||||
margin-top: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
44
fix_apply.py
Normal file
44
fix_apply.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import re
|
||||
|
||||
with open("C:\\Users\\Jone\\Documents\\HBuilderProjects\\crmeb\\pages\\annex\\web_view\\index.vue", "r", encoding="utf-8") as f:
|
||||
c = f.read()
|
||||
|
||||
# Find and replace the whole _applyIdentity method
|
||||
pattern = r'(\t\t_applyIdentity\(targetUrl, openid, unionid, urlOpenid\) \{[^}]+\}[^}]+?this\._finalizeUrl\(targetUrl\);)'
|
||||
replacement = """\t\t_applyIdentity(targetUrl, openid, unionid, urlOpenid) {
|
||||
\t\t\tif (urlOpenid && urlOpenid !== openid) {
|
||||
\t\t\t\ttargetUrl = stripAuthParams(targetUrl);
|
||||
\t\t\t\tconst joiner = targetUrl.indexOf('?') !== -1 ? '&' : '?';
|
||||
\t\t\t\tconst t = Math.floor(Date.now() / 1000);
|
||||
\t\t\t\ttargetUrl = targetUrl + joiner + 'openid=' + encodeURIComponent(openid);
|
||||
\t\t\t\tif (unionid) targetUrl += '&unionid=' + encodeURIComponent(unionid);
|
||||
\t\t\t\ttargetUrl += '&t=' + t;
|
||||
\t\t\t\tconsole.log('[web_view] 替换为当前用户身份');
|
||||
\t\t\t} else if (!urlOpenid) {
|
||||
\t\t\t\ttargetUrl = stripAuthParams(targetUrl);
|
||||
\t\t\t\tconst joiner = targetUrl.indexOf('?') !== -1 ? '&' : '?';
|
||||
\t\t\t\tconst t = Math.floor(Date.now() / 1000);
|
||||
\t\t\t\ttargetUrl = targetUrl + joiner + 'openid=' + encodeURIComponent(openid);
|
||||
\t\t\t\tif (unionid) targetUrl += '&unionid=' + encodeURIComponent(unionid);
|
||||
\t\t\t\ttargetUrl += '&t=' + t;
|
||||
\t\t\t\tconsole.log('[web_view] 补充身份参数');
|
||||
\t\t\t} else {
|
||||
\t\t\t\tconsole.log('[web_view] 身份一致,保留原 URL');
|
||||
\t\t\t}
|
||||
\t\t\tthis.debugMsg += ' [最终URL] ' + targetUrl.substring(0, 120);
|
||||
\t\t\tthis._finalizeUrl(targetUrl);
|
||||
\t\t},"""
|
||||
|
||||
if re.search(pattern, c, re.DOTALL):
|
||||
c = re.sub(pattern, replacement, c, count=1, flags=re.DOTALL)
|
||||
with open("C:\\Users\\Jone\\Documents\\HBuilderProjects\\crmeb\\pages\\annex\\web_view\\index.vue", "w", encoding="utf-8") as f:
|
||||
f.write(c)
|
||||
script = c[c.find("<script>"):c.find("</script>")]
|
||||
print(f"Balance: {script.count('{') - script.count('}')}")
|
||||
print("SUCCESS")
|
||||
else:
|
||||
print("Pattern not found")
|
||||
# Debug: show what's around _applyIdentity
|
||||
idx = c.find("_applyIdentity")
|
||||
if idx > 0:
|
||||
print(c[idx:idx+500])
|
||||
@@ -28,6 +28,26 @@ import {
|
||||
} from './../config/cache';
|
||||
import Routine from '@/libs/routine';
|
||||
|
||||
/**
|
||||
* 不依赖 atob 的 base64 解码(小程序环境不提供 atob)
|
||||
*/
|
||||
function base64Decode(str) {
|
||||
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
var output = '';
|
||||
var i = 0;
|
||||
str = String(str).replace(/[^A-Za-z0-9+/=]/g, '');
|
||||
while (i < str.length) {
|
||||
var b1 = chars.indexOf(str.charAt(i++));
|
||||
var b2 = chars.indexOf(str.charAt(i++));
|
||||
var b3 = chars.indexOf(str.charAt(i++));
|
||||
var b4 = chars.indexOf(str.charAt(i++));
|
||||
output += String.fromCharCode((b1 << 2) | (b2 >> 4));
|
||||
if (b3 !== 64) output += String.fromCharCode(((b2 & 15) << 4) | (b3 >> 2));
|
||||
if (b4 !== 64) output += String.fromCharCode(((b3 & 3) << 6) | b4);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function prePage() {
|
||||
let pages = getCurrentPages();
|
||||
let prePage = pages[pages.length - 1];
|
||||
@@ -42,7 +62,6 @@ function prePage() {
|
||||
|
||||
|
||||
|
||||
|
||||
export const toLogin = Debounce(_toLogin, 800)
|
||||
|
||||
function _toLogin(push, pathLogin) {
|
||||
@@ -80,16 +99,6 @@ function _toLogin(push, pathLogin) {
|
||||
uni.navigateTo({
|
||||
url
|
||||
})
|
||||
// Routine.getCode()
|
||||
// .then(code => {
|
||||
// console.log(code)
|
||||
// Routine.silenceAuth(code).then(res => {
|
||||
// console.log(res)
|
||||
// })
|
||||
// })
|
||||
// .catch(err => {
|
||||
// uni.hideLoading();
|
||||
// });
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
@@ -114,11 +123,46 @@ export function checkLogin() {
|
||||
return false;
|
||||
} else {
|
||||
store.commit('UPDATE_LOGIN', token);
|
||||
// 从 JWT payload 中恢复 uid / unionid
|
||||
try {
|
||||
let parts = token.split('.');
|
||||
if (parts.length === 3) {
|
||||
let payload = parts[1]
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
while (payload.length % 4 !== 0) payload += '=';
|
||||
let data = JSON.parse(base64Decode(payload));
|
||||
if (data.uid) store.commit('SETUID', data.uid);
|
||||
if (data.unionid) {
|
||||
store.commit('SET_UNIONID', data.unionid);
|
||||
Cache.set('UNIONID', data.unionid);
|
||||
console.log('[checkLogin] unionid 已从 JWT 恢复:', data.unionid);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
// 恢复缓存的 openid
|
||||
const cachedOpenid = Cache.get('OPENID');
|
||||
if (cachedOpenid) {
|
||||
store.commit('SET_OPENID', cachedOpenid);
|
||||
console.log('[checkLogin] openid 已从缓存恢复:', cachedOpenid);
|
||||
}
|
||||
// 恢复缓存的 unionid
|
||||
const cachedUnionid = Cache.get('UNIONID');
|
||||
if (cachedUnionid) {
|
||||
store.commit('SET_UNIONID', cachedUnionid);
|
||||
console.log('[checkLogin] unionid 已从缓存恢复:', cachedUnionid);
|
||||
} else {
|
||||
console.log('[checkLogin] unionid 缓存为空');
|
||||
}
|
||||
// 从 userInfo 缓存恢复
|
||||
let userInfo = Cache.get(USER_INFO, true);
|
||||
if (userInfo) {
|
||||
store.commit('UPDATE_USERINFO', userInfo);
|
||||
if (!cachedUnionid && userInfo.unionid) {
|
||||
console.log('[checkLogin] unionid 已从 userInfo 恢复:', userInfo.unionid);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
162
main.js
162
main.js
@@ -1,82 +1,82 @@
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import store from './store'
|
||||
import Cache from './utils/cache'
|
||||
import util from 'utils/util'
|
||||
import configs from './config/app.js'
|
||||
import socket from './libs/new_chat.js'
|
||||
Vue.prototype.$util = util;
|
||||
Vue.prototype.$config = configs;
|
||||
Vue.prototype.$Cache = Cache;
|
||||
Vue.prototype.$eventHub = new Vue();
|
||||
Vue.prototype.$socket = new socket();
|
||||
Vue.config.productionTip = false;
|
||||
import home from "./components/home/index.vue"
|
||||
import skeleton from './components/skeleton/index.vue'
|
||||
import BaseMoney from './components/BaseMoney.vue';
|
||||
import BaseTag from './components/BaseTag.vue';
|
||||
import easyLoadimage from './components/easy-loadimage/easy-loadimage.vue'
|
||||
import baseDrawer from '@/components/tui-drawer/tui-drawer.vue';
|
||||
Vue.component('home', home)
|
||||
Vue.component('skeleton', skeleton)
|
||||
Vue.component('BaseMoney', BaseMoney)
|
||||
Vue.component('BaseTag', BaseTag)
|
||||
Vue.component('baseDrawer', baseDrawer)
|
||||
Vue.component('easyLoadimage', easyLoadimage)
|
||||
// #ifdef H5
|
||||
|
||||
|
||||
import { parseQuery } from "./utils";
|
||||
import Auth from './libs/wechat';
|
||||
import { SPREAD } from './config/cache';
|
||||
Vue.prototype.$wechat = Auth;
|
||||
|
||||
|
||||
|
||||
let cookieName = "VCONSOLE",
|
||||
query = parseQuery(),
|
||||
vconsole = query[cookieName.toLowerCase()],
|
||||
md5Crmeb = "b14d1e9baeced9bb7525ab19ee35f2d2", //CRMEB MD5 加密开启vconsole模式
|
||||
md5UnCrmeb = "3dca2162c4e101b7656793a1af20295c"; //UN_CREMB MD5 加密关闭vconsole模式
|
||||
|
||||
if (vconsole !== undefined) {
|
||||
if (vconsole === md5UnCrmeb && Cache.has(cookieName))
|
||||
Cache.clear(cookieName);
|
||||
} else vconsole = Cache.get(cookieName);
|
||||
|
||||
import VConsole from './pages/extension/components/vconsole.min.js'
|
||||
|
||||
if (vconsole !== undefined && vconsole === md5Crmeb) {
|
||||
Cache.set(cookieName, md5Crmeb, 3600);
|
||||
let vConsole = new VConsole();
|
||||
}
|
||||
|
||||
// 统计代码
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://cdn.oss.9gt.net/js/es.js?version=prov3.5.1";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
// #endif
|
||||
|
||||
App.mpType = 'app'
|
||||
|
||||
|
||||
const app = new Vue({
|
||||
...App,
|
||||
store,
|
||||
Cache,
|
||||
})
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import store from './store'
|
||||
import Cache from './utils/cache'
|
||||
import util from 'utils/util'
|
||||
import configs from './config/app.js'
|
||||
import socket from './libs/new_chat.js'
|
||||
Vue.prototype.$util = util;
|
||||
Vue.prototype.$config = configs;
|
||||
Vue.prototype.$Cache = Cache;
|
||||
Vue.prototype.$eventHub = new Vue();
|
||||
Vue.prototype.$socket = new socket();
|
||||
Vue.config.productionTip = false;
|
||||
import home from "./components/home/index.vue"
|
||||
import skeleton from './components/skeleton/index.vue'
|
||||
import BaseMoney from './components/BaseMoney.vue';
|
||||
import BaseTag from './components/BaseTag.vue';
|
||||
import easyLoadimage from './components/easy-loadimage/easy-loadimage.vue'
|
||||
import baseDrawer from '@/components/tui-drawer/tui-drawer.vue';
|
||||
Vue.component('home', home)
|
||||
Vue.component('skeleton', skeleton)
|
||||
Vue.component('BaseMoney', BaseMoney)
|
||||
Vue.component('BaseTag', BaseTag)
|
||||
Vue.component('baseDrawer', baseDrawer)
|
||||
Vue.component('easyLoadimage', easyLoadimage)
|
||||
// #ifdef H5
|
||||
|
||||
|
||||
import { parseQuery } from "./utils";
|
||||
import Auth from './libs/wechat';
|
||||
import { SPREAD } from './config/cache';
|
||||
Vue.prototype.$wechat = Auth;
|
||||
|
||||
|
||||
|
||||
let cookieName = "VCONSOLE",
|
||||
query = parseQuery(),
|
||||
vconsole = query[cookieName.toLowerCase()],
|
||||
md5Crmeb = "b14d1e9baeced9bb7525ab19ee35f2d2", //CRMEB MD5 加密开启vconsole模式
|
||||
md5UnCrmeb = "3dca2162c4e101b7656793a1af20295c"; //UN_CREMB MD5 加密关闭vconsole模式
|
||||
|
||||
if (vconsole !== undefined) {
|
||||
if (vconsole === md5UnCrmeb && Cache.has(cookieName))
|
||||
Cache.clear(cookieName);
|
||||
} else vconsole = Cache.get(cookieName);
|
||||
|
||||
import VConsole from './pages/extension/components/vconsole.min.js'
|
||||
|
||||
if (vconsole !== undefined && vconsole === md5Crmeb) {
|
||||
Cache.set(cookieName, md5Crmeb, 3600);
|
||||
let vConsole = new VConsole();
|
||||
}
|
||||
|
||||
// 统计代码
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://cdn.oss.9gt.net/js/es.js?version=prov3.5.1";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
// #endif
|
||||
|
||||
App.mpType = 'app'
|
||||
|
||||
|
||||
const app = new Vue({
|
||||
...App,
|
||||
store,
|
||||
Cache,
|
||||
})
|
||||
app.$mount();
|
||||
4214
pages.json
4214
pages.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,258 +1,258 @@
|
||||
<template>
|
||||
<view :style="colorStyle">
|
||||
<view class="px-32">
|
||||
<view class="fs-36 fw-500 lh-54rpx mt-44">{{ articleInfo.title }}</view>
|
||||
<view class="flex fs-24 text--w111-999 mt-16">
|
||||
<text>{{ articleInfo.catename }}</text>
|
||||
<text class="px-16">|</text>
|
||||
<text>{{ articleInfo.add_time }}</text>
|
||||
</view>
|
||||
<view class="content fs-30 text--w111-333 lh-52rpx mt-32">
|
||||
<jyf-parser :html="content" ref="article" :tag-style="tagStyle"></jyf-parser>
|
||||
</view>
|
||||
<view class="bg--w111-f5f5f5 rd-24rpx w-full flex-between-center p-20 mt-32" @tap="goDetail" v-if="store_info.id">
|
||||
<image :src="store_info.image" class="w-160 h-160 rd-16rpx" mode="aspectFill"></image>
|
||||
<view class="flex-1 h-160 flex-col justify-between pl-20">
|
||||
<view class="w-full fs-28 lh-40rpx line2">{{ store_info.store_name }}</view>
|
||||
<view class="flex justify-between items-end">
|
||||
<baseMoney :money="store_info.price" symbolSize="28" integerSize="44" decimalSize="28" weight></baseMoney>
|
||||
<view class="w-136 h-52 rd-30rpx flex-center fs-22 text--w111-fff bg-gradient">立即购买</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="h-200"></view>
|
||||
<view class="bg--w111-fff w-full z-99 fixed-lb pb-safe">
|
||||
<view class="w-full h-128 px-32 flex-y-center" v-if="store_info.id">
|
||||
<view class="w-346 h-72 px-24 rd-36rpx flex-center bg--w111-f5f5f5" @tap="goDetail">
|
||||
<image :src="store_info.image" class="w-48 h-48 rd-8rpx"></image>
|
||||
<view class="pl-12">
|
||||
<view class="fs-22 fw-500 w-238 line1">{{ store_info.store_name }}</view>
|
||||
<view class="fs-20 SemiBold">¥{{ store_info.price }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-1 flex-between-center ml-38">
|
||||
<view class=" flex-y-center">
|
||||
<text class="iconfont icon-ic_Eyes fs-38"></text>
|
||||
<text class="fs-26 pl-12">{{ articleInfo.visit }}</text>
|
||||
</view>
|
||||
<view class=" flex-y-center" :class="{ active: articleInfo.is_like }" @tap="giveLike">
|
||||
<text class="iconfont icon-ic_Like fs-38"></text>
|
||||
<text class="fs-26 pl-12">{{ articleInfo.likes }}</text>
|
||||
</view>
|
||||
<!-- #ifdef H5 -->
|
||||
<text class="iconfont icon-ic_share fs-44" @tap="listenerActionSheet"></text>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
<button open-type="share" hover-class="none">
|
||||
<text class="iconfont icon-ic_share fs-38"></text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="w-full px-120 h-128 flex-between-center" v-else>
|
||||
<view class="flex-y-center" :class="{ active: articleInfo.is_like }" @tap="giveLike">
|
||||
<text class="iconfont icon-ic_Like fs-38"></text>
|
||||
<text class="fs-26 pl-12">{{ articleInfo.likes }}</text>
|
||||
</view>
|
||||
<view class="flex-y-center">
|
||||
<text class="iconfont icon-ic_Eyes fs-38"></text>
|
||||
<text class="fs-26 pl-12">{{ articleInfo.visit }}</text>
|
||||
</view>
|
||||
<!-- #ifdef H5 -->
|
||||
<text class="iconfont icon-ic_share fs-38" @tap="listenerActionSheet"></text>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
<button open-type="share" hover-class="none">
|
||||
<text class="iconfont icon-ic_share fs-44"></text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
<shareInfo @setShareInfoStatus="setShareInfoStatus" :shareInfoStatus="shareInfoStatus"></shareInfo>
|
||||
<home></home>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getArticleDetails } from '@/api/api.js';
|
||||
import { articleStarApi, userShare } from '@/api/user.js';
|
||||
import shareInfo from '../components/shareInfo/index.vue';
|
||||
import parser from '@/components/jyf-parser/jyf-parser';
|
||||
import colors from '@/mixins/color';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { toLogin } from '@/libs/login.js';
|
||||
import { HTTP_REQUEST_URL } from '@/config/app';
|
||||
export default {
|
||||
components: {
|
||||
shareInfo,
|
||||
'jyf-parser': parser
|
||||
},
|
||||
mixins: [colors],
|
||||
data() {
|
||||
return {
|
||||
id: 0,
|
||||
articleInfo: {},
|
||||
store_info: {},
|
||||
content: '',
|
||||
shareInfoStatus: false,
|
||||
tagStyle: {
|
||||
img: 'width:100%;display:block;border-radius:8px;',
|
||||
table: 'width:100%',
|
||||
video: 'width:100%;'
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: mapGetters(['isLogin', 'uid']),
|
||||
onPageScroll(object) {
|
||||
uni.$emit('scroll');
|
||||
},
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
// #ifdef MP
|
||||
onShareAppMessage: function() {
|
||||
let that = this;
|
||||
return {
|
||||
title: that.articleInfo.title,
|
||||
imageUrl: that.articleInfo.image_input[0] || '',
|
||||
path: '/pages/extension/news_details/index?id=' + that.id + '&spid=' + that.uid
|
||||
};
|
||||
},
|
||||
onShareTimeline() {
|
||||
let that = this;
|
||||
return {
|
||||
title: that.articleInfo.title,
|
||||
imageUrl: that.articleInfo.image_input[0] || '',
|
||||
path: '/pages/extension/news_details/index?id=' + that.id + '&spid=' + that.uid
|
||||
};
|
||||
},
|
||||
// #endif
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad: function (options) {
|
||||
if (options.hasOwnProperty('id')) {
|
||||
this.id = options.id;
|
||||
} else {
|
||||
// #ifndef H5
|
||||
uni.navigateBack({ delta: 1 });
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
history.back();
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
onShow: function () {
|
||||
uni.removeStorageSync('form_type_cart');
|
||||
this.getArticleOne();
|
||||
},
|
||||
methods: {
|
||||
getArticleOne: function () {
|
||||
let that = this;
|
||||
getArticleDetails(that.id).then((res) => {
|
||||
uni.setNavigationBarTitle({
|
||||
title: res.data.title
|
||||
});
|
||||
that.$set(that, 'articleInfo', res.data);
|
||||
that.$set(that, 'store_info', res.data.store_info ? res.data.store_info : {});
|
||||
that.content = res.data.content;
|
||||
// #ifdef H5
|
||||
if (this.$wechat.isWeixin()) {
|
||||
this.setShareInfo();
|
||||
}
|
||||
// #endif
|
||||
});
|
||||
},
|
||||
giveLike() {
|
||||
if (!this.isLogin) {
|
||||
toLogin();
|
||||
} else {
|
||||
articleStarApi(this.id, {
|
||||
status: this.articleInfo.is_like ? 0 : 1
|
||||
}).then((res) => {
|
||||
let that = this;
|
||||
uni.showToast({
|
||||
title: this.articleInfo.is_like ? '取消点赞' : '点赞成功',
|
||||
icon: 'none',
|
||||
success: () => {
|
||||
if (this.articleInfo.is_like) {
|
||||
that.articleInfo.likes = that.articleInfo.likes - 1;
|
||||
that.articleInfo.is_like = 0;
|
||||
} else {
|
||||
that.articleInfo.likes = that.articleInfo.likes + 1;
|
||||
that.articleInfo.is_like = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
goDetail() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/goods_details/index?id=' + this.store_info.id
|
||||
});
|
||||
},
|
||||
listenerActionSheet() {
|
||||
if (this.$wechat.isWeixin()) {
|
||||
this.shareInfoStatus = true;
|
||||
} else {
|
||||
let that = this;
|
||||
uni.setClipboardData({
|
||||
data: HTTP_REQUEST_URL + '/pages/extension/news_details/index?id=' + that.id,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '已复制页面链接'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
setShareInfoStatus() {
|
||||
this.shareInfoStatus = false;
|
||||
},
|
||||
setShareInfo: function () {
|
||||
let href = location.href;
|
||||
let configAppMessage = {
|
||||
desc: this.articleInfo.synopsis,
|
||||
title: this.articleInfo.title,
|
||||
link: href,
|
||||
imgUrl: this.articleInfo.image_input.length ? this.articleInfo.image_input[0] : ''
|
||||
};
|
||||
this.$wechat.wechatEvevt(['updateAppMessageShareData', 'updateTimelineShareData'], configAppMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
page {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.active {
|
||||
color: var(--view-theme);
|
||||
}
|
||||
.content {
|
||||
// text-indent: 2em;
|
||||
}
|
||||
.ml-52 {
|
||||
margin-left: 52rpx;
|
||||
}
|
||||
.ml-80 {
|
||||
margin-left: 80rpx;
|
||||
}
|
||||
.abs-num {
|
||||
position: absolute;
|
||||
right: -26rpx;
|
||||
top: -10rpx;
|
||||
}
|
||||
.likes-num {
|
||||
position: absolute;
|
||||
right: -16rpx;
|
||||
top: -10rpx;
|
||||
}
|
||||
.SemiBold {
|
||||
font-family: 'SemiBold';
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view :style="colorStyle">
|
||||
<view class="px-32">
|
||||
<view class="fs-36 fw-500 lh-54rpx mt-44">{{ articleInfo.title }}</view>
|
||||
<view class="flex fs-24 text--w111-999 mt-16">
|
||||
<text>{{ articleInfo.catename }}</text>
|
||||
<text class="px-16">|</text>
|
||||
<text>{{ articleInfo.add_time }}</text>
|
||||
</view>
|
||||
<view class="content fs-30 text--w111-333 lh-52rpx mt-32">
|
||||
<jyf-parser :html="content" ref="article" :tag-style="tagStyle"></jyf-parser>
|
||||
</view>
|
||||
<view class="bg--w111-f5f5f5 rd-24rpx w-full flex-between-center p-20 mt-32" @tap="goDetail" v-if="store_info.id">
|
||||
<image :src="store_info.image" class="w-160 h-160 rd-16rpx" mode="aspectFill"></image>
|
||||
<view class="flex-1 h-160 flex-col justify-between pl-20">
|
||||
<view class="w-full fs-28 lh-40rpx line2">{{ store_info.store_name }}</view>
|
||||
<view class="flex justify-between items-end">
|
||||
<baseMoney :money="store_info.price" symbolSize="28" integerSize="44" decimalSize="28" weight></baseMoney>
|
||||
<view class="w-136 h-52 rd-30rpx flex-center fs-22 text--w111-fff bg-gradient">立即购买</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="h-200"></view>
|
||||
<view class="bg--w111-fff w-full z-99 fixed-lb pb-safe">
|
||||
<view class="w-full h-128 px-32 flex-y-center" v-if="store_info.id">
|
||||
<view class="w-346 h-72 px-24 rd-36rpx flex-center bg--w111-f5f5f5" @tap="goDetail">
|
||||
<image :src="store_info.image" class="w-48 h-48 rd-8rpx"></image>
|
||||
<view class="pl-12">
|
||||
<view class="fs-22 fw-500 w-238 line1">{{ store_info.store_name }}</view>
|
||||
<view class="fs-20 SemiBold">¥{{ store_info.price }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-1 flex-between-center ml-38">
|
||||
<view class=" flex-y-center">
|
||||
<text class="iconfont icon-ic_Eyes fs-38"></text>
|
||||
<text class="fs-26 pl-12">{{ articleInfo.visit }}</text>
|
||||
</view>
|
||||
<view class=" flex-y-center" :class="{ active: articleInfo.is_like }" @tap="giveLike">
|
||||
<text class="iconfont icon-ic_Like fs-38"></text>
|
||||
<text class="fs-26 pl-12">{{ articleInfo.likes }}</text>
|
||||
</view>
|
||||
<!-- #ifdef H5 -->
|
||||
<text class="iconfont icon-ic_share fs-44" @tap="listenerActionSheet"></text>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
<button open-type="share" hover-class="none">
|
||||
<text class="iconfont icon-ic_share fs-38"></text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="w-full px-120 h-128 flex-between-center" v-else>
|
||||
<view class="flex-y-center" :class="{ active: articleInfo.is_like }" @tap="giveLike">
|
||||
<text class="iconfont icon-ic_Like fs-38"></text>
|
||||
<text class="fs-26 pl-12">{{ articleInfo.likes }}</text>
|
||||
</view>
|
||||
<view class="flex-y-center">
|
||||
<text class="iconfont icon-ic_Eyes fs-38"></text>
|
||||
<text class="fs-26 pl-12">{{ articleInfo.visit }}</text>
|
||||
</view>
|
||||
<!-- #ifdef H5 -->
|
||||
<text class="iconfont icon-ic_share fs-38" @tap="listenerActionSheet"></text>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
<button open-type="share" hover-class="none">
|
||||
<text class="iconfont icon-ic_share fs-44"></text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
<shareInfo @setShareInfoStatus="setShareInfoStatus" :shareInfoStatus="shareInfoStatus"></shareInfo>
|
||||
<home></home>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getArticleDetails } from '@/api/api.js';
|
||||
import { articleStarApi, userShare } from '@/api/user.js';
|
||||
import shareInfo from '../components/shareInfo/index.vue';
|
||||
import parser from '@/components/jyf-parser/jyf-parser';
|
||||
import colors from '@/mixins/color';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { toLogin } from '@/libs/login.js';
|
||||
import { HTTP_REQUEST_URL } from '@/config/app';
|
||||
export default {
|
||||
components: {
|
||||
shareInfo,
|
||||
'jyf-parser': parser
|
||||
},
|
||||
mixins: [colors],
|
||||
data() {
|
||||
return {
|
||||
id: 0,
|
||||
articleInfo: {},
|
||||
store_info: {},
|
||||
content: '',
|
||||
shareInfoStatus: false,
|
||||
tagStyle: {
|
||||
img: 'width:100%;display:block;border-radius:8px;',
|
||||
table: 'width:100%',
|
||||
video: 'width:100%;'
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: mapGetters(['isLogin', 'uid']),
|
||||
onPageScroll(object) {
|
||||
uni.$emit('scroll');
|
||||
},
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
// #ifdef MP
|
||||
onShareAppMessage: function() {
|
||||
let that = this;
|
||||
return {
|
||||
title: that.articleInfo.title,
|
||||
imageUrl: that.articleInfo.image_input[0] || '',
|
||||
path: '/pages/extension/news_details/index?id=' + that.id + '&spid=' + that.uid
|
||||
};
|
||||
},
|
||||
onShareTimeline() {
|
||||
let that = this;
|
||||
return {
|
||||
title: that.articleInfo.title,
|
||||
imageUrl: that.articleInfo.image_input[0] || '',
|
||||
path: '/pages/extension/news_details/index?id=' + that.id + '&spid=' + that.uid
|
||||
};
|
||||
},
|
||||
// #endif
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad: function (options) {
|
||||
if (options.hasOwnProperty('id')) {
|
||||
this.id = options.id;
|
||||
} else {
|
||||
// #ifndef H5
|
||||
uni.navigateBack({ delta: 1 });
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
history.back();
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
onShow: function () {
|
||||
uni.removeStorageSync('form_type_cart');
|
||||
this.getArticleOne();
|
||||
},
|
||||
methods: {
|
||||
getArticleOne: function () {
|
||||
let that = this;
|
||||
getArticleDetails(that.id).then((res) => {
|
||||
uni.setNavigationBarTitle({
|
||||
title: res.data.title
|
||||
});
|
||||
that.$set(that, 'articleInfo', res.data);
|
||||
that.$set(that, 'store_info', res.data.store_info ? res.data.store_info : {});
|
||||
that.content = res.data.content;
|
||||
// #ifdef H5
|
||||
if (this.$wechat.isWeixin()) {
|
||||
this.setShareInfo();
|
||||
}
|
||||
// #endif
|
||||
});
|
||||
},
|
||||
giveLike() {
|
||||
if (!this.isLogin) {
|
||||
toLogin();
|
||||
} else {
|
||||
articleStarApi(this.id, {
|
||||
status: this.articleInfo.is_like ? 0 : 1
|
||||
}).then((res) => {
|
||||
let that = this;
|
||||
uni.showToast({
|
||||
title: this.articleInfo.is_like ? '取消点赞' : '点赞成功',
|
||||
icon: 'none',
|
||||
success: () => {
|
||||
if (this.articleInfo.is_like) {
|
||||
that.articleInfo.likes = that.articleInfo.likes - 1;
|
||||
that.articleInfo.is_like = 0;
|
||||
} else {
|
||||
that.articleInfo.likes = that.articleInfo.likes + 1;
|
||||
that.articleInfo.is_like = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
goDetail() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/goods_details/index?id=' + this.store_info.id
|
||||
});
|
||||
},
|
||||
listenerActionSheet() {
|
||||
if (this.$wechat.isWeixin()) {
|
||||
this.shareInfoStatus = true;
|
||||
} else {
|
||||
let that = this;
|
||||
uni.setClipboardData({
|
||||
data: HTTP_REQUEST_URL + '/pages/extension/news_details/index?id=' + that.id,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '已复制页面链接'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
setShareInfoStatus() {
|
||||
this.shareInfoStatus = false;
|
||||
},
|
||||
setShareInfo: function () {
|
||||
let href = location.href;
|
||||
let configAppMessage = {
|
||||
desc: this.articleInfo.synopsis,
|
||||
title: this.articleInfo.title,
|
||||
link: href,
|
||||
imgUrl: this.articleInfo.image_input.length ? this.articleInfo.image_input[0] : ''
|
||||
};
|
||||
this.$wechat.wechatEvevt(['updateAppMessageShareData', 'updateTimelineShareData'], configAppMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
page {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.active {
|
||||
color: var(--view-theme);
|
||||
}
|
||||
.content {
|
||||
// text-indent: 2em;
|
||||
}
|
||||
.ml-52 {
|
||||
margin-left: 52rpx;
|
||||
}
|
||||
.ml-80 {
|
||||
margin-left: 80rpx;
|
||||
}
|
||||
.abs-num {
|
||||
position: absolute;
|
||||
right: -26rpx;
|
||||
top: -10rpx;
|
||||
}
|
||||
.likes-num {
|
||||
position: absolute;
|
||||
right: -16rpx;
|
||||
top: -10rpx;
|
||||
}
|
||||
.SemiBold {
|
||||
font-family: 'SemiBold';
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -415,7 +415,7 @@
|
||||
},
|
||||
watch:{
|
||||
reset(val){
|
||||
this.getCartList(1);
|
||||
if (this.isLogin) { this.getCartList(1); }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -424,7 +424,7 @@
|
||||
this.getScrollHeight();
|
||||
}, 500);
|
||||
if(this.isLogin){
|
||||
this.getCartList(1);
|
||||
if (this.isLogin) { this.getCartList(1); }
|
||||
}
|
||||
uni.$on('newAttrNum', (data) => {
|
||||
if (data.length) {
|
||||
@@ -765,7 +765,7 @@
|
||||
item.cart_num = item.copy_cart_num;
|
||||
item.showInput =false;
|
||||
this.getCartNum();
|
||||
this.getCartList(1);
|
||||
if (this.isLogin) { this.getCartList(1); }
|
||||
})
|
||||
.catch(err => {
|
||||
item.copy_cart_num = item.cart_num;
|
||||
|
||||
@@ -380,7 +380,7 @@
|
||||
},
|
||||
watch:{
|
||||
reset(val){
|
||||
this.getCartList(1);
|
||||
if (this.isLogin) { this.getCartList(1); }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -389,7 +389,7 @@
|
||||
this.getScrollHeight();
|
||||
}, 500)
|
||||
if(this.isLogin){
|
||||
this.getCartList(1);
|
||||
if (this.isLogin) { this.getCartList(1); }
|
||||
}
|
||||
uni.$on('newAttrNum', (data) => {
|
||||
if (data.length) {
|
||||
@@ -689,7 +689,7 @@
|
||||
item.cart_num = item.copy_cart_num;
|
||||
item.showInput =false;
|
||||
this.getCartNum();
|
||||
this.getCartList(1);
|
||||
if (this.isLogin) { this.getCartList(1); }
|
||||
})
|
||||
.catch(err => {
|
||||
item.copy_cart_num = item.cart_num;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
275
pages/index/components/appCenter.vue
Normal file
275
pages/index/components/appCenter.vue
Normal file
@@ -0,0 +1,275 @@
|
||||
<template>
|
||||
<view v-if="showApps" :style="[boxStyle]">
|
||||
<view :style="[boxContentStyle]">
|
||||
<view class="list-menu" :style="[gridColumns]">
|
||||
<view
|
||||
class="item flex-col flex-center"
|
||||
v-for="(item, index) in displayList"
|
||||
:key="index"
|
||||
@click="onAppTap(item)"
|
||||
>
|
||||
<!-- 更多按钮 -->
|
||||
<template v-if="item._isMore">
|
||||
<view class="img-box skeleton-radius" v-if="menuStyleConfig !== 2">
|
||||
<view class="more-icon-inner flex-col flex-center">
|
||||
<view class="more-dots">
|
||||
<view class="more-dot"></view>
|
||||
<view class="more-dot"></view>
|
||||
<view class="more-dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text
|
||||
class="fs-24"
|
||||
:class="{ 'mt-14': menuStyleConfig == 0 }"
|
||||
:style="[nameColorStyle]"
|
||||
v-if="menuStyleConfig !== 1"
|
||||
>更多</text>
|
||||
</template>
|
||||
<!-- 普通应用 -->
|
||||
<template v-else>
|
||||
<view class="img-box skeleton-radius" v-if="menuStyleConfig !== 2">
|
||||
<image
|
||||
:src="item.logo"
|
||||
mode="aspectFill"
|
||||
:style="{ borderRadius: (imgStyle * 2) + 'rpx' }"
|
||||
v-if="item.logo"
|
||||
></image>
|
||||
<view class="empty-box" v-else></view>
|
||||
</view>
|
||||
<text
|
||||
class="fs-24"
|
||||
:class="{ 'mt-14': menuStyleConfig == 0 }"
|
||||
:style="[nameColorStyle]"
|
||||
v-if="menuStyleConfig !== 1"
|
||||
>{{ item.name }}</text>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const STORAGE_KEY = 'MY_HOME_APPS'
|
||||
const MAX_HOME_APPS = 6
|
||||
|
||||
export default {
|
||||
name: 'appCenter',
|
||||
props: {
|
||||
dataConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
menuStyleConfig() {
|
||||
return this.dataConfig.menuStyleConfig?.tabVal ?? 0
|
||||
},
|
||||
number() {
|
||||
return this.dataConfig.number?.tabVal ?? 0
|
||||
},
|
||||
boxStyle() {
|
||||
return {
|
||||
padding: `${(this.dataConfig.topConfig?.val ?? 0) * 2}rpx ${(this.dataConfig.prConfig?.val ?? 0) * 2}rpx ${(this.dataConfig.bottomConfig?.val ?? 0) * 2}rpx`,
|
||||
marginTop: `${(this.dataConfig.mbConfig?.val ?? 0) * 2}rpx`,
|
||||
background: this.dataConfig.bottomBgColor?.color?.[0]?.item ?? 'transparent'
|
||||
}
|
||||
},
|
||||
boxContentStyle() {
|
||||
let borderRadius = `${(this.dataConfig.fillet?.val ?? 0) * 2}rpx`
|
||||
if (this.dataConfig.fillet?.type) {
|
||||
const vl = this.dataConfig.fillet.valList || []
|
||||
borderRadius = `${(vl[0]?.val ?? 0) * 2}rpx ${(vl[1]?.val ?? 0) * 2}rpx ${(vl[3]?.val ?? 0) * 2}rpx ${(vl[2]?.val ?? 0) * 2}rpx`
|
||||
}
|
||||
return {
|
||||
borderRadius,
|
||||
background: `linear-gradient(90deg, ${this.dataConfig.bgColor?.color?.[0]?.item ?? '#fff'} 0%, ${this.dataConfig.bgColor?.color?.[1]?.item ?? '#fff'} 100%)`
|
||||
}
|
||||
},
|
||||
imgStyle() {
|
||||
return this.dataConfig.filletImg?.val ?? 0
|
||||
},
|
||||
gridColumns() {
|
||||
// 应用中心用 4 列固定网格
|
||||
return {
|
||||
gridTemplateColumns: `repeat(4, 1fr)`
|
||||
}
|
||||
},
|
||||
nameColorStyle() {
|
||||
return {
|
||||
color: this.dataConfig.nameColor?.color?.[0]?.item ?? '#333333'
|
||||
}
|
||||
},
|
||||
// 是否在首页展示
|
||||
showApps() {
|
||||
return this.homeApps.showOnHome && this.displayList.length > 0
|
||||
},
|
||||
// 已选应用的 id 集合(快速查找)
|
||||
selectedIds() {
|
||||
return new Set(this.homeApps.appIds || [])
|
||||
},
|
||||
// 最终展示列表(过滤 + 按 seq 排序 + 截取 + 追加"更多")
|
||||
displayList() {
|
||||
const ids = this.homeApps.appIds || []
|
||||
if (!ids.length) return []
|
||||
|
||||
const idSet = new Set(ids)
|
||||
// 按 allApps API 返回顺序排列(即后台设置的 seq 顺序)
|
||||
const filtered = this.allApps.filter(item => idSet.has(item.id))
|
||||
const indexMap = new Map(this.allApps.map((item, i) => [item.id, i]))
|
||||
filtered.sort((a, b) => (indexMap.get(a.id) ?? 999) - (indexMap.get(b.id) ?? 999))
|
||||
const sliced = filtered.slice(0, MAX_HOME_APPS)
|
||||
|
||||
// 追加"更多"条目
|
||||
sliced.push({
|
||||
_isMore: true,
|
||||
name: '更多'
|
||||
})
|
||||
|
||||
return sliced
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allApps: [],
|
||||
homeApps: {
|
||||
showOnHome: true,
|
||||
appIds: []
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadHomeApps()
|
||||
this.fetchApps()
|
||||
},
|
||||
// 从个人中心编辑保存后跳回首页时,重新读取 storage
|
||||
onShow() {
|
||||
this.loadHomeApps()
|
||||
},
|
||||
methods: {
|
||||
// 读取本地存储的首页应用配置
|
||||
loadHomeApps() {
|
||||
try {
|
||||
const raw = uni.getStorageSync(STORAGE_KEY)
|
||||
if (raw) {
|
||||
this.homeApps = raw
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[appCenter] loadHomeApps error:', e)
|
||||
}
|
||||
},
|
||||
fetchApps() {
|
||||
uni.request({
|
||||
url: 'https://mp.yizhezhe.com/api/mp/apps',
|
||||
data: {
|
||||
isPublished: 1,
|
||||
pageSize: 50
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.data?.code === 200 && res.data?.result?.items) {
|
||||
this.allApps = res.data.result.items
|
||||
} else if (res.data?.status === 200 && res.data?.data) {
|
||||
this.allApps = res.data.data
|
||||
} else {
|
||||
return
|
||||
}
|
||||
this.autoFillHomeApps()
|
||||
},
|
||||
fail: () => {
|
||||
this.allApps = []
|
||||
}
|
||||
})
|
||||
},
|
||||
// 自动填充首页展示应用
|
||||
autoFillHomeApps() {
|
||||
const ids = this.homeApps.appIds || []
|
||||
const currentCount = ids.length
|
||||
if (currentCount >= MAX_HOME_APPS) return
|
||||
|
||||
const idSet = new Set(ids)
|
||||
// 从推荐应用中筛选尚未选中的
|
||||
const recommended = this.allApps
|
||||
.filter(item => item.isRecommended && !idSet.has(item.id))
|
||||
|
||||
if (!recommended.length) return
|
||||
|
||||
const slots = MAX_HOME_APPS - currentCount
|
||||
const toAdd = recommended.slice(0, slots).map(item => item.id)
|
||||
|
||||
this.homeApps.appIds = [...ids, ...toAdd]
|
||||
try {
|
||||
uni.setStorageSync(STORAGE_KEY, this.homeApps)
|
||||
} catch (e) {
|
||||
console.error('[appCenter] autoFill save error:', e)
|
||||
}
|
||||
},
|
||||
onAppTap(item) {
|
||||
if (item._isMore) {
|
||||
// 点击"更多"跳转到我的页面
|
||||
uni.switchTab({
|
||||
url: '/pages/user/index'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!item.url) return
|
||||
this.$util.JumpPath(item.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-menu {
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
grid-row-gap: 24rpx;
|
||||
width: 100%;
|
||||
padding: 20rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.item {
|
||||
text-align: center;
|
||||
|
||||
.img-box {
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
margin: 0 auto 6rpx;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.empty-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f3f9ff;
|
||||
}
|
||||
}
|
||||
|
||||
// 更多图标
|
||||
.more-icon-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
|
||||
.more-dots {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
|
||||
.more-dot {
|
||||
width: 8rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 50%;
|
||||
background: #bbb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,250 +1,250 @@
|
||||
<template>
|
||||
<!-- 图片组件 -->
|
||||
<view v-show="!dataConfig.isHide" class="images-component" :style="containerStyle">
|
||||
<!-- 无链接时使用普通view -->
|
||||
<view v-if="!linkUrl" class="images-navigator" :style="imgContainerStyle">
|
||||
<image
|
||||
v-if="imgUrl"
|
||||
:src="imgUrl"
|
||||
:mode="imgMode"
|
||||
class="images-img"
|
||||
:style="imgStyle"
|
||||
:show-menu-by-longpress="isQrCode"
|
||||
@load="onImgLoad"
|
||||
@error="onImgError"
|
||||
></image>
|
||||
<!-- 占位图 -->
|
||||
<view v-else class="images-placeholder">
|
||||
<text class="placeholder-text">暂无图片</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 有链接时使用navigator -->
|
||||
<navigator v-else :url="linkUrl" hover-class="none" class="images-navigator" :style="imgContainerStyle">
|
||||
<image
|
||||
v-if="imgUrl"
|
||||
:src="imgUrl"
|
||||
:mode="imgMode"
|
||||
class="images-img"
|
||||
:style="imgStyle"
|
||||
:show-menu-by-longpress="isQrCode"
|
||||
@load="onImgLoad"
|
||||
@error="onImgError"
|
||||
></image>
|
||||
<!-- 占位图 -->
|
||||
<view v-else class="images-placeholder">
|
||||
<text class="placeholder-text">暂无图片</text>
|
||||
</view>
|
||||
</navigator>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'home_image',
|
||||
props: {
|
||||
dataConfig: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imgError: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const debug = uni.getStorageSync('DEBUG_DIY_RENDER') === true
|
||||
if (!debug) return
|
||||
console.log('[DIY][component]', 'home_image', 'mounted', {
|
||||
id: this.dataConfig?.id,
|
||||
timestamp: this.dataConfig?.timestamp,
|
||||
url: this.imgConfig?.url || this.dataConfig?.imgConfig?.url
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
// 图片配置
|
||||
imgConfig() {
|
||||
return this.dataConfig.imgConfig || {}
|
||||
},
|
||||
// 链接配置
|
||||
linkConfig() {
|
||||
return this.dataConfig.linkConfig || {}
|
||||
},
|
||||
// 二维码模式
|
||||
isQrCode() {
|
||||
return this.dataConfig.isQrCode?.val === true
|
||||
},
|
||||
// 上边距
|
||||
topConfig() {
|
||||
return this.dataConfig.topConfig || {}
|
||||
},
|
||||
// 下边距
|
||||
bottomConfig() {
|
||||
return this.dataConfig.bottomConfig || {}
|
||||
},
|
||||
// 左右边距
|
||||
prConfig() {
|
||||
return this.dataConfig.prConfig || {}
|
||||
},
|
||||
// 页面间距
|
||||
mbConfig() {
|
||||
return this.dataConfig.mbConfig || {}
|
||||
},
|
||||
// 圆角配置
|
||||
fillet() {
|
||||
return this.dataConfig.fillet || {}
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor() {
|
||||
return this.dataConfig.bgColor || {}
|
||||
},
|
||||
// 图片地址
|
||||
imgUrl() {
|
||||
const url = this.imgConfig.url || ''
|
||||
if (!url) return ''
|
||||
// 处理安全域名
|
||||
if (url.indexOf('https://') > -1) return url
|
||||
return url.replace('http://', 'https://')
|
||||
},
|
||||
// 链接地址(从 imgConfig.link 获取)
|
||||
linkUrl() {
|
||||
// 直接从 imgConfig.link 获取字符串链接
|
||||
const link = this.imgConfig.link || this.linkConfig?.link || ''
|
||||
if (!link) return ''
|
||||
// 支持字符串链接
|
||||
if (typeof link === 'string') {
|
||||
return link
|
||||
}
|
||||
// 支持对象结构 { url: '', title: '' }
|
||||
if (typeof link === 'object') {
|
||||
return link.url || ''
|
||||
}
|
||||
// 兼容旧的数组结构
|
||||
if (Array.isArray(link) && link.length > 0) {
|
||||
return link[0]?.link || link[0]?.url || ''
|
||||
}
|
||||
return ''
|
||||
},
|
||||
// 图片显示模式
|
||||
imgMode() {
|
||||
return 'widthFix'
|
||||
},
|
||||
// 容器样式
|
||||
containerStyle() {
|
||||
const style = {}
|
||||
|
||||
// 上边距
|
||||
if (this.topConfig.val) {
|
||||
style.marginTop = this.topConfig.val + 'rpx'
|
||||
}
|
||||
|
||||
// 页面间距
|
||||
if (this.mbConfig.val) {
|
||||
style.marginBottom = this.mbConfig.val + 'rpx'
|
||||
}
|
||||
|
||||
// 下边距
|
||||
if (this.bottomConfig.val) {
|
||||
style.paddingBottom = this.bottomConfig.val + 'rpx'
|
||||
}
|
||||
|
||||
// 背景颜色
|
||||
if (this.bgColor.color && this.bgColor.color[0]?.item) {
|
||||
style.backgroundColor = this.bgColor.color[0].item
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
// 图片容器样式
|
||||
imgContainerStyle() {
|
||||
const style = {}
|
||||
const fillet = this.fillet
|
||||
|
||||
// 左右边距
|
||||
if (this.prConfig.val) {
|
||||
style.paddingLeft = this.prConfig.val + 'rpx'
|
||||
style.paddingRight = this.prConfig.val + 'rpx'
|
||||
}
|
||||
|
||||
// 圆角 - 全部圆角模式
|
||||
if (fillet.type === 0 && fillet.val) {
|
||||
style.borderRadius = fillet.val + 'rpx'
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
// 图片样式
|
||||
imgStyle() {
|
||||
const style = {}
|
||||
const fillet = this.fillet
|
||||
|
||||
// 圆角 - 单独设置模式
|
||||
if (fillet.type === 1 && fillet.valList) {
|
||||
const [tl, tr, br, bl] = fillet.valList
|
||||
const radius = `${tl?.val || 0}rpx ${tr?.val || 0}rpx ${br?.val || 0}rpx ${bl?.val || 0}rpx`
|
||||
style.borderRadius = radius
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onImgLoad(e) {
|
||||
const debug = uni.getStorageSync('DEBUG_DIY_RENDER') === true
|
||||
if (!debug) return
|
||||
console.log('[DIY][component]', 'home_image', 'img_load', {
|
||||
id: this.dataConfig?.id,
|
||||
timestamp: this.dataConfig?.timestamp,
|
||||
src: this.imgUrl,
|
||||
width: e?.detail?.width,
|
||||
height: e?.detail?.height
|
||||
})
|
||||
},
|
||||
// 图片加载失败处理
|
||||
onImgError(e) {
|
||||
const debug = uni.getStorageSync('DEBUG_DIY_RENDER') === true
|
||||
if (debug) {
|
||||
console.log('[DIY][component]', 'home_image', 'img_error', {
|
||||
id: this.dataConfig?.id,
|
||||
timestamp: this.dataConfig?.timestamp,
|
||||
src: this.imgUrl,
|
||||
detail: e?.detail
|
||||
})
|
||||
}
|
||||
this.imgError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.images-component {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.images-navigator {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.images-img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.images-placeholder {
|
||||
width: 100%;
|
||||
height: 300rpx;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.placeholder-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<!-- 图片组件 -->
|
||||
<view v-show="!dataConfig.isHide" class="images-component" :style="containerStyle">
|
||||
<!-- 无链接时使用普通view -->
|
||||
<view v-if="!linkUrl" class="images-navigator" :style="imgContainerStyle">
|
||||
<image
|
||||
v-if="imgUrl"
|
||||
:src="imgUrl"
|
||||
:mode="imgMode"
|
||||
class="images-img"
|
||||
:style="imgStyle"
|
||||
:show-menu-by-longpress="isQrCode"
|
||||
@load="onImgLoad"
|
||||
@error="onImgError"
|
||||
></image>
|
||||
<!-- 占位图 -->
|
||||
<view v-else class="images-placeholder">
|
||||
<text class="placeholder-text">暂无图片</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 有链接时使用navigator -->
|
||||
<navigator v-else :url="linkUrl" hover-class="none" class="images-navigator" :style="imgContainerStyle">
|
||||
<image
|
||||
v-if="imgUrl"
|
||||
:src="imgUrl"
|
||||
:mode="imgMode"
|
||||
class="images-img"
|
||||
:style="imgStyle"
|
||||
:show-menu-by-longpress="isQrCode"
|
||||
@load="onImgLoad"
|
||||
@error="onImgError"
|
||||
></image>
|
||||
<!-- 占位图 -->
|
||||
<view v-else class="images-placeholder">
|
||||
<text class="placeholder-text">暂无图片</text>
|
||||
</view>
|
||||
</navigator>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'home_image',
|
||||
props: {
|
||||
dataConfig: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imgError: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const debug = uni.getStorageSync('DEBUG_DIY_RENDER') === true
|
||||
if (!debug) return
|
||||
console.log('[DIY][component]', 'home_image', 'mounted', {
|
||||
id: this.dataConfig?.id,
|
||||
timestamp: this.dataConfig?.timestamp,
|
||||
url: this.imgConfig?.url || this.dataConfig?.imgConfig?.url
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
// 图片配置
|
||||
imgConfig() {
|
||||
return this.dataConfig.imgConfig || {}
|
||||
},
|
||||
// 链接配置
|
||||
linkConfig() {
|
||||
return this.dataConfig.linkConfig || {}
|
||||
},
|
||||
// 二维码模式
|
||||
isQrCode() {
|
||||
return this.dataConfig.isQrCode?.val === true
|
||||
},
|
||||
// 上边距
|
||||
topConfig() {
|
||||
return this.dataConfig.topConfig || {}
|
||||
},
|
||||
// 下边距
|
||||
bottomConfig() {
|
||||
return this.dataConfig.bottomConfig || {}
|
||||
},
|
||||
// 左右边距
|
||||
prConfig() {
|
||||
return this.dataConfig.prConfig || {}
|
||||
},
|
||||
// 页面间距
|
||||
mbConfig() {
|
||||
return this.dataConfig.mbConfig || {}
|
||||
},
|
||||
// 圆角配置
|
||||
fillet() {
|
||||
return this.dataConfig.fillet || {}
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor() {
|
||||
return this.dataConfig.bgColor || {}
|
||||
},
|
||||
// 图片地址
|
||||
imgUrl() {
|
||||
const url = this.imgConfig.url || ''
|
||||
if (!url) return ''
|
||||
// 处理安全域名
|
||||
if (url.indexOf('https://') > -1) return url
|
||||
return url.replace('http://', 'https://')
|
||||
},
|
||||
// 链接地址(从 imgConfig.link 获取)
|
||||
linkUrl() {
|
||||
// 直接从 imgConfig.link 获取字符串链接
|
||||
const link = this.imgConfig.link || this.linkConfig?.link || ''
|
||||
if (!link) return ''
|
||||
// 支持字符串链接
|
||||
if (typeof link === 'string') {
|
||||
return link
|
||||
}
|
||||
// 支持对象结构 { url: '', title: '' }
|
||||
if (typeof link === 'object') {
|
||||
return link.url || ''
|
||||
}
|
||||
// 兼容旧的数组结构
|
||||
if (Array.isArray(link) && link.length > 0) {
|
||||
return link[0]?.link || link[0]?.url || ''
|
||||
}
|
||||
return ''
|
||||
},
|
||||
// 图片显示模式
|
||||
imgMode() {
|
||||
return 'widthFix'
|
||||
},
|
||||
// 容器样式
|
||||
containerStyle() {
|
||||
const style = {}
|
||||
|
||||
// 上边距
|
||||
if (this.topConfig.val) {
|
||||
style.marginTop = this.topConfig.val + 'rpx'
|
||||
}
|
||||
|
||||
// 页面间距
|
||||
if (this.mbConfig.val) {
|
||||
style.marginBottom = this.mbConfig.val + 'rpx'
|
||||
}
|
||||
|
||||
// 下边距
|
||||
if (this.bottomConfig.val) {
|
||||
style.paddingBottom = this.bottomConfig.val + 'rpx'
|
||||
}
|
||||
|
||||
// 背景颜色
|
||||
if (this.bgColor.color && this.bgColor.color[0]?.item) {
|
||||
style.backgroundColor = this.bgColor.color[0].item
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
// 图片容器样式
|
||||
imgContainerStyle() {
|
||||
const style = {}
|
||||
const fillet = this.fillet
|
||||
|
||||
// 左右边距
|
||||
if (this.prConfig.val) {
|
||||
style.paddingLeft = this.prConfig.val + 'rpx'
|
||||
style.paddingRight = this.prConfig.val + 'rpx'
|
||||
}
|
||||
|
||||
// 圆角 - 全部圆角模式
|
||||
if (fillet.type === 0 && fillet.val) {
|
||||
style.borderRadius = fillet.val + 'rpx'
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
// 图片样式
|
||||
imgStyle() {
|
||||
const style = {}
|
||||
const fillet = this.fillet
|
||||
|
||||
// 圆角 - 单独设置模式
|
||||
if (fillet.type === 1 && fillet.valList) {
|
||||
const [tl, tr, br, bl] = fillet.valList
|
||||
const radius = `${tl?.val || 0}rpx ${tr?.val || 0}rpx ${br?.val || 0}rpx ${bl?.val || 0}rpx`
|
||||
style.borderRadius = radius
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onImgLoad(e) {
|
||||
const debug = uni.getStorageSync('DEBUG_DIY_RENDER') === true
|
||||
if (!debug) return
|
||||
console.log('[DIY][component]', 'home_image', 'img_load', {
|
||||
id: this.dataConfig?.id,
|
||||
timestamp: this.dataConfig?.timestamp,
|
||||
src: this.imgUrl,
|
||||
width: e?.detail?.width,
|
||||
height: e?.detail?.height
|
||||
})
|
||||
},
|
||||
// 图片加载失败处理
|
||||
onImgError(e) {
|
||||
const debug = uni.getStorageSync('DEBUG_DIY_RENDER') === true
|
||||
if (debug) {
|
||||
console.log('[DIY][component]', 'home_image', 'img_error', {
|
||||
id: this.dataConfig?.id,
|
||||
timestamp: this.dataConfig?.timestamp,
|
||||
src: this.imgUrl,
|
||||
detail: e?.detail
|
||||
})
|
||||
}
|
||||
this.imgError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.images-component {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.images-navigator {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.images-img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.images-placeholder {
|
||||
width: 100%;
|
||||
height: 300rpx;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.placeholder-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,216 +1,216 @@
|
||||
<template>
|
||||
<view v-show="!isHidden" class="page-title-wrap" :style="wrapStyleStr">
|
||||
<view class="page-title-box px-20" :style="pageTitleBoxStyleStr">
|
||||
<view class="nav-inner" :style="navInnerStyleStr">
|
||||
<view class="side" :style="leftSideStyleStr" @tap="onBack">
|
||||
<text class="iconfont icon-ic_leftarrow" :style="iconStyleStr"></text>
|
||||
</view>
|
||||
<view class="title" :style="titleStyleStr">{{ titleText }}</view>
|
||||
<view class="side" :style="rightSideStyleStr"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'pageTopbar',
|
||||
props: {
|
||||
dataConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
__statusBarRpx: 0,
|
||||
__statusBarPx: 0,
|
||||
__rpx2px: 0.5,
|
||||
__navHeightPx: 44
|
||||
}
|
||||
},
|
||||
created() {
|
||||
try {
|
||||
const info = uni.getWindowInfo ? uni.getWindowInfo() : uni.getSystemInfoSync()
|
||||
const statusBarPx = Number(info?.statusBarHeight || 0)
|
||||
const windowWidthPx = Number(info?.windowWidth || 375)
|
||||
this.__statusBarPx = statusBarPx
|
||||
this.__rpx2px = windowWidthPx ? windowWidthPx / 750 : 0.5
|
||||
this.__navHeightPx = 80 * this.__rpx2px
|
||||
this.__statusBarRpx = windowWidthPx ? (statusBarPx * 750) / windowWidthPx : 0
|
||||
} catch (e) {
|
||||
this.__statusBarPx = 0
|
||||
this.__rpx2px = 0.5
|
||||
this.__navHeightPx = 44
|
||||
this.__statusBarRpx = 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const debug = uni.getStorageSync('DEBUG_DIY_RENDER') === true
|
||||
if (!debug) return
|
||||
console.log('[DIY][component]', 'pageTopbar', 'mounted', {
|
||||
id: this.dataConfig?.id,
|
||||
timestamp: this.dataConfig?.timestamp,
|
||||
title: this.dataConfig?.titleConfig?.value,
|
||||
statusBarRpx: this.__statusBarRpx,
|
||||
statusBarPx: this.__statusBarPx,
|
||||
rpx2px: this.__rpx2px,
|
||||
navHeightPx: this.__navHeightPx,
|
||||
sideWidthPx: this.sideWidthPx
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
try {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('.page-title-wrap')
|
||||
.boundingClientRect(rect => {
|
||||
console.log('[DIY][component]', 'pageTopbar', 'rect', rect)
|
||||
})
|
||||
.select('.page-title-box')
|
||||
.boundingClientRect(rect => {
|
||||
console.log('[DIY][component]', 'pageTopbar', 'rect_box', rect)
|
||||
})
|
||||
.select('.nav-inner')
|
||||
.boundingClientRect(rect => {
|
||||
console.log('[DIY][component]', 'pageTopbar', 'rect_inner', rect)
|
||||
})
|
||||
.exec()
|
||||
} catch (e) {}
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
isHidden() {
|
||||
return this.dataConfig?.isHide === true || this.dataConfig?.isHide === '1'
|
||||
},
|
||||
titleText() {
|
||||
return this.dataConfig?.titleConfig?.value || ''
|
||||
},
|
||||
linkValue() {
|
||||
return this.dataConfig?.linkConfig?.value || ''
|
||||
},
|
||||
themeColorValue() {
|
||||
return this.dataConfig?.themeColor?.color?.[0]?.item || '#282828'
|
||||
},
|
||||
titleColorValue() {
|
||||
return this.dataConfig?.titleColor?.color?.[0]?.item || '#282828'
|
||||
},
|
||||
bgColorValue() {
|
||||
return this.dataConfig?.bgColor?.color?.[0]?.item || '#ffffff'
|
||||
},
|
||||
fontSizeVal() {
|
||||
return Number(this.dataConfig?.fontSize?.val ?? 16)
|
||||
},
|
||||
iconSizeVal() {
|
||||
return Number(this.dataConfig?.iconSize?.val ?? 18)
|
||||
},
|
||||
navHeightRpx() {
|
||||
return 80
|
||||
},
|
||||
topVal() {
|
||||
return Number(this.dataConfig?.topConfig?.val ?? 0)
|
||||
},
|
||||
bottomVal() {
|
||||
return Number(this.dataConfig?.bottomConfig?.val ?? 0)
|
||||
},
|
||||
prVal() {
|
||||
return Number(this.dataConfig?.prConfig?.val ?? 0)
|
||||
},
|
||||
mbVal() {
|
||||
return Number(this.dataConfig?.mbConfig?.val ?? 0)
|
||||
},
|
||||
fillet() {
|
||||
return this.dataConfig?.fillet || {}
|
||||
},
|
||||
wrapStyleStr() {
|
||||
const top = this.topVal * 2
|
||||
const padTop = Math.max(0, Number(top))
|
||||
return `background:#f5f5f5;margin-top:${this.mbVal * 2}rpx;padding-top:${padTop}rpx;padding-bottom:${this.bottomVal * 2}rpx;padding-left:${this.prVal * 2}rpx;padding-right:${this.prVal * 2}rpx;`
|
||||
},
|
||||
pageTitleBoxStyleStr() {
|
||||
const base = `width:100%;display:flex;align-items:center;padding-top:${this.__statusBarPx}px;box-sizing:border-box;`
|
||||
const bg = `background:${this.bgColorValue};`
|
||||
let radius = ''
|
||||
if (this.fillet.type === 0 && this.fillet.val !== undefined) {
|
||||
radius = `border-radius:${Number(this.fillet.val) * 2}rpx;`
|
||||
}
|
||||
if (this.fillet.type === 1 && Array.isArray(this.fillet.valList)) {
|
||||
const [tl, tr, br, bl] = this.fillet.valList
|
||||
radius = `border-radius:${Number(tl?.val || 0) * 2}rpx ${Number(tr?.val || 0) * 2}rpx ${Number(br?.val || 0) * 2}rpx ${Number(bl?.val || 0) * 2}rpx;`
|
||||
}
|
||||
return base + bg + radius
|
||||
},
|
||||
navInnerStyleStr() {
|
||||
return `width:100%;display:flex;align-items:center;height:${this.__navHeightPx}px;box-sizing:border-box;`
|
||||
},
|
||||
leftSideStyleStr() {
|
||||
return `width:${this.sideWidthPx}px;height:100%;display:flex;align-items:center;justify-content:flex-start;`
|
||||
},
|
||||
rightSideStyleStr() {
|
||||
return `width:${this.sideWidthPx}px;height:100%;display:flex;align-items:center;justify-content:flex-end;`
|
||||
},
|
||||
iconStyleStr() {
|
||||
return `font-size:${this.iconSizeVal * 2}rpx;color:${this.themeColorValue};font-weight:bold;`
|
||||
},
|
||||
titleStyleStr() {
|
||||
return `font-size:${this.fontSizeVal * 2}rpx;color:${this.titleColorValue};flex:1;font-weight:bold;text-align:center;line-height:${this.__navHeightPx}px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`
|
||||
},
|
||||
sideWidthRpx() {
|
||||
return Math.max(80, this.iconSizeVal * 2 + 28)
|
||||
},
|
||||
sideWidth() {
|
||||
return `${this.sideWidthRpx}rpx`
|
||||
},
|
||||
sideWidthPx() {
|
||||
return this.sideWidthRpx * (this.__rpx2px || 0.5)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onBack() {
|
||||
const url = this.linkValue
|
||||
if (!url) return
|
||||
const pure = url.split('?')[0]
|
||||
const tabPages = [
|
||||
'/pages/index/index',
|
||||
'/pages/goods_cate/goods_cate',
|
||||
'/pages/discoverIndex/index',
|
||||
'/pages/order_addcart/order_addcart',
|
||||
'/pages/user/index'
|
||||
]
|
||||
if (tabPages.includes(pure)) {
|
||||
uni.switchTab({ url })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-title-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
.page-title-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.nav-inner {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.title {
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view v-show="!isHidden" class="page-title-wrap" :style="wrapStyleStr">
|
||||
<view class="page-title-box px-20" :style="pageTitleBoxStyleStr">
|
||||
<view class="nav-inner" :style="navInnerStyleStr">
|
||||
<view class="side" :style="leftSideStyleStr" @tap="onBack">
|
||||
<text class="iconfont icon-ic_leftarrow" :style="iconStyleStr"></text>
|
||||
</view>
|
||||
<view class="title" :style="titleStyleStr">{{ titleText }}</view>
|
||||
<view class="side" :style="rightSideStyleStr"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'pageTopbar',
|
||||
props: {
|
||||
dataConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
__statusBarRpx: 0,
|
||||
__statusBarPx: 0,
|
||||
__rpx2px: 0.5,
|
||||
__navHeightPx: 44
|
||||
}
|
||||
},
|
||||
created() {
|
||||
try {
|
||||
const info = uni.getWindowInfo ? uni.getWindowInfo() : uni.getSystemInfoSync()
|
||||
const statusBarPx = Number(info?.statusBarHeight || 0)
|
||||
const windowWidthPx = Number(info?.windowWidth || 375)
|
||||
this.__statusBarPx = statusBarPx
|
||||
this.__rpx2px = windowWidthPx ? windowWidthPx / 750 : 0.5
|
||||
this.__navHeightPx = 80 * this.__rpx2px
|
||||
this.__statusBarRpx = windowWidthPx ? (statusBarPx * 750) / windowWidthPx : 0
|
||||
} catch (e) {
|
||||
this.__statusBarPx = 0
|
||||
this.__rpx2px = 0.5
|
||||
this.__navHeightPx = 44
|
||||
this.__statusBarRpx = 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const debug = uni.getStorageSync('DEBUG_DIY_RENDER') === true
|
||||
if (!debug) return
|
||||
console.log('[DIY][component]', 'pageTopbar', 'mounted', {
|
||||
id: this.dataConfig?.id,
|
||||
timestamp: this.dataConfig?.timestamp,
|
||||
title: this.dataConfig?.titleConfig?.value,
|
||||
statusBarRpx: this.__statusBarRpx,
|
||||
statusBarPx: this.__statusBarPx,
|
||||
rpx2px: this.__rpx2px,
|
||||
navHeightPx: this.__navHeightPx,
|
||||
sideWidthPx: this.sideWidthPx
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
try {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('.page-title-wrap')
|
||||
.boundingClientRect(rect => {
|
||||
console.log('[DIY][component]', 'pageTopbar', 'rect', rect)
|
||||
})
|
||||
.select('.page-title-box')
|
||||
.boundingClientRect(rect => {
|
||||
console.log('[DIY][component]', 'pageTopbar', 'rect_box', rect)
|
||||
})
|
||||
.select('.nav-inner')
|
||||
.boundingClientRect(rect => {
|
||||
console.log('[DIY][component]', 'pageTopbar', 'rect_inner', rect)
|
||||
})
|
||||
.exec()
|
||||
} catch (e) {}
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
isHidden() {
|
||||
return this.dataConfig?.isHide === true || this.dataConfig?.isHide === '1'
|
||||
},
|
||||
titleText() {
|
||||
return this.dataConfig?.titleConfig?.value || ''
|
||||
},
|
||||
linkValue() {
|
||||
return this.dataConfig?.linkConfig?.value || ''
|
||||
},
|
||||
themeColorValue() {
|
||||
return this.dataConfig?.themeColor?.color?.[0]?.item || '#282828'
|
||||
},
|
||||
titleColorValue() {
|
||||
return this.dataConfig?.titleColor?.color?.[0]?.item || '#282828'
|
||||
},
|
||||
bgColorValue() {
|
||||
return this.dataConfig?.bgColor?.color?.[0]?.item || '#ffffff'
|
||||
},
|
||||
fontSizeVal() {
|
||||
return Number(this.dataConfig?.fontSize?.val ?? 16)
|
||||
},
|
||||
iconSizeVal() {
|
||||
return Number(this.dataConfig?.iconSize?.val ?? 18)
|
||||
},
|
||||
navHeightRpx() {
|
||||
return 80
|
||||
},
|
||||
topVal() {
|
||||
return Number(this.dataConfig?.topConfig?.val ?? 0)
|
||||
},
|
||||
bottomVal() {
|
||||
return Number(this.dataConfig?.bottomConfig?.val ?? 0)
|
||||
},
|
||||
prVal() {
|
||||
return Number(this.dataConfig?.prConfig?.val ?? 0)
|
||||
},
|
||||
mbVal() {
|
||||
return Number(this.dataConfig?.mbConfig?.val ?? 0)
|
||||
},
|
||||
fillet() {
|
||||
return this.dataConfig?.fillet || {}
|
||||
},
|
||||
wrapStyleStr() {
|
||||
const top = this.topVal * 2
|
||||
const padTop = Math.max(0, Number(top))
|
||||
return `background:#f5f5f5;margin-top:${this.mbVal * 2}rpx;padding-top:${padTop}rpx;padding-bottom:${this.bottomVal * 2}rpx;padding-left:${this.prVal * 2}rpx;padding-right:${this.prVal * 2}rpx;`
|
||||
},
|
||||
pageTitleBoxStyleStr() {
|
||||
const base = `width:100%;display:flex;align-items:center;padding-top:${this.__statusBarPx}px;box-sizing:border-box;`
|
||||
const bg = `background:${this.bgColorValue};`
|
||||
let radius = ''
|
||||
if (this.fillet.type === 0 && this.fillet.val !== undefined) {
|
||||
radius = `border-radius:${Number(this.fillet.val) * 2}rpx;`
|
||||
}
|
||||
if (this.fillet.type === 1 && Array.isArray(this.fillet.valList)) {
|
||||
const [tl, tr, br, bl] = this.fillet.valList
|
||||
radius = `border-radius:${Number(tl?.val || 0) * 2}rpx ${Number(tr?.val || 0) * 2}rpx ${Number(br?.val || 0) * 2}rpx ${Number(bl?.val || 0) * 2}rpx;`
|
||||
}
|
||||
return base + bg + radius
|
||||
},
|
||||
navInnerStyleStr() {
|
||||
return `width:100%;display:flex;align-items:center;height:${this.__navHeightPx}px;box-sizing:border-box;`
|
||||
},
|
||||
leftSideStyleStr() {
|
||||
return `width:${this.sideWidthPx}px;height:100%;display:flex;align-items:center;justify-content:flex-start;`
|
||||
},
|
||||
rightSideStyleStr() {
|
||||
return `width:${this.sideWidthPx}px;height:100%;display:flex;align-items:center;justify-content:flex-end;`
|
||||
},
|
||||
iconStyleStr() {
|
||||
return `font-size:${this.iconSizeVal * 2}rpx;color:${this.themeColorValue};font-weight:bold;`
|
||||
},
|
||||
titleStyleStr() {
|
||||
return `font-size:${this.fontSizeVal * 2}rpx;color:${this.titleColorValue};flex:1;font-weight:bold;text-align:center;line-height:${this.__navHeightPx}px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`
|
||||
},
|
||||
sideWidthRpx() {
|
||||
return Math.max(80, this.iconSizeVal * 2 + 28)
|
||||
},
|
||||
sideWidth() {
|
||||
return `${this.sideWidthRpx}rpx`
|
||||
},
|
||||
sideWidthPx() {
|
||||
return this.sideWidthRpx * (this.__rpx2px || 0.5)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onBack() {
|
||||
const url = this.linkValue
|
||||
if (!url) return
|
||||
const pure = url.split('?')[0]
|
||||
const tabPages = [
|
||||
'/pages/index/index',
|
||||
'/pages/goods_cate/goods_cate',
|
||||
'/pages/discoverIndex/index',
|
||||
'/pages/order_addcart/order_addcart',
|
||||
'/pages/user/index'
|
||||
]
|
||||
if (tabPages.includes(pure)) {
|
||||
uni.switchTab({ url })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-title-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
.page-title-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.nav-inner {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.title {
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,102 +1,102 @@
|
||||
<template>
|
||||
<!-- 富文本 -->
|
||||
<view v-if="!dataConfig.isHide" :style="[richTextWrapStyle]">
|
||||
<view class="richText" v-if="description" :style="[richTextStyle]">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- <rich-text :nodes="description"></rich-text> -->
|
||||
<jyf-parser :html="description" :tag-style="tagStyle" ref="article"></jyf-parser>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<view v-html="description"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import parser from '@/components/jyf-parser/jyf-parser';
|
||||
export default {
|
||||
name: 'richText',
|
||||
props: {
|
||||
dataConfig: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
isSortType: {
|
||||
type: String | Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
components: { 'jyf-parser': parser },
|
||||
data() {
|
||||
return {
|
||||
bgColor: '',
|
||||
lrConfig: 0,
|
||||
udConfig: 0,
|
||||
tagStyle: {
|
||||
img: 'width:100%;display:block;',
|
||||
table: 'width:100%',
|
||||
video: 'width:100%;float:left;'
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
description() {
|
||||
let description = this.dataConfig.richText.val;
|
||||
if (description) {
|
||||
description = description.replace(
|
||||
/<img/gi,
|
||||
'<img style="max-width:100%;height:auto;float:left;display:block" '
|
||||
);
|
||||
description = description.replace(
|
||||
/<video/gi,
|
||||
'<video style="width:100%;height:auto;display:block" '
|
||||
);
|
||||
}
|
||||
return description;
|
||||
},
|
||||
richTextStyle() {
|
||||
let borderRadius = `${this.dataConfig.fillet.val * 2}rpx`;
|
||||
if (this.dataConfig.fillet.type) {
|
||||
borderRadius =
|
||||
`${this.dataConfig.fillet.valList[0].val * 2}rpx ${this.dataConfig.fillet.valList[1].val * 2}rpx ${this.dataConfig.fillet.valList[2].val * 2}rpx ${this.dataConfig.fillet.valList[3].val * 2}rpx`;
|
||||
}
|
||||
return {
|
||||
'border-radius': borderRadius,
|
||||
'background': this.dataConfig.bgColor.color[0].item,
|
||||
};
|
||||
},
|
||||
richTextWrapStyle() {
|
||||
return {
|
||||
'padding': `${this.dataConfig.topConfig.val * 2}rpx ${this.dataConfig.lrConfig.val * 2}rpx ${this.dataConfig.bottomConfig.val * 2}rpx`,
|
||||
'margin-top': `${this.dataConfig.udConfig.val * 2}rpx`,
|
||||
'background': this.dataConfig.bottomBgColor.color[0].item,
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.richText {
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
|
||||
// margin: 0 20rpx;
|
||||
// border-radius: 24rpx;
|
||||
&::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/uni-video {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/deep/video {
|
||||
width: 100% !important;
|
||||
}
|
||||
<template>
|
||||
<!-- 富文本 -->
|
||||
<view v-if="!dataConfig.isHide" :style="[richTextWrapStyle]">
|
||||
<view class="richText" v-if="description" :style="[richTextStyle]">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- <rich-text :nodes="description"></rich-text> -->
|
||||
<jyf-parser :html="description" :tag-style="tagStyle" ref="article"></jyf-parser>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<view v-html="description"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import parser from '@/components/jyf-parser/jyf-parser';
|
||||
export default {
|
||||
name: 'richText',
|
||||
props: {
|
||||
dataConfig: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
isSortType: {
|
||||
type: String | Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
components: { 'jyf-parser': parser },
|
||||
data() {
|
||||
return {
|
||||
bgColor: '',
|
||||
lrConfig: 0,
|
||||
udConfig: 0,
|
||||
tagStyle: {
|
||||
img: 'width:100%;display:block;',
|
||||
table: 'width:100%',
|
||||
video: 'width:100%;float:left;'
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
description() {
|
||||
let description = this.dataConfig.richText.val;
|
||||
if (description) {
|
||||
description = description.replace(
|
||||
/<img/gi,
|
||||
'<img style="max-width:100%;height:auto;float:left;display:block" '
|
||||
);
|
||||
description = description.replace(
|
||||
/<video/gi,
|
||||
'<video style="width:100%;height:auto;display:block" '
|
||||
);
|
||||
}
|
||||
return description;
|
||||
},
|
||||
richTextStyle() {
|
||||
let borderRadius = `${this.dataConfig.fillet.val * 2}rpx`;
|
||||
if (this.dataConfig.fillet.type) {
|
||||
borderRadius =
|
||||
`${this.dataConfig.fillet.valList[0].val * 2}rpx ${this.dataConfig.fillet.valList[1].val * 2}rpx ${this.dataConfig.fillet.valList[2].val * 2}rpx ${this.dataConfig.fillet.valList[3].val * 2}rpx`;
|
||||
}
|
||||
return {
|
||||
'border-radius': borderRadius,
|
||||
'background': this.dataConfig.bgColor.color[0].item,
|
||||
};
|
||||
},
|
||||
richTextWrapStyle() {
|
||||
return {
|
||||
'padding': `${this.dataConfig.topConfig.val * 2}rpx ${this.dataConfig.lrConfig.val * 2}rpx ${this.dataConfig.bottomConfig.val * 2}rpx`,
|
||||
'margin-top': `${this.dataConfig.udConfig.val * 2}rpx`,
|
||||
'background': this.dataConfig.bottomBgColor.color[0].item,
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.richText {
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
|
||||
// margin: 0 20rpx;
|
||||
// border-radius: 24rpx;
|
||||
&::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/uni-video {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/deep/video {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -73,14 +73,14 @@ export default {
|
||||
</view>
|
||||
</view>
|
||||
<!-- 余额、优惠券 -->
|
||||
<view class="acea-row balance-coupon">
|
||||
<!-- <view class="acea-row balance-coupon">
|
||||
<view class="item" v-for="(item, index) in property" @tap="goMenuPage(item.url)" :key="index">
|
||||
<view class="value num-fa-semi">{{ item.value || 0 }}</view>
|
||||
<view>{{ item.label }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- 会员中心、积分商城 -->
|
||||
<view class="acea-row member-points">
|
||||
<!-- <view class="acea-row member-points">
|
||||
<view class="acea-row row-middle row-center item" @tap="intoPage(userInfo.level_status == 1 ? '/pages/users/user_vip/index' : '/pages/annex/vip_grade_active/index')">
|
||||
<view>
|
||||
<view>会员中心</view>
|
||||
@@ -101,7 +101,7 @@ export default {
|
||||
</view>
|
||||
<image src="@/static/images/user-points.png" class="image"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
435
pages/user/components/myApps/index.vue
Normal file
435
pages/user/components/myApps/index.vue
Normal file
@@ -0,0 +1,435 @@
|
||||
<template>
|
||||
<view class="my-apps-wrap">
|
||||
<!-- 展示模式 -->
|
||||
<template v-if="!editing">
|
||||
<!-- 顶部:标题 + 编辑按钮 -->
|
||||
<view class="pt-34 pr-30 pb-20 pl-32 bg--w111-fff rd-t-16rpx mt-20 service ml-20 mr-20">
|
||||
<view class="flex-row-between">
|
||||
<view class="fs-30 fw-500 lh-42rpx text--w111-333">我的应用</view>
|
||||
<view class="edit-btn" @click="enterEdit">
|
||||
<text class="fs-24 text--w111-1890ff">编辑首页应用</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 应用列表 -->
|
||||
<view class="pt-10 pr-30 pb-32 pl-32 bg--w111-fff rd-b-16rpx service ml-20 mr-20" v-if="allApps.length">
|
||||
<view class="grid-column-4 grid-gap-x-76rpx grid-gap-y-54rpx">
|
||||
<view v-for="(item, index) in allApps" :key="index">
|
||||
<view class="flex-col flex-center" @click="onAppTap(item)">
|
||||
<image :src="item.logo" class="app-icon rd-16rpx" v-if="item.logo"></image>
|
||||
<view class="empty-box" v-else></view>
|
||||
<text class="fs-26 lh-36rpx text--w111-282828 pt-22">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 编辑模式 -->
|
||||
<template v-else>
|
||||
<!-- 编辑卡片:整合头栏 + 首页展示栏 + 全部应用 -->
|
||||
<view class="bg--w111-fff rd-16rpx mt-20 ml-20 mr-20">
|
||||
<!-- 顶部操作栏 -->
|
||||
<view class="edit-header">
|
||||
<view class="edit-header-inner">
|
||||
<view class="header-btn btn-cancel" @click="cancelEdit">取消</view>
|
||||
<view class="fs-30 fw-500 text--w111-333">编辑首页展示</view>
|
||||
<view class="header-btn btn-save" @click="saveEdit">保存</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 首页展示栏(横向滚动) -->
|
||||
<view class="home-bar-wrap" v-if="editSelectedIds.length">
|
||||
<view class="home-bar-label fs-28 text--w111-333">首页展示栏</view>
|
||||
<scroll-view class="home-bar-scroll" scroll-x enhanced show-scrollbar="false">
|
||||
<view class="home-bar-inner">
|
||||
<view
|
||||
class="home-bar-item"
|
||||
v-for="(item, index) in editSelectedList"
|
||||
:key="item.id"
|
||||
>
|
||||
<view class="home-bar-icon-wrap">
|
||||
<image :src="item.logo" class="home-bar-icon rd-16rpx" v-if="item.logo"></image>
|
||||
<view class="empty-box-sm" v-else></view>
|
||||
<!-- 删除标记 -->
|
||||
<view class="badge badge-remove" @click="removeFromHome(item.id)">✕</view>
|
||||
</view>
|
||||
<text class="home-bar-name">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 全部应用列表 -->
|
||||
<view class="all-apps-list">
|
||||
<view class="fs-28 text--w111-333 mb-20">全部应用</view>
|
||||
<view class="grid-column-4 grid-gap-x-76rpx grid-gap-y-54rpx">
|
||||
<view v-for="(item, index) in allApps" :key="index">
|
||||
<view
|
||||
class="flex-col flex-center pos-relative"
|
||||
@click="toggleAppSelect(item)"
|
||||
>
|
||||
<view class="app-icon-wrap">
|
||||
<image :src="item.logo" class="app-icon rd-16rpx" v-if="item.logo"></image>
|
||||
<view class="empty-box" v-else></view>
|
||||
<!-- 已选遮罩 -->
|
||||
<view class="selected-overlay" v-if="isSelected(item.id)">
|
||||
<text class="selected-check">✓</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 添加标记(未选中时) -->
|
||||
<view
|
||||
class="badge badge-add"
|
||||
v-if="!isSelected(item.id) && editSelectedIds.length < 6"
|
||||
@click.stop="addToHome(item)"
|
||||
>+</view>
|
||||
<text
|
||||
class="fs-26 lh-36rpx pt-22"
|
||||
:class="isSelected(item.id) ? 'text--w111-ccc' : 'text--w111-282828'"
|
||||
>{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const STORAGE_KEY = 'MY_HOME_APPS'
|
||||
const MAX_HOME_APPS = 6
|
||||
|
||||
export default {
|
||||
name: 'userMyApps',
|
||||
data() {
|
||||
return {
|
||||
allApps: [],
|
||||
// Storage 数据
|
||||
homeApps: {
|
||||
showOnHome: true,
|
||||
appIds: []
|
||||
},
|
||||
editing: false,
|
||||
// 编辑中的临时副本
|
||||
editAppIds: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
editSelectedIds() {
|
||||
return this.editAppIds
|
||||
},
|
||||
// 编辑模式中已选应用的完整对象列表(按 allApps 顺序排列)
|
||||
editSelectedList() {
|
||||
const idSet = new Set(this.editAppIds)
|
||||
const filtered = this.allApps.filter(item => idSet.has(item.id))
|
||||
const indexMap = new Map(this.allApps.map((item, i) => [item.id, i]))
|
||||
filtered.sort((a, b) => (indexMap.get(a.id) ?? 999) - (indexMap.get(b.id) ?? 999))
|
||||
return filtered
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
this.fetchApps()
|
||||
},
|
||||
// 从首页"更多"跳转过来时刷新数据
|
||||
onShow() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
// ---------- 数据层 ----------
|
||||
loadData() {
|
||||
try {
|
||||
const raw = uni.getStorageSync(STORAGE_KEY)
|
||||
if (raw) {
|
||||
this.homeApps = raw
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[myApps] loadData error:', e)
|
||||
}
|
||||
},
|
||||
saveData() {
|
||||
try {
|
||||
uni.setStorageSync(STORAGE_KEY, this.homeApps)
|
||||
} catch (e) {
|
||||
console.error('[myApps] saveData error:', e)
|
||||
}
|
||||
},
|
||||
fetchApps() {
|
||||
uni.request({
|
||||
url: 'https://mp.yizhezhe.com/api/mp/apps',
|
||||
data: {
|
||||
isPublished: 1,
|
||||
pageSize: 50
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.data?.code === 200 && res.data?.result?.items) {
|
||||
this.allApps = res.data.result.items
|
||||
} else if (res.data?.status === 200 && res.data?.data) {
|
||||
this.allApps = res.data.data
|
||||
} else {
|
||||
return
|
||||
}
|
||||
this.autoFillHomeApps()
|
||||
},
|
||||
fail: () => {
|
||||
this.allApps = []
|
||||
}
|
||||
})
|
||||
},
|
||||
// 自动填充首页展示应用(与 appCenter 逻辑一致)
|
||||
autoFillHomeApps() {
|
||||
const ids = this.homeApps.appIds || []
|
||||
const currentCount = ids.length
|
||||
if (currentCount >= 6) return
|
||||
|
||||
const idSet = new Set(ids)
|
||||
const recommended = this.allApps
|
||||
.filter(item => item.isRecommended && !idSet.has(item.id))
|
||||
|
||||
if (!recommended.length) return
|
||||
|
||||
const slots = 6 - currentCount
|
||||
const toAdd = recommended.slice(0, slots).map(item => item.id)
|
||||
|
||||
this.homeApps.appIds = [...ids, ...toAdd]
|
||||
this.saveData()
|
||||
},
|
||||
|
||||
// ---------- 展示模式 ----------
|
||||
onAppTap(item) {
|
||||
if (!item.url) return
|
||||
this.$util.JumpPath(item.url)
|
||||
},
|
||||
|
||||
// ---------- 编辑模式 ----------
|
||||
enterEdit() {
|
||||
// 复制当前 appIds 作为编辑副本
|
||||
this.editAppIds = [...(this.homeApps.appIds || [])]
|
||||
this.editing = true
|
||||
},
|
||||
cancelEdit() {
|
||||
this.editing = false
|
||||
this.editAppIds = []
|
||||
},
|
||||
saveEdit() {
|
||||
this.homeApps.appIds = [...this.editAppIds]
|
||||
this.saveData()
|
||||
this.editing = false
|
||||
},
|
||||
// 判断应用是否已选
|
||||
isSelected(id) {
|
||||
return this.editAppIds.includes(id)
|
||||
},
|
||||
// 添加到首页栏(从全部应用点击+)
|
||||
addToHome(item) {
|
||||
if (this.editAppIds.length >= MAX_HOME_APPS) {
|
||||
uni.showToast({ title: `最多选择${MAX_HOME_APPS}个应用`, icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!this.editAppIds.includes(item.id)) {
|
||||
this.editAppIds.push(item.id)
|
||||
}
|
||||
},
|
||||
// 从首页栏移除(点击✕)
|
||||
removeFromHome(id) {
|
||||
this.editAppIds = this.editAppIds.filter(i => i !== id)
|
||||
},
|
||||
// 点击应用图标切换选中状态
|
||||
toggleAppSelect(item) {
|
||||
if (this.isSelected(item.id)) {
|
||||
this.removeFromHome(item.id)
|
||||
} else {
|
||||
this.addToHome(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.my-apps-wrap {
|
||||
// 左右布局
|
||||
.flex-row-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// 应用图标(展示模式 + 编辑模式通用)
|
||||
.app-icon {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
}
|
||||
.empty-box {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
background-color: #f3f9ff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.app-icon-wrap {
|
||||
position: relative;
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
}
|
||||
|
||||
// 编辑按钮
|
||||
.edit-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6rpx 20rpx;
|
||||
border: 2rpx solid #1890ff;
|
||||
border-radius: 10rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// ---------- 编辑模式 ----------
|
||||
.edit-header {
|
||||
padding: 24rpx 30rpx 0;
|
||||
|
||||
.edit-header-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 56rpx;
|
||||
padding: 0 28rpx;
|
||||
border-radius: 28rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
color: #666;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #1890ff, #096dd9);
|
||||
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.35);
|
||||
}
|
||||
}
|
||||
|
||||
// 首页展示栏
|
||||
.home-bar-wrap {
|
||||
padding: 24rpx 30rpx 0;
|
||||
margin-top: 20rpx;
|
||||
|
||||
.home-bar-label {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.home-bar-scroll {
|
||||
white-space: nowrap;
|
||||
overflow: visible !important;
|
||||
|
||||
.home-bar-inner {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
|
||||
.home-bar-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 100rpx;
|
||||
padding-top: 12rpx;
|
||||
|
||||
.home-bar-icon-wrap {
|
||||
position: relative;
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
overflow: visible;
|
||||
|
||||
.home-bar-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.empty-box-sm {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f3f9ff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.home-bar-name {
|
||||
font-size: 22rpx;
|
||||
color: #333;
|
||||
margin-top: 8rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 全部应用列表
|
||||
.all-apps-list {
|
||||
padding: 30rpx 30rpx 34rpx;
|
||||
}
|
||||
|
||||
// ---------- 通用徽标 ----------
|
||||
.badge {
|
||||
position: absolute;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
font-size: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.badge-remove {
|
||||
top: -8rpx;
|
||||
right: -8rpx;
|
||||
background: #ff4d4f;
|
||||
}
|
||||
|
||||
.badge-add {
|
||||
top: -8rpx;
|
||||
right: 6rpx;
|
||||
background: #52c41a;
|
||||
}
|
||||
|
||||
.selected-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
|
||||
.selected-check {
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.pos-relative {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -351,9 +351,16 @@
|
||||
this.isShow = false
|
||||
},
|
||||
back() {
|
||||
uni.navigateBack({
|
||||
delta: this.configData.wechat_auth_switch ? 2 : 1
|
||||
})
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack({
|
||||
delta: this.configData.wechat_auth_switch ? 2 : 1
|
||||
})
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
},
|
||||
privacy(type) {
|
||||
uni.navigateTo({
|
||||
|
||||
@@ -149,6 +149,16 @@ export default {
|
||||
</view>
|
||||
</navigator>
|
||||
</view>
|
||||
<!-- #ifdef MP -->
|
||||
<view class="list rd-24rpx mx-20">
|
||||
<view class="item acea-row row-between-wrapper" @click="outLogin">
|
||||
<view>退出登录</view>
|
||||
<view class="grab">
|
||||
<text class="iconfont icon-ic_rightarrow"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="log-out list rd-24rpx mx-20 acea-row row-center-wrapper" @click="outLogin" v-if="!this.$wechat.isWeixin()">退出登录</view>
|
||||
<!-- #endif -->
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,19 +24,23 @@
|
||||
<view class="wechat-login">
|
||||
<view class="btn-wrapper">
|
||||
<!-- #ifdef H5 -->
|
||||
<button hover-class="none" @tap="wechatLogin" class="bg-color btn1">授权登录</button>
|
||||
<button hover-class="none" @tap="handleAuthBtnTap" class="bg-color btn1">授权登录</button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
<template v-if="configData.wechat_auth_switch">
|
||||
<button hover-class="none" v-if="bindPhone" open-type="getPhoneNumber" @getphonenumber="getphonenumber"
|
||||
<button hover-class="none" v-if="bindPhone && protocol" open-type="getPhoneNumber" @getphonenumber="getphonenumber"
|
||||
class="bg-color btn1">授权登录</button>
|
||||
<button hover-class="none" v-else-if="!bindPhone" @tap="getAuthLogin"
|
||||
<button hover-class="none" v-else-if="bindPhone && !protocol" @tap="handleAuthBtnTap"
|
||||
class="bg-color btn1">授权登录</button>
|
||||
<button hover-class="none" v-else-if="mp_is_new" @tap="userLogin"
|
||||
<button hover-class="none" v-else-if="!bindPhone" @tap="handleAuthBtnTap"
|
||||
class="bg-color btn1">授权登录</button>
|
||||
<button v-else-if="canUseGetUserProfile && code" hover-class="none" @tap="getUserProfile"
|
||||
class="bg-color btn1">授权登录</button>
|
||||
<button v-else hover-class="none" open-type="getUserInfo" @getuserinfo="setUserInfo"
|
||||
<button hover-class="none" v-else-if="mp_is_new" @tap="onAuthorizeLoginTap('mp_userLogin')"
|
||||
class="bg-color btn1">授权登录</button>
|
||||
<button v-else-if="canUseGetUserProfile && code" hover-class="none" @tap="onAuthorizeLoginTap('mp_userProfile')"
|
||||
class="bg-color btn1">授权登录</button>
|
||||
<button v-else-if="protocol" hover-class="none" open-type="getUserInfo" @getuserinfo="setUserInfo"
|
||||
class="bg-color btn1">授权登录</button>
|
||||
<button v-else hover-class="none" @tap="onAuthorizeLoginTap('mp_userinfo')"
|
||||
class="bg-color btn1">授权登录</button>
|
||||
</template>
|
||||
<button v-if="configData.phone_auth_switch" hover-class="none" @tap="phoneLogin" class="btn2">手机号登录</button>
|
||||
@@ -67,6 +71,23 @@
|
||||
<privacyAgreementPopup v-if="canGetPrivacySetting" @onReject="onReject" @onAgree="onAgree">
|
||||
</privacyAgreementPopup>
|
||||
<!-- #endif -->
|
||||
|
||||
<view v-if="showProtocolDialog" class="protocol-modal">
|
||||
<view class="protocol-modal__mask" @tap="rejectProtocol"></view>
|
||||
<view class="protocol-modal__box">
|
||||
<view class="protocol-modal__title">用户协议与隐私协议</view>
|
||||
<view class="protocol-modal__content">
|
||||
<text>已阅读并同意</text>
|
||||
<text class="font-num" @tap.stop="privacy('user')">《用户协议》</text>
|
||||
<text>与</text>
|
||||
<text class="font-num" @tap.stop="privacy('privacy')">《隐私协议》</text>
|
||||
</view>
|
||||
<view class="protocol-modal__actions">
|
||||
<view class="protocol-modal__btn" @tap="rejectProtocol">不同意</view>
|
||||
<view class="protocol-modal__btn protocol-modal__btn--primary bg-color" @tap="agreeProtocol">同意并继续</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -123,6 +144,8 @@
|
||||
protocol: false,
|
||||
bindPhone: false,
|
||||
isShow: false,
|
||||
showProtocolDialog: false,
|
||||
pendingAuthorizeAction: '',
|
||||
};
|
||||
},
|
||||
components: {
|
||||
@@ -169,8 +192,62 @@
|
||||
uni.removeStorageSync('form_type_cart');
|
||||
},
|
||||
methods: {
|
||||
handleAuthBtnTap() {
|
||||
let action = '';
|
||||
// #ifdef H5
|
||||
action = 'h5_wechat';
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
action = this.bindPhone ? 'mp_phone' : 'mp_auth';
|
||||
// #endif
|
||||
this.openProtocolDialog(action);
|
||||
},
|
||||
onAuthorizeLoginTap(action) {
|
||||
this.openProtocolDialog(action);
|
||||
},
|
||||
openProtocolDialog(action) {
|
||||
this.pendingAuthorizeAction = action || '';
|
||||
this.showProtocolDialog = true;
|
||||
},
|
||||
agreeProtocol() {
|
||||
const action = this.pendingAuthorizeAction;
|
||||
this.pendingAuthorizeAction = '';
|
||||
this.showProtocolDialog = false;
|
||||
this.protocol = true;
|
||||
this.$nextTick(() => {
|
||||
this.runAuthorizeAction(action, {
|
||||
afterAgree: true
|
||||
});
|
||||
});
|
||||
},
|
||||
rejectProtocol() {
|
||||
this.pendingAuthorizeAction = '';
|
||||
this.showProtocolDialog = false;
|
||||
},
|
||||
runAuthorizeAction(action, options = {}) {
|
||||
if (!action) return;
|
||||
if (action === 'h5_wechat') return this.wechatLogin();
|
||||
if (action === 'mp_auth') return this.getAuthLogin();
|
||||
if (action === 'mp_userLogin') return this.userLogin();
|
||||
if (action === 'mp_userProfile') return this.getUserProfile();
|
||||
if (action === 'mp_phone' || action === 'mp_userinfo') {
|
||||
if (options.afterAgree) {
|
||||
return this.$util.Tips({
|
||||
title: '请点击授权登录继续'
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
cancelLogin(){
|
||||
uni.navigateBack()
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
},
|
||||
wechatAuthLogin(d, back_url) {
|
||||
let that = this;
|
||||
@@ -286,10 +363,24 @@
|
||||
this.protocol = true
|
||||
},
|
||||
onReject() {
|
||||
uni.navigateBack();
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
},
|
||||
back() {
|
||||
uni.navigateBack();
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
},
|
||||
home() {
|
||||
uni.switchTab({
|
||||
@@ -531,7 +622,7 @@
|
||||
},
|
||||
// 公众号登录
|
||||
wechatLogin() {
|
||||
if (!this.protocol && this.configData.store_user_agreement) {
|
||||
if (!this.protocol) {
|
||||
return this.$util.Tips({
|
||||
title: '请先阅读并同意协议'
|
||||
});
|
||||
@@ -690,4 +781,69 @@
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.protocol-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 20000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.protocol-modal__mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.protocol-modal__box {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 620rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.protocol-modal__title {
|
||||
padding: 40rpx 40rpx 0;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.protocol-modal__content {
|
||||
padding: 32rpx 40rpx 40rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
line-height: 42rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.protocol-modal__actions {
|
||||
display: flex;
|
||||
border-top: 1px solid #EEEEEE;
|
||||
}
|
||||
|
||||
.protocol-modal__btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.protocol-modal__btn--primary {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
1402
static/css/base.css
1402
static/css/base.css
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -21,11 +21,33 @@ import {
|
||||
} from '@/config/cache';
|
||||
import Cache from '@/utils/cache';
|
||||
|
||||
/**
|
||||
* 不依赖 atob 的 base64 解码(小程序环境不提供 atob)
|
||||
*/
|
||||
function base64Decode(str) {
|
||||
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
var output = '';
|
||||
var i = 0;
|
||||
str = String(str).replace(/[^A-Za-z0-9+/=]/g, '');
|
||||
while (i < str.length) {
|
||||
var b1 = chars.indexOf(str.charAt(i++));
|
||||
var b2 = chars.indexOf(str.charAt(i++));
|
||||
var b3 = chars.indexOf(str.charAt(i++));
|
||||
var b4 = chars.indexOf(str.charAt(i++));
|
||||
output += String.fromCharCode((b1 << 2) | (b2 >> 4));
|
||||
if (b3 !== 64) output += String.fromCharCode(((b2 & 15) << 4) | (b3 >> 2));
|
||||
if (b4 !== 64) output += String.fromCharCode(((b3 & 3) << 6) | b4);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
const state = {
|
||||
token: Cache.get(LOGIN_STATUS) || false,
|
||||
backgroundColor: "#fff",
|
||||
userInfo: Cache.get(USER_INFO) || {},
|
||||
uid: Cache.get(UID) || 0,
|
||||
openid: Cache.get('OPENID') || '',
|
||||
unionid: Cache.get('UNIONID') || '',
|
||||
homeActive: false,
|
||||
phoneStatus: true,
|
||||
autoplay: Cache.get(NON_WIFI_AUTOPLAY) || false,
|
||||
@@ -89,11 +111,46 @@ const mutations = {
|
||||
LOGIN(state, opt) {
|
||||
state.token = opt.token;
|
||||
Cache.set(LOGIN_STATUS, opt.token, opt.time);
|
||||
// 登录时自动提取 openid 缓存起来
|
||||
if (opt.token) {
|
||||
try {
|
||||
let parts = opt.token.split('.');
|
||||
if (parts.length !== 3) {
|
||||
console.warn('[LOGIN] token 不是 JWT 格式,parts=' + parts.length);
|
||||
} else {
|
||||
let payload = parts[1]
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
while (payload.length % 4 !== 0) payload += '=';
|
||||
const data = JSON.parse(base64Decode(payload));
|
||||
if (data.openid) {
|
||||
state.openid = data.openid;
|
||||
Cache.set('OPENID', data.openid);
|
||||
console.log('[LOGIN] openid 已缓存:', data.openid);
|
||||
} else {
|
||||
console.warn('[LOGIN] JWT payload 无 openid 字段:', JSON.stringify(data));
|
||||
}
|
||||
if (data.unionid) {
|
||||
state.unionid = data.unionid;
|
||||
Cache.set('UNIONID', data.unionid);
|
||||
console.log('[LOGIN] unionid 已缓存:', data.unionid);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[LOGIN] JWT 解码失败:', e.message, 'token前20位:', opt.token.substring(0, 20));
|
||||
}
|
||||
}
|
||||
},
|
||||
SETUID(state, val) {
|
||||
state.uid = val;
|
||||
Cache.set(UID, val);
|
||||
},
|
||||
SET_OPENID(state, val) {
|
||||
state.openid = val || '';
|
||||
},
|
||||
SET_UNIONID(state, val) {
|
||||
state.unionid = val || '';
|
||||
},
|
||||
UPDATE_LOGIN(state, token) {
|
||||
state.token = token;
|
||||
},
|
||||
@@ -102,8 +159,12 @@ const mutations = {
|
||||
Cache.clear(UID);
|
||||
Cache.clear(USER_INFO);
|
||||
Cache.clear('newcomerGift');
|
||||
Cache.clear('OPENID');
|
||||
Cache.clear('UNIONID');
|
||||
state.token = undefined;
|
||||
state.uid = undefined
|
||||
state.uid = undefined;
|
||||
state.openid = '';
|
||||
state.unionid = '';
|
||||
state.userInfo = {}
|
||||
},
|
||||
BACKGROUND_COLOR(state, color) {
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
let propertyList = [{
|
||||
label: "余额",
|
||||
value: 0,
|
||||
k: "now_money",
|
||||
url: '/pages/users/user_money/index'
|
||||
},
|
||||
{
|
||||
label: "优惠券",
|
||||
value: 1,
|
||||
k: "couponCount",
|
||||
url: '/pages/users/user_coupon/index'
|
||||
},
|
||||
{
|
||||
label: "积分",
|
||||
value: 2,
|
||||
k: "integral",
|
||||
url: '/pages/users/user_integral/index'
|
||||
},
|
||||
{
|
||||
label: "收藏",
|
||||
value: 3,
|
||||
k: "collectProductCount",
|
||||
url: '/pages/users/user_goods_collection/index'
|
||||
},
|
||||
{
|
||||
label: "浏览记录",
|
||||
value: 5,
|
||||
k: "visit_num",
|
||||
url: '/pages/users/visit_list/index'
|
||||
},
|
||||
{
|
||||
label: "推广佣金",
|
||||
value: 6,
|
||||
k: "brokerage_price",
|
||||
url: '/pages/users/user_spread_user/index'
|
||||
},
|
||||
{
|
||||
label: "推广人",
|
||||
value: 7,
|
||||
k: "spread_user_count",
|
||||
url: '/pages/users/user_spread_user/index'
|
||||
},
|
||||
{
|
||||
label: "推广订单",
|
||||
value: 8,
|
||||
k: "spread_order_count",
|
||||
url: '/pages/users/user_spread_user/index'
|
||||
},
|
||||
];
|
||||
let propertyList = [{
|
||||
label: "余额",
|
||||
value: 0,
|
||||
k: "now_money",
|
||||
url: '/pages/users/user_money/index'
|
||||
},
|
||||
{
|
||||
label: "优惠券",
|
||||
value: 1,
|
||||
k: "couponCount",
|
||||
url: '/pages/users/user_coupon/index'
|
||||
},
|
||||
{
|
||||
label: "积分",
|
||||
value: 2,
|
||||
k: "integral",
|
||||
url: '/pages/users/user_integral/index'
|
||||
},
|
||||
{
|
||||
label: "收藏",
|
||||
value: 3,
|
||||
k: "collectProductCount",
|
||||
url: '/pages/users/user_goods_collection/index'
|
||||
},
|
||||
{
|
||||
label: "浏览记录",
|
||||
value: 5,
|
||||
k: "visit_num",
|
||||
url: '/pages/users/visit_list/index'
|
||||
},
|
||||
{
|
||||
label: "推广佣金",
|
||||
value: 6,
|
||||
k: "brokerage_price",
|
||||
url: '/pages/users/user_spread_user/index'
|
||||
},
|
||||
{
|
||||
label: "推广人",
|
||||
value: 7,
|
||||
k: "spread_user_count",
|
||||
url: '/pages/users/user_spread_user/index'
|
||||
},
|
||||
{
|
||||
label: "推广订单",
|
||||
value: 8,
|
||||
k: "spread_order_count",
|
||||
url: '/pages/users/user_spread_user/index'
|
||||
},
|
||||
];
|
||||
export default propertyList;
|
||||
2633
utils/util.js
2633
utils/util.js
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
||||
module.exports = {
|
||||
productionSourceMap: false, // 生产打包时不输出map文件,增加打包速度
|
||||
configureWebpack: config => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
config.optimization.minimizer[0].options.terserOptions.compress.warnings = false
|
||||
config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
|
||||
config.optimization.minimizer[0].options.terserOptions.compress.drop_debugger = true
|
||||
config.optimization.minimizer[0].options.terserOptions.compress.pure_funcs = ['console.log']
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
productionSourceMap: false, // 生产打包时不输出map文件,增加打包速度
|
||||
configureWebpack: config => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
config.optimization.minimizer[0].options.terserOptions.compress.warnings = false
|
||||
config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
|
||||
config.optimization.minimizer[0].options.terserOptions.compress.drop_debugger = true
|
||||
config.optimization.minimizer[0].options.terserOptions.compress.pure_funcs = ['console.log']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user