This commit is contained in:
wangchen147 2024-10-24 16:20:50 +08:00
parent 7f0a8d746a
commit 490b56d4f1
561 changed files with 67930 additions and 0 deletions

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
/webroot/public/
/webroot/runtime/

364
pom.xml Normal file
View File

@ -0,0 +1,364 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<modules>
<!-- Maven将依次构建子模块 -->
<module>niucloud-core</module>
<module>niucloud-addon</module>
<module>niucloud-web-app</module>
<module>niucloud-boot</module>
</modules>
<groupId>com.niu</groupId>
<artifactId>niucloud-admin-java</artifactId>
<version>1.0</version>
<name>niucloud-admin-java</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
<!-- 集成框架 -->
<spring-boot.version>2.7.18</spring-boot.version>
<spring-boot-satoken.version>1.38.0</spring-boot-satoken.version>
<swagger.version>3.0.0</swagger.version>
<quartz.version>2.7.2</quartz.version>
<log4j2.version>3.1.5</log4j2.version>
<!-- 持久框架 -->
<mysql-driver.version>8.0.21</mysql-driver.version>
<alibaba-druid.version>1.1.22</alibaba-druid.version>
<mybatis-plus.version>3.5.2</mybatis-plus.version>
<mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
<mybatis-plus-join.version>1.2.4</mybatis-plus-join.version>
<pagehelper.version>1.4.5</pagehelper.version>
<!-- 工具包类 -->
<commons-collections.version>3.2.2</commons-collections.version>
<commons-lang3.version>3.7</commons-lang3.version>
<hutool.version>5.8.27</hutool.version>
<guava.version>33.2.1-jre</guava.version>
<modelmapper.version>3.2.0</modelmapper.version>
<!-- 类库框架-->
<jackson.version>2.17.1</jackson.version>
<fastJson2.version>2.0.26</fastJson2.version>
<gson.version>2.11.0</gson.version>
<pinyin4j.version>2.5.1</pinyin4j.version>
<zxing.version>3.5.3</zxing.version>
<!-- 扩展框架 -->
<apache.httpcore5>5.2.4</apache.httpcore5>
<apache.httpclient5>5.3.1</apache.httpclient5>
<netty-all.version>4.1.110.Final</netty-all.version>
<easyexcel.version>3.3.3</easyexcel.version>
<weixin.version>4.6.0</weixin.version>
<!-- 辅助工具 -->
<lombok.version>1.18.24</lombok.version>
<alioss.version>3.17.4</alioss.version>
<qiniuoss.version>[7.13.0, 7.13.99]</qiniuoss.version>
<qcloud.version>5.6.219</qcloud.version>
<alisms.version>3.0.0</alisms.version>
<alipay.version>2.2.3</alipay.version>
<jtscore.version>1.18.1</jtscore.version>
<image-combiner.version>2.6.9</image-combiner.version>
</properties>
<dependencyManagement>
<dependencies>
<!--spring-boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- ============================================================================= -->
<!--swagger3-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- spring-boot SA Token -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${spring-boot-satoken.version}</version>
</dependency>
<!-- Sa-Token 整合 jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>${spring-boot-satoken.version}</version>
</dependency>
<!-- Sa-Token整合Redis -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>${spring-boot-satoken.version}</version>
</dependency>
<!-- Sa-Token插件权限缓存与业务缓存分离 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-alone-redis</artifactId>
<version>${spring-boot-satoken.version}</version>
</dependency>
<!-- Log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.6</version>
</dependency>
<!-- ============================================================================= -->
<!-- Mysql-Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-driver.version}</version>
</dependency>
<!--alibaba-druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${alibaba-druid.version}</version>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MyBatis-Plus添加代码生成器的依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus-generator.version}</version>
</dependency>
<!-- MyBatis-Plus-Join -->
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join</artifactId>
<version>${mybatis-plus-join.version}</version>
</dependency>
<!-- PageHelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<!-- ============================================================================= -->
<!-- Java集合工具包 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons-collections.version}</version>
</dependency>
<!-- commons.lang3-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<!-- hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- modelmapper -->
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>${modelmapper.version}</version>
</dependency>
<!-- ============================================================================= -->
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastJson2.version}</version>
</dependency>
<!-- gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- pinyin4j -->
<dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
<version>${pinyin4j.version}</version>
</dependency>
<!-- zxing=>二维码 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>${zxing.version}</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>${zxing.version}</version>
</dependency>
<!-- EasyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<!-- 微信小程序 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>${weixin.version}</version>
</dependency>
<!-- 微信公众号 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${weixin.version}</version>
</dependency>
<!--微信开放平台-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-open</artifactId>
<version>${weixin.version}</version>
</dependency>
<!-- 阿里云oss -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${alioss.version}</version>
</dependency>
<!-- 七牛云oss -->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>${qiniuoss.version}</version>
</dependency>
<!-- 腾讯云oss -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>${qcloud.version}</version>
</dependency>
<!-- 阿里云短信 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>${alisms.version}</version>
</dependency>
<!-- ============================================================================= -->
<!-- Apache httpcore5 -->
<dependency>
<groupId>org.apache.httpcomponents.core5</groupId>
<artifactId>httpcore5</artifactId>
<version>${apache.httpcore5}</version>
</dependency>
<!-- Apache httpclient5 -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>${apache.httpclient5}</version>
</dependency>
<!-- netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty-all.version}</version>
</dependency>
<!-- ============================================================================= -->
<!-- 公共类库 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-common</artifactId>
<version>${weixin.version}</version>
</dependency>
<!-- 微信支付 SDK -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>${weixin.version}</version>
</dependency>
<!-- 微信小程序 SDK -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>${weixin.version}</version>
</dependency>
<!-- 公众号订阅号、服务号SDK -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${weixin.version}</version>
</dependency>
<!-- 微信开放平台第三方平台SDK -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-open</artifactId>
<version>${weixin.version}</version>
</dependency>
<!-- 支付宝支付-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>${alipay.version}</version>
</dependency>
<!-- 空间分析 -->
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>${jtscore.version}</version>
</dependency>
<!-- 图片合成 -->
<dependency>
<groupId>com.freewayso</groupId>
<artifactId>image-combiner</artifactId>
<version>${image-combiner.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 顶级父工程只声明 + 引入编译时工具依赖 -->
<!-- lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

3
uni-app/.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

25
uni-app/.env.development Normal file
View File

@ -0,0 +1,25 @@
NODE_ENV = 'development'
# api请求地址
VITE_APP_BASE_URL = ''
# 图片服务器地址
VITE_IMG_DOMAIN = ''
# 站点id 仅在编译为小程序时生效
VITE_SITE_ID = ''
# 本地存储时token的参数名
VITE_REQUEST_STORAGE_TOKEN_KEY='wapToken'
# 请求时header中token的参数名
VITE_REQUEST_HEADER_TOKEN_KEY='token'
# 请求时header中站点的参数名
VITE_REQUEST_HEADER_SITEID_KEY='site-id'
# 请求时header中来源场景的参数名
VITE_REQUEST_HEADER_CHANNEL_KEY='channel'
# 应用版本
VITE_APP_VERSION='1.0.1'

25
uni-app/.env.production Normal file
View File

@ -0,0 +1,25 @@
NODE_ENV = 'production'
# api请求地址
VITE_APP_BASE_URL = ''
# 图片服务器地址
VITE_IMG_DOMAIN = ''
# 站点id 仅在编译为小程序时生效
VITE_SITE_ID = ''
# 本地存储时token的参数名
VITE_REQUEST_STORAGE_TOKEN_KEY='wapToken'
# 请求时header中token的参数名
VITE_REQUEST_HEADER_TOKEN_KEY='token'
# 请求时header中站点的参数名
VITE_REQUEST_HEADER_SITEID_KEY='site-id'
# 请求时header中来源场景的参数名
VITE_REQUEST_HEADER_CHANNEL_KEY='channel'
# 应用版本
VITE_APP_VERSION='1.0.1'

21
uni-app/.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
*.local
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

20
uni-app/index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

21866
uni-app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

85
uni-app/package.json Normal file
View File

@ -0,0 +1,85 @@
{
"name": "uni-preset-vue",
"version": "0.0.0",
"scripts": {
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-jd": "uni -p mp-jd",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
"build:h5": "uni build && node publish.cjs",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-jd": "uni build -p mp-jd",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
"type-check": "vue-tsc --noEmit"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-3080720230703001",
"@dcloudio/uni-app-plus": "3.0.0-3080720230703001",
"@dcloudio/uni-components": "3.0.0-3080720230703001",
"@dcloudio/uni-h5": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-alipay": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-baidu": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-jd": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-lark": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-qq": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-toutiao": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-weixin": "3.0.0-3080720230703001",
"@dcloudio/uni-quickapp-webview": "3.0.0-3080720230703001",
"html2canvas": "^1.4.1",
"image-tools": "^1.4.0",
"lodash-es": "^4.17.21",
"pinia": "2.0.36",
"qrcode": "^1.5.1",
"qs": "6.7.0",
"sortablejs": "^1.15.0",
"uview-plus": "^3.1.29",
"vue": "^3.3.0",
"vue-i18n": "^9.2.2",
"weixin-js-sdk": "^1.6.0"
},
"devDependencies": {
"@dcasia/mini-program-tailwind-webpack-plugin": "^1.5.6",
"@dcloudio/types": "^3.3.2",
"@dcloudio/uni-automator": "3.0.0-3080720230703001",
"@dcloudio/uni-cli-shared": "3.0.0-3080720230703001",
"@dcloudio/uni-stacktracey": "3.0.0-3080720230703001",
"@dcloudio/vite-plugin-uni": "3.0.0-3080720230703001",
"@rollup/plugin-commonjs": "^24.0.1",
"@types/qrcode": "^1.5.0",
"@types/sortablejs": "^1.15.0",
"@vue/tsconfig": "^0.1.3",
"sass": "^1.54.5",
"typescript": "^4.9.4",
"vite": "4.0.4",
"vite-plugin-windicss": "^1.8.10",
"vue-tsc": "^1.0.24",
"windicss": "^3.5.6"
}
}

BIN
uni-app/public/niucloud.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

51
uni-app/publish.cjs Normal file
View File

@ -0,0 +1,51 @@
const fs = require('fs')
const publish = () => {
const src = './dist/build/h5'
const dest = '../webroot/public/wap'
solve()
// 目标目录不存在停止复制
try {
const dir = fs.readdirSync(dest)
} catch (e) {
return
}
// 删除目标目录下文件
fs.rm(dest, { recursive: true }, err => {
if(err) {
console.log(err)
return
}
fs.cp(src, dest, { recursive: true }, (err) => {
if (err) {
console.error(err)
}
})
})
}
const solve = () => {
const src = './dist/build/h5/assets'
const filemaps = fs.readdirSync(src)
filemaps.forEach(file => {
if (/^(index-)(\w{8})(.js)$/.test(file)) {
const path = `${src}/${file}`
let content = fs.readFileSync(path, 'utf-8')
const first = 'const match = location.href.match(/\\/wap\\/(\\d*)\\//);'
if (content.indexOf(first) == -1) {
content = first + content
const replace = 'router:{mode:"history",base: match ? `/wap/${match[1]}/` : "/wap/",assets:"assets",routerBase: match ? `/wap/${match[1]}/` : "/wap/"},darkmode'
content = content.replace(/router:{(.*?)},darkmode/s, replace)
fs.writeFileSync(path, content, 'utf8')
}
}
})
}
publish()

170
uni-app/src/App.vue Normal file
View File

@ -0,0 +1,170 @@
<script setup lang="ts">
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
import { launchInterceptor } from '@/utils/interceptor'
import { getToken, isWeixinBrowser, getSiteId, currRoute } from '@/utils/common'
import useMemberStore from '@/stores/member'
import useConfigStore from '@/stores/config'
import useSystemStore from '@/stores/system'
import { useLogin } from '@/hooks/useLogin'
import { useShare } from '@/hooks/useShare'
onLaunch(async(data) => {
//
launchInterceptor()
// #ifdef H5
uni.getSystemInfoSync().platform == 'ios' && (uni.setStorageSync('initUrl', location.href))
//
window.parent.postMessage(JSON.stringify({
type: 'appOnLaunch',
message: '初始化加载完成'
}), '*');
//
window.addEventListener('message', event => {
try {
let data = {
type: ''
};
if (typeof event.data == 'string') {
data = JSON.parse(event.data)
} else if (typeof event.data == 'object') {
data = event.data
}
if (data.type && data.type == 'appOnReady') {
window.parent.postMessage(JSON.stringify({
type: 'appOnReady',
message: '加载完成'
}), '*');
}
} catch (e) {
console.log('uni-app App.vue 接受数据错误', e)
}
}, false);
// id
if (process.env.NODE_ENV == 'development' && (getSiteId(uni.getStorageSync('wap_site_id') || import.meta.env.VITE_SITE_ID) === '')) return;
const { wechatInit } = useShare()
wechatInit()
// #endif
// #ifdef MP
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate(function(res) {
//
});
updateManager.onUpdateReady(function(res) {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success(res) {
if (res.confirm) {
// applyUpdate
updateManager.applyUpdate();
}
}
});
});
updateManager.onUpdateFailed(function(res) {
//
});
// #endif
const configStore = useConfigStore()
await configStore.getTabbarConfig()
await configStore.getLoginConfig()
useSystemStore().getMapFn()
useSystemStore().getSiteInfoFn()
useMemberStore().getMemberLevel()
try {
// tabbar
uni.hideTabBar()
} catch (e) {
}
//
let url = currRoute()
if ((url == 'app/pages/auth/login' || url == 'app/pages/auth/register') && (configStore.login.is_username || configStore.login.is_mobile || configStore.login.is_bind_mobile)) {
return false
}
//
console.log(getToken())
if (getToken()) {
const memberStore = useMemberStore()
await memberStore.setToken(getToken())
setTimeout(() => {
if (!uni.getStorageSync('openid')) {
const memberInfo = useMemberStore().info
const login = useLogin()
// #ifdef MP-WEIXIN
if (memberInfo && memberInfo.weapp_openid) {
uni.setStorageSync('openid', memberInfo.weapp_openid)
} else {
login.getAuthCode('', true)
}
// #endif
// #ifdef H5
if (isWeixinBrowser()) {
if (memberInfo && memberInfo.wx_openid) {
uni.setStorageSync('openid', memberInfo.wx_openid)
} else {
data.query.code ? login.updateOpenid(data.query.code) : login.getAuthCode('snsapi_userinfo')
}
}
// #endif
}
//
if (uni.getStorageSync('isbindmobile')) {
uni.removeStorageSync('isbindmobile');
}
if (configStore.login.is_bind_mobile && !memberStore.info.mobile) {
//
uni.setStorageSync('isbindmobile', true)
}
}, 1000)
}
if (!getToken()) {
// todo 退
// #ifdef MP
if(uni.getStorageSync('autoLoginLock')){
return false
}
// #endif
//
if (configStore.login.is_auth_register) {
const login = useLogin()
//
// #ifdef MP
login.getAuthCode()
// #endif
// #ifdef H5
if (isWeixinBrowser()) {
console.log("authlogin", data)
data.query.code ? login.authLogin(data.query.code) : login.getAuthCode('snsapi_userinfo')
}
// #endif
}
}
})
onShow(() => {
})
onHide(() => {
})
</script>
<style>
uni-page-head {
display: none !important;
}
</style>

View File

@ -0,0 +1,29 @@
.draggable-element,.ignore-draggable-element {
&.decorate {
&:hover:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 4rpx dotted $u-primary;
z-index: 10;
pointer-events: none;
cursor: move;
}
&.selected:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 4rpx solid $u-primary;
z-index: 10;
pointer-events: none;
cursor: move;
}
}
}

View File

@ -0,0 +1,92 @@
<template>
<view class="diy-group" id="componentList">
<top-tabbar :scrollBool="diyGroup.componentsScrollBool.TopTabbar" v-if="data.global && Object.keys(data.global).length && data.global.topStatusBar && data.global.topStatusBar.isShow" ref="topTabbarRef" :data="data.global" />
<view v-for="(component, index) in data.value" :key="component.id"
@click="diyStore.changeCurrentIndex(index, component)"
:class="diyGroup.getComponentClass(index,component)" :style="component.pageStyle">
<view class="relative" :style="{ marginTop : component.margin.top < 0 ? (component.margin.top * 2) + 'rpx' : '0' }">
<!-- 装修模式下设置负上边距后超出的内容禁止选中设置 -->
<view v-if="diyGroup.isShowPlaceHolder(index,component)" class="absolute w-full z-1" :style="{ height : (component.margin.top * 2 * -1) + 'rpx' }" @click.stop="diyGroup.placeholderEvent"></view>
<template v-if="component.componentName == 'ActiveCube'">
<diy-active-cube :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.ActiveCube" />
</template>
<template v-if="component.componentName == 'CarouselSearch'">
<diy-carousel-search :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.CarouselSearch" />
</template>
<template v-if="component.componentName == 'FloatBtn'">
<diy-float-btn :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.FloatBtn" />
</template>
<template v-if="component.componentName == 'GraphicNav'">
<diy-graphic-nav :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.GraphicNav" />
</template>
<template v-if="component.componentName == 'HorzBlank'">
<diy-horz-blank :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.HorzBlank" />
</template>
<template v-if="component.componentName == 'HorzLine'">
<diy-horz-line :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.HorzLine" />
</template>
<template v-if="component.componentName == 'HotArea'">
<diy-hot-area :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.HotArea" />
</template>
<template v-if="component.componentName == 'ImageAds'">
<diy-image-ads :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.ImageAds" />
</template>
<template v-if="component.componentName == 'MemberInfo'">
<diy-member-info :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.MemberInfo" />
</template>
<template v-if="component.componentName == 'MemberLevel'">
<diy-member-level :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.MemberLevel" />
</template>
<template v-if="component.componentName == 'Notice'">
<diy-notice :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.Notice" />
</template>
<template v-if="component.componentName == 'RichText'">
<diy-rich-text :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.RichText" />
</template>
<template v-if="component.componentName == 'RubikCube'">
<diy-rubik-cube :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.RubikCube" />
</template>
<template v-if="component.componentName == 'Text'">
<diy-text :component="component" :global="data.global" :index="index" :pullDownRefreshCount="props.pullDownRefreshCount" :scrollBool="diyGroup.componentsScrollBool.Text" />
</template>
</view>
</view>
<template v-if="diyStore.mode == '' && data.global.bottomTabBarSwitch">
<view class="pt-[20rpx]"></view>
<tabbar />
</template>
</view>
</template>
<script lang="ts" setup>
import topTabbar from '@/components/top-tabbar/top-tabbar.vue'
import useDiyStore from '@/app/stores/diy';
import { useDiyGroup } from './useDiyGroup';
import { ref } from 'vue';
const props = defineProps(['data','pullDownRefreshCount']);
const topTabbarRef = ref(null);
const diyStore = useDiyStore();
const diyGroup = useDiyGroup({
...props,
getFormRef() {
return {
topTabbarRef: topTabbarRef.value
}
}
});
const data = ref(diyGroup.data);
//
diyGroup.onMounted();
//
diyGroup.onPageScroll();
defineExpose({
refresh: diyGroup.refresh
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -0,0 +1,162 @@
import { ref, reactive, onMounted, nextTick, computed } from 'vue';
import Sortable from 'sortablejs';
import { range } from 'lodash-es';
import { onPageScroll, onHide, onShow } from '@dcloudio/uni-app';
import useDiyStore from '@/app/stores/diy';
import { getLocation } from '@/utils/common';
export function useDiyGroup(params: any = {}) {
let scrollVal: any = ""; //组件滚动值集合
const componentsScrollBool: any = ref({}); //组件是否根据滚动进行相应改变
const diyStore = useDiyStore();
const positionFixed = ref(['fixed', 'top_fixed', 'right_fixed', 'bottom_fixed', 'left_fixed']);
const data = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore;
} else {
return params.data;
}
})
const getComponentClass = (index: any, component: any) => {
let obj: any = {
relative: true,
selected: diyStore.currentIndex == index,
decorate: diyStore.mode == 'decorate'
}
obj['top-fixed-' + diyStore.topFixedStatus] = true;
if (component.position && positionFixed.value.indexOf(component.position) != -1) {
// 找出置顶组件,设置禁止拖动
obj['ignore-draggable-element'] = true;
} else {
obj['draggable-element'] = true;
}
return obj;
}
// 是否显示占位区域,用于禁止选中负上边距的内容
const isShowPlaceHolder = (index: any, component: any) => {
// #ifdef H5
if (diyStore.mode == 'decorate') {
let el: any = document.getElementById('componentList');
if (el && el.children.length && el.children[index]) {
let height = el.children[index].offsetHeight;
let top = 0;
if (component.margin.top < 0) {
top = component.margin.top * 2 * -1;
// 若负上边距大于组件的高度,则允许选中进行装修
if (top > height) {
return false;
}
}
}
return true;
}
// #endif
return false;
}
// 监听页面加载完成
const onMountedLifeCycle = () => {
onMounted(() => {
// #ifdef H5
if (diyStore.mode == 'decorate') {
var el = document.getElementById('componentList');
const sortable = Sortable.create(el, {
draggable: '.draggable-element',
animation: 200,
// 结束拖拽
onEnd: event => {
let temp = diyStore.value[event.oldIndex!];
diyStore.value.splice(event.oldIndex!, 1);
diyStore.value.splice(event.newIndex!, 0, temp);
nextTick(() => {
sortable.sort(range(diyStore.value.length).map((value: any) => {
return value.toString();
}));
diyStore.postMessage(event.newIndex, diyStore.value[event.newIndex]);
});
}
});
}
// #endif
nextTick(() => {
setTimeout(() => {
if (data.value.global && data.value.global.topStatusBar && data.value.global.topStatusBar.style == 'style-4') {
// 第一次获取经纬度
getLocation()
}
// 初始化组件滚动值
scrollVal = uni.getStorageSync('componentsScrollValGroup');
if (scrollVal) {
for (let key in scrollVal) {
componentsScrollBool.value[key] = -1;
}
}
}, 500)
});
});
}
// 页面onShow调用时也会触发改方法
const refresh = () => {
nextTick(() => {
params.getFormRef().topTabbarRef?.refresh();
})
}
// 空函数,禁止选中
const placeholderEvent = () => {
}
const isPagesHide = ref(false)
onShow(() => {
isPagesHide.value = false;
})
onHide(() => {
isPagesHide.value = true;
})
// 监听滚动事件
const scrollValStr = ref()
const onPageScrollLifeCycle = () => {
onPageScroll((e) => {
if (scrollVal && !isPagesHide.value) {
for (let key in scrollVal) {
if (e.scrollTop <= 0) {
// -1 表示页面滚动值小于零,组件随页面下拉而下来
componentsScrollBool.value[key] = -1;
} else if (e.scrollTop > scrollVal[key]) {
// 1 表示页面滚动值大于传入滚动值,组件随页面上拉背景、文字颜色等采用滚动后的变量
componentsScrollBool.value[key] = 1;
} else {
// 2 表示页面滚动值小于传入滚动值,组件随页面下拉背景、文字颜色等采用滚动前的变量
componentsScrollBool.value[key] = 2
}
}
}
})
}
return {
scrollV: scrollValStr.value,
data: data.value,
componentsScrollBool: componentsScrollBool.value,
placeholderEvent,
refresh,
isShowPlaceHolder,
getComponentClass,
onPageScroll: onPageScrollLifeCycle,
onMounted: onMountedLifeCycle
}
}

View File

@ -0,0 +1,15 @@
<template>
<view class="fixed-group">
<template v-if="props.data.global.component == 'demo-index'">
<fixed-demo-index :data="props.data" :pullDownRefreshCount="props.pullDownRefreshCount"></fixed-demo-index>
</template>
</view>
</template>
<script lang="ts" setup>
const props = defineProps(['data','pullDownRefreshCount']);
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -0,0 +1,8 @@
<template>
</template>
<script setup lang="ts">
</script>
<style lang="scss">
</style>

118
uni-app/src/app/api/auth.ts Normal file
View File

@ -0,0 +1,118 @@
import request from '@/utils/request'
/**
*
*/
export function usernameLogin(data : AnyObject) {
return request.get('login', data, { showErrorMessage: true })
}
/**
*
*/
export function mobileLogin(data : AnyObject) {
return request.post('login/mobile', data, { showErrorMessage: true })
}
/**
*
*/
export function getConfig() {
return request.get('login/config')
}
/**
* 退
*/
export function logout() {
return request.put('auth/logout')
}
/**
*
*/
export function usernameRegister(data : AnyObject) {
let url = 'register'
if(uni.getStorageSync('pid')){
data.pid = uni.getStorageSync('pid');
}
return request.post(url, data, { showErrorMessage: true })
}
/**
*
*/
export function mobileRegister(data : AnyObject) {
let url = 'register/mobile'
if(uni.getStorageSync('pid')){
data.pid = uni.getStorageSync('pid');
}
return request.post(url, data, { showErrorMessage: true })
}
/**
*
*/
export function wechatUser(data : AnyObject) {
return request.get('wechat/user', data, { showErrorMessage: false })
}
/**
* openid
*/
export function wechatUserLogin(data : AnyObject) {
if(uni.getStorageSync('pid')){
data.pid = uni.getStorageSync('pid');
}
return request.post('wechat/userlogin', data, { showErrorMessage: true })
}
/**
*
*/
export function wechatLogin(data : AnyObject) {
if(uni.getStorageSync('pid')){
data.pid = uni.getStorageSync('pid');
}
return request.post('wechat/login', data, { showErrorMessage: false })
}
/**
* openid
*/
export function updateWechatOpenid(data : AnyObject) {
return request.put('wechat/update_openid', data, { showErrorMessage: false })
}
/**
*
*/
export function weappLogin(data : AnyObject) {
if(uni.getStorageSync('pid')){
data.pid = uni.getStorageSync('pid');
}
return request.post('weapp/login', data, { showErrorMessage: false })
}
/**
* openid
*/
export function updateWeappOpenid(data : AnyObject) {
return request.put('weapp/update_openid', data, { showErrorMessage: false })
}
/**
*
*/
export function bind(data : AnyObject) {
let url = 'bind'
if(uni.getStorageSync('pid')){
data.pid = uni.getStorageSync('pid');
}
return request.post(url, data, { showErrorMessage: true })
}
/**
* 访
*/
export function memberLog(data : AnyObject) {
return request.post('member/log', data, { showErrorMessage: false })
}

View File

@ -0,0 +1,29 @@
import request from '@/utils/request'
/**
*
*/
export function getDiyInfo(params: Record<string, any>) {
return request.get('diy/diy', params)
}
/**
*
*/
export function getTabbarInfo(params: Record<string, any>) {
return request.get('diy/tabbar', params)
}
/**
*
*/
export function getTabbarList(params: Record<string, any>) {
return request.get('diy/tabbar/list', params)
}
/**
*
*/
export function getShareInfo(params: Record<string, any>) {
return request.get('diy/share', params)
}

View File

@ -0,0 +1,249 @@
import request from '@/utils/request'
export function getMemberInfo() {
return request.get('member/member')
}
/**
*
*/
export function getPointList(data : AnyObject) {
return request.get('member/account/point', data)
}
/**
*
*/
export function getPointType(account_type : string) {
return request.get(`member/account/fromtype/${account_type}`)
}
/**
*
*/
export function getBalanceList(data : AnyObject) {
return request.get('member/account/balance', data)
}
/**
* ,
*/
export function getBalanceListAll(data : AnyObject) {
return request.get('member/account/balance_list', data)
}
/**
*
*/
export function getMoneyList(data : AnyObject) {
return request.get('member/account/money', data)
}
/**
*
*/
export function modifyMember(data : AnyObject) {
return request.put(`member/modify/${data.field}`, data, { showErrorMessage: true })
}
/**
*
*/
export function bindMobile(data : AnyObject) {
return request.put('member/mobile', data, { showErrorMessage: true })
}
/**
*
*/
export function cashOutTransferType() {
return request.get('member/cash_out/transfertype')
}
/**
*
*/
export function cashOutConfig() {
return request.get('member/cash_out/config')
}
/**
*
*/
export function cashOutApply(data : AnyObject) {
return request.post('member/cash_out/apply', data, { showSuccessMessage: true, showErrorMessage: true })
}
/**
*
*/
export function getCashoutAccountInfo(data : AnyObject) {
return request.get(`member/cashout_account/${data.account_id}`, {})
}
/**
*
*/
export function getFirstCashoutAccountInfo(data : AnyObject) {
return request.get('member/cashout_account/firstinfo', data)
}
/**
*
*/
export function getCashoutAccountList(data : AnyObject) {
return request.get(`member/cashout_account`, data)
}
/**
*
*/
export function getCashOutList(data : AnyObject) {
return request.get(`member/cash_out`, data)
}
/**
*
*/
export function getCashOutDetail(id : number) {
return request.get(`member/cash_out/${id}`)
}
/**
*
*/
export function addCashoutAccount(data : AnyObject) {
return request.post('member/cashout_account', data, { showSuccessMessage: true, showErrorMessage: true })
}
/**
*
*/
export function editCashoutAccount(data : AnyObject) {
return request.put(`member/cashout_account/${data.account_id}`, data, { showSuccessMessage: true, showErrorMessage: true })
}
/**
*
*/
export function deleteCashoutAccount(accountId: number) {
return request.delete(`member/cashout_account/${accountId}`, { showSuccessMessage: true, showErrorMessage: true })
}
/**
*
*/
export function getMemberCommission(data : AnyObject) {
return request.get(`member/account/commission`,data)
}
/**
*
*/
export function getCommissionList(data : AnyObject) {
return request.get(`member/account/commission`, data)
}
/**
*
*/
export function getAccountType(params: Record<string, any>) {
return request.get(`member/account/fromtype/${params.account_type}`)
}
/**
*
* @param params
* @returns
*/
export function getAddressList(params: Record<string, any>) {
return request.get(`member/address`, params)
}
/**
*
* @param id id
* @returns
*/
export function getAddressInfo(id: number) {
return request.get(`member/address/${id}`);
}
/**
*
* @param params
* @returns
*/
export function addAddress(params: Record<string, any>) {
return request.post('member/address', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function editAddress(params: Record<string, any>) {
return request.put(`member/address/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @returns
*/
export function deleteAddress(id: number) {
return request.delete(`member/address/${id}`, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
*/
export function getMemberLevel() {
return request.get(`member/level`);
}
/**
*
*/
export function getTaskGrowth() {
return request.get(`task/growth`);
}
/**
*
*/
export function getSignInfo(data : AnyObject) {
return request.get(`member/sign/info/${data.year}/${data.month}`, {})
}
/**
*
*/
export function getDayPack(data : AnyObject) {
return request.get(`member/sign/award/${data.year}/${data.month}/${data.day}`)
}
/**
*
*/
export function getSignConfig() {
return request.get(`member/sign/config`)
}
/**
*
* @returns
*/
export function setSign() {
return request.post('member/sign')
}
/**
*
*/
export function getMemberAccountPointcount() {
return request.get(`member/account/pointcount`)
}
/**
*
*/
export function getTaskPoint() {
return request.get(`task/point`)
}

View File

@ -0,0 +1,15 @@
import request from '@/utils/request'
/**
*
*/
export function pay(data : AnyObject) {
return request.post(`pay`, data, { showErrorMessage: true })
}
/**
*
*/
export function getPayInfo(tradeType : string, tradeId : number) {
return request.get(`pay/info/${tradeType}/${tradeId}`, {}, { showErrorMessage: true })
}

View File

@ -0,0 +1,147 @@
import request from '@/utils/request'
/**
*
*/
export function getCaptcha() {
return request.get('captcha', {}, {showErrorMessage: true})
}
/**
*
*/
export function getWechatAuthCode(data: AnyObject) {
return request.get('wechat/codeurl', data, {showErrorMessage: false })
}
/**
*
*/
export function wechatSync(data: AnyObject) {
return request.post('wechat/sync', data, {showErrorMessage: false})
}
/**
*
*/
export function getAgreementInfo(key: string) {
return request.get(`agreement/${key}`)
}
/**
*
*/
export function resetPassword(data: AnyObject) {
return request.post(`password/reset`, data, {showErrorMessage: true})
}
/**
*
*/
export function sendSms(data: AnyObject) {
return request.post(`send/mobile/${data.type}`, data, {showErrorMessage: true})
}
/**
* jssdk config
*/
export function getWechatSkdConfig(data: AnyObject) {
return request.get('wechat/jssdkconfig', data, {showErrorMessage: false})
}
/**
*
*/
export function uploadImage(data: AnyObject) {
return request.upload('file/image', data, {showErrorMessage: true})
}
/**
*
*/
export function fetchImage(data: AnyObject) {
return request.post('file/image/fetch', data)
}
/**
* base64图片
*/
export function fetchBase64Image(data: AnyObject) {
return request.post('file/image/base64', data)
}
/**
*
*/
export function getSiteInfo() {
return request.get('site')
}
/**
* id
*/
export function getWeappTemplateId(keys: string) {
return request.get('weapp/subscribemsg', {keys})
}
/**
*
* @param pid
*/
export function getAreaListByPid(pid: number = 0) {
return request.get(`area/list_by_pid/${pid}`)
}
/**
*
* @param level
*/
export function getAreatree(level: number = 1) {
return request.get(`area/tree/${level}`)
}
/**
*
* @param code
*/
export function getAreaByCode(code: number | string) {
return request.get(`area/code/${code}`)
}
/**
*
* @param params
*/
export function getAddressByLatlng(params: Record<string, any>) {
return request.get(`area/address_by_latlng`, params, {showErrorMessage: true})
}
/**
*
*/
export function getWapIndexList(data: AnyObject) {
return request.get('wap_index', data)
}
/**
*
* @returns
*/
export function getPoster(params: Record<string, any>) {
return request.get("poster", params)
}
/**
*
*/
export function getMap() {
return request.get('map')
}
/**
*
* @param params
*/
export function getMsgJumpPath(params: Record<string, any>) {
return request.get('weapp/getMsgJumpPath', params)
}

View File

@ -0,0 +1,43 @@
import request from '@/utils/request'
/**
*
*/
export function getVerifyCode(type: string, params: AnyObject) {
return request.get('verify', { type, data: params })
}
/**
*
*/
export function getVerifyRecords(params: Record<string, any>) {
return request.get('verify_records', params)
}
/**
*
*/
export function getCheckVerifier() {
return request.get('check_verifier')
}
/**
*
*/
export function getVerifierInfo(code: string) {
return request.get(`get_verify_by_code/${ code }`)
}
/**
*
*/
export function verify(code: string) {
return request.post(`verify/${ code }`, {}, { showErrorMessage: true })
}
/**
*
*/
export function getVerifyDetail(code: string) {
return request.get(`verify_detail/${ code }`, {}, { showErrorMessage: true })
}

View File

@ -0,0 +1,249 @@
<template>
<view :style="warpCss">
<view :style="maskLayer"></view>
<view class="diy-active-cube relative">
<view class="active-cube-wrap p-[20rpx]">
<view class="flex items-center" v-if="diyComponent.titleStyle.value == 'style-1'">
<view class="mr-[20rpx] font-bold text-[32rpx]" :style="{color: diyComponent.titleColor }" @click="diyStore.toRedirect(diyComponent.textLink)">{{ diyComponent.text }}</view>
<view v-if="diyComponent.subTitle.text" @click="diyStore.toRedirect(diyComponent.subTitle.link)" class="text-center text-[24rpx] rounded-[40rpx] rounded-tl-none py-[10rpx] px-[20rpx]" :style="{'color': diyComponent.subTitle.textColor, background: 'linear-gradient(90deg, '+ diyComponent.subTitle.startColor + ', '+ diyComponent.subTitle.endColor + ')'}">{{ diyComponent.subTitle.text }}</view>
</view>
<view class="flex items-center" v-if="diyComponent.titleStyle.value == 'style-2'">
<view class="mr-[20rpx] font-bold text-[32rpx]" :style="{color: diyComponent.titleColor }" @click="diyStore.toRedirect(diyComponent.textLink)">{{ diyComponent.text }}</view>
<view v-if="diyComponent.subTitle.text" @click="diyStore.toRedirect(diyComponent.subTitle.link)" class="text-center text-[24rpx] rounded-[10rpx] py-[10rpx] px-[20rpx]" :style="{'color': diyComponent.subTitle.textColor, background: 'linear-gradient(90deg, '+ diyComponent.subTitle.startColor + ', '+ diyComponent.subTitle.endColor + ')'}">{{ diyComponent.subTitle.text }}</view>
</view>
<view class="flex items-center" v-if="diyComponent.titleStyle.value == 'style-3'">
<view class="mr-[20rpx] font-bold text-[32rpx]" @click="diyStore.toRedirect(diyComponent.textLink)" :style="{color: diyComponent.titleColor }">{{ diyComponent.text }}</view>
<view class="relative h-[44rpx]" @click="diyStore.toRedirect(diyComponent.subTitle.link)">
<view v-if="diyComponent.subTitle.text" class="text-center text-[24rpx] py-[10rpx] pl-[16rpx] pr-[36rpx]" :style="{'color': diyComponent.subTitle.textColor, background: 'linear-gradient(90deg, '+ diyComponent.subTitle.startColor + ', '+ diyComponent.subTitle.endColor + ')'}">{{ diyComponent.subTitle.text }}</view>
<image class="absolute left-0 top-0 bottom-0 !w-[16rpx] !h-[44rpx]" :src="img('static/resource/images/diy/active_cube/block_style2_1.png')" mode="scaleToFill"/>
<image class="absolute right-0 top-0 bottom-0 !w-[28rpx] !h-[44rpx]" :src="img('static/resource/images/diy/active_cube/block_style2_2.png')" mode="scaleToFill"/>
</view>
</view>
<view class="flex items-center justify-between" v-if="diyComponent.titleStyle.value == 'style-4'">
<view class="font-bold text-[32rpx]" @click="diyStore.toRedirect(diyComponent.textLink)" :style="{color: diyComponent.titleColor }">{{ diyComponent.text }}</view>
<view v-if="diyComponent.subTitle.text" @click="diyStore.toRedirect(diyComponent.subTitle.link)" class="text-[24rpx] rounded-[40rpx] py-[10rpx] pl-[16rpx] pr-[12rpx] flex items-center" :style="{'color': diyComponent.subTitle.textColor, background: 'linear-gradient(90deg, '+ diyComponent.subTitle.startColor + ', '+ diyComponent.subTitle.endColor + ')'}">
<text>{{ diyComponent.subTitle.text }}</text>
<text class="nc-iconfont nc-icon-youV6xx !text-[26rpx]"></text>
</view>
</view>
<view class="bd flex flex-wrap justify-between">
<template v-for="item in diyComponent.list" :key="item.id">
<view v-if="diyComponent.blockStyle.value == 'style-1'" @click="diyStore.toRedirect(item.link)" class="item flex justify-between p-[20rpx] bg-white mt-[20rpx] rounded-[16rpx]" :style="{ backgroundColor : diyComponent.elementBgColor }">
<view class="flex-1 flex items-baseline flex-col">
<view class="text--[28rpx] pb-[20rpx]" :style="{ fontWeight : diyComponent.blockStyle.fontWeight }">{{ item.title.text }}</view>
<view class="text--[24rpx] text-gray-500 pb-[20rpx]">{{ item.subTitle.text }}</view>
<view class="link relative text-[24rpx] leading-[40rpx] flex items-center text-white rounded-r-[20rpx] h-[40rpx] pl-[26rpx] pr-[10rpx]" :style="btnCss(item.moreTitle)" v-if="item.moreTitle.text">
<text class="mr-[8rpx]">{{ item.moreTitle.text }}</text>
<text class="iconfont iconjiantou-you-cuxiantiao-fill !text-[20rpx] text-[#fff]"></text>
<image class="absolute left-0 top-0 bottom-0 !w-[28rpx]" :src="img('static/resource/images/diy/active_cube/block_style1_1.png')" mode="scaleToFill"/>
</view>
</view>
<view class="img-box ml-[10rpx] w-[130rpx]" v-if="item.imageUrl">
<image :src="img(item.imageUrl)" mode="aspectFit" />
</view>
<view class="img-box ml-[10rpx] flex items-center justify-center w-[130rpx] bg-[#f3f4f6]" v-else>
<u-icon name="photo" color="#999" size="50"></u-icon>
</view>
</view>
<view v-if="diyComponent.blockStyle.value == 'style-2'" @click="diyStore.toRedirect(item.link)" class="item flex justify-between p-[20rpx] bg-white mt-[20rpx] rounded-[16rpx]" :style="{ backgroundColor : diyComponent.elementBgColor }">
<view class="flex-1 flex items-baseline flex-col">
<view class="text--[28rpx] pb-[20rpx]" :style="{ fontWeight : diyComponent.blockStyle.fontWeight }">{{ item.title.text }}</view>
<view class="text--[24rpx] text-gray-500 pb-[20rpx]">{{ item.subTitle.text }}</view>
<view class="link relative text-[24rpx] leading-[40rpx] flex items-center text-white rounded-[20rpx] h-[40rpx] pl-[20rpx] pr-[10rpx]" :style="btnCss(item.moreTitle)" v-if="item.moreTitle.text">
<text class="mr-[8rpx]">{{ item.moreTitle.text }}</text>
<text class="iconfont iconjiantou-you-cuxiantiao-fill !text-[20rpx] text-[#fff]"></text>
</view>
</view>
<view class="img-box ml-[10rpx] w-[130rpx]" v-if="item.imageUrl">
<image :src="img(item.imageUrl)" mode="aspectFit" />
</view>
<view class="img-box ml-[10rpx] flex items-center justify-center w-[130rpx] bg-[#f3f4f6]" v-else>
<u-icon name="photo" color="#999" size="50"></u-icon>
</view>
</view>
</template>
</view>
<scroll-view scroll-x="true" class="whitespace-nowrap" :id="'warpStyle3-'+diyComponent.id" v-if="diyComponent.blockStyle.value == 'style-3'">
<view v-for="(item,index) in diyComponent.list" :key="item.id" class="inline-flex">
<view :id="'item'+index+diyComponent.id" @click="diyStore.toRedirect(item.link)" class="flex flex-col items-center justify-between p-[10rpx] bg-white mt-[20rpx] w-[157rpx] h-[200rpx] rounded-[10rpx] box-border" :style="itemStyle3" :class="{'!mr-[0rpx]': index+1 === diyComponent.list.length}">
<view class="w-[141rpx] h-[141rpx]" v-if="item.imageUrl">
<image class="w-[141rpx] h-[141rpx]" :src="img(item.imageUrl)" mode="aspectFit" />
</view>
<view class="w-[141rpx] h-[141rpx] relative flex-shrink-0" v-else>
<view class="absolute left-0 top-0 flex items-center justify-center w-[141rpx] h-[141rpx] bg-[#f3f4f6]">
<u-icon name="photo" color="#999" size="50"></u-icon>
</view>
</view>
<view class="my-[10rpx] text-[26rpx]" :style="{ color : item.title.textColor,fontWeight : diyComponent.blockStyle.fontWeight }">{{ item.title.text }}</view>
</view>
</view>
</scroll-view>
<scroll-view scroll-x="true" class="whitespace-nowrap" :id="'warpStyle4-'+diyComponent.id" v-if="diyComponent.blockStyle.value == 'style-4'">
<view v-for="(item,index) in diyComponent.list" :key="item.id" class="inline-flex">
<view :id="'item'+index+diyComponent.id" @click="diyStore.toRedirect(item.link)" class="flex flex-col items-center justify-between p-[4rpx] bg-[#F93D02] mt-[20rpx] rounded-[20rpx] box-border" :class="{'!mr-[0rpx]': index+1 === diyComponent.list.length}" :style="'background :linear-gradient('+ item.listFrame.startColor +','+ item.listFrame.endColor + ');'+itemStyle4">
<view class="w-[149rpx] h-[149rpx] box-border px-[18rpx] pt-[16rpx] pb-[6rpx] bg-[#fff] flex flex-col items-center rounded-[16rpx]">
<view class="w-[112rpx] h-[102rpx]" v-if="item.imageUrl">
<image class="w-[112rpx] h-[102rpx]" :src="img(item.imageUrl)" mode="aspectFit" />
</view>
<view class="w-[112rpx] h-[102rpx] relative flex-shrink-0" v-else>
<view class="absolute left-0 top-0 flex items-center justify-center w-[112rpx] h-[102rpx] bg-[#f3f4f6]">
<u-icon name="photo" color="#999" size="50"></u-icon>
</view>
</view>
<view class="relative -mt-[10rpx] text-[22rpx] bg-[#F3DAC5] text-[#ED6E00] rounded-[16rpx] px-[12rpx] leading-[36rpx]" :style="{ color : item.subTitle.textColor, background : 'linear-gradient(to right,'+ item.subTitle.startColor +','+ item.subTitle.endColor + ')' }">{{ item.subTitle.text }}</view>
</view>
<view class="mt-[10rpx] mb-[6rpx] text-[28rpx] text-[#fff]" :style="{ fontWeight : diyComponent.blockStyle.fontWeight }">{{ item.title.text }}</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
//
import { ref,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue';
import useDiyStore from '@/app/stores/diy';
import { img } from '@/utils/common';
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if(diyComponent.value.componentBgUrl) {
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`;
style += 'background-size: cover;background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
//
const maskLayer = computed(()=>{
var style = '';
if(diyComponent.value.componentBgUrl) {
style += 'position:absolute;top:0;width:100%;';
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`;
style += `height:${height.value}px;`;
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
}
return style;
});
//
const itemStyle3 = ref('');
const setItemStyle3 = ()=>{
// #ifdef MP-WEIXIN
uni.createSelectorQuery().in(instance).select('#warpStyle3-'+diyComponent.value.id).boundingClientRect((res:any) => {
uni.createSelectorQuery().in(instance).select('#item0'+diyComponent.value.id).boundingClientRect((data:any) => {
itemStyle3.value = `margin-right:${(res.width - data.width*4)/3}px;`
}).exec()
}).exec()
// #endif
// #ifdef H5
itemStyle3.value= 'margin-right:14rpx;'
// #endif
};
//
const itemStyle4 = ref('');
const setItemStyle4 = ()=>{
// #ifdef MP-WEIXIN
uni.createSelectorQuery().in(instance).select('#warpStyle4-'+diyComponent.value.id).boundingClientRect((res:any) => {
uni.createSelectorQuery().in(instance).select('#item0'+diyComponent.value.id).boundingClientRect((data:any) => {
itemStyle4.value = `margin-right:${(res.width - data.width*4)/3}px;`
}).exec()
}).exec()
// #endif
// #ifdef H5
itemStyle4.value= 'margin-right:14rpx;'
// #endif
};
const btnCss = (item:any) => {
var style = '';
style += `background:linear-gradient(90deg,${item.startColor},${item.endColor});`;
return style;
};
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
onMounted(() => {
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'ActiveCube') {
refresh();
}
}
)
}
});
const instance = getCurrentInstance();
const height = ref(0)
const refresh = ()=> {
nextTick(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-active-cube').boundingClientRect((data: any) => {
height.value = data.height;
}).exec();
if(diyComponent.value.blockStyle.value == 'style-3') setItemStyle3()
if(diyComponent.value.blockStyle.value == 'style-4') setItemStyle4()
})
}
</script>
<style lang="scss" scoped>
.active-cube-wrap {
.bd {
.item {
width: calc(46% - 20rpx);
image {
width: 100%;
height: 100%;
}
}
}
}
</style>

View File

@ -0,0 +1,656 @@
<template>
<view :style="warpCss" class="goods-carousel-search-wrap">
<view class="relative pb-[20rpx]">
<view class="bg-img" :class="{'!-bottom-[200rpx]': diyComponent.bgGradient == true}">
<image v-if="diyComponent.swiper.list && diyComponent.swiper.list[swiperIndex].imageUrl" :src="img(diyComponent.swiper.list[swiperIndex].imageUrl)" mode="scaleToFill" class="w-full h-full" :show-menu-by-longpress="true"/>
<view v-else class="w-full h-full bg-[#ccc]"></view>
<view class="bg-img-box" :style="bgImgBoxStyle"></view>
</view>
<view class="fixed-wrap" :style="fixedStyle">
<view class="diy-search-wrap relative z-10" @click="diyStore.toRedirect(diyComponent.search.link)" :style="navbarInnerStyle">
<view class="img-wrap" v-if="diyComponent.search.logo">
<image :src="img(diyComponent.search.logo)" mode="aspectFit"/>
</view>
<view class="search-content" @click="diyStore.toRedirect(diyComponent.search.link)">
<text class="input-content text-[#fff] text-[24rpx] leading-[68rpx]">{{isShowSearchPlaceholder ? diyComponent.search.text : ''}}</text>
<text class="nc-iconfont nc-icon-sousuo-duanV6xx1"></text>
<swiper class="swiper-wrap" :interval="diyComponent.search.hotWord.interval * 1000" autoplay="true" vertical="true" circular="true" v-if="!isShowSearchPlaceholder">
<swiper-item class="swiper-item" v-for="(item) in diyComponent.search.hotWord.list" :key="item.id">
<view class=" leading-[64rpx] text-[24rpx]">{{ item.text }}</view>
</swiper-item>
</swiper>
</view>
</view>
<view class="tab-list-wrap relative z-10" v-if="diyComponent.tab.control">
<scroll-view scroll-x="true" class="scroll-wrap" :scroll-into-view="'a' + currTabIndex">
<view @click="changeData({ source : 'home' },-1)" class="scroll-item" :class="[{ active: currTabIndex == -1 }]">
<view class="name" :style="{'color': getTabColor(currTabIndex == -1)}">首页</view>
<view class="line" :style="{'background-color': getTabColor(currTabIndex == -1)}" v-if="currTabIndex == -1"></view>
</view>
<view v-for="(item, index) in diyComponent.tab.list" class="scroll-item" :class="[{ active: index == currTabIndex }]" @click="changeData(item,index)" :id="'a' + index" :key="index">
<view class="name" :style="{'color': getTabColor(index == currTabIndex)}">{{ item.text }}</view>
<view class="line" :style="{'background-color': getTabColor(index == currTabIndex)}" v-if="index == currTabIndex"></view>
</view>
</scroll-view>
<view v-if="diyComponent.tab.list.length" class="absolute tab-btn nc-iconfont nc-icon-yingyongliebiaoV6xx" @click="tabAllPopup = true"></view>
</view>
<view class="bg-img" v-if="fixedStyleBg">
<image v-if="diyComponent.swiper.list && diyComponent.swiper.list[swiperIndex].imageUrl" :src="img(diyComponent.swiper.list[swiperIndex].imageUrl)" mode="scaleToFill" class="w-full h-full" :show-menu-by-longpress="true"/>
<view v-else class="w-full h-full bg-[#ccc]"></view>
</view>
</view>
<!-- 解决fixed定位后导航栏塌陷的问题 -->
<template v-if="diyStore.mode != 'decorate'">
<view v-if="diyComponent.positionWay == 'fixed' && props.scrollBool != -1" class="u-navbar-placeholder" :style="{ width: '100%', paddingTop: moduleHeight }"></view>
</template>
<!-- 轮播图 -->
<view class="relative" :class="{'mx-[20rpx]': swiperStyle2}">
<swiper v-if="diyComponent.swiper.control" class="swiper" :style="{ height: imgHeight }" autoplay="true" circular="true" @change="swiperChange"
:class="{
'swiper-left': diyComponent.swiper.indicatorAlign == 'left',
'swiper-right': diyComponent.swiper.indicatorAlign == 'right',
'ns-indicator-dots': diyComponent.swiper.indicatorStyle == 'style-2'
}"
:previous-margin="swiperStyle2 ? 0 : '36rpx'" :next-margin="swiperStyle2 ? 0 : '36rpx'"
:interval="diyComponent.swiper.interval * 1000" :indicator-dots="isShowDots"
:indicator-color="diyComponent.swiper.indicatorColor" :indicator-active-color="diyComponent.swiper.indicatorActiveColor">
<swiper-item class="swiper-item" v-for="(item,index) in diyComponent.swiper.list" :key="item.id" :style="swiperWarpCss">
<view @click="diyStore.toRedirect(item.link)">
<view class="item" :style="{height: imgHeight}">
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="scaleToFill" :style="swiperWarpCss" :class="['w-full h-full',{'swiper-animation': swiperIndex != index}]" :show-menu-by-longpress="true"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" :style="swiperWarpCss" mode="scaleToFill" :class="['w-full h-full',{'swiper-animation': swiperIndex != index}]" :show-menu-by-longpress="true"/>
</view>
</view>
</swiper-item>
</swiper>
<!-- #ifdef MP-WEIXIN -->
<view v-if="diyComponent.swiper.list.length > 1" :class="[
'swiper-dot-box',
{ 'straightLine': diyComponent.swiper.indicatorStyle == 'style-2' },
{ 'swiper-left': diyComponent.swiper.indicatorAlign == 'left' },
{ 'swiper-right': diyComponent.swiper.indicatorAlign == 'right' }
]">
<view v-for="(numItem, numIndex) in diyComponent.swiper.list" :key="numIndex" :class="['swiper-dot', { active: numIndex == swiperIndex }]" :style="[numIndex == swiperIndex ? { backgroundColor: diyComponent.swiper.indicatorActiveColor } : { backgroundColor: diyComponent.swiper.indicatorColor }]"></view>
</view>
<!-- #endif -->
</view>
<!-- 分类展开 -->
<u-popup :safeAreaInsetTop="true" :show="tabAllPopup" mode="top" @close="tabAllPopup = false">
<view class="text-sm px-[30rpx] pt-3" :style="{'padding-top':(menuButtonInfo.top+'px')}">全部分类</view>
<view class="flex flex-wrap pl-[30rpx] pt-[30rpx]">
<view @click="changeData({ source : 'home' },-1)" :class="['px-[26rpx] border-[2rpx] border-solid border-transparent h-[60rpx] mr-[30rpx] mb-[30rpx] flex items-center justify-center bg-[#F4F4F4] rounded-[8rpx] text-xs', { 'tab-select-popup': currTabIndex == -1 }]">
首页
</view>
<text @click="changeData(item,index)" v-for="(item, index) in diyComponent.tab.list" :key="index"
:class="['px-[26rpx] border-[2rpx] border-solid border-transparent h-[60rpx] mr-[30rpx] mb-[30rpx] flex items-center justify-center bg-[#F4F4F4] rounded-[8rpx] text-xs', { 'tab-select-popup': index == currTabIndex }]">
{{ item.text }}
</text>
</view>
</u-popup>
</view>
<!-- 展示微页面数据 -->
<template v-if="currentSource == 'diy_page'">
<view class="child-diy-template-wrap bg-index">
<diy-group :data="diyPageData"></diy-group>
</view>
</template>
</view>
</template>
<script setup lang="ts">
//
import { ref, reactive, computed, watch, onMounted, nextTick, getCurrentInstance } from 'vue';
import { img } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
import diyGroup from '@/addon/components/diy/group/index.vue'
import { getDiyInfo } from '@/app/api/diy';
const instance = getCurrentInstance();
const props = defineProps(['component', 'index', 'pullDownRefreshCount', 'global', 'scrollBool']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
const moduleHeight:any = ref('')
const setModuleLocation = ()=> {
nextTick(() => {
setTimeout(()=>{
const query = uni.createSelectorQuery().in(instance);
query.select('.fixed-wrap').boundingClientRect((data:any) => {
moduleHeight.value = (data.height || 0) + 'px';
}).exec();
})
})
}
const fixedStyleBg = ref(false);
const fixedStyle = computed(()=>{
if (diyStore.mode == 'decorate') return '';
var style = '';
// #ifdef H5
if(props.global.topStatusBar.isShow && props.global.topStatusBar.style == 'style-4') {
style += 'top:' + diyStore.topTabarHeight + 'px;';
}
// #endif
if(diyComponent.value.positionWay == 'fixed') {
if (props.scrollBool != -1) {
style += 'position: fixed;z-index: 10;left: 0;right: 0;';
}
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
if(props.global.topStatusBar.isShow) {
style += 'top:' + diyStore.topTabarHeight + 'px;';
}
// #endif
fixedStyleBg.value = false;
if (props.scrollBool == 1) {
let str = diyComponent.value.fixedBgColor;
let arr = str.split(',');
let num = diyComponent.value.fixedBgColor ? parseInt(arr[arr.length-1]) : 0;
if(!diyComponent.value.fixedBgColor || num == 0 ){
fixedStyleBg.value = true;
}else{
fixedStyleBg.value = false;
style += 'background-color:' + diyComponent.value.fixedBgColor + ';';
}
}
}
return style;
})
const getTabColor = (flag:any)=>{
let color = '';
if(flag){
color = diyComponent.value.tab.selectColor;
if(diyComponent.value.positionWay == 'fixed' && props.scrollBool == 1) {
color = diyComponent.value.tab.fixedSelectColor;
}
}else{
color = diyComponent.value.tab.noColor;
if(diyComponent.value.positionWay == 'fixed' && props.scrollBool == 1) {
color = diyComponent.value.tab.fixedNoColor;
}
}
return color;
}
const isShowSearchPlaceholder = computed(()=> {
let flag = true;
for (let i = 0; i < diyComponent.value.search.hotWord.list.length; i++) {
let item = diyComponent.value.search.hotWord.list[i];
if (item.text) {
flag = false;
break;
}
}
return flag;
})
//
const bgImgBoxStyle = computed(()=>{
var style = '';
let str = props.global.pageStartBgColor ? props.global.pageStartBgColor : 'rgba(255,255,255,1)';
if(str.indexOf('(') > -1) {
let arr = str.split('(')[1].split(')')[0].split(',');
if (diyComponent.value.bgGradient == true) {
style += `background: linear-gradient(rgba(${arr[0]}, ${arr[1]}, ${arr[2]}, 0) 65%, rgba(${arr[0]}, ${arr[1]}, ${arr[2]}, 0.6) 70%, rgba(${arr[0]}, ${arr[1]}, ${arr[2]}, 0.85) 80%, rgba(${arr[0]}, ${arr[1]}, ${arr[2]}, 0.95) 90%, rgb(${arr[0]}, ${arr[1]}, ${arr[2]}, 1) 100%);`;
}
}else{
style += `background: (${str});`;
}
return style;
});
//
const swiperStyle2 = computed(()=>{
var style = false;
style = diyComponent.value.swiper.swiperStyle == 'style-2' ? true : false;
return style;
})
const imgHeight = computed(() => {
return (diyComponent.value.swiper.imageHeight * 2) + 'rpx';
})
const swiperIndex = ref(0);
const swiperChange = e => {
swiperIndex.value = e.detail.current;
};
const swiperWarpCss = computed(() => {
var style = '';
if (diyComponent.value.swiper.topRounded) style += 'border-top-left-radius:' + diyComponent.value.swiper.topRounded * 2 + 'rpx;';
if (diyComponent.value.swiper.topRounded) style += 'border-top-right-radius:' + diyComponent.value.swiper.topRounded * 2 + 'rpx;';
if (diyComponent.value.swiper.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.swiper.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.swiper.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.swiper.bottomRounded * 2 + 'rpx;';
return style;
})
const currTabIndex = ref(-1)
const currentSource = ref('')
const changeData = (item:any,index:any)=> {
if (diyStore.mode == 'decorate') return false;
currentSource.value = item.source;
currTabIndex.value = index;
if(item.source == 'home'){
//
diyStore.topFixedStatus = 'home'
}else if (item.source == 'diy_page') {
//
diyStore.topFixedStatus = 'diy'
getDiyInfoFn(item.diy_id);
}
}
const tabAllPopup = ref(false);
let menuButtonInfo:any = {};
const navbarInnerStyle = ref('')
onMounted(() => {
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'CarouselSearch') {
refresh();
}
}
)
}
// (API)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
if(diyComponent.value.positionWay == 'fixed') {
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
//
//
//
if(props.global.topStatusBar.isShow == false) {
let rightButtonWidth = menuButtonInfo.width ? menuButtonInfo.width * 2 + 'rpx' : '70rpx';
navbarInnerStyle.value += 'padding-right:calc(' + rightButtonWidth + ' + 30rpx);';
navbarInnerStyle.value += 'padding-top:' + menuButtonInfo.top + 'px;';
}
}
// #endif
});
const refresh = ()=> {
setModuleLocation();
changeData({ source : 'home' },-1)
diyComponent.value.swiper.list.forEach((item : any) => {
if (item.imageUrl == '') {
item.imgWidth = 690;
item.imgHeight = 330;
}
});
}
const diyPageData = reactive({
pageMode: 'diy',
title: '',
global: <any>{},
value: []
})
const getDiyInfoFn = (id:any) => {
if(!id){
diyPageData.pageMode = 'diy';
diyPageData.title = '';
diyPageData.global = {};
diyPageData.value = [];
return;
}
getDiyInfo({
id
}).then((res : any) => {
if (res.data.value) {
let data = res.data;
diyPageData.pageMode = data.mode;
diyPageData.title = data.title;
let sources = JSON.parse(data.value);
diyPageData.global = sources.global;
diyPageData.global.topStatusBar.isShow = false; //
diyPageData.global.bottomTabBarSwitch = false; //
diyPageData.value = sources.value;
diyPageData.value.forEach((item, index) => {
item.pageStyle = '';
if(item.pageStartBgColor) {
if (item.pageStartBgColor && item.pageEndBgColor) item.pageStyle += `background:linear-gradient(${item.pageGradientAngle},${item.pageStartBgColor},${item.pageEndBgColor});`;
else item.pageStyle += 'background-color:' + item.pageStartBgColor + ';';
}
if (item.margin) {
if (item.margin.top > 0) {
item.pageStyle += 'padding-top:' + item.margin.top * 2 + 'rpx' + ';';
}
item.pageStyle += 'padding-bottom:' + item.margin.bottom * 2 + 'rpx' + ';';
item.pageStyle += 'padding-right:' + item.margin.both * 2 + 'rpx' + ';';
item.pageStyle += 'padding-left:' + item.margin.both * 2 + 'rpx' + ';';
}
});
uni.setNavigationBarTitle({
title: diyPageData.title
})
}
});
}
//
let isShowDots = ref(true)
// #ifdef H5
isShowDots.value = true;
// #endif
// #ifdef MP-WEIXIN
isShowDots.value = false;
// #endif
/******************************* 存储滚动值-start ***********************/
//
let componentsScrollVal = uni.getStorageSync('componentsScrollValGroup')
if(componentsScrollVal && (typeof componentsScrollVal == "object")){
componentsScrollVal.CarouselSearch = 20
uni.setStorageSync('componentsScrollValGroup', componentsScrollVal);
}else{
let obj = {
CarouselSearch: 20
}
uni.setStorageSync('componentsScrollValGroup', obj);
}
/******************************* 存储滚动值-end ***********************/
</script>
<style lang="scss" scoped>
.goods-carousel-search-wrap{
.bg-img{
position: absolute;
width: 100%;
top: 0;
bottom: 0;
z-index: 0;
-webkit-filter: blur(0);
filter: blur(0);
overflow: hidden;
uni-image, image{
-webkit-filter: blur(15px);
filter: blur(15px);
-webkit-transform: scale(1.5);
transform: scale(1.5);
}
.bg-img-box{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
}
}
.fixed-wrap {
&.fixed {
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 991;
transition: background .3s;
}
}
.diy-search-wrap{
display: flex;
position: relative;
align-items: center;
padding:20rpx;
.img-wrap{
display: flex;
align-items: center;
justify-content: center;
width: 140rpx;
height: 60rpx;
margin-right: 20rpx;
image{
width: 100%;
height:100%;
}
}
.search-content {
display: flex;
align-items: center;
padding: 0 32rpx;
border-radius: 50rpx;
background-color: rgba(255,255,255,.2);
flex: 1;
position: relative;
.input-content, .uni-input {
box-sizing: border-box;
display: block;
height: 64rpx;
line-height: 68rpx;
width: 100%;
padding-right: 20rpx;
color: #fff;
background: none;
}
.iconfont {
font-size: 30rpx;
font-weight: bold;
color: #fff;
}
.swiper-wrap{
position: absolute;
width:80%;
height: 64rpx;
line-height: 64rpx;
color:#fff;
}
}
}
.tab-list-wrap {
.scroll-wrap {
left: 0;
right: 0;
z-index: 5;
width: 100%;
white-space: nowrap;
box-sizing: border-box;
padding: 20rpx 80rpx 20rpx 20rpx;
}
.scroll-item {
display: inline-block;
text-align: center;
vertical-align: top;
width: auto;
position: relative;
padding: 0 20rpx;
.name {
font-size: 28rpx;
color: #333;
line-height: 38rpx;
margin-bottom: 10rpx;
}
&.active {
position: relative;
.name {
font-size: 32rpx;
line-height: 38rpx;
font-weight: bold;
}
.line{
position: absolute;
bottom: 0;
width: 34rpx;
height: 4rpx;
border-radius: 29%;
left: 50%;
transform: translateX(-50%);
}
}
}
.tab-btn{
font-size: 34rpx;
/* #ifdef H5 */
top: 22rpx;
right: 20rpx;
line-height: 1;
color: #fff;
&::after{
content: "";
position: absolute;
top: 6rpx;
bottom: -2rpx;
left: -14rpx;
width: 4rpx;
background: linear-gradient( 180deg, #FFFFFF 16%, rgba(255,255,255,0) 92%);
}
/* #endif */
/* #ifdef MP-WEIXIN */
top: 24rpx;
right: 20rpx;
color: #fff;
&::after{
content: "";
position: absolute;
top: 2rpx;
bottom: 0;
left: -16rpx;
width: 4rpx;
background: linear-gradient( 180deg, #FFFFFF 16%, rgba(255,255,255,0) 92%);
}
/* #endif */
}
}
.tab-select-popup{
color: var(--primary-color);
border-color: var(--primary-color);
background-color: var(--primary-color-light);
}
.swiper-animation{
transform: scale(0.94, 0.94);
transition-duration: 0.3s;
transition-timing-function: ease;
}
//
.swiper-right :deep(.uni-swiper-dots-horizontal) {
right: 80rpx;
display: flex;
justify-content: flex-end;
transform: translate(0);
}
.swiper-left :deep(.uni-swiper-dots-horizontal) {
left: 80rpx;
transform: translate(0);
}
.swiper :deep(.uni-swiper-dot) {
width: 12rpx;
height: 12rpx;
}
.swiper.ns-indicator-dots :deep(.uni-swiper-dot) {
width: 18rpx;
height: 6rpx;
border-radius: 4rpx;
}
.swiper.ns-indicator-dots :deep(.uni-swiper-dot-active) {
width: 36rpx;
}
.swiper-dot-box {
position: absolute;
bottom: 20rpx;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 0 80rpx 8rpx;
box-sizing: border-box;
&.swiper-left {
justify-content: flex-start;
}
&.swiper-right {
justify-content: flex-end;
}
.swiper-dot {
background-color: #b2b2b2;
width: 12rpx;
border-radius: 50%;
height: 12rpx;
margin: 8rpx;
}
&.straightLine {
.swiper-dot {
width: 18rpx;
height: 6rpx;
border-radius: 4rpx;
&.active {
width: 36rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,173 @@
<template>
<view class="float-btn fixed z-1000" :class="[diyComponent.style,diyComponent.bottomPosition, diyStore.mode == 'decorate' ? 'float-btn-border' : '']" :style="floatBtnWrapCss">
<view v-if="diyComponent.style==='style-1'" class="flex flex-col items-center p-[24rpx]" :style="warpCss">
<view v-for="(item,index) in diyComponent.list" :key="index" @click="diyStore.toRedirect(item.link)" :class="{'flex items-center justify-center' : true, 'mb-[20rpx]': diyComponent.list.length != index+1 }" :style="floatBtnItemCss">
<image v-if="item && item.imageUrl" :style="floatBtnItemCss" :src="img(item.imageUrl)" mode="aspectFit"></image>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="aspectFit" :style="floatBtnItemCss"/>
</view>
</view>
<!-- <view v-if="diyComponent.style==='style-2'" class="relative w-[3rpx] h-[3rpx]">
<view class="py-[14rpx] overflow-hidden absolute right-[25rpx] top-[1rpx] transform -translate-y-1/2" :style="styleTwoWarpCss">
<swiper :style="{'width':diyComponent.imageSize * 2+24+'rpx','height':diyComponent.imageSize * 2+44+'rpx !important',}" circular>
<swiper-item v-for="(item,index) in diyComponent.list" :key="index">
<view @click="diyStore.toRedirect(item.link)" class="px-[12rpx] flex flex-col items-center justify-center">
<image v-if="item && item.imageUrl" :style="floatBtnItemCss" :src="img(item.imageUrl)" mode="aspectFit"></image>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="aspectFit" :style="floatBtnItemCss"/>
<view class="text-[24rpx] text-[303133] text-center mt-[20rpx]">{{ item.link.title }}</view>
</view>
</swiper-item>
</swiper>
</view>
<view class="w-[60rpx] h-[60rpx] absolute right-[-64rpx] top-[1rpx] transform -translate-y-1/2 rounded-[30rpx] flex items-center" :style="styleTwoSphere">
<text class="!text-[60rpx] iconfont iconxiaolian-1 text-[var(--primary-color)] font-400 transform rotate-90 translate-x-[-13rpx]"></text>
</view>
</view> -->
</view>
</template>
<script setup lang="ts">
//
import { computed, watch,ref } from 'vue';
import { onPageScroll } from '@dcloudio/uni-app';
import useDiyStore from '@/app/stores/diy';
import { img } from '@/utils/common';
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
const floatBtnItemCss = computed(() => {
var style = '';
style += 'width:' + diyComponent.value.imageSize * 2 + 'rpx;';
style += 'height:' + diyComponent.value.imageSize * 2 + 'rpx;';
style += 'border-radius:' + diyComponent.value.aroundRadius * 2 + 'rpx;';
return style;
})
const floatBtnWrapCss = computed(() => {
let style = '';
if(diyComponent.value.offset){
if(diyComponent.value.bottomPosition == 'lowerRight' || diyComponent.value.bottomPosition == 'lowerLeft'){
style += 'transform: translateY('+ ((-diyComponent.value.offset) * 2) + 'rpx)';
}else if(diyComponent.value.bottomPosition == 'upperRight' || diyComponent.value.bottomPosition == 'upperLeft'){
style += 'transform: translateX('+ diyComponent.value.offset * 2 + 'rpx);';
}
}
return style;
})
//
const styleTwoRepeat = ref(true)
const styleTwoRepeatTime = ref(null)
const styleTwoWarpCss = computed(() => {
var style = '';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
style += 'transition: right .25s;box-shadow:0px 32rpx 96rpx 32rpx rgba(0, 0, 0, .08), 0px 24rpx 64px rgba(0, 0, 0, .12), 0px 16rpx 32rpx -16rpx rgba(0, 0, 0, .16);'
style +=styleTwoRepeat.value?'transition-delay: 0.25s;':('right:-'+(diyComponent.value.imageSize * 2+24)+'rpx !important;');
return style;
})
const styleTwoSphere = computed(()=>{
var style = 'transition: right .25s;background: rgba(0, 0, 0, 0.5);';
style +=styleTwoRepeat.value?'':'right:-32rpx !important;transition-delay: 0.25s;';
return style
})
onPageScroll(() => {
if(diyComponent.value.style==='style-2'){
if(styleTwoRepeatTime) clearTimeout(styleTwoRepeatTime.value)
styleTwoRepeat.value = false
styleTwoRepeatTime.value = setTimeout(()=>{
styleTwoRepeat.value = true
clearTimeout(styleTwoRepeatTime.value)
},200)
}
})
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
</script>
<style lang="scss" scoped>
.float-btn{
&.upperLeft {
top: 100rpx;
left: 30rpx;
&.style-2{
left:0 ;
}
}
&.upperRight {
top: 100rpx;
right: 30rpx;
&.style-2{
right:0 ;
}
}
&.lowerLeft {
bottom: 160rpx;
left: 30rpx;
padding-bottom: constant(safe-area-inset-bottom);
/*兼容 IOS<11.2*/
padding-bottom: env(safe-area-inset-bottom);
/*兼容 IOS>11.2*/
&.style-2{
left:0 ;
}
}
&.lowerRight {
bottom: 160rpx;
right: 30rpx;
padding-bottom: constant(safe-area-inset-bottom);
/*兼容 IOS<11.2*/
padding-bottom: env(safe-area-inset-bottom);
/*兼容 IOS>11.2*/
&.style-2{
right:0 ;
}
}
}
.z-1000{
z-index: 1000;
}
.float-btn-border{
border: 4rpx dashed var(--primary-color);
}
</style>

View File

@ -0,0 +1,336 @@
<template>
<view :style="warpCss">
<view :style="maskLayer"></view>
<view class="diy-graphic-nav relative">
<view v-if="diyComponent.layout == 'vertical'" class="graphic-nav">
<view class="graphic-nav-item" v-for="(item, index) in diyComponent.list" :key="item.id">
<view @click="diyStore.toRedirect(item.link)" :class="['flex items-center justify-between py-3 px-4', index == 0 ? 'border-t-0':'border-t']">
<view class="graphic-img relative flex items-center w-10 h-10 mr-[20rpx]"
v-if="diyComponent.mode != 'text'"
:style="{ width: diyComponent.imageSize * 2 + 'rpx', height: diyComponent.imageSize * 2 + 'rpx' }">
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="aspectFill"
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="aspectFill"
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/>
<text v-if="item.label.control"
class="tag absolute -top-[10rpx] -right-[24rpx] text-white rounded-[24rpx] rounded-bl-none transform scale-80 py-1 px-2 text-xs"
:style="{ color: item.label.textColor, backgroundImage: 'linear-gradient(' + item.label.bgColorStart + ',' + item.label.bgColorEnd + ')' }">
{{ item.label.text }}
</text>
</view>
<text v-if="diyComponent.mode != 'img'" class="graphic-text w-full truncate leading-normal"
:style="{ fontSize: diyComponent.font.size * 2 + 'rpx', fontWeight: diyComponent.font.weight, color: diyComponent.font.color }">
{{ item.title }}
</text>
<u-icon name="arrow-right" color="#999999" size="12"></u-icon>
</view>
</view>
</view>
<swiper v-else-if="diyComponent.layout == 'horizontal' && diyComponent.showStyle == 'pageSlide'"
class="graphic-nav box-border relative" circular :indicator-dots="false"
:style="{ height: swiperHeight }" @change="swiperChange">
<swiper-item class="graphic-nav-wrap flex flex-wrap" v-for="(numItem, numIndex) in Math.ceil(diyComponent.list.length / (diyComponent.pageCount * diyComponent.rowCount))">
<template v-for="(item, index) in diyComponent.list">
<view :class="[diyComponent.mode]" :key="item.id" v-if="swiperCondition(index,numItem)" :style="{ width: 100 / diyComponent.rowCount + '%' }">
<view @click="diyStore.toRedirect(item.link)" class="graphic-nav-item flex flex-col items-center box-border py-2">
<view class="graphic-img relative flex items-center justify-center w-10 h-10"
v-if="diyComponent.mode != 'text'"
:style="{ width: diyComponent.imageSize * 2 + 'rpx', height: diyComponent.imageSize * 2 + 'rpx' }">
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="aspectFill"
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="aspectFill"
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/>
<text
class="tag absolute -top-[10rpx] -right-[24rpx] text-white rounded-[24rpx] rounded-bl-none transform scale-80 py-1 px-2 text-xs"
v-if="item.label.control"
:style="{ color: item.label.textColor, backgroundImage: 'linear-gradient(' + item.label.bgColorStart + ',' + item.label.bgColorEnd + ')' }">
{{ item.label.text }}
</text>
</view>
<text v-if="diyComponent.mode != 'img'"
class="graphic-text w-full text-center truncate leading-normal"
:class="{ 'pt-1.5' : diyComponent.mode != 'text' }"
:style="{ fontSize: diyComponent.font.size * 2 + 'rpx', fontWeight: diyComponent.font.weight, color: diyComponent.font.color }">
{{ item.title }}
</text>
</view>
</view>
</template>
</swiper-item>
</swiper>
<scroll-view v-else-if="diyComponent.layout == 'horizontal' && diyComponent.pageCount == 2 && diyComponent.showStyle == 'singleSlide'" :scroll-x="diyComponent.showStyle == 'singleSlide'" :class="['graphic-nav','graphic-nav-' + diyComponent.showStyle]" class="py-[10rpx]">
<!-- #ifdef MP -->
<view class="uni-scroll-view-content">
<!-- #endif -->
<view :style="horizontalSingleSlideStyle" class="flex">
<view class="graphic-nav-wrap flex flex-wrap" :style="horizontalSingleSlideBoxStyle(numIndex)" v-for="(numItem, numIndex) in Math.ceil(diyComponent.list.length / (diyComponent.pageCount * diyComponent.rowCount))">
<template v-for="(item, index) in diyComponent.list">
<view v-if="swiperCondition(index,numItem)" @click="diyStore.toRedirect(item.link)" :style="horizontalSingleSlideItemStyle(numIndex)" class="graphic-nav-item flex flex-col items-center box-border py-2">
<view class="graphic-img relative flex items-center justify-center w-10 h-10"
v-if="diyComponent.mode != 'text'"
:style="{ width: diyComponent.imageSize * 2 + 'rpx', height: diyComponent.imageSize * 2 + 'rpx' }">
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="aspectFill"
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="aspectFill"
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/>
<text
:class="['tag absolute -top-[10rpx] -right-[24rpx] text-white rounded-[24rpx] rounded-bl-none transform scale-80 py-1 px-2 text-xs']"
v-if="item.label.control"
:style="{ color: item.label.textColor, backgroundImage: 'linear-gradient(' + item.label.bgColorStart + ',' + item.label.bgColorEnd + ')' }">
{{ item.label.text }}
</text>
</view>
<text v-if="diyComponent.mode != 'img'"
class="graphic-text w-full text-center truncate leading-normal"
:class="{ 'pt-1.5' : diyComponent.mode != 'text' }"
:style="{ fontSize: diyComponent.font.size * 2 + 'rpx', fontWeight: diyComponent.font.weight, color: diyComponent.font.color }">
{{ item.title }}
</text>
</view>
</template>
</view>
</view>
<!-- #ifdef MP -->
</view>
<!-- #endif -->
</scroll-view>
<scroll-view v-else :scroll-x="diyComponent.showStyle == 'singleSlide'" :class="['graphic-nav','graphic-nav-' + diyComponent.showStyle]" class=" py-[10rpx]">
<!-- #ifdef MP -->
<view class="uni-scroll-view-content">
<!-- #endif -->
<view class="graphic-nav-item" :class="{'flex-shrink-0' : diyComponent.showStyle == 'singleSlide'}"
v-for="(item, index) in diyComponent.list" :key="item.id"
:style="{ width: 100 / diyComponent.rowCount + '%' }">
<view @click="diyStore.toRedirect(item.link)" class="flex flex-col items-center box-border py-2">
<view class="graphic-img relative flex items-center justify-center w-10 h-10"
v-if="diyComponent.mode != 'text'"
:style="{ width: diyComponent.imageSize * 2 + 'rpx', height: diyComponent.imageSize * 2 + 'rpx' }">
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="aspectFill"
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="aspectFill"
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/>
<text
:class="['tag absolute -top-[10rpx] -right-[24rpx] text-white rounded-[24rpx] rounded-bl-none transform scale-80 py-1 px-2 text-xs']"
v-if="item.label.control"
:style="{ color: item.label.textColor, backgroundImage: 'linear-gradient(' + item.label.bgColorStart + ',' + item.label.bgColorEnd + ')' }">
{{ item.label.text }}
</text>
</view>
<text v-if="diyComponent.mode != 'img'"
class="graphic-text w-full text-center truncate leading-normal"
:class="{ 'pt-1.5' : diyComponent.mode != 'text' }"
:style="{ fontSize: diyComponent.font.size * 2 + 'rpx', fontWeight: diyComponent.font.weight, color: diyComponent.font.color }">
{{ item.title }}
</text>
</view>
</view>
<!-- #ifdef MP -->
</view>
<!-- #endif -->
</scroll-view>
</view>
</view>
</template>
<script lang="ts" setup>
//
import { ref,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue';
import { img } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if(diyComponent.value.componentBgUrl) {
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`;
style += 'background-size: cover;background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
//
const maskLayer = computed(()=>{
var style = '';
if(diyComponent.value.componentBgUrl) {
style += 'position:absolute;top:0;width:100%;';
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`;
style += `height:${height.value}px;`;
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
}
return style;
});
//
const horizontalSingleSlideStyle = computed(()=>{
let style = {width: ""};
let widthStr = 100 / diyComponent.value.rowCount; //
let itemLen = (parseInt(diyComponent.value.list.length / (diyComponent.value.rowCount*2))*diyComponent.value.rowCount) + (diyComponent.value.list.length%(diyComponent.value.rowCount*2)); //
let marginLen = diyComponent.value.margin.both*4
style.width = `calc(${widthStr * itemLen}vw - ${marginLen}rpx)`;
return style;
})
const horizontalSingleSlideBoxStyle = (index: any)=>{
let style = {width: ""};
let widthStr = 100 / diyComponent.value.rowCount; //
let marginLen = diyComponent.value.margin.both * 4 / diyComponent.value.rowCount;
if(parseInt(diyComponent.value.list.length / (diyComponent.value.rowCount*2)) >= (index+1)){
style.width = `calc(${widthStr * diyComponent.value.rowCount}vw - ${marginLen*diyComponent.value.rowCount}rpx)`;
}else{
let len = diyComponent.value.list.length%(diyComponent.value.rowCount*2);
if(len > diyComponent.value.rowCount){ //
style.width = `calc(${widthStr * diyComponent.value.rowCount}vw - ${marginLen*diyComponent.value.rowCount}rpx)`;
}else{
style.width = `calc(${widthStr * len}vw - ${marginLen * len}rpx)`; //
}
}
return style;
}
const horizontalSingleSlideItemStyle = (index: any)=>{
let style = {width: ""};
if(parseInt(diyComponent.value.list.length / (diyComponent.value.rowCount*2)) >= (index+1)){
style.width = `${100 / diyComponent.value.rowCount}%`;
}else{
let len = diyComponent.value.list.length%(diyComponent.value.rowCount*2);
if(len > diyComponent.value.rowCount){ //
style.width = `${100 / diyComponent.value.rowCount}%`;
}else{
style.width = `${100 / len}%`; //
}
}
return style;
}
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
const swiperIndex = ref(0);
const swiperChange = e => {
swiperIndex.value = e.detail.current;
};
const swiperCondition = (index, numItem) => {
let count = diyComponent.value.pageCount * diyComponent.value.rowCount;
let result = true;
result = index >= [(numItem - 1) * (count)] && index < [numItem * (count)];
return result;
}
const swiperHeight = ref('');
const handleData = () => {
if(diyComponent.value.layout == 'horizontal' && diyComponent.value.showStyle == 'pageSlide') {
var height = 0;
const query = uni.createSelectorQuery().in(instance);
query.select('.graphic-nav-item').boundingClientRect((data: any) => {
let len = 1;
if(diyComponent.value.pageCount == 2){
len = (diyComponent.value.list.length / diyComponent.value.rowCount) > 1 ? 2 : 1 ;
}
height = data.height * len;
swiperHeight.value = (height * 2) + 'rpx';
}).exec();
}
};
onMounted(() => {
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'GraphicNav') {
refresh();
}
}
)
}
});
const instance = getCurrentInstance();
const height = ref(0)
const refresh = () => {
nextTick(() => {
handleData()
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-graphic-nav').boundingClientRect((data: any) => {
height.value = data.height;
}).exec();
})
}
</script>
<style>
/* 固定显示 */
.graphic-nav-fixed>>>.uni-scroll-view-content {
display: flex;
flex-wrap: wrap;
}
/* 单行滑动 */
.graphic-nav-singleSlide>>>.uni-scroll-view-content {
display: flex;
}
</style>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,44 @@
<template>
<view :style="warpCss"></view>
</template>
<script setup lang="ts">
//
import { computed, watch } from 'vue';
import useDiyStore from '@/app/stores/diy';
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'height:' + diyComponent.value.height * 2 + 'rpx;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
</script>
<style></style>

View File

@ -0,0 +1,40 @@
<template>
<view class="horz-line-wrap">
<view v-if="diyStore.mode == 'decorate'" class="h-[30rpx]"></view>
<view :style="warpCss"></view>
<view v-if="diyStore.mode == 'decorate'" class="h-[30rpx]"></view>
</view>
</template>
<script setup lang="ts">
// 线
import { computed, watch } from 'vue';
import useDiyStore from '@/app/stores/diy';
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'border-top:' + (diyComponent.value.borderWidth * 2) + 'rpx ' + diyComponent.value.borderStyle + ' ' + diyComponent.value.borderColor + ';';
return style;
})
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
</script>
<style></style>

View File

@ -0,0 +1,91 @@
<template>
<view :style="warpCss">
<view class="simple-graph-wrap overflow-hidden relative leading-0">
<image v-if="diyComponent.imageUrl" :style="itemCss" :src="img(diyComponent.imageUrl)" mode="widthFix" :show-menu-by-longpress="true" class="w-full"/>
<image v-else :style="itemCss" :src="img('static/resource/images/diy/figure.png')" mode="widthFix" :show-menu-by-longpress="true" class="w-full"/>
<template v-if="diyStore.mode != 'decorate'">
<!-- 热区功能 -->
<view @click="diyStore.toRedirect(mapItem.link)" class="absolute" v-for="(mapItem, mapIndex) in diyComponent.heatMapData"
:key="mapIndex" :style="{
width: mapItem.width + '%',
height: mapItem.height + '%',
left: mapItem.left + '%',
top: mapItem.top + '%'
}"></view>
</template>
</view>
</view>
</template>
<script setup lang="ts">
//
import { computed, watch, onMounted } from 'vue';
import useDiyStore from '@/app/stores/diy';
import { img } from '@/utils/common';
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
return style;
})
const itemCss = computed(() => {
var style = '';
style += 'height:' + diyComponent.value.imgHeight + ';';
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
onMounted(() => {
refresh();
//
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'HotArea') {
refresh();
}
}
)
});
const refresh = () => {
if (diyStore.mode == 'decorate') {
//
if (diyComponent.value.imageUrl == '') {
diyComponent.value.imgWidth = 690;
diyComponent.value.imgHeight = 330;
}
}
}
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
</script>
<style lang="scss">
</style>

View File

@ -0,0 +1,140 @@
<template>
<view :style="warpCss">
<view :style="maskLayer"></view>
<view class="diy-image-ads">
<view v-if="diyComponent.list.length == 1" class="leading-0 overflow-hidden" :style="swiperWarpCss">
<view @click="diyStore.toRedirect(diyComponent.list[0].link)">
<image v-if="diyComponent.list[0].imageUrl" :src="img(diyComponent.list[0].imageUrl)" :style="{height: imgHeight}" mode="heightFix" class="!w-full" :show-menu-by-longpress="true"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" :style="{height: imgHeight}" mode="heightFix" class="!w-full" :show-menu-by-longpress="true"/>
</view>
</view>
<swiper v-else class="swiper" :style="{ height: imgHeight }" autoplay="true" circular="true" @change="swiperChange">
<swiper-item class="swiper-item" v-for="(item) in diyComponent.list" :key="item.id" :style="swiperWarpCss">
<view @click="diyStore.toRedirect(item.link)">
<view class="item" :style="{height: imgHeight}">
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="scaleToFill" class="w-full h-full" :show-menu-by-longpress="true"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" class="w-full h-full" :show-menu-by-longpress="true"/>
</view>
</view>
</swiper-item>
</swiper>
</view>
</view>
</template>
<script lang="ts" setup>
// 广
import { ref,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue';
import useDiyStore from '@/app/stores/diy';
import { img } from '@/utils/common';
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if(diyComponent.value.componentBgUrl) {
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`;
style += 'background-size: cover;background-repeat: no-repeat;';
}
return style;
})
const swiperWarpCss = computed(() => {
var style = '';
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
//
const maskLayer = computed(()=>{
var style = '';
if(diyComponent.value.componentBgUrl) {
style += 'position:absolute;top:0;width:100%;';
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`;
style += `height:${height.value}px;`;
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
}
return style;
});
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
const imgHeight = computed(() => {
return (diyComponent.value.imageHeight * 2) + 'rpx';
})
const swiperIndex = ref(0);
const swiperChange = e => {
swiperIndex.value = e.detail.current;
};
onMounted(() => {
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'ImageAds') {
refresh();
}
}
)
}
});
const instance = getCurrentInstance();
const height = ref(0)
const refresh = () => {
//
if (diyStore.mode == 'decorate') {
diyComponent.value.list.forEach((item : any) => {
if (item.imageUrl == '') {
item.imgWidth = 690;
item.imgHeight = 330;
}
});
}
nextTick(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-image-ads').boundingClientRect((data: any) => {
height.value = data.height;
}).exec();
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,206 @@
<template>
<view :style="warpCss">
<view class="pt-[34rpx] member-info">
<!-- #ifdef MP-WEIXIN -->
<!-- 解决fixed定位后导航栏塌陷的问题 -->
<view :style="navbarInnerStyle"></view>
<!-- #endif -->
<view v-if="info" class="flex ml-[32rpx] mr-[52rpx] items-center relative">
<!-- 唤起获取微信 -->
<u-avatar :src="img(info.headimg)" size="55" leftIcon="none" :default-url="img('static/resource/images/default_headimg.png')" @click="clickAvatar"></u-avatar>
<view class="ml-[22rpx]">
<view class="text-[#222222] flex pr-[50rpx] flex-wrap items-center">
<view class="text-[#222222] truncate max-w-[320rpx] font-bold text-lg mr-[16rpx]" :style="{ color : diyComponent.textColor }">{{ info.nickname }}</view>
</view>
<view class="text-[#696B70] text-[24rpx] mt-[10rpx]" :style="{ color : diyComponent.textColor }">UID{{ info.member_no }}</view>
</view>
<view class="set-icon flex items-center absolute right-0 top-2">
<view @click="redirect({ url: '/app/pages/setting/index' })">
<text class="nc-iconfont nc-icon-shezhiV6xx-1 text-[40rpx] ml-[10rpx]" :style="{ color : diyComponent.textColor }"></text>
</view>
</view>
</view>
<view v-else class="flex ml-[32rpx] mr-[52rpx] items-center relative" @click="toLogin">
<u-avatar src="" size="55" :default-url="img('static/resource/images/default_headimg.png')" />
<view class="ml-[22rpx]">
<view class="text-[#222222] font-bold text-lg" :style="{ color : diyComponent.textColor }">
{{ t('login') }}/{{ t('register') }}
</view>
</view>
<view class="set-icon flex items-center absolute right-0 top-2">
<view @click="redirect({ url: '/app/pages/setting/index' })">
<text class="nc-iconfont nc-icon-shezhiV6xx-1 text-[40rpx] ml-[10rpx]" :style="{ color : diyComponent.textColor }"></text>
</view>
</view>
</view>
<view class="flex m-[30rpx] mb-0 py-[30rpx] items-center">
<view class="flex-1 text-center">
<view class="font-bold">
<view @click="redirect({ url: info ? '/app/pages/member/balance' : '' })" :style="{ color : diyComponent.textColor }">{{ money }}</view>
</view>
<view class="text-sm mt-[10rpx]">
<view @click="redirect({ url: info ? '/app/pages/member/balance' : '' })" :style="{ color : diyComponent.textColor }">{{ t('balance') }}</view>
</view>
</view>
<view class="border-solid border-white border-l border-b-0 border-t-0 border-r-0 h-[60rpx]"></view>
<view class="flex-1 text-center">
<view class="font-bold">
<view @click="redirect({ url: info ? '/app/pages/member/point' : '' })" :style="{ color : diyComponent.textColor }">{{ parseInt(info?.point) || 0 }}</view>
</view>
<view class="text-sm mt-[10rpx]">
<view @click="redirect({ url: info ? '/app/pages/member/point' : '' })" :style="{ color : diyComponent.textColor }">{{ t('point') }}</view>
</view>
</view>
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<information-filling ref="infoFill"></information-filling>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<!-- 小程序隐私协议 -->
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
<!-- #endif -->
</view>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import useMemberStore from '@/stores/member'
import { useLogin } from '@/hooks/useLogin'
import { img, isWeixinBrowser, redirect, urlDeconstruction, moneyFormat } from '@/utils/common'
import { t } from '@/locale'
import { wechatSync } from '@/app/api/system'
import useDiyStore from '@/app/stores/diy'
import useConfigStore from '@/stores/config'
const props = defineProps(['component', 'index', 'pullDownRefreshCount','global']);
const configStore = useConfigStore()
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.bgUrl) {
style += 'background-image:url(' + img(diyComponent.value.bgUrl) + ');';
style += 'background-size: 100%;';
style += 'background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
const memberStore = useMemberStore()
// #ifdef H5
const { query } = urlDeconstruction(location.href)
if (query.code && isWeixinBrowser()) {
wechatSync({ code: query.code }).then(res => {
memberStore.getMemberInfo()
})
}
// #endif
const info = computed(() => {
//
if (diyStore.mode == 'decorate') {
return {
headimg: '',
nickname: '昵称',
balance: 0,
point: 0,
money: 0,
member_no: 'NIU0000021'
}
} else {
return memberStore.info;
}
})
const money = computed(() => {
if (info.value) {
let m = parseFloat(info.value.balance) + parseFloat(info.value.money)
return moneyFormat(m.toString());
} else {
return 0;
}
})
const toLogin = () => {
if(configStore.login.is_username || configStore.login.is_mobile || configStore.login.is_bind_mobile){
useLogin().setLoginBack({ url: '/app/pages/member/index' })
}else if(configStore.login.is_auth_register){ //
//
// #ifdef MP
useLogin().getAuthCode()
// #endif
// #ifdef H5
if (isWeixinBrowser()) {
useLogin().getAuthCode('snsapi_userinfo')
}
// #endif
}else{
uni.showToast({ title: '商家未开启注册方式', icon: 'none' })
}
}
const infoFill = ref(false)
const clickAvatar = () => {
// #ifdef MP-WEIXIN
infoFill.value.show = true
// #endif
// #ifdef H5
if (isWeixinBrowser()) {
useLogin().getAuthCode('snsapi_userinfo')
} else {
redirect({ url: '/app/pages/member/personal' })
}
// #endif
}
let menuButtonInfo = {};
// (API)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
//
const navbarInnerStyle = computed(() => {
let style = '';
//
// #ifdef MP
if (props.global.topStatusBar.isShow == false) {
style += 'height:' + menuButtonInfo.height + 'px;';
style += 'padding-top:' + menuButtonInfo.top + 'px;';
}
// #endif
return style;
})
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,220 @@
<template>
<view :style="warpCss" class="overflow-hidden" v-if="info&&list && list.length">
<view v-if="diyComponent.style == 'style-1'" class="rounded-t-[16rpx] flex items-center justify-between style-bg-1 py-[20rpx] px-[30rpx]">
<view class="flex items-end">
<image :src="img('static/resource/images/diy/member/VIP_02.png')" mode="aspectFit" class="w-[50rpx] h-[36rpx]" />
<text class="text-[28rpx] text-[#FFDAA8] ml-[10rpx] font-bold max-w-[440rpx] truncate">{{info.member_level_name}}</text>
</view>
<view class="flex items-center justify-center rounded-[30rpx] box-border style-btn w-[120rpx] h-[50rpx]" @click="toLink('/app/pages/member/level')">
<text class="text-[24rpx] text-[#333]">{{ info.member_level ? (upgradeGrowth > 0 ? '去升级' : '去查看') : '去解锁' }}</text>
<text class="iconfont iconxiayibu1 ml-[2rpx] !text-[20rpx] text-[#333]"></text>
</view>
</view>
<view v-if="diyComponent.style == 'style-2'" class="rounded-[16rpx] flex items-center justify-between style-bg-2 p-[30rpx]">
<view class="flex flex-col">
<view class="flex items-center">
<image :src="img('static/resource/images/diy/member/VIP_01.png')" mode="aspectFit" class="w-[74rpx] h-[30rpx]" />
<text class="text-[32rpx] text-[#FFE3B1] leading-[normal] ml-[14rpx] font-bold max-w-[420rpx] truncate">{{info.member_level_name}}</text>
</view>
<text class="text-[#fff] text-[24rpx] mt-[10rpx] leading-[32rpx]" v-if="benefits_arr && benefits_arr.length">{{info.member_level_name}}购物享{{benefits_arr[0].title}}</text>
</view>
<view class="flex items-center justify-center rounded-[30rpx] box-border style-btn w-[120rpx] h-[50rpx]" @click="toLink('/app/pages/member/level')">
<text class="text-[24rpx] text-[#333]">{{ info.member_level ? (upgradeGrowth > 0 ? '去升级' : '去查看') : '去解锁' }}</text>
<text class="iconfont iconxiayibu1 ml-[2rpx] !text-[20rpx] text-[#333]"></text>
</view>
</view>
<view v-if="diyComponent.style == 'style-3'" class="rounded-[16rpx] style-bg-3 p-[30rpx]">
<view class="flex items-center justify-between style-border-3 mb-[22rpx] pb-[22rpx]">
<view class="flex flex-col flex-1">
<view class="flex items-center justify-between">
<view class="flex items-center">
<view class="flex justify-end leading-[30rpx] box-border text-[#fff] pr-[10rpx] text-[26rpx] w-[120rpx] h-[30rpx] bg-contain bg-no-repeat" :style="{'backgroundImage': 'url('+img('static/resource/images/diy/member/VIP.png')+')'}">
VIP.{{currIndex}}
</view>
<text class="text-[#733F02] ml-[8rpx] text-[30rpx] font-bold max-w-[380rpx] truncate">{{info.member_level_name}}</text>
</view>
<view class="flex items-center" @click="toLink('/app/pages/member/level')">
<view class="flex items-center">
<image :src="img('static/resource/images/diy/member/rule.png')" mode="aspectFit" class="w-[20rpx] h-[20rpx]" />
<text class="text-[18rpx] text-[#733F02] ml-[6rpx] leading-[24rpx]">规则</text>
</view>
<view class="ml-[6rpx] text-[#733F02] !text-[26rpx] nc-iconfont nc-icon-youV6xx"></view>
</view>
</view>
<text class="text-[28rpx] text-[#794200] mt-[16rpx]">购物或邀请好友可以提升等级</text>
</view>
</view>
<view class="flex items-center justify-between">
<view class="flex flex-col flex-1">
<view class="overflow-hidden rounded-[20rpx]">
<progress :percent="progress()" activeColor="#fff" backgroundColor="rgba(255,5,5,.1)" stroke-width="6" />
</view>
<text class="text-[24rpx] leading-[1.4] text-[#794200] mt-[16rpx]" v-if="upgradeGrowth > 0">还差{{upgradeGrowth}}成长值即可升级为{{ list[afterCurrIndex].level_name }}</text>
<text class="text-[24rpx] text-[#794200] mt-[16rpx]" v-else>恭喜您升级为最高等级</text>
</view>
<view class="flex items-center rounded-[30rpx] bg-[rgb(245,230,185)] p-[16rpx] ml-[40rpx]" @click="toLink('/app/pages/member/level')">
<text class="text-[28rpx] text-[#733F02]">{{info.member_level ? (upgradeGrowth > 0 ? '做任务' : '点击查看') : '去解锁'}}</text>
<image :src="img('static/resource/images/diy/member/vector.png')" mode="aspectFit" class="ml-[8rpx] w-[12rpx] h-[18rpx]" />
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import { img, redirect } from '@/utils/common'
import useMemberStore from '@/stores/member'
import { t } from '@/locale'
import { getMemberLevel } from '@/app/api/member';
import useDiyStore from '@/app/stores/diy'
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const memberStore = useMemberStore()
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
//
const upgradeGrowth = ref(0) //
const currIndex = ref(0) //
const afterCurrIndex = ref(-1) //
const benefits_arr:any = ref([]) //
const wap_member_info = ref(uni.getStorageSync('wap_member_info'));
const info:any = computed(() => {
//
if (diyStore.mode == 'decorate') {
upgradeGrowth.value = 0;
benefits_arr.value = [{'title': '商品包邮'}];
currIndex.value = 1;
return {
member_level_name: '会员等级',
growth: 5
}
} else {
return wap_member_info.value||{};
}
})
const list:any = computed(() => {
//
if (diyStore.mode == 'decorate') {
return [{}];
}else{
getMemberLevelFn(memberStore.levelList)
return memberStore.levelList
}
})
const getMemberLevelFn = (list:any)=> {
if(!list||!list.length) return false;
let isSet = false;
//
if (info.value && list && list.length) {
list.forEach((item: any, index: any) => {
if (item.level_id == info.value.member_level) {
currIndex.value = index + 1;
//
if (item.level_benefits) {
Object.values(item.level_benefits).forEach((bItem: any) => {
if (bItem.content) {
benefits_arr.value.push(bItem.content)
}
})
}
}
if (item.growth > info.value.growth && !isSet) {
afterCurrIndex.value = index;
isSet = true;
}
})
}
if (info.value.member_level) {
if(afterCurrIndex.value == -1){
afterCurrIndex.value = list.length - 1;
}
if (list[afterCurrIndex.value] && list[afterCurrIndex.value].growth) {
upgradeGrowth.value = list[afterCurrIndex.value].growth - info.value.growth;
}
}else{
//
info.value.member_level_name = list[0].level_name;
upgradeGrowth.value = list[0].growth;
afterCurrIndex.value = 0;
currIndex.value = 1;
}
}
//
let progress = () => {
let num = 100
if(list.value[afterCurrIndex.value] && list.value[afterCurrIndex.value].growth) {
if(info.value.growth) {
num = info.value.growth / list.value[afterCurrIndex.value].growth * 100
}else{
num = 0
}
}
return num
}
//
const toLink = (link: string)=>{
if (diyStore.mode == 'decorate') return false;
redirect({ url: link })
}
</script>
<style lang="scss" scoped>
.style-bg-1{
background: linear-gradient(to right, #1F1313, #4D4646);
}
.style-btn{
background: linear-gradient(to right, #FFEACB, #FFD195);
}
.style-bg-2{
background: linear-gradient(to right, #484846, #222222);
border-bottom-left-radius: 320rpx 16rpx;
border-bottom-right-radius: 320rpx 16rpx;
}
.style-bg-3{
background: linear-gradient(to right, #FFE6C2, #E39F42);
}
.style-border-3{
position: relative;
&:after{
content: "";
position: absolute;
background: linear-gradient(to right, #F0D2A9, #DBA051);
height: 2rpx;
bottom: 0;
left: 0;
right: 0;
}
}
</style>

View File

@ -0,0 +1,303 @@
<template>
<view :style="warpCss">
<view :style="maskLayer"></view>
<view class="diy-notice relative overflow-hidden">
<view class="flex items-center pl-[28rpx] p-[22rpx]">
<template v-if="diyComponent.noticeType == 'img'">
<template v-if="diyComponent.imgType == 'system'">
<image v-if="diyComponent.systemUrl == 'style_1'" :src="img(`static/resource/images/diy/notice/${diyComponent.systemUrl}.png`)" class="h-[40rpx] max-w-[130rpx] mr-[20rpx] flex-shrink-0" mode="heightFix"/>
<image v-else-if="diyComponent.systemUrl == 'style_2'" :src="img(`static/resource/images/diy/notice/${diyComponent.systemUrl}.png`)" class="w-[200rpx] mr-[20rpx] h-[30rpx] flex-shrink-0" mode="heightFix" />
</template>
<image v-else-if="diyComponent.imgType == 'diy'" :src="img(diyComponent.imageUrl || '')" class="w-[200rpx] h-[30rpx] mr-[20rpx] flex-shrink-0" mode="heightFix"/>
</template>
<view v-if="diyComponent.noticeType == 'text' && diyComponent.noticeTitle" class="max-w-[128rpx] px-[12rpx] text-[26rpx] h-[40rpx] leading-[40rpx] text-[var(--primary-color)] bg-[var(--primary-color-light)] truncate rounded-[8rpx] mr-[20rpx] flex-shrink-0">{{ diyComponent.noticeTitle }}</view>
<view class="flex-1 flex overflow-hidden horizontal-body" :id="'horizontal-body-'+diyComponent.id" :class="{'items-center':diyComponent.scrollWay == 'upDown'}">
<!-- 横向滚动 -->
<view class="horizontal-wrap" :style="marqueeStyle" v-if="diyComponent.scrollWay == 'horizontal'">
<view class="marquee marquee-one" id="marquee-one">
<view class="item flex-shrink-0 !leading-[40rpx] h-[40rpx]" :class="{'ml-[80rpx]':index}" v-for="(item, index) in diyComponent.list" :key="index" @click="toRedirect(item)" :style="{ color: diyComponent.textColor, fontSize: diyComponent.fontSize * 2 + 'rpx', fontWeight: diyComponent.fontWeight }">{{ item.text }}</view>
</view>
<view class="marquee" v-if="marqueeBodyWidth < (marqueeOneWidth-30)">
<view class="item flex-shrink-0 !leading-[40rpx] h-[40rpx]" :class="{'ml-[80rpx]':index}" v-for="(item, index) in diyComponent.list" :key="index" @click="toRedirect(item)" :style="{ color: diyComponent.textColor, fontSize: diyComponent.fontSize * 2 + 'rpx', fontWeight: diyComponent.fontWeight }">{{ item.text }}</view>
</view>
</view>
<!-- 上下滚动 -->
<template v-if="diyComponent.scrollWay == 'upDown'">
<swiper :vertical="true" :duration="500" autoplay="true" circular="true" class="flex-1">
<swiper-item v-for="(item, index) in diyComponent.list" :key="index" @touchmove.prevent.stop>
<text @click="toRedirect(item)" class="beyond-hiding using-hidden" :style="{ color: diyComponent.textColor, fontSize: diyComponent.fontSize * 2 + 'rpx', fontWeight: diyComponent.fontWeight }">
{{ item.text }}
</text>
</swiper-item>
</swiper>
<text class="nc-iconfont nc-icon-youV6xx text-[26rpx] -ml-[8rpx] pl-[30rpx]" :style="{'color': '#999', 'fontWeight': diyComponent.fontWeight}"></text>
</template>
</view>
</view>
<u-popup :show="noticeShow" @close="noticeShow = false" mode="center" :round="5" :safeAreaInsetBottom="false">
<view @touchmove.prevent.stop>
<view class="py-[25rpx] text-sm leading-none border-0 border-solid border-b-[2rpx] border-[#eee] flex items-center justify-between">
<text class="ml-[30rpx]">公告</text>
<text class="mr-[20rpx] nc-iconfont nc-icon-guanbiV6xx text-[35rpx]" @click="noticeShow = false"></text>
</view>
<scroll-view scroll-y="true" class="px-6 py-3 w-[480rpx] h-[500rpx] text-sm">{{ noticeContent }}</scroll-view>
<button @click="noticeShow = false" class="!mx-[30rpx] !mb-[40rpx] !w-auto !h-[70rpx] text-[24rpx] leading-[70rpx] rounded-full text-white !bg-[#ff4500] !text-[#fff]">我知道了</button>
</view>
</u-popup>
</view>
</view>
</template>
<script setup lang="ts">
//
import { ref,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue';
import { img } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const noticeShow = ref(false);
const noticeContent = ref('');
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if(diyComponent.value.componentBgUrl) {
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`;
style += 'background-size: cover;background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
//
const maskLayer = computed(()=>{
var style = '';
if(diyComponent.value.componentBgUrl) {
style += 'position:absolute;top:0;width:100%;';
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`;
style += `height:${height.value}px;`;
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
}
return style;
});
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
const marqueeBodyWidth = ref(0); //
const marqueeOneWidth = ref(0); //
const marqueeStyle = ref(''); //
const time = ref(0); //
const delayTime = ref(800); //
//
const bindCrossSlipEvent = ()=> {
if (diyComponent.value.scrollWay == 'horizontal') {
setTimeout(() => {
nextTick(() => {
// #ifdef MP-WEIXIN
uni.createSelectorQuery().in(instance).select('#horizontal-body-' + diyComponent.value.id).boundingClientRect(res => {
marqueeBodyWidth.value = res.width;
const query = uni.createSelectorQuery().in(instance);
query.select('#horizontal-body-' + diyComponent.value.id + ' .marquee-one').boundingClientRect((data: any) => {
marqueeOneWidth.value = data.width
time.value = Math.ceil(marqueeOneWidth.value * 14);
if (marqueeBodyWidth.value > (marqueeOneWidth.value-30)) {
marqueeStyle.value = `animation: none;`;
} else {
marqueeStyle.value = `
animation-duration: ${ time.value }ms;
animation-delay: ${ delayTime.value }ms;`;
}
}).exec();
}).exec();
// #endif
// #ifdef H5
let documentObj = window.document.getElementById('horizontal-body-' + diyComponent.value.id);
let marqueeOne = window.document.getElementById('marquee-one');
if(documentObj && marqueeOne) {
marqueeBodyWidth.value = documentObj.offsetWidth;
marqueeOneWidth.value = marqueeOne.offsetWidth;
time.value = Math.ceil(marqueeOneWidth.value * 14);
if (marqueeBodyWidth.value > (marqueeOneWidth.value-30)) {
marqueeStyle.value = `animation: none;`;
} else {
marqueeStyle.value = `
animation-duration: ${ time.value }ms;
animation-delay: ${ delayTime.value }ms;`;
}
}
// #endif
});
});
}
}
onMounted(() => {
bindCrossSlipEvent();
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'Notice') {
bindCrossSlipEvent();
refresh();
}
}
)
}
});
const instance = getCurrentInstance();
const height = ref(0)
const refresh = ()=> {
nextTick(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-notice').boundingClientRect((data: any) => {
height.value = data.height;
}).exec();
})
}
const toRedirect = (data: {}) => {
if (diyStore.mode == 'decorate') return false;
if (diyComponent.value.showType == 'popup') {
noticeShow.value = true;
noticeContent.value = data.text;
} else {
diyStore.toRedirect(data.link);
}
}
</script>
<style lang="scss" scoped>
.main-wrap {
display: inline-block;
width: calc(100% - 100rpx);
position: relative;
}
swiper {
height: 50rpx;
}
.beyond-hiding {
display: inline-block;
width: 100%;
white-space: nowrap;
line-height: 50rpx
}
.notice-popup {
padding: 0 30rpx 40rpx;
background-color: #fff;
.head-wrap {
font-size: 32rpx;
line-height: 100rpx;
height: 100rpx;
display: block;
text-align: center;
position: relative;
border-bottom: 2rpx solid #eeeeee;
margin-bottom: 20rpx;
.iconfont {
position: absolute;
float: right;
right: 0;
font-size: 32rpx;
}
}
.content-wrap {
max-height: 600rpx;
overflow-y: auto;
}
button {
margin-top: 40rpx;
}
}
.horizontal-wrap {
height: 40rpx;
display: flex;
align-items: center;
transform: translateZ(0);
animation: marquee 0s 0s linear infinite;
transform-style: preserve-3d;
backface-visibility: hidden;
}
.marquee {
display: flex;
align-items: center;
height: 100%;
white-space: nowrap;
padding-right: 30px;
// -webkit-perspective: 1000;
// -moz-perspective: 1000;
// -ms-perspective: 1000;
// perspective: 1000;
}
@keyframes marquee {
0% {
transform: translateX(0);
}
20%{
transform: translateX(-10%);
}
40%{
transform: translateX(-20%);
}
60%{
transform: translateX(-30%);
}
80%{
transform: translateX(-40%);
}
100% {
transform: translateX(-50%);
}
}
</style>

View File

@ -0,0 +1,125 @@
<template>
<view :style="warpCss">
<view :style="maskLayer"></view>
<view class="diy-rich-text relative">
<view v-if="diyComponent.html && diyComponent.html != '<p><br></p>'">
<u-parse :content="diyComponent.html" :tagStyle="{img: 'vertical-align: top;'}"></u-parse>
</view>
<template v-else>
<view>点此编辑富文本内容 &gt;</view>
<view>
<text>你可以对文字进行</text>
<text></text>
<text class="font-bold">加粗</text>
<text></text>
<text class="italic">斜体</text>
<text></text>
<text class="underline">下划线</text>
<text></text>
<text class="line-through">删除线</text>
<text>文字</text>
<text style="color: rgb(0, 176, 240);">颜色</text>
<text></text>
<text style="background-color: rgb(255, 192, 0); color: rgb(255, 255, 255);">背景色</text>
<text>以及字号</text>
<text class="text-lg"></text>
<text class="text-sm"></text>
<text class="pl-[10rpx]">等简单排版操作</text>
</view>
<view>也可在这里插入图片并对图片加上超级链接方便用户点击</view>
</template>
</view>
</view>
</template>
<script setup lang="ts">
//
import { ref,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue';
import useDiyStore from '@/app/stores/diy';
import { img } from '@/utils/common';
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if(diyComponent.value.componentBgUrl) {
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`;
style += 'background-size: cover;background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
//
const maskLayer = computed(()=>{
var style = '';
if(diyComponent.value.componentBgUrl) {
style += 'position:absolute;top:0;width:100%;';
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`;
style += `height:${height.value}px;`;
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
}
return style;
});
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
onMounted(() => {
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'RichText') {
refresh();
}
}
)
}
});
const instance = getCurrentInstance();
const height = ref(0)
const refresh = ()=> {
nextTick(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-rich-text').boundingClientRect((data: any) => {
height.value = data.height;
}).exec();
})
}
</script>
<style></style>

View File

@ -0,0 +1,590 @@
<template>
<view :style="warpCss">
<view :style="maskLayer"></view>
<view :class="['rubik-cube relative', diyStore.mode]">
<!-- 1左2右 -->
<template v-if="diyComponent.mode == 'row1-lt-of2-rt'">
<view class="template-left">
<view @click="diyStore.toRedirect(diyComponent.list[0].link)" :class="['item', diyComponent.mode]"
:style="{ marginRight: diyComponent.imageGap * 2 + 'rpx', width: diyComponent.list[0].imgWidth, height: diyComponent.list[0].imgHeight + 'px' }">
<image v-if="diyComponent.list[0].imageUrl" :src="img(diyComponent.list[0].imageUrl)" mode="scaleToFill" :style="diyComponent.list[0].pageItemStyle" :show-menu-by-longpress="true"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" :style="diyComponent.list[0].pageItemStyle" :show-menu-by-longpress="true"/>
</view>
</view>
<view class="template-right">
<template v-for="(item, index) in diyComponent.list" :key="index">
<template v-if="index > 0">
<view @click="diyStore.toRedirect(item.link)" :class="['item', diyComponent.mode]"
:style="{ marginBottom: diyComponent.imageGap * 2 + 'rpx', width: item.imgWidth, height: item.imgHeight + 'px' }">
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="scaleToFill" :style="item.pageItemStyle" :show-menu-by-longpress="true"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" :style="item.pageItemStyle" :show-menu-by-longpress="true"/>
</view>
</template>
</template>
</view>
</template>
<!-- 1左3右 -->
<template v-else-if="diyComponent.mode == 'row1-lt-of1-tp-of2-bm'">
<view class="template-left">
<view @click="diyStore.toRedirect(diyComponent.list[0].link)" :class="['item', diyComponent.mode]"
:style="{ marginRight: diyComponent.imageGap * 2 + 'rpx', width: diyComponent.list[0].imgWidth, height: diyComponent.list[0].imgHeight + 'px' }">
<image v-if="diyComponent.list[0].imageUrl" :src="img(diyComponent.list[0].imageUrl)" mode="scaleToFill" :style="diyComponent.list[0].pageItemStyle" :show-menu-by-longpress="true"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" :style="diyComponent.list[0].pageItemStyle" :show-menu-by-longpress="true"/>
</view>
</view>
<view class="template-right">
<view @click="diyStore.toRedirect(diyComponent.list[1].link)" :class="['item', diyComponent.mode]"
:style="{ marginBottom: diyComponent.imageGap * 2 + 'rpx', width: diyComponent.list[1].imgWidth, height: diyComponent.list[1].imgHeight + 'px' }">
<image v-if="diyComponent.list[1].imageUrl" :src="img(diyComponent.list[1].imageUrl)" mode="scaleToFill" :style="diyComponent.list[1].pageItemStyle" :show-menu-by-longpress="true"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" :style="diyComponent.list[1].pageItemStyle" :show-menu-by-longpress="true"/>
</view>
<view class="template-bottom">
<template v-for="(item, index) in diyComponent.list" :key="index">
<template v-if="index > 1">
<view @click="diyStore.toRedirect(item.link)" :class="['item', diyComponent.mode]" :style="{
marginRight: diyComponent.imageGap * 2 + 'rpx',
width: item.imgWidth,
height: item.imgHeight + 'px'
}">
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="scaleToFill" :style="item.pageItemStyle" :show-menu-by-longpress="true"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" :style="item.pageItemStyle" :show-menu-by-longpress="true"/>
</view>
</template>
</template>
</view>
</view>
</template>
<template v-else>
<view :class="['item', diyComponent.mode]" v-for="(item, index) in diyComponent.list" :key="index"
@click="diyStore.toRedirect(item.link)"
:style="{ marginRight: diyComponent.imageGap * 2 + 'rpx', marginBottom: diyComponent.imageGap * 2 + 'rpx', width: item.widthStyle, height: item.imgHeight + 'px' }">
<image v-if="item.imageUrl" :src="img(item.imageUrl)" :mode="'scaleToFill'" :style="item.pageItemStyle" :show-menu-by-longpress="true"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" :mode="'scaleToFill'" :style="item.pageItemStyle" :show-menu-by-longpress="true"/>
</view>
</template>
</view>
</view>
</template>
<script setup lang="ts">
//
import { ref,onMounted, computed, watch,nextTick,getCurrentInstance } from 'vue';
import useDiyStore from '@/app/stores/diy';
import { img } from '@/utils/common';
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
/**
* 处理rpx渲染之后变成rem存在小数的问题
* @param rpx
*/
const upx2px = (rpx: number) => {
return uni.upx2px(rpx) + 1
}
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if(diyComponent.value.componentBgUrl) {
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`;
style += 'background-size: cover;background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
//
const maskLayer = computed(()=>{
var style = '';
if(diyComponent.value.componentBgUrl) {
style += 'position:absolute;top:0;width:100%;';
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`;
style += `height:${height.value}px;`;
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
}
return style;
});
const countBorderRadius = (type, index) => {
var obj = '';
if (diyComponent.value.elementAngle == 'right') {
return obj;
}
var defaultData:any = {
'row1-lt-of2-rt': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row1-lt-of1-tp-of2-bm': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
['border-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row1-tp-of2-bm': [
['border-bottom-left-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-right-radius', 'border-top-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row2-lt-of2-rt': [
['border-top-right-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'],
['border-top-left-radius', 'border-bottom-right-radius', 'border-top-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row1-of4': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-radius'],
['border-radius'],
['border-top-left-radius', 'border-bottom-left-radius']
],
'row1-of3': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-radius'],
['border-top-left-radius', 'border-bottom-left-radius']
],
'row1-of2': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius']
]
};
defaultData[type][index].forEach((item, index) => {
obj += 'border-top-left-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
obj += 'border-top-right-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
});
return obj;
}
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
onMounted(() => {
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'RubikCube') {
refresh();
}
}
)
}else{
watch(
() => diyComponent.value,
(newValue, oldValue) => {
refresh();
}
)
}
});
const instance = getCurrentInstance();
const height = ref(0)
const refresh = () => {
if (diyStore.mode == 'decorate') {
diyComponent.value.list.forEach((item : any) => {
//
if (item.imageUrl == '') {
item.imgWidth = 690;
item.imgHeight = 330;
}
});
}
handleData()
nextTick(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.rubik-cube').boundingClientRect((data: any) => {
height.value = data.height;
}).exec();
})
}
const handleData = () => {
var singleRow:any = {
'row1-of2': {
ratio: 2,
width: 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 2) + 'px) / 2)'
},
'row1-of3': {
ratio: 3,
width: 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 4) + 'px) / 3)'
},
'row1-of4': {
ratio: 4,
width: 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 6) + 'px) / 4)'
}
};
diyComponent.value.list.forEach((item, index) => {
item.pageItemStyle = countBorderRadius(diyComponent.value.mode, index);
});
if (singleRow[diyComponent.value.mode]) {
calcSingleRow(singleRow[diyComponent.value.mode]);
} else if (diyComponent.value.mode == 'row2-lt-of2-rt') {
calcFourSquare();
} else if (diyComponent.value.mode == 'row1-lt-of2-rt') {
calcRowOneLeftOfTwoRight();
} else if (diyComponent.value.mode == 'row1-tp-of2-bm') {
calcRowOneTopOfTwoBottom();
} else if (diyComponent.value.mode == 'row1-lt-of1-tp-of2-bm') {
calcRowOneLeftOfOneTopOfTwoBottom();
}
};
/**
* 魔方单行多个平分宽度
* 公式
* 宽度屏幕宽度/2示例375/2=187.5
* 比例原图高/原图宽示例322/690=0.46
* 高度宽度*比例示例187.5*0.46=86.25
*/
const calcSingleRow = (params:any) => {
uni.getSystemInfo({
success: res => {
let maxHeight = 0;
diyComponent.value.list.forEach((item, index) => {
var ratio = item.imgHeight / item.imgWidth;
let width = res.windowWidth - upx2px(diyComponent.value.margin.both * 2); //
if (diyComponent.value.imageGap > 0) {
width -= upx2px(params.ratio * diyComponent.value.imageGap * 2); //
}
item.imgWidth = width / params.ratio;
item.imgHeight = item.imgWidth * ratio;
if (maxHeight == 0 || maxHeight < item.imgHeight) maxHeight = item.imgHeight;
})
diyComponent.value.list.forEach((item, index) => {
item.widthStyle = params.width;
item.imgHeight = maxHeight;
});
}
})
};
/**
* 魔方四方型各占50%
*/
const calcFourSquare = () => {
uni.getSystemInfo({
success: res => {
let maxHeightFirst = 0;
let maxHeightTwo = 0;
diyComponent.value.list.forEach((item, index) => {
var ratio = item.imgHeight / item.imgWidth;
item.imgWidth = res.windowWidth;
item.imgWidth -= upx2px(diyComponent.value.margin.both * 4);
if (diyComponent.value.imageGap > 0) {
item.imgWidth -= upx2px(diyComponent.value.imageGap * 2);
}
item.imgWidth = item.imgWidth / 2;
item.imgHeight = item.imgWidth * ratio;
//
if (index <= 1) {
if (maxHeightFirst == 0 || maxHeightFirst < item.imgHeight) {
maxHeightFirst = item.imgHeight;
}
} else if (index > 1) {
if (maxHeightTwo == 0 || maxHeightTwo < item.imgHeight) {
maxHeightTwo = item.imgHeight;
}
}
});
diyComponent.value.list.forEach((item, index) => {
item.imgWidth = 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 2) + 'px) / 2)';
item.widthStyle = item.imgWidth;
if (index <= 1) {
item.imgHeight = maxHeightFirst;
} else if (index > 1) {
item.imgHeight = maxHeightTwo;
}
});
}
});
}
/**
* 魔方1左2右
*/
const calcRowOneLeftOfTwoRight = () => {
let rightHeight = 0; //
let divide = 'left'; // leftright
if (diyComponent.value.list[1].imgWidth === diyComponent.value.list[2].imgWidth) divide = 'right';
uni.getSystemInfo({
success: res => {
diyComponent.value.list.forEach((item, index) => {
if (index == 0) {
var ratio = item.imgHeight / item.imgWidth; //
item.imgWidth = res.windowWidth - upx2px(diyComponent.value.margin.both * 4) - upx2px(diyComponent.value.imageGap * 2);
item.imgWidth = item.imgWidth / 2;
item.imgHeight = item.imgWidth * ratio;
rightHeight = (item.imgHeight - upx2px(diyComponent.value.imageGap * 2)) / 2;
item.imgWidth += 'px';
} else {
item.imgWidth = diyComponent.value.list[0].imgWidth;
item.imgHeight = rightHeight;
}
});
}
});
}
/**
* 魔方1上2下
*/
const calcRowOneTopOfTwoBottom = () => {
var maxHeight = 0;
uni.getSystemInfo({
success: res => {
diyComponent.value.list.forEach((item, index) => {
var ratio = item.imgHeight / item.imgWidth; //
if (index == 0) {
item.imgWidth = res.windowWidth - upx2px(diyComponent.value.margin.both * 4);
} else if (index > 0) {
item.imgWidth = res.windowWidth - upx2px(diyComponent.value.margin.both * 4) - upx2px(diyComponent.value.imageGap * 2);
item.imgWidth = item.imgWidth / 2;
}
item.imgHeight = item.imgWidth * ratio;
//
if (index > 0 && (maxHeight == 0 || maxHeight < item.imgHeight)) maxHeight = item.imgHeight;
});
diyComponent.value.list.forEach((item, index) => {
item.imgWidth += 'px';
item.widthStyle = item.imgWidth;
if (index > 0) item.imgHeight = maxHeight;
});
}
});
}
/**
* 魔方1左3右
*/
const calcRowOneLeftOfOneTopOfTwoBottom = () => {
uni.getSystemInfo({
success: res => {
diyComponent.value.list.forEach((item, index) => {
//
if (index == 0) {
var ratio = item.imgHeight / item.imgWidth; //
item.imgWidth = res.windowWidth - upx2px(diyComponent.value.margin.both * 4) - upx2px(diyComponent.value.imageGap * 2);
item.imgWidth = item.imgWidth / 2;
item.imgHeight = item.imgWidth * ratio;
} else if (index == 1) {
item.imgWidth = diyComponent.value.list[0].imgWidth;
item.imgHeight = (diyComponent.value.list[0].imgHeight - upx2px(diyComponent.value.imageGap * 2)) / 2;
} else if (index > 1) {
item.imgWidth = (diyComponent.value.list[0].imgWidth - upx2px(diyComponent.value.imageGap * 2)) / 2;
item.imgHeight = diyComponent.value.list[1].imgHeight;
}
});
diyComponent.value.list.forEach((item, index) => {
item.imgWidth += 'px';
});
}
});
}
</script>
<style lang="scss">
.rubik-cube {
overflow: hidden;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.item {
text-align: center;
line-height: 0;
overflow: hidden;
image {
width: 100%;
max-width: 100%;
height: 100%;
}
}
}
//
.rubik-cube .item.row1-of2 {
box-sizing: border-box;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-of2:nth-child(1) {
margin-left: 0 !important;
}
.rubik-cube .item.row1-of2:nth-child(2) {
margin-right: 0 !important;
}
//
.rubik-cube .item.row1-of3 {
box-sizing: border-box;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-of3:nth-child(1) {
margin-left: 0 !important;
}
.rubik-cube .item.row1-of3:nth-child(3) {
margin-right: 0 !important;
}
//
.rubik-cube .item.row1-of4 {
box-sizing: border-box;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-of4:nth-child(1) {
margin-left: 0 !important;
}
.rubik-cube .item.row1-of4:nth-child(4) {
margin-right: 0 !important;
}
//
.rubik-cube .item.row2-lt-of2-rt {
// width: 50%;
display: inline-block;
box-sizing: border-box;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(1) {
margin-left: 0 !important;
margin-top: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(2) {
margin-right: 0 !important;
margin-top: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(3) {
margin-left: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(4) {
margin-right: 0 !important;
margin-bottom: 0 !important;
}
//
.rubik-cube .template-left,
.rubik-cube .template-right {
// width: 50%;
box-sizing: border-box;
}
.rubik-cube .template-left .item.row1-lt-of2-rt:nth-child(1) {
margin-bottom: 0;
}
.rubik-cube .template-right .item.row1-lt-of2-rt:nth-child(2) {
margin-bottom: 0 !important;
}
.rubik-cube.row1-lt-of2-rt .template-right {
display: flex;
flex-direction: column;
justify-content: space-between;
}
//
.rubik-cube .item.row1-tp-of2-bm:nth-child(1) {
width: 100%;
box-sizing: border-box;
margin-top: 0 !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
.rubik-cube .item.row1-tp-of2-bm:nth-child(2) {
// width: 50%;
box-sizing: border-box;
margin-left: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-tp-of2-bm:nth-child(3) {
// width: 50%;
box-sizing: border-box;
margin-right: 0 !important;
margin-bottom: 0 !important;
}
//
.rubik-cube .template-left .item.row1-lt-of1-tp-of2-bm {
width: 100%;
box-sizing: border-box;
}
.rubik-cube .template-bottom {
display: flex;
align-items: center;
justify-content: space-between;
}
.rubik-cube .template-bottom .item:nth-child(2) {
margin-right: 0 !important;
}
</style>

View File

@ -0,0 +1,129 @@
<template>
<view :style="warpCss">
<view :style="maskLayer"></view>
<view class="diy-text relative">
<view v-if="diyComponent.style == 'style-1'" class=" px-[20rpx]">
<view @click="diyStore.toRedirect(diyComponent.link)">
<view :style="{
fontSize: diyComponent.fontSize * 2 + 'rpx',
color: diyComponent.textColor,
fontWeight: diyComponent.fontWeight,
textAlign : diyComponent.textAlign
}">
{{ diyComponent.text }}
</view>
</view>
</view>
<view v-if="diyComponent.style == 'style-2'" class=" px-[20rpx] flex items-center">
<view @click="diyStore.toRedirect(diyComponent.link)">
<view class="max-w-[200rpx] truncate" :style="{
fontSize: diyComponent.fontSize * 2 + 'rpx',
color: diyComponent.textColor,
fontWeight: diyComponent.fontWeight
}">
{{ diyComponent.text }}
</view>
</view>
<text class="ml-[16rpx] max-w-[300rpx] truncate" :style="{ color: diyComponent.subTitle.color, fontSize: diyComponent.subTitle.fontSize * 2 + 'rpx', }">{{ diyComponent.subTitle.text }}</text>
<view class="ml-auto text-right " v-if="diyComponent.more.isShow" :style="{ color: diyComponent.more.color }">
<view @click="diyStore.toRedirect(diyComponent.more.link)" class="flex items-center">
<text class="max-w-[200rpx] truncate text-[24rpx]">{{ diyComponent.more.text }}</text>
<text class="nc-iconfont nc-icon-youV6xx text-[26rpx]" :style="{ color: diyComponent.more.color }"></text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
//
import { ref, computed, watch,onMounted,nextTick,getCurrentInstance } from 'vue';
import useDiyStore from '@/app/stores/diy';
import { img } from '@/utils/common';
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if(diyComponent.value.componentBgUrl) {
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`;
style += 'background-size: cover;background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
//
const maskLayer = computed(()=>{
var style = '';
if(diyComponent.value.componentBgUrl) {
style += 'position:absolute;top:0;width:100%;';
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`;
style += `height:${height.value}px;`;
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
}
return style;
});
watch(
() => props.pullDownRefreshCount,
(newValue, oldValue) => {
//
}
)
onMounted(() => {
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'Text') {
refresh();
}
}
)
}
});
const instance = getCurrentInstance();
const height = ref(0)
const refresh = ()=> {
nextTick(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-text').boundingClientRect((data: any) => {
height.value = data.height;
}).exec();
})
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,15 @@
<template>
<view>
固定模板示例我也可以装修
<!-- 自定义模板渲染 -->
<diy-group :data="props.data" :pullDownRefreshCount="props.pullDownRefreshCount"></diy-group>
</view>
</template>
<script setup lang="ts">
import { computed, watch } from 'vue';
import diyGroup from '@/addon/components/diy/group/index.vue'
const props = defineProps(['data', 'pullDownRefreshCount']);
</script>
<style></style>

View File

@ -0,0 +1,6 @@
{
"personalSettings": "Personal settings",
"switchLang": "Switch language",
"version": "Version",
"logout": "Log out"
}

View File

@ -0,0 +1,5 @@
{
"detail": "文章详情",
"abstract": "摘要",
"loadingText": "正在加载"
}

View File

@ -0,0 +1,7 @@
{
"list": "文章列表",
"noData": "~ 暂无数据 ~",
"all": "全部",
"end": "-- 到底了 --",
"searchPlaceholder": "请输入搜索关键词"
}

View File

@ -0,0 +1,11 @@
{
"bindMobile": "绑定手机号",
"bind": "绑定",
"binding": "绑定中",
"agreeTips": "请阅读并同意",
"pleaceAgree": "请勾选已阅读并同意",
"mobilePlaceholder": "请输入手机号",
"codePlaceholder": "请输入验证码",
"weixinUserAuth": "一键绑定",
"mobileQuickLogin": "手机号快捷登录"
}

View File

@ -0,0 +1,15 @@
{
"logining": "登录中",
"usernamePlaceholder": "请输入账号",
"passwordPlaceholder": "请输入密码",
"resetpwd": "忘记密码",
"noAccount": "还没有账号",
"toRegister": "去注册",
"and": "和",
"agreeTips": "登录代表您同意",
"isAgreeTips": "请先阅读并同意协议",
"usernameLogin": "密码登录",
"mobileLogin": "验证码登录",
"mobilePlaceholder": "请输入手机号",
"oneClicklogin":"一键登录"
}

View File

@ -0,0 +1,16 @@
{
"registering": "注册中",
"usernamePlaceholder": "请输入账号",
"passwordPlaceholder": "请输入密码",
"confirmPasswordPlaceholder": "请再次确认密码",
"confirmPasswordError": "两次输入的密码不一致",
"resetpwd": "忘记密码",
"haveAccount": "已有账号",
"toLogin": "去登录",
"and": "和",
"registerAgreeTips": "注册代表您同意",
"isAgreeTips": "请先阅读并同意协议",
"usernameRegister": "账号注册",
"mobileRegister": "手机号注册",
"mobilePlaceholder": "请输入手机号"
}

View File

@ -0,0 +1,6 @@
{
"findPassword": "找回密码",
"passwordPlaceholder": "请输入密码",
"confirmPasswordPlaceholder": "请再次确认密码",
"confirmPasswordError": "两次输入的密码不一致"
}

View File

@ -0,0 +1,10 @@
{
"developTitle":"开发环境配置",
"baseUrl":"API请求地址",
"imgUrl":"图片服务器地址",
"siteId":"站点IDVITE_SITE_ID",
"siteIdPlaceholder": "请输入站点ID",
"pleaseEnterNumber":"请输入数字",
"maximumCannotExceed":"最大不能超过"
}

View File

@ -0,0 +1,7 @@
{
"alipayAccountNo": "支付宝账号",
"addBankCard": "添加银行卡",
"addAlipayAccount": "添加支付宝账号",
"endNumber": "尾号",
"bankCard": "银行卡"
}

View File

@ -0,0 +1,17 @@
{
"addBankCard": "添加银行卡",
"addBankCardTips": "请添加持卡人本人的银行卡",
"addAlipayAccount": "添加支付宝账号",
"addAlipayAccountTips": "请添加已实名的支付宝账号",
"bankRealname": "持卡人姓名",
"bankRealnamePlaceholder": "请输入持卡人姓名",
"bankName": "银行名称",
"bankNamePlaceholder": "请输入银行名称",
"bankAccountNo": "银行卡号",
"bankAccountNoPlaceholder": "请输入银行卡号",
"alipayRealname": "真实姓名",
"alipayRealnamePlaceholder": "请输入真实姓名",
"alipayAccountNo": "支付宝账号",
"alipayAccountNoPlaceholder": "请输入支付宝账号",
"deleteConfirm": "确定要删除该账号吗?"
}

View File

@ -0,0 +1,6 @@
{
"address": "快递地址",
"locationAddress": "同城配送地址",
"createAddress": "新建收货地址",
"default": "默认"
}

View File

@ -0,0 +1,12 @@
{
"name": "收货人",
"namePlaceholder": "请输入收货人姓名",
"mobile": "手机号码",
"mobilePlaceholder": "请输入手机号码",
"selectArea":"选择地区",
"selectAreaPlaceholder":"请选择地区",
"address": "详细地址",
"addressPlaceholder": "请填写详细地址",
"defaultAddress": "设为默认地址",
"selectAddressPlaceholder":"请选择地址"
}

View File

@ -0,0 +1,30 @@
{
"cashOutNow": "立即提现",
"balanceDetail": "余额明细",
"cashOutTo": "提现到",
"cashOutTypePlaceholder": "请选择提现方式",
"wechatpay": "微信默认钱包",
"cashOutMoneyTip": "提现金额",
"money": "可提现余额",
"allTx": "全部提现",
"minWithdrawal": "最小提现金额为",
"commissionTo": "手续费为",
"cashOutList": "提现记录",
"cashOutToWechat": "提现至微信",
"cashOutToWechatTips": "提现至微信零钱",
"cashOutToAlipay": "提现至支付宝",
"cashOutToAlipayTips": "请先添加支付宝账号",
"cashOutToBank": "提现至银行卡",
"cashOutToBankTips": "请先添加银行卡",
"alipayAccountNo": "支付宝账号",
"debitCard": "储蓄卡",
"abnormalOperation": "异常操作",
"noAvailableCashOutType": "没有可用的提现方式",
"applyMoneyPlaceholder": "请输入提现金额",
"moneyformatError": "提现金额格式错误",
"applyMoneyExceed": "提现金额超出可提现金额",
"applyMoneyBelow": "提现金额小于最低提现金额",
"replace": "更换",
"isOpenApply": "提现设置未开启",
"toAdd": "添加"
}

View File

@ -0,0 +1,15 @@
{
"balanceInfo": "我的余额",
"recharge": "充值",
"cashOut":"提现",
"balanceDetail": "余额明细",
"accountBalance":"账户余额 (元)",
"balance":"余额 (元)",
"money":"可提现余额 (元)",
"availableBalance": "可用余额",
"rechargeAmountError": "充值金额错误",
"clickRecharge": "立即充值",
"rechargeAmountPlaceholder": "请输入充值金额",
"yuan":"元",
"rechargeRecord":"充值记录"
}

View File

@ -0,0 +1,11 @@
{
"applyTime": "申请时间",
"toBeReviewed": "官方正在审核,请耐心等待",
"toBeTransfer": "官方正在转账,请耐心等待",
"transfer": "官方已转账,请及时查收",
"cancelApply": "申请已取消",
"balanceDetail": "余额记录",
"commissionDetail": "佣金记录",
"emptyTip": "暂无提现记录",
"commissemptyTip": "暂无佣金记录"
}

View File

@ -0,0 +1,12 @@
{
"statusName": "当前状态",
"cashOutNo": "交易号",
"serviceMoney": "手续费",
"createTime": "申请时间",
"auditTime": "审核时间",
"transferBank": "银行名称",
"transferAccount": "收款账号",
"refuseReason": "拒绝理由",
"transferTypeName": "转账方式名称",
"transferTime": "转账时间"
}

View File

@ -0,0 +1,15 @@
{
"recharge": "充值",
"cashOut":"提现",
"transferMoney":"提现",
"commissionDetail": "佣金明细",
"accountCommission":"当前佣金(元)",
"commission":"累计佣金(元)",
"money":"提现中佣金(元)",
"availableCommission": "可用佣金",
"rechargeAmountError": "充值金额错误",
"clickRecharge": "立即充值",
"rechargeAmountPlaceholder": "请输入充值金额",
"yuan":"元",
"commissionInfo": "我的佣金"
}

View File

@ -0,0 +1,7 @@
{
"balanceDetail": "余额明细",
"commissionDetail": "佣金明细",
"balanceEmptyTip": "暂无余额明细",
"moneyEmptyTip": "暂无提现明细",
"commissionEmptyTip": "暂无佣金明细"
}

View File

@ -0,0 +1,14 @@
{
"name": "收货人",
"namePlaceholder": "请输入收货人姓名",
"mobile": "手机号码",
"mobilePlaceholder": "请输入手机号码",
"deliveryAddress":"收货地址",
"selectAddress":"选择地址",
"selectAddressPlaceholder":"请选择地址",
"address": "楼号门牌",
"addressPlaceholder": "详细地址如 1单元101",
"addressError": "请填写门牌号",
"defaultAddress": "设为默认地址",
"update": "修改"
}

View File

@ -0,0 +1,12 @@
{
"nickname": "昵称",
"sex": "性别",
"mobile": "手机号",
"birthday": "生日",
"unknown": "未知",
"updateHeadimg": "更换头像",
"updateNickname": "修改昵称",
"man": "男",
"woman": "女",
"bindMobile": "绑定手机"
}

View File

@ -0,0 +1,4 @@
{
"rechargeRecord": "充值记录",
"emptyTip": "暂无充值记录"
}

View File

@ -0,0 +1,4 @@
{
"orderNo": "订单编号",
"createTime": "创建时间"
}

View File

@ -0,0 +1,13 @@
{
"recharge": "充值",
"cashOut":"提现",
"balanceDetail": "余额明细",
"accountBalance":"账户余额(元)",
"balance":"余额(元)",
"money":"可提现余额(元)",
"availableBalance": "可用余额",
"rechargeAmountError": "充值金额错误",
"clickRecharge": "立即充值",
"rechargeAmountPlaceholder": "请输入充值金额",
"yuan":"元"
}

View File

@ -0,0 +1,6 @@
{
"personalSettings": "个人设置",
"switchLang": "切换语言",
"version": "版本号",
"logout": "退出登录"
}

View File

@ -0,0 +1,31 @@
<template>
<view :style="themeColor()" v-if="agreement">
<view v-if="agreement.content" class="p-[30rpx]">
<u-parse :content="agreement.content" :tagStyle="{img: 'vertical-align: top;'}"></u-parse>
</view>
<view v-else class="h-[100vh] w-full flex items-center justify-center">
<u-empty :icon="img('static/resource/images/empty.png')" text="暂无协议" />
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getAgreementInfo } from '@/app/api/system'
import { img} from '@/utils/common'
const agreement = ref(null)
onLoad((option)=> {
getAgreementInfo(option.key).then((res: AnyObject) => {
agreement.value = res.data
uni.setNavigationBarTitle({
title: res.data.title
})
})
})
</script>
<style lang="scss">
</style>

View File

@ -0,0 +1,212 @@
<template>
<view class="w-screen h-screen flex flex-col" :style="themeColor()">
<view class="flex-1">
<!-- #ifdef H5 -->
<view class="h-[100rpx]"></view>
<!-- #endif -->
<view class="px-[60rpx] pt-[100rpx] mb-[100rpx]">
<view class="font-bold text-lg">{{ t('bindMobile') }}</view>
</view>
<view class="px-[60rpx]">
<u-form labelPosition="left" :model="formData" errorType='toast' :rules="rules" ref="formRef">
<u-form-item label="" prop="mobile" :border-bottom="true">
<u-input v-model="formData.mobile" border="none" clearable :placeholder="t('mobilePlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]"/>
</u-form-item>
<view class="mt-[40rpx]">
<u-form-item label="" prop="mobile_code" :border-bottom="true">
<u-input v-model="formData.mobile_code" border="none" clearable :placeholder="t('codePlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]">
<template #suffix>
<sms-code :mobile="formData.mobile" type="bind_mobile" v-model="formData.mobile_key"></sms-code>
</template>
</u-input>
</u-form-item>
</view>
<view class="flex items-start mt-[30rpx]" v-if="!info && config.agreement_show">
<u-checkbox-group>
<u-checkbox activeColor="var(--primary-color)" :checked="isAgree" shape="shape" size="14" @change="agreeChange" :customStyle="{'marginTop': '4rpx'}" />
</u-checkbox-group>
<view class="text-xs text-gray-400 flex flex-wrap">
{{ t('agreeTips') }}
<view @click="redirect({ url: '/app/pages/auth/agreement?key=service' })">
<text class="text-primary">{{ t('userAgreement') }}</text>
</view>
<view @click="redirect({ url: '/app/pages/auth/agreement?key=privacy' })">
<text class="text-primary">{{ t('privacyAgreement') }}</text>
</view>
</view>
</view>
<view class="mt-[60rpx]">
<button hover-class="none" class="bg-[var(--primary-color)] text-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[28rpx]" :loading="loading" :loadingText="t('binding')" @click="handleBind">{{t('bind')}}</button>
</view>
<!-- #ifdef MP-WEIXIN -->
<view class="mt-[30rpx]">
<u-button type="primary" :plain="true" :text="t('weixinUserAuth')" @click="agreeTips" v-if="!info && config.agreement_show && !isAgree"></u-button>
<u-button type="primary" :plain="true" :text="t('mobileQuickLogin')" open-type="getPhoneNumber" @getphonenumber="mobileAuth" @click="checkWxPrivacy" v-else></u-button>
</view>
<!-- #endif -->
</u-form>
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<!-- 小程序隐私协议 -->
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
<!-- #endif -->
</view>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { t } from '@/locale'
import { bind } from '@/app/api/auth'
import { bindMobile } from '@/app/api/member'
import useMemberStore from '@/stores/member'
import useConfigStore from '@/stores/config'
import { useLogin } from '@/hooks/useLogin'
import { redirect } from '@/utils/common'
const memberStore = useMemberStore()
const info = computed(() => memberStore.info)
const config = computed(() => {
return useConfigStore().login
})
const loading = ref(false)
const isAgree = ref(false)
const formData = reactive({
mobile: '',
mobile_code: '',
mobile_key: ''
})
const real_name_input = ref(true);
onMounted(() => {
//
setTimeout(()=>{
real_name_input.value = false;
},800)
});
uni.getStorageSync('openid') && (Object.assign(formData, { openid: uni.getStorageSync('openid') }))
uni.getStorageSync('pid') && (Object.assign(formData, { pid: uni.getStorageSync('pid') }))
uni.getStorageSync('unionid') && (Object.assign(formData, { unionid: uni.getStorageSync('unionid') }))
const rules = {
'mobile': [
{
type: 'string',
required: true,
message: t('mobilePlaceholder'),
trigger: ['blur', 'change'],
},
{
validator(rule, value, callback) {
let mobile = /^1[3-9]\d{9}$/;
if (!mobile.test(value)){
callback(new Error('请输入正确的手机号'))
} else {
callback()
}
},
message: t('mobileError'),
trigger: ['change', 'blur'],
}
],
'mobile_code': {
type: 'string',
required: true,
message: t('codePlaceholder'),
trigger: ['blur', 'change']
}
}
const agreeChange = () => {
isAgree.value = !isAgree.value
}
const agreeTips = () => {
uni.showToast({ title: `${t('pleaceAgree')}${t('userAgreement')}》《${t('privacyAgreement')}`, icon: 'none' })
}
const formRef = ref(null)
const handleBind = () => {
formRef.value.validate().then(() => {
if (loading.value) return
loading.value = true
const request = info.value ? bindMobile : bind
request(formData).then((res) => {
if (info.value) {
memberStore.getMemberInfo()
redirect({ url: '/app/pages/member/personal', mode: 'redirectTo' })
} else {
memberStore.setToken(res.data.token)
useLogin().handleLoginBack()
}
}).catch(() => {
loading.value = false
})
})
}
const wxPrivacyPopupRef:any = ref(null)
//
const checkWxPrivacy = ()=>{
wxPrivacyPopupRef.value.proactive();
}
const mobileAuth = (e) => {
if (!isAgree.value && !info.value && config.value.agreement_show) {
uni.showToast({
title: `${ t('pleaceAgree') }${ t('userAgreement') }》《${ t('privacyAgreement') }`,
icon: 'none'
})
return
}
if (e.detail.errMsg == 'getPhoneNumber:ok') {
uni.showLoading({ title: '' })
const request = info.value ? bindMobile : bind
request({
openid: formData.openid,
mobile_code: e.detail.code
}).then((res) => {
uni.hideLoading()
if (info.value) {
memberStore.getMemberInfo()
redirect({ url: '/app/pages/member/personal', mode: 'redirectTo' })
} else {
memberStore.setToken(res.data.token)
useLogin().handleLoginBack()
}
}).catch((res) => {
setTimeout(() => {
uni.hideLoading()
}, 2000);
})
}
if (e.detail.errno == 104) {
let msg = '用户未授权隐私权限';
uni.showToast({ title: msg, icon: 'none' })
}
if (e.detail.errMsg == "getPhoneNumber:fail user deny") {
let msg = '用户拒绝获取手机号码';
uni.showToast({ title: msg, icon: 'none' })
}
}
</script>
<style lang="scss">
.u-input{
background-color: transparent !important;
}
</style>

View File

@ -0,0 +1,268 @@
<template>
<view class="w-screen h-screen flex flex-col" :style="themeColor()">
<view class="flex-1">
<!-- #ifdef H5 -->
<view class="h-[100rpx]"></view>
<!-- #endif -->
<view class="px-[60rpx] pt-[100rpx] mb-[100rpx]">
<view class="font-bold text-xl">{{ t('login') }}</view>
</view>
<view class="px-[60rpx] text-sm flex mb-[50rpx] font-bold leading-none" v-if="loginType.length > 1">
<block v-for="(item, index) in loginType">
<view :class="{'text-gray-300' : item.type != type}" @click="type = item.type">{{ item.title }}</view>
<view class="mx-[30rpx] border-solid border-0 border-r-[2px] border-gray-300" v-show="index == 0"></view>
</block>
</view>
<view class="px-[60rpx]">
<u-form labelPosition="left" :model="formData" errorType='toast' :rules="rules" ref="formRef">
<view v-show="type == 'username'">
<u-form-item label="" prop="username" :border-bottom="true">
<u-input v-model="formData.username" border="none" clearable :placeholder="t('usernamePlaceholder')" autocomplete="off" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2] text-[26rpx]"/>
</u-form-item>
<view class="mt-[40rpx]">
<u-form-item label="" prop="password" :border-bottom="true">
<u-input v-model="formData.password" border="none" type="password" clearable :placeholder="t('passwordPlaceholder')" autocomplete="new-password" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2] text-[26rpx]"/>
</u-form-item>
</view>
</view>
<view v-show="type == 'mobile'">
<u-form-item label="" prop="mobile" :border-bottom="true">
<u-input v-model="formData.mobile" border="none" clearable :placeholder="t('mobilePlaceholder')" autocomplete="off" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]"/>
</u-form-item>
<view class="mt-[40rpx]">
<u-form-item label="" prop="mobile_code" :border-bottom="true">
<u-input v-model="formData.mobile_code" border="none" clearable class="!bg-transparent" fontSize="26rpx" :disabled="real_name_input" :placeholder="t('codePlaceholder')" placeholderClass="!text-[#8288A2]">
<template #suffix>
<sms-code :mobile="formData.mobile" type="login" v-model="formData.mobile_key"></sms-code>
</template>
</u-input>
</u-form-item>
</view>
</view>
<view class="flex text-xs justify-between mt-[20rpx] text-[#8288A2]">
<view @click="redirect({ url: '/app/pages/auth/register' })">{{ t('noAccount') }}
<text class="text-primary">{{ t('toRegister') }}</text>
</view>
<view @click="redirect({ url: '/app/pages/auth/resetpwd' })">{{ t('resetpwd') }}</view>
</view>
<view class="mt-[80rpx]">
<button hover-class="none" class="bg-[var(--primary-color)] text-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[28rpx]" :loading="loading" :loadingText="t('logining')" @click="handleLogin">{{t('login')}}</button>
</view>
<!-- #ifdef MP-WEIXIN -->
<view class="mt-[20rpx]" v-if="configStore.login.is_auth_register">
<button hover-class="none" class="text-[var(--primary-color)] bg-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[28rpx]" :loading="loginLoading" :loadingText="t('logining')" @click="oneClickLogin">{{t('oneClicklogin')}}</button>
</view>
<view class="mt-[20rpx]" v-else-if="type == 'mobile'">
<u-button :customStyle="{border:'none',color:'var(--primary-color)',fontSize:'26rpx',height:'40rpx', lineHeight:'40rpx'}" :text="t('mobileQuickLogin')" open-type="getPhoneNumber" @getphonenumber="mobileAuth" @click="checkWxPrivacy"></u-button>
</view>
<!-- #endif -->
</u-form>
</view>
</view>
<view class="text-xs py-[50rpx] flex justify-center w-full" v-if="configStore.login.agreement_show">
<text class="iconfont text-[var(--primary-color)] text-[34rpx] mr-[12rpx]" :class="isAgree ? 'iconxuanze1' : 'nc-iconfont nc-icon-yuanquanV6xx'" @click="isAgree = !isAgree"></text>
{{ t('agreeTips') }}
<view @click="redirect({ url: '/app/pages/auth/agreement?key=service' })">
<text class="text-primary">{{ t('userAgreement') }}</text>
</view>
{{ t('and') }}
<view @click="redirect({ url: '/app/pages/auth/agreement?key=privacy' })">
<text class="text-primary">{{ t('privacyAgreement') }}</text>
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<!-- 小程序隐私协议 -->
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
<!-- #endif -->
</view>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { usernameLogin, mobileLogin,bind } from '@/app/api/auth'
import useMemberStore from '@/stores/member'
import useConfigStore from '@/stores/config'
import { useLogin } from '@/hooks/useLogin'
import { t } from '@/locale'
import { redirect,getToken } from '@/utils/common'
import { onLoad,onShow } from '@dcloudio/uni-app';
const formData = reactive({
username: '',
password: '',
mobile: '',
mobile_code: '',
mobile_key: ''
})
const real_name_input = ref(true);
onMounted(() => {
//
setTimeout(()=>{
real_name_input.value = false;
},800)
});
const memberStore = useMemberStore()
const configStore = useConfigStore()
onLoad(async()=>{
await configStore.getLoginConfig()
uni.getStorageSync('openid') && (Object.assign(formData, { openid: uni.getStorageSync('openid') }))
uni.getStorageSync('unionid') && (Object.assign(formData, { unionid: uni.getStorageSync('unionid') }))
uni.getStorageSync('pid') && (Object.assign(formData, { pid: uni.getStorageSync('pid') }))
if(!getToken() && !configStore.login.is_username && !configStore.login.is_mobile && !configStore.login.is_bind_mobile){
uni.showToast({ title: '商家未开启普通账号登录注册', icon: 'none' })
setTimeout(() => {
redirect({ url: '/app/pages/index/index', mode: 'reLaunch' })
}, 100)
}
})
const loading = ref(false)
const type = ref('')
const loginType = computed(() => {
const value = []
if(configStore.login.is_username){
value.push({ type: 'username', title: t('usernameLogin') })
}
if(configStore.login.is_bind_mobile || configStore.login.is_mobile){
value.push({ type: 'mobile', title: t('mobileLogin') })
}
type.value = value[0] ? value[0].type : ''
return value
})
const rules = computed(() => {
return {
'username': {
type: 'string',
required: type.value == 'username',
message: t('usernamePlaceholder'),
trigger: ['blur', 'change'],
},
'password': {
type: 'string',
required: type.value == 'username',
message: t('passwordPlaceholder'),
trigger: ['blur', 'change']
},
'mobile': [
{
type: 'string',
required: type.value == 'mobile',
message: t('mobilePlaceholder'),
trigger: ['blur', 'change'],
},
{
validator(rule, value) {
if (type.value != 'mobile') return true
else return uni.$u.test.mobile(value)
},
message: t('mobileError'),
trigger: ['change', 'blur'],
}
],
'mobile_code': {
type: 'string',
required: type.value == 'mobile',
message: t('codePlaceholder'),
trigger: ['blur', 'change']
}
}
})
const isAgree = ref(false)
const formRef = ref(null)
const handleLogin = () => {
formRef.value.validate().then(() => {
if (configStore.login.agreement_show && !isAgree.value) {
uni.showToast({ title: t('isAgreeTips'), icon: 'none' });
return false;
}
if (loading.value) return
loading.value = true
const login = type.value == 'username' ? usernameLogin : mobileLogin
login(formData).then((res) => {
memberStore.setToken(res.data.token)
if(configStore.login.is_bind_mobile && !res.data.mobile){
uni.setStorageSync('isbindmobile', true)
}
useLogin().handleLoginBack()
}).catch(() => {
loading.value = false
})
})
}
// todo
const loginLoading = ref(false)
const oneClickLogin = () =>{
//
if (loginLoading.value) return
loginLoading.value =true
const login = useLogin()
login.getAuthCode('',false,true)
}
const wxPrivacyPopupRef:any = ref(null)
//
const checkWxPrivacy = ()=>{
wxPrivacyPopupRef.value.proactive();
}
const mobileAuth = (e) => {
if (!isAgree.value && configStore.login.agreement_show) {
uni.showToast({
title: `${ t('pleaceAgree') }${ t('userAgreement') }》《${ t('privacyAgreement') }`,
icon: 'none'
})
return
}
const login = useLogin()
login.getAuthCode('',false,true,(data) => {
if (e.detail.errMsg == 'getPhoneNumber:ok') {
uni.showLoading({ title: '' })
bind({
openid:data.openid,
unionid:data.unionid,
mobile_code: e.detail.code
}).then((res) => {
uni.hideLoading()
memberStore.setToken(res.data.token)
useLogin().handleLoginBack()
}).catch((res) => {
setTimeout(() => {
uni.hideLoading()
}, 2000);
})
}
if (e.detail.errno == 104) {
let msg = '用户未授权隐私权限';
uni.showToast({ title: msg, icon: 'none' })
}
if (e.detail.errMsg == "getPhoneNumber:fail user deny") {
let msg = '用户拒绝获取手机号码';
uni.showToast({ title: msg, icon: 'none' })
}
})
}
</script>
<style lang="scss">
.u-input{
background-color: transparent !important;
}
</style>

View File

@ -0,0 +1,238 @@
<template>
<view class="w-screen h-screen flex flex-col" :style="themeColor()" >
<view class="flex-1">
<!-- #ifdef H5 -->
<view class="h-[100rpx]"></view>
<!-- #endif -->
<view class="px-[60rpx] pt-[100rpx] mb-[100rpx]">
<view class="font-bold text-xl">{{ t('register') }}</view>
</view>
<view class="px-[60rpx] text-sm flex mb-[50rpx] font-bold leading-none" v-if="registerType.length > 1">
<block v-for="(item, index) in registerType">
<view :class="{'text-gray-300' : item.type != type}" @click="type = item.type">{{ item.title }}</view>
<view class="mx-[30rpx] border-solid border-0 border-r-[2px] border-gray-300" v-show="index == 0"></view>
</block>
</view>
<view class="px-[60rpx]">
<u-form labelPosition="left" :model="formData" errorType='toast' :rules="rules" ref="formRef">
<view v-show="type == 'username'">
<view class="mt-[30rpx]">
<u-form-item label="" prop="username" :border-bottom="true">
<u-input v-model="formData.username" border="none" clearable :placeholder="t('usernamePlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]"/>
</u-form-item>
</view>
<view class="mt-[30rpx]">
<u-form-item label="" prop="password" :border-bottom="true">
<u-input v-model="formData.password" border="none" type="password" clearable :placeholder="t('passwordPlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]"/>
</u-form-item>
</view>
<view class="mt-[30rpx]">
<u-form-item label="" prop="confirm_password" :border-bottom="true">
<u-input v-model="formData.confirm_password" border="none" type="password" clearable :placeholder="t('confirmPasswordPlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]"/>
</u-form-item>
</view>
</view>
<view v-show="type == 'mobile' || configStore.login.is_bind_mobile">
<view class="mt-[30rpx]">
<u-form-item label="" prop="mobile" :border-bottom="true">
<u-input v-model="formData.mobile" border="none" clearable :placeholder="t('mobilePlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]"/>
</u-form-item>
</view>
<view class="mt-[30rpx]">
<u-form-item label="" prop="mobile_code" :border-bottom="true">
<u-input v-model="formData.mobile_code" border="none" clearable :placeholder="t('codePlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]">
<template #suffix>
<sms-code v-show="type" :mobile="formData.mobile" type="register" v-model="formData.mobile_key"></sms-code>
</template>
</u-input>
</u-form-item>
</view>
</view>
<view v-show="type == 'username'">
<view class="mt-[30rpx]">
<u-form-item label="" prop="captcha_code" :border-bottom="true">
<u-input v-model="formData.captcha_code" border="none" clearable :placeholder="t('captchaPlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]">
<template #suffix>
<image :src="captcha.image.value" class="h-[48rpx] w-[60rpx] ml-[20rpx]" mode="heightFix" @click="captcha.refresh()"></image>
</template>
</u-input>
</u-form-item>
</view>
</view>
<view class="flex text-xs justify-between mt-[20rpx] text-[#8288A2]">
<view @click="redirect({ url: '/app/pages/auth/login' })">{{ t('haveAccount') }}<text class="text-primary">{{ t('toLogin') }}</text></view>
</view>
<view class="mt-[80rpx]">
<button hover-class="none" class="bg-[var(--primary-color)] text-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[28rpx]" :loading="loading" :loadingText="t('registering')" @click="handleRegister">{{t('register')}}</button>
</view>
</u-form>
</view>
</view>
<view class="text-xs py-[50rpx] flex justify-center w-full" v-if="configStore.login.agreement_show">
<text class="iconfont text-[var(--primary-color)] text-[34rpx] mr-[12rpx]" :class="isAgree ? 'iconxuanze1' : 'nc-iconfont nc-icon-yuanquanV6xx'" @click="isAgree = !isAgree"></text>
{{ t('registerAgreeTips') }}
<view @click="redirect({ url: '/app/pages/auth/agreement?key=service' })">
<text class="text-primary">{{ t('userAgreement') }}</text>
</view>
{{ t('and') }}
<view @click="redirect({ url: '/app/pages/auth/agreement?key=privacy' })">
<text class="text-primary">{{ t('privacyAgreement') }}</text>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { usernameRegister, mobileRegister } from '@/app/api/auth'
import useMemberStore from '@/stores/member'
import useConfigStore from '@/stores/config'
import { useLogin } from '@/hooks/useLogin'
import { useCaptcha } from '@/hooks/useCaptcha'
import { t } from '@/locale'
import { redirect, getToken } from '@/utils/common'
import { onLoad } from '@dcloudio/uni-app';
const formData = reactive({
username: '',
password: '',
confirm_password: '',
mobile: '',
mobile_code: '',
mobile_key: '',
captcha_key: '',
captcha_code: ''
})
const real_name_input = ref(true);
onMounted(() => {
//
setTimeout(()=>{
real_name_input.value = false;
},800)
});
const memberStore = useMemberStore()
const configStore = useConfigStore()
onLoad(async() =>{
await configStore.getLoginConfig()
if (!uni.getStorageSync('autoLoginLock')) {
uni.getStorageSync('openid') && (Object.assign(formData, {openid: uni.getStorageSync('openid')}))
uni.getStorageSync('pid') && (Object.assign(formData, {pid: uni.getStorageSync('pid')}))
}
uni.getStorageSync('unionid') && (Object.assign(formData, { unionid: uni.getStorageSync('unionid') }))
if(!getToken() && !configStore.login.is_username && !configStore.login.is_mobile && !configStore.login.is_bind_mobile){
uni.showToast({ title: '商家未开启普通账号登录注册', icon: 'none' })
setTimeout(() => {
redirect({ url: '/app/pages/index/index', mode: 'reLaunch' })
}, 100)
}
})
const captcha = useCaptcha(formData)
captcha.refresh()
const loading = ref(false)
const type = ref('')
const registerType = computed(()=> {
const value = []
configStore.login.is_username && (value.push({ type: 'username', title: t('usernameRegister') }))
configStore.login.is_mobile && !configStore.login.is_bind_mobile && (value.push({ type: 'mobile', title: t('mobileRegister') }))
type.value = value[0] ? value[0].type : ''
return value
})
const rules = computed(()=>{
return {
'username': {
type: 'string',
required: type.value == 'username',
message: t('usernamePlaceholder'),
trigger: ['blur', 'change'],
},
'password': {
type: 'string',
required: type.value == 'username',
message: t('passwordPlaceholder'),
trigger: ['blur', 'change']
},
'confirm_password': [
{
type: 'string',
required: type.value == 'username',
message: t('confirmPasswordPlaceholder'),
trigger: ['blur', 'change']
},
{
validator(rule, value){
return value == formData.password
},
message: t('confirmPasswordError'),
trigger: ['change','blur'],
}
],
'mobile': [
{
type: 'string',
required: type.value == 'mobile' || configStore.login.is_bind_mobile,
message: t('mobilePlaceholder'),
trigger: ['blur', 'change'],
},
{
validator(rule, value){
if (type.value != 'mobile' && !configStore.login.is_bind_mobile) return true
else return uni.$u.test.mobile(value)
},
message: t('mobileError'),
trigger: ['change','blur'],
}
],
'mobile_code': {
type: 'string',
required: type.value == 'mobile' || configStore.login.is_bind_mobile,
message: t('codePlaceholder'),
trigger: ['blur', 'change']
},
'captcha_code': {
type: 'string',
required: type.value == 'username',
message: t('captchaPlaceholder'),
trigger: ['blur', 'change'],
}
}
})
const isAgree = ref(false)
const formRef = ref(null)
const handleRegister = () => {
formRef.value.validate().then(() => {
if (configStore.login.agreement_show && !isAgree.value) {
uni.showToast({ title: t('isAgreeTips'), icon: 'none' });
return false;
}
if (loading.value) return
loading.value = true
const register = type.value == 'username' ? usernameRegister : mobileRegister
register(formData).then((res: responseResult) => {
memberStore.setToken(res.data.token)
useLogin().handleLoginBack()
}).catch(() => {
loading.value = false
captcha.refresh()
})
})
}
</script>
<style lang="scss">
.u-input{
background-color: transparent !important;
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<view class="w-screen h-screen flex flex-col" :style="themeColor()">
<view class="flex-1">
<!-- #ifdef H5 -->
<view class="h-[100rpx]"></view>
<!-- #endif -->
<view class="px-[60rpx] pt-[100rpx] mb-[100rpx]">
<view class="font-bold text-xl">{{ t('findPassword') }}</view>
</view>
<view class="px-[60rpx]">
<u-form labelPosition="left" :model="formData" errorType='toast' :rules="rules" ref="formRef">
<view class="mt-[30rpx]">
<u-form-item label="" prop="mobile" :border-bottom="true">
<u-input v-model="formData.mobile" border="none" clearable :placeholder="t('mobilePlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]"/>
</u-form-item>
</view>
<view class="mt-[30rpx]">
<u-form-item label="" prop="code" :border-bottom="true">
<u-input v-model="formData.mobile_code" border="none" clearable :placeholder="t('codePlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]">
<template #suffix>
<sms-code :mobile="formData.mobile" type="find_pass" v-model="formData.mobile_key"></sms-code>
</template>
</u-input>
</u-form-item>
</view>
<view class="mt-[30rpx]">
<u-form-item label="" prop="password" :border-bottom="true">
<u-input v-model="formData.password" border="none" type="password" clearable :placeholder="t('passwordPlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]"/>
</u-form-item>
</view>
<view class="mt-[30rpx]">
<u-form-item label="" prop="confirm_password" :border-bottom="true">
<u-input v-model="formData.confirm_password" border="none" type="password" clearable :placeholder="t('confirmPasswordPlaceholder')" class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx" placeholderClass="!text-[#8288A2]"/>
</u-form-item>
</view>
<view class="mt-[80rpx]">
<button hover-class="none" class="bg-[var(--primary-color)] text-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[28rpx]" :loading="loading" :loadingText="t('confirm')" @click="handleConfirm">{{t('confirm')}}</button>
</view>
</u-form>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { t } from '@/locale'
import { resetPassword } from '@/app/api/system'
import { redirect } from '@/utils/common'
const formData = reactive({
mobile: '',
mobile_code: '',
mobile_key: '',
password: '',
confirm_password: ''
})
const real_name_input = ref(true);
onMounted(() => {
//
setTimeout(()=>{
real_name_input.value = false;
},800)
});
const loading = ref(false)
const formRef = ref(null)
const rules = {
'password': {
type: 'string',
required: true,
message: t('passwordPlaceholder'),
trigger: ['blur', 'change']
},
'confirm_password': [
{
type: 'string',
required: true,
message: t('confirmPasswordPlaceholder'),
trigger: ['blur', 'change']
},
{
validator(rule, value) {
return value == formData.password
},
message: t('confirmPasswordError'),
trigger: ['change', 'blur'],
}
],
'mobile': [
{
type: 'string',
required: true,
message: t('mobilePlaceholder'),
trigger: ['blur', 'change'],
},
{
validator(rule, value) {
return uni.$u.test.mobile(value)
},
message: t('mobileError'),
trigger: ['change', 'blur'],
}
],
'mobile_code': {
type: 'string',
required: true,
message: t('codePlaceholder'),
trigger: ['blur', 'change']
}
}
const handleConfirm = () => {
formRef.value.validate().then(() => {
if (loading.value) return
loading.value = true
resetPassword(formData).then((res : responseResult) => {
redirect({ url: '/app/pages/auth/login', mode: 'redirectTo' })
}).catch(() => {
loading.value = false
})
})
}
</script>
<style lang="scss">
.u-input{
background-color: transparent !important;
}
</style>

View File

@ -0,0 +1,12 @@
<template>
<view class="w-screen h-screen flex flex-col items-center justify-center" :style="themeColor()">
<u-empty :icon="img('static/resource/images/site/close.png')" :text="t('siteClose')"></u-empty>
</view>
</template>
<script setup lang="ts">
import { img } from '@/utils/common'
import { t } from '@/locale'
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,61 @@
<template>
<view class="mt-[80rpx] px-[60rpx]" :style="themeColor()">
<view class="font-bold text-xl mb-[80rpx]">{{t('developTitle')}}</view>
<view class="mb-[40rpx] flex flex-col">
<text class="mb-[10rpx]">{{ t('baseUrl') }}</text>
<text class="text-sm">{{formData.baseUrl}}</text>
</view>
<view class="mb-[40rpx] flex flex-col">
<text class="mb-[10rpx]">{{ t('imgUrl') }}</text>
<text class="text-sm">{{formData.imgUrl}}</text>
</view>
<view class="mb-[40rpx] flex flex-col">
<text class="mb-[20rpx]">{{ t('siteId') }}</text>
<u-input v-model="formData.siteId" clearable :placeholder="t('siteIdPlaceholder')" />
</view>
<button class="mt-[80rpx] bg-[var(--primary-color)] text-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[28rpx]" @click="save">{{t('confirm')}}</button>
</view>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { t } from '@/locale'
import manifestJson from '@/manifest.json'
const formData = reactive({
siteId: '',
baseUrl: import.meta.env.VITE_APP_BASE_URL || `${location.origin}/api/`,
imgUrl: import.meta.env.VITE_IMG_DOMAIN
})
const save = () => {
if (formData.siteId.length == 0) {
uni.showToast({ title: t('siteIdPlaceholder'), icon: 'none' });
return;
}
var reg = /^[0-9]+$/;
if (!reg.test(formData.siteId)) {
uni.showToast({ title: t('pleaseEnterNumber'), icon: 'none' });
return;
}
// if (formData.siteId > 9999999) {
// uni.showToast({ title: t('maximumCannotExceed') + '9999999', icon: 'none' });
// return;
// }
uni.setStorageSync('wap_site_id', formData.siteId);
let url = uni.getStorageSync('develop_before_path') || '/app/pages/index/index';
if (url == '/app/pages/index/develop') url = '/app/pages/index/index';
url = url.replace('/','')
uni.removeStorageSync('develop_before_path');
//
location.href = `${location.origin}${manifestJson.h5.router.base}${url}`
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,79 @@
<template>
<view :style="themeColor()">
<u-loading-page :loading="diy.getLoading()" loadingText="" bg-color="#f7f7f7" />
<view v-show="!diy.getLoading()">
<!-- 自定义模板渲染 -->
<view class="diy-template-wrap bg-index" v-if="diy.data.pageMode != 'fixed'" :style="diy.pageStyle()">
<diy-group ref="diyGroupRef" :data="diy.data" :pullDownRefreshCount="diy.pullDownRefreshCount" />
</view>
<!-- 固定模板渲染 -->
<view class="fixed-template-wrap" v-if="diy.data.pageMode == 'fixed'">
<fixed-group :data="diy.data" :pullDownRefreshCount="diy.pullDownRefreshCount" />
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<!-- 小程序隐私协议 -->
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
<!-- #endif -->
</view>
</template>
<script setup lang="ts">
import {ref} from 'vue';
import {useDiy} from '@/hooks/useDiy'
import {useShare} from '@/hooks/useShare'
import diyGroup from '@/addon/components/diy/group/index.vue'
import fixedGroup from '@/addon/components/fixed/group/index.vue'
const {setShare} = useShare()
const diy = useDiy({})
const diyGroupRef = ref(null)
//
diy.onLoad();
//
diy.onShow((data: any) => {
let share = data.share ? JSON.parse(data.share) : null;
setShare(share);
diyGroupRef.value?.refresh();
});
//
diy.onUnload();
//
diy.onPullDownRefresh()
//
diy.onPageScroll()
</script>
<style lang="scss" scoped>
@import '@/styles/diy.scss';
</style>
<style lang="scss">
.diy-template-wrap {
/* #ifdef MP */
.child-diy-template-wrap {
::v-deep .diy-group {
> .draggable-element.top-fixed-diy {
display: block !important;
}
}
}
/* #endif */
}
</style>

View File

@ -0,0 +1,85 @@
<template>
<view :style="themeColor()">
<u-loading-page :loading="diy.getLoading()" loadingText="" bg-color="#f7f7f7" />
<view v-show="!diy.getLoading()">
<!-- 自定义模板渲染 -->
<view class="diy-template-wrap bg-index" v-if="diy.data.pageMode != 'fixed'" :style="diy.pageStyle()">
<diy-group ref="diyGroupRef" :data="diy.data" :pullDownRefreshCount="diy.pullDownRefreshCount" />
</view>
<!-- 固定模板渲染 -->
<view class="fixed-template-wrap" v-if="diy.data.pageMode == 'fixed'">
<fixed-group :data="diy.data" :pullDownRefreshCount="diy.pullDownRefreshCount" />
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<!-- 小程序隐私协议 -->
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
<!-- #endif -->
</view>
</template>
<script setup lang="ts">
import {ref} from 'vue';
import {useDiy} from '@/hooks/useDiy'
import {redirect} from '@/utils/common';
import diyGroup from '@/addon/components/diy/group/index.vue'
import fixedGroup from '@/addon/components/fixed/group/index.vue'
const diy = useDiy({
name: 'DIY_INDEX'
})
const diyGroupRef = ref(null)
//
diy.onLoad();
//
diy.onShow((data: any) => {
if (data.value) {
// uni.setNavigationBarTitle({
// title: diyData.title
// })
} else if (data.page) {
//
redirect({url: data.page, mode: 'reLaunch'})
}
diyGroupRef.value?.refresh();
});
//
diy.onUnload();
//
diy.onPullDownRefresh()
//
diy.onPageScroll()
</script>
<style lang="scss" scoped>
@import '@/styles/diy.scss';
</style>
<style lang="scss">
.diy-template-wrap {
/* #ifdef MP */
.child-diy-template-wrap {
::v-deep .diy-group {
> .draggable-element.top-fixed-diy {
display: block !important;
}
}
}
/* #endif */
}
</style>

View File

@ -0,0 +1,12 @@
<template>
<view class="w-screen h-screen flex flex-col items-center justify-center" :style="themeColor()">
<u-empty :icon="img('static/resource/images/site/close.png')" :text="t('noSite')"></u-empty>
</view>
</template>
<script setup lang="ts">
import { img } from '@/utils/common'
import { t } from '@/locale'
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,104 @@
<template>
<view class="w-screen h-screen bg-page" :style="themeColor()">
<mescroll-body ref="mescrollRef" @init="mescrollInit" :down="{ use: false }" @up="getCashoutAccountListFn">
<view class="h-[10rpx]"></view>
<u-swipe-action-item :options="accountOptions" @click="swipeClick(index)" v-for="(item, index) in accountList" :key="index" class="sidebar-marign my-[20rpx]">
<view class="p-[30rpx] bg-white rounded flex justify-between" >
<view class="flex">
<view class="w-[100rpx] h-[100rpx] flex items-center justify-center mr-[10rpx]" @click="handleClick(item)">
<image
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAACnVJREFUeF7tWXtQVNcZ/91FQQZZdhcXfKCCqFXQuKBV0YSHqWJ9AbU+qkZQG+uEyu66i0maB5jEaSetBdvG1vrCRhM1bQS1SUw6gn0kJhkDpqLNQ10jLApEHmrCuuyeznfWRZZd3L03/MOw38zOPfee8517vt/5fY9zV0AfF6GP2w8/AH4G9HEE/C7QxwngD4J+F/C7QB9HwO8CfZwA/iwgyQWuQznBBmGxAKGdwd4u41e0070Ndhtd7z2zUT+DYKN7GWzt14PDAtZOXfIjgLULYNaziuGfQ0A73QPMxtt2wdGG0H6vzwaB2kI7omyjoMZkMFgRav8MTGZFP+Eu7PZ23gaz8nGMWRHArGABVtht7QiQfY08Q2VXxosGwAzlVkDQAggR6z7bx87EjrEzcKtfkFhVIBjAcAYMZeJ1uYbwDWR4BnmG4s4TiALAjPBfAuwpKSvYFTsVBRPnSFF16ExigFKq8R2vvQmdMVwSAGYoVwHCq1IsqA4bjIXJOWgL6CdFHYhljt3vCWEsDfr8CudUPjOgFqpDArBMyhp2x07F899l96fbgQFS3uxBp/l2AAoL7aIBMEN1GUCMlGXkTsnE0agJUlSBgQCmdKxX2hz3tf4NnfER0S5QA4VGBplbBPV1NTNm58IUovR1uOu4IQC+10MACMJWaA3PigbADOV6QNgpxYKL8gg8Omu9FFWHzlg7MFS6uoumTJaOvE3vSgBAtRvAOinLeH2kBoaEBVJUHTqT7UCodHUXzf7BocjNvS0agFqoPhWAiQMy56HfpAmwXb2Gb0te5/MEps5EYMpMWMrehrXqv24r3ayZhwPRiZIsSNVEISV9mItuVUMDyi5/KX4+AR9Ba5zWVdFrFriOsBg7AigAYlBlBfprJuLb/YfQnJPL5wqvOMYBaExI9QjA7LSfgtKgFCktWIQMTSzONTZAERSEkaFyPs2a995ByYVqcVMK7DfQ5ueLBsAM5QpAOEiKQ9jXXL8p6zG0lb7F2/SMtbTiusI9QZiD5ZiSniduoZ1GN732BBTBQYjZtwum1lZUrVyNSYPU2PLhByg88764ee1sETblH5cAgIpKRy1RPbz8GNe/oRwFe3MLZwOxoq3sbTRlruJ9MkUY7yM5NiwOh9bkISU+CvvLL8BU39rtohUhjvKYrjROE6NG5W9X4eqtVkTv3cUZULliNaLlciS89iqqGurFAWBlkcjPd1Py6gJ1UL3PgKQQ3QbIi7bCeu48GjUp/OXOZ636Z/g99dtMXyEgegQH4bllzyJi41pER8iR87uT3Kh9G9P5ffMdC9Kee4MbfPSpRR3G03Plqh3QLUxE0doUvvOll75AZuwYbvz+i9WoqLmGouQ0mFpb0GyxICYsDJnHy3B0QQYHjNhB7e1VnziYwnAOeqPGE2IPBOAmlGFtEIj3AYqSVxCcvdzF/5WlBzAg44fc/wkM6r+95WUMLNjM3/WDec9gd8mT3GAy6srOdWj5xgLdngpu9PYTlRyIgmXTeTtzWixGquUQsopQYkhH9sNx3CACodnShtJLX/J2+eKlHAiS7PHxvE0xgZ6frq0BY4yDojn4Fw4QGH4PvdGjLz4QgBoo58sgnKAXOYPdne070ar7Bad6ZNNlnhHqozUdAbJOCOdxof1qDZKKTuHj4tU4XV2DklMXsG/jHGw5fIZT3NlOnRDFXSTmZ3tQ/uISKAcGQbFyB67sWYdolTvdSxdmIGPUaO4GmbGjUTAtCfp/VqC48iyY1tCxyS6BUmDLoM0/IpoBZihfAgTOb+duE7UtpW/x9EdUp4B4t+I/HAxyDwKHYsWVwyfwQn1gh6E0B+10yalqaGIiEBMph0Z/gPu5IIC3iSEEFrkLZ4vFAsWf/uCy7oofL0PKsChknSjjbtA5JjgBIBak/vXwfT1mjYH+aZMEAFRUNc0mRTI2tPBJbjiJteo87hT/kRtPwVBevJW3qRYgd3jzZCU+mzQFtMOFhz5A1ZUGlD69iO922UeX+DOif8VLS1D64SUOTPG6VM4UU30LCh9PQtXNeuhOl7usOycuHsXJaWi+a+FpsTNIzizhGiSFi9AZ4rqLmA90ATNUzQDCxIVbx+iVST9BeWSsFFWAEkKSuPqfmHBlzeM8HuS8+06n9wo7oTNsEA3ADaiSbIDIZHv/NXHzDGgOpM84EkTNgHhx53+K+mlRwxG9b5cj8DmFCWugN5SIBqAOKiMDfi1h+ahSDMG81O6PDuTflBmKj3+Cc6ZGnh61CxK4K1AmcAr5OUX+pg0/548o7WnUES5LIrpTcVQyZ67nAokFxEGvvygaADNUbwLIkgJAd5+/KOdTpKcASDGBhHyegiOlx+LjlRipDkXO3Hgog4N4AKRIT7vrzP/7Zs/l+Z0kZ3w8qhod8xA4rtTnjy9DZ3ygH3YbA8xQ1QBwPYn4iMb6qYtxYuh4t9G061QIVZy/1tF3ztTAawK6UlAk4ZXjPV8uTkmDVpPIix+qBokBlPYyRrna1XLXwoshF2HYD70x50HL9giAGeHjANYtbbzh8P30jagNdo+dBEBJXrpLaUysIPoTIwgAhTwImhFqpP3tCDeaAhulSdpd2n2HoaWIlofxe2cf+b2H8jgXOuMO0QDUQrVOAOgbgGgxhagwY/YTHvVyZsVzY8nnnf5P91T8EP0zpsZi/6fVyJgWy098ZGTlisc4EFQBUgokZjhPhalRw3nAI8OJFW4ACJgCrfGsaADqoNrLgDWirQdwZMRD0CU6avuu4qzvie4U8JwVYeeS2NTUihabhZexuoTJKEpO5UYTGFQAUdAjV6C4QFdnGUzGu0R/oBY6Y5Q3G7pxAdX/6EucN2VP/fma+TgYneBRlYoiCoL6vac5A4gRVBJn/eoYrw4pGAovb+N1ABlHtT35PFV1hdNn8DYdjQkYnSaRF0NNbW0cFA/yBnTGpd5scAPgOgZG2BF4w5tid/2pj27A56GDPHYXLk/iRtLOUzVI/p+d5ijSiBWTotW8Tf5PhQ35eFchV6A+chFyAToMdfOBxAidcZs3O9wAMCM8C2CUAkVLc/9gxM2/fyDpOgEZTP5PQiBQ0HPeUxDUJKqBkYz7fGdxRn9KdeQKTr93PvdAf8BuT8amzf/yZoQHAFSE2iZvip763xs8FtnTvbKu+6nHMGCYuArQ82RCA4beisLSwrve7HADoBaqMwLg9vHQ20TUvzV+Fl4ZM8OXoZ7H9NQXYIa/Q2/06VO0CwAM6FcHlVWqBRmPZOPj8OHS1GUAksUdgLp9kSA8D63hRV8W4gLANShSAyBzPX/6Msu9MUMzXf50EaFJHwMZoOkJ+vPXzoXOeNKXBXRlQGCd4wgs6Ri3Kmk5TkWO9uW97mOIAQ/bAbp+N9kGndHo6xSegqDkf4G2jUsG/STLODsg7S8EQIAZEP4MrWGLmPd7LIS+AIIGQJEkZqLOYwseSudfYL8KCav/R+QYs6h51BiMMAxGsKwRKjsdyHyTTv/5+6bgGOX1s7iYyXrjWD8AvXHXenLNfgb0JJq9cS4/A3rjrvXkmv0M6Ek0e+Ncfgb0xl3ryTX7GdCTaPbGufo8A/4PpckqfWjPHTQAAAAASUVORK5CYII="
mode="widthFix" class="w-[80rpx]" v-if="item.account_type == 'bank'"></image>
<text class="iconfont iconzhifubaoxuanzhong text-[#188dfb] text-[80rpx]" v-else></text>
</view>
<view @click="handleClick(item)">
<view>{{ item.account_type == 'bank' ? item.bank_name : t('alipayAccountNo') }}</view>
<view v-if="item.account_type == 'bank'" class="text-sm text-gray-subtitle mt-[10rpx]">{{ t('endNumber') }} {{ item.account_no.substring(item.account_no.length - 4) }}{{ t('bankCard') }}</view>
<view v-else class="text-sm text-gray-subtitle mt-[10rpx]">{{ item.account_no }}</view>
</view>
</view>
<text class="flex items-center nc-iconfont nc-icon-xiugaiV6xx shrink-0 text-[32rpx] p-[20rpx] pr-0" @click="editAccount(item)"></text>
</view>
</u-swipe-action-item>
<view class="p-[30rpx] bg-white sidebar-marign my-[20rpx] rounded flex" @click="redirect({ url: '/app/pages/member/account_edit', param: { type: accountType, mode } })">
<u-icon name="plus" color="#333" size="16"></u-icon>
<text class="text-sm ml-[10rpx] flex-1">{{ accountType == 'bank' ? t('addBankCard') : t('addAlipayAccount') }}</text>
<u-icon name="arrow-right" color="#333" size="14"></u-icon>
</view>
</mescroll-body>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { redirect } from '@/utils/common'
import { getCashoutAccountList, deleteCashoutAccount } from '@/app/api/member'
import MescrollBody from '@/components/mescroll/mescroll-body/mescroll-body.vue'
import useMescroll from '@/components/mescroll/hooks/useMescroll.js'
import { onPageScroll, onReachBottom, onLoad } from '@dcloudio/uni-app'
import { t } from '@/locale'
const { mescrollInit, downCallback, getMescroll } = useMescroll(onPageScroll, onReachBottom)
const accountList = ref<Array<any>>([])
const loading = ref(false)
const accountType = ref('bank')
const mescrollRef = ref(null)
const mode = ref('get')
onLoad((data: any) => {
data.type && (accountType.value = data.type)
data.mode && (mode.value = data.mode)
})
const getCashoutAccountListFn = (mescroll : mescrollStructure) => {
loading.value = false;
let data : object = {
page: mescroll.num,
limit: mescroll.size,
account_type: accountType.value
};
getCashoutAccountList(data).then((res) => {
const newArr = (res.data.data as Array<Object>);
//
if (mescroll.num == 1) {
accountList.value = []; //
}
accountList.value = accountList.value.concat(newArr);
mescroll.endSuccess(newArr.length);
loading.value = true;
}).catch(() => {
loading.value = true;
mescroll.endErr(); // ,
})
}
const editAccount = (item: any)=> {
const url = `/app/pages/member/account_edit`
redirect({ url, param: { id : item.account_id, type : item.account_type, mode:mode.value} })
}
const accountOptions = ref([
{
text: t('delete'),
style: {
backgroundColor: '#F56C6C'
}
}
])
const swipeClick = (index: any) => {
const data = accountList.value[index]
deleteCashoutAccount(data.account_id).then(() => {
accountList.value.splice(index, 1)
}).catch()
}
const handleClick = (data: AnyObject) => {
if (mode.value == 'get') redirect({ url: '/app/pages/member/account_edit', param: { id: data.account_id, type: accountType.value, mode: mode.value }, mode: 'redirectTo' })
else redirect({ url: '/app/pages/member/apply_cash_out', param: { account_id: data.account_id, type: accountType.value }, mode: 'redirectTo'})
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,142 @@
<template>
<view :style="themeColor()">
<scroll-view scroll-y="true" class="w-screen h-screen bg-page">
<view class="h-[20rpx]"></view>
<view class="p-[30rpx] bg-white sidebar-marign rounded">
<block v-if="formData.account_type == 'bank'">
<view class="text-center text-base font-bold mt-[50rpx]">{{ t('addBankCard') }}</view>
<view class="text-center text-sm mt-[10rpx]">{{ t('addBankCardTips') }}</view>
<view class="mt-[50rpx]">
<u-form labelPosition="left" :model="formData" :label-style="{'font-size':'28rpx'}" labelWidth="200rpx" errorType='toast' :rules="rules" ref="formRef">
<view class="mt-[10rpx]">
<u-form-item :label="t('bankRealname')" prop="realname" :border-bottom="true">
<u-input v-model.trim="formData.realname" fontSize="28rpx" maxlength="30" border="none" clearable :placeholder="t('bankRealnamePlaceholder')"/>
</u-form-item>
</view>
<view class="mt-[10rpx]">
<u-form-item :label="t('bankName')" prop="bank_name" :border-bottom="true">
<u-input v-model.trim="formData.bank_name" fontSize="28rpx" maxlength="30" border="none" clearable :placeholder="t('bankNamePlaceholder')"/>
</u-form-item>
</view>
<view class="mt-[10rpx]">
<u-form-item :label="t('bankAccountNo')" prop="account_no" :border-bottom="true">
<u-input v-model.trim="formData.account_no" fontSize="28rpx" maxlength="30" border="none" clearable :placeholder="t('bankAccountNoPlaceholder')"/>
</u-form-item>
</view>
</u-form>
</view>
</block>
<block v-if="formData.account_type == 'alipay'">
<view class="text-center text-base font-bold mt-[50rpx]">{{ t('addAlipayAccount') }}</view>
<view class="text-center text-sm mt-[10rpx]">{{ t('addAlipayAccountTips') }}</view>
<view class="mt-[50rpx]">
<u-form labelPosition="left" :model="formData" labelWidth="200rpx" errorType='toast' :rules="rules" ref="formRef">
<view class="mt-[10rpx]">
<u-form-item :label="t('alipayRealname')" prop="realname" :border-bottom="true">
<u-input v-model.trim="formData.realname" maxlength="30" border="none" clearable :placeholder="t('alipayRealnamePlaceholder')"/>
</u-form-item>
</view>
<view class="mt-[10rpx]">
<u-form-item :label="t('alipayAccountNo')" prop="account_no" :border-bottom="true">
<u-input v-model.trim="formData.account_no" border="none" maxlength="30" clearable :placeholder="t('alipayAccountNoPlaceholder')"/>
</u-form-item>
</view>
</u-form>
</view>
</block>
<view class="mt-[100rpx]">
<button :loading="loading" class="bg-[var(--primary-color)] text-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[28rpx]" @click="handleSave">{{t('save')}}</button>
</view>
</view>
</scroll-view>
<u-modal :show="deleteConfirm" :content="t('deleteConfirm')" :confirmText="t('confirm')" :cancelText="t('cancel')" :showCancelButton="true" @confirm="handleDelete" @cancel="deleteConfirm = false" confirmColor="var(--primary-color)"></u-modal>
</view>
</template>
<script setup lang="ts">
import { ref, computed, reactive } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getCashoutAccountInfo, addCashoutAccount, editCashoutAccount, deleteCashoutAccount } from '@/app/api/member'
import { t } from '@/locale'
import { redirect } from '@/utils/common'
const loading = ref(false)
const formRef = ref(null)
const mode = ref('get')
const deleteConfirm = ref(false)
const formData = reactive<AnyObject>({
account_id: 0,
account_type: 'bank',
bank_name: '',
realname: '',
account_no: ''
})
const rules = computed(() => {
return {
'realname': {
type: 'string',
required: true,
message: formData.account_type == 'bank' ? t('bankRealnamePlaceholder') : t('alipayRealnamePlaceholder'),
trigger: ['blur', 'change'],
},
'bank_name': {
type: 'string',
required: formData.account_type == 'bank',
message: t('bankNamePlaceholder'),
trigger: ['blur', 'change'],
},
'account_no': {
type: 'string',
required: true,
message: formData.account_type == 'bank' ? t('bankAccountNoPlaceholder') : t('alipayAccountNoPlaceholder'),
trigger: ['blur', 'change'],
}
}
})
onLoad((data: any) => {
data.type && (formData.account_type = data.type)
data.mode && (mode.value = data.mode)
if (data.id) {
formData.account_id = data.id
getCashoutAccountInfo({ account_id: data.id }).then((res : any) => {
if (res.data) {
Object.keys(formData).forEach((key : string) => {
if (res.data[key] != undefined) formData[key] = res.data[key]
})
}
})
}
})
const handleSave = () => {
const save = formData.account_id ? editCashoutAccount : addCashoutAccount
formRef.value.validate().then(() => {
if (loading.value) return
loading.value = true
save(formData).then((res) => {
if (mode.value == 'get') redirect({ url: '/app/pages/member/account', param: { type: formData.account_type, mode: mode.value } })
else redirect({ url: '/app/pages/member/apply_cash_out', param: { account_id: formData.account_id ? formData.account_id : res.data.id, type: formData.account_type } , mode: 'redirectTo'})
}).catch(() => {
loading.value = false
})
})
}
const handleDelete = () => {
deleteCashoutAccount(formData.account_id).then(() => {
redirect({ url: '/app/pages/member/account', mode: 'redirectTo' })
})
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,158 @@
<template>
<view class="address bg-[var(--page-bg-color)] min-h-[100vh]" v-if="!loading" :style="themeColor()">
<scroll-view scroll-y="true">
<view class="sidebar-margin pt-[var(--top-m)]" v-if="addressList.length">
<view class="mb-[var(--top-m)] rounded-[var(--rounded-big)] overflow-hidden" v-for="(item, index) in addressList">
<view class="flex flex-col card-template">
<view class="flex-1 line-feed mr-[20rpx]" @click="selectAddress(item)">
<view class="flex items-center">
<view class="text-[#333] text-[30rpx] leading-[34rpx] font-500">{{ item.name }}</view>
<text class="text-[#333] text-[30rpx] ml-[10rpx]">{{ mobileHide(item.mobile) }}</text>
</view>
<view class="mt-[16rpx] text-[26rpx] line-feed text-[var(--text-color-light9)] leading-[1.4]">{{ item.full_address }}</view>
</view>
<view class="flex justify-between pt-[26rpx]">
<view class="flex items-center text-[28rpx] leading-none" @click.stop="setDefault(index)">
<text class="iconfont !text-[28rpx] mr-[10rpx]" :class="{ 'iconduigou text-primary': item.is_default, 'iconcheckbox_nol': !item.is_default }"></text>
设为默认
</view>
<view class="flex">
<view class="text-[28rpx]" @click.stop="editAddressFn(item.id)"><text class="nc-iconfont nc-icon-xiugaiV6xx shrink-0 text-[28rpx] mr-[4rpx]"></text>编辑</view>
<view @click.stop="deleteAddressFn(index)" class="ml-[40rpx] text-[28rpx]"><text class="nc-iconfont nc-icon-shanchu-yuangaizhiV6xx shrink-0 text-[28rpx] mr-[4rpx]"></text>删除</view>
</view>
</view>
</view>
</view>
</view>
<mescroll-empty v-if="!addressList.length" :option="{tip : '暂无收货地址'}"></mescroll-empty>
<view class="w-full footer">
<view class="py-[var(--top-m)] px-[var(--sidebar-m)] footer w-full fixed bottom-0 left-0 right-0 box-border">
<button hover-class="none" class="primary-btn-bg text-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[26rpx] font-500" @click="addAddress">{{t('createAddress')}}</button>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onLoad,onShow } from '@dcloudio/uni-app'
import { redirect, img, mobileHide } from '@/utils/common'
import { getAddressList, deleteAddress, editAddress } from '@/app/api/member'
import { t } from '@/locale'
import MescrollEmpty from '@/components/mescroll/mescroll-empty/mescroll-empty.vue';
const loading = ref(true)
const current = ref(0)
const addressList = ref<object[]>([])
const locationAddressList = ref<object[]>([])
const type = ref('')
const source = ref('')
onLoad((data: any) => {
type.value = data.type || ''
source.value = data.source || ''
if (data.type) current.value = data.type == 'address' ? 0 : 1
//
if(uni.getStorageSync('selectAddressCallback')){uni.removeStorage({ key: 'selectAddressCallback' })}
})
const getAddressListFn = () => {
getAddressList({}).then(({ data }) => {
const address: any = [], locationAddress: any = []
data.forEach((item: any) => {
item.type == 'address' ? address.push(item) : locationAddress.push(item)
})
if(!source.value){ //使
addressList.value = data;
}else{ //
addressList.value = current.value == 0 ? address : locationAddress;
}
loading.value = false
}).catch(() => {
loading.value = false
})
}
onShow(()=>{
getAddressListFn()
})
const addAddress = ()=> {
const url = `/app/pages/member/address_edit`
redirect({ url, param: { source : source.value } })
}
const editAddressFn = (id: number)=> {
const url = `/app/pages/member/address_edit`
redirect({ url, param: { id, source : source.value} })
}
const addressOptions = ref([
{
text: t('delete'),
style: {
backgroundColor: '#F56C6C'
}
}
])
const selectAddress = (data: any) => {
const selectAddress = uni.getStorageSync('selectAddressCallback')
if (selectAddress) {
selectAddress.address_id = data.id
uni.setStorage({
key: 'selectAddressCallback',
data: selectAddress,
success() {
redirect({url: selectAddress.back, mode: 'redirectTo'})
}
})
}
}
const deleteAddressFn = (index: any) => {
const data: any = addressList.value[index]
deleteAddress(data.id).then(() => {
addressList.value.splice(index, 1)
}).catch()
}
const setDefault = (index: any) => {
const data: any = addressList.value[index]
if (data.is_default) return
data.is_default = 1;
editAddress(data).then(() => {
addressList.value.forEach((item, itemIndex) => {
item.is_default && (item.is_default = 0)
itemIndex == index && (item.is_default = 1)
})
}).catch()
}
</script>
<style lang="scss" scoped>
:deep(.u-tabs__wrapper__nav__line) {
bottom: 0;
background: var(--primary-color) !important;
}
.line-feed{
word-wrap:break-word;
word-break: break-all;
}
:deep(.u-swipe-action-item__right__button__wrapper__text.u-line-1){
font-size: 24rpx !important;
}
:deep(.u-swipe-action-item__right__button__wrapper){
padding:0 10rpx !important;
}
.footer{
height: calc(80rpx + var(--top-m) + var(--top-m) + constant(safe-area-inset-bottom)) !important;
height: calc(80rpx + var(--top-m) + var(--top-m) + env(safe-area-inset-bottom)) !important;
}
</style>

View File

@ -0,0 +1,333 @@
<template>
<view class="bg-[var(--page-bg-color)] min-h-[100vh] overflow-hidden address-edit" :style="themeColor()">
<u-form labelPosition="left" :model="formData" errorType='toast' :rules="rules" ref="formRef">
<view class="sidebar-margin card-template mt-[var(--top-m)] py-[20rpx]">
<view class="">
<u-form-item :label="t('name')" prop="name" labelWidth="200rpx">
<u-input fontSize="28rpx" v-model.trim="formData.name" border="none" clearable maxlength="25" placeholderStyle="color: #888" :placeholder="t('namePlaceholder')"/>
</u-form-item>
</view>
<view class="mt-[16rpx]">
<u-form-item :label="t('mobile')" prop="mobile" labelWidth="200rpx">
<u-input fontSize="28rpx" v-model.trim="formData.mobile" maxlength="11" border="none" clearable :placeholder="t('mobilePlaceholder')" placeholderStyle="color: #888"/>
</u-form-item>
</view>
<view class="mt-[16rpx]">
<u-form-item :label="t('selectArea')" prop="area" labelWidth="200rpx">
<view class="flex w-full items-center h-[52rpx]" v-if="addressType == 'address' && isSelectMap != 1" @click="selectArea">
<view v-if="!formData.area" class="text-[#888] text-[28rpx] flex-1">{{ t('selectAreaPlaceholder') }}</view>
<view v-else class="text-[28rpx] flex-1 leading-[1.4]">{{ formData.area }}</view>
<view @click.stop="chooseLocation" class="flex items-center">
<text class="nc-iconfont nc-icon-dizhiguanliV6xx mr-[4rpx] text-[32rpx] text-[#e93323]"></text>
<text class="text-[24rpx] whitespace-nowrap text-[#e93323]">定位</text>
</view>
</view>
<view v-else class="flex justify-between items-center flex-1 h-[52rpx]" @click="chooseLocation">
<view class="text-[28rpx] text-[#303133] leading-[1.4]" v-if="formData.area || formData.address_name">{{formData.area || formData.address_name}}</view>
<view class="text-[#888] text-[28rpx]" v-else>{{t('selectAddressPlaceholder')}}</view>
<view class="flex items-center">
<text class="nc-iconfont nc-icon-dizhiguanliV6xx text-[32rpx] mr-[4rpx] text-[#e93323]"></text>
<text class="text-[24rpx] whitespace-nowrap text-[#e93323]">定位</text>
</view>
</view>
</u-form-item>
</view>
<view class="mt-[16rpx]">
<u-form-item :label="t('address')" prop="address" labelWidth="200rpx">
<u-input fontSize="28rpx" v-model="formData.address" border="none" clearable maxlength="120" :placeholder="t('addressPlaceholder')" placeholderStyle="color: #888"/>
</u-form-item>
</view>
</view>
<view class="sidebar-margin card-template mt-[var(--top-m)] py-[10rpx]">
<u-form-item :label="t('defaultAddress')" prop="name" :border-bottom="false" labelWidth="200rpx">
<u-switch v-model="formData.is_default" size="20" :activeValue="1" :inactiveValue="0" activeColor="var(--primary-color)" inactiveColor="var(--temp-bg)"/>
</u-form-item>
</view>
</u-form>
<view class="w-full footer">
<view class="py-[var(--top-m)] px-[var(--sidebar-m)] footer w-full fixed bottom-0 left-0 right-0 box-border">
<button hover-class="none" class="primary-btn-bg !text-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[26rpx] font-500"
@click="save" :disabled="btnDisabled" :loading="operateLoading" :class="{'opacity-50': btnDisabled}">{{t('save')}}</button>
</view>
</view>
<area-select ref="areaRef" @complete="areaSelectComplete" :area-id="formData.district_id"/>
<!-- #ifdef MP-WEIXIN -->
<!-- 小程序隐私协议 -->
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
<!-- #endif -->
</view>
</template>
<script setup lang="ts">
import { ref, computed,nextTick } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { redirect } from '@/utils/common'
import { t } from '@/locale'
import { addAddress, editAddress, getAddressInfo } from '@/app/api/member'
import manifestJson from '@/manifest.json'
import { getAddressByLatlng } from '@/app/api/system'
const formData: any = ref({
id: 0,
name: '',
mobile: '',
province_id: 0,
city_id: 0,
district_id: 0,
lat: '',
lng: '',
address: '',
address_name: '',
full_address: '',
is_default: 0,
area: ''
})
const areaRef = ref()
const formRef: any = ref(null)
const source = ref('')
const btnDisabled = ref(false)
const isSelectAddress = ref(false)
const addressType = ref('address');
const isSelectMap = ref(2) // 1
const wxPrivacyPopupRef:any = ref(null)
onLoad((data: any) => {
isSelectMap.value = data.isSelectMap || '';
const selectAddress = uni.getStorageSync('selectAddressCallback')
if (data.id) {
getAddressInfo(data.id).then((res: any) => {
res.data && Object.assign(formData.value, res.data)
//
if (selectAddress) {
addressType.value = selectAddress.delivery == 'express' ? 'address' : 'locationAddress';
}
})
} else if (data.name) {
if (uni.getStorageSync('addressInfo')) {
Object.assign(formData.value, uni.getStorageSync('addressInfo'))
}
formData.value.address = data.name;
getAddress(data.latng);
var tempArr = getQueryVariable('latng').split(',');
formData.value.lat = tempArr[0];
formData.value.lng = tempArr[1];
}
source.value = data.source || ''
if (selectAddress) {
addressType.value = selectAddress.delivery == 'express' ? 'address' : 'locationAddress';
}
// #ifdef MP
nextTick(() => {
if (wxPrivacyPopupRef.value) wxPrivacyPopupRef.value.proactive();
})
// #endif
})
const rules = computed(() => {
return {
'name': {
type: 'string',
required: true,
message: t('namePlaceholder'),
trigger: ['blur', 'change'],
},
'mobile': [
{
type: 'string',
required: true,
message: t('mobilePlaceholder'),
trigger: ['blur', 'change'],
},
{
validator(rule: any, value: any, callback: any) {
let mobile = /^1[3-9]\d{9}$/;
if (!mobile.test(value)){
callback(new Error(t('mobileError')))
} else {
callback()
}
}
}
],
'area': {
validator() {
let bool = true;
if(uni.$u.test.isEmpty(formData.value.area) && uni.$u.test.isEmpty(formData.value.address_name)){
bool = false;
}
return bool
},
message: t('selectAreaPlaceholder')
},
'address': {
type: 'string',
required: true,
message: t('addressPlaceholder'),
trigger: ['blur', 'change']
}
}
})
const selectArea = () => {
isSelectAddress.value = true
areaRef.value.open()
}
const areaSelectComplete = (event: any) => {
if(isSelectAddress.value && (formData.value.province_id == event.province.id || formData.value.city_id != event.city.id || formData.value.district_id != event.district.id)){
formData.value.lat = '';
formData.value.lng = '';
}
formData.value.province_id = event.province.id || 0
formData.value.city_id = event.city.id || 0
formData.value.district_id = event.district.id || 0
formData.value.area = `${event.province.name || ''}${event.city.name || ''}${event.district.name || ''}`
isSelectAddress.value = false;
}
const operateLoading = ref(false)
const save = ()=> {
const save = formData.value.id ? editAddress : addAddress
formRef.value.validate().then(() => {
if (operateLoading.value) return
operateLoading.value = true
btnDisabled.value = true
formData.value.full_address = formData.value.area + formData.value.address
if(isSelectMap.value == 1 && !formData.value.lat && !formData.value.lng){
uni.showToast({
title: '缺少经纬度,请在地图上重新选点',
icon: 'none'
});
operateLoading.value = false;
btnDisabled.value = false
return false;
}
save(formData.value).then((res: any) => {
operateLoading.value = false
setTimeout(() => {
btnDisabled.value = false
if(source.value == 'shop_order_payment'){
const selectAddress = uni.getStorageSync('selectAddressCallback')
if (selectAddress) {
selectAddress.address_id = res.data.id || formData.value.id
uni.setStorage({
key: 'selectAddressCallback',
data: selectAddress,
success() {
redirect({url: selectAddress.back, mode: 'redirectTo'})
}
})
}
}else{
redirect({
url: '/app/pages/member/address',
mode: 'redirectTo',
param: {source: source.value}
})
}
}, 1000)
}).catch(() => {
operateLoading.value = false
btnDisabled.value = false
})
})
}
//
const chooseLocation = ()=> {
// #ifdef MP
uni.chooseLocation({
success: (res) => {
res.latitude && (formData.value.lat = res.latitude)
res.longitude && (formData.value.lng = res.longitude)
res.address && (formData.value.area = res.address)
res.name && (formData.value.address_name = res.address)
res.name && (formData.value.address = res.name)
if (res.latitude && res.longitude) {
let latng = res.latitude + ',' + res.longitude;
getAddress(latng);
}
},
fail: (res)=>{
// chooseLocation:fail api
if(res.errMsg && res.errno) {
if(res.errno == 104){
let msg = '用户未授权隐私权限,选择位置失败';
uni.showToast({title: msg, icon: 'none'})
}else if(res.errno == 112){
let msg = '隐私协议中未声明,打开地图选择位置失败';
uni.showToast({title: msg, icon: 'none'})
}else {
uni.showToast({title: res.errMsg, icon: 'none'})
}
}
}
});
// #endif
// #ifdef H5
var urlencode = formData.value;
uni.setStorageSync('addressInfo', urlencode);
let backurl = location.origin + location.pathname + '?source=' + source.value;
if(isSelectMap.value){
backurl = backurl + '&isSelectMap=' + isSelectMap.value
}
window.location.href = 'https://apis.map.qq.com/tools/locpicker?search=1&type=0&backurl=' + encodeURIComponent(backurl) + '&key=' + manifestJson.h5.sdkConfigs.maps.qqmap.key + '&referer=myapp';
// #endif
}
//
const getAddress = (latlng:any)=> {
getAddressByLatlng({latlng}).then((res: any) => {
if (res.data) {
formData.value.full_address = '';
formData.value.full_address += res.data.province != undefined ? res.data.province : '';
formData.value.full_address += res.data.city != undefined ? '' + res.data.city : '';
formData.value.full_address += res.data.district != undefined ? '' + res.data.district : '';
formData.value.address_name = formData.value.full_address.replace(/-/g,'');
formData.value.area = (res.data.province + res.data.city + res.data.district) || res.data.full_address;
formData.value.province_id = res.data.province_id != undefined ? res.data.province_id : 0;
formData.value.city_id = res.data.city_id != undefined ? res.data.city_id : 0;
formData.value.district_id = res.data.district_id != undefined ? res.data.district_id : 0;
} else {
uni.showToast({title: res.msg, icon: 'none'})
}
})
}
const getQueryVariable = (variable:any)=> {
var query = window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (pair[0] == variable) {
return pair[1];
}
}
return false;
}
</script>
<style lang="scss" scoped>
.address-edit :deep(.u-form-item__body__left__content__label){
font-size: 28rpx !important;
}
.address-edit :deep(.u-form-item__body__right){
display:flex;
align-items: center;
}
.footer{
height: calc(100rpx + var(--top-m) + var(--top-m) + constant(safe-area-inset-bottom)) !important;
height: calc(100rpx + var(--top-m) + var(--top-m) + env(safe-area-inset-bottom)) !important;
}
</style>

View File

@ -0,0 +1,320 @@
<template>
<view :style="themeColor()">
<scroll-view :scroll-y="true" class="w-screen h-screen bg-page" v-if="!pageLoading && config.is_open == 1">
<view class="sidebar-marign pt-[20rpx]">
<view class="card-template">
<view class="font-500 text-[32rpx] text-[#333] leading-[44rpx]">{{t('cashOutMoneyTip')}}</view>
<view class="flex pt-[30rpx] pb-[8rpx] items-center border-0 border-b-[2rpx] border-solid border-[#F1F2F5]">
<text class="text-[54rpx] font-500 leading-[76rpx] ">{{ t('currency') }}</text>
<input type="digit" class="h-[76rpx] leading-[76rpx] pl-[10rpx] flex-1 font-bold text-[54rpx] bg-[#fff]" v-model="applyData.apply_money" maxlength="7" :placeholder="applyData.apply_money?'':(t('minWithdrawal') + t('currency') + moneyFormat(config.min))" placeholder-class="apply-price" :adjust-position="false"/>
<image @click="clearMoney" v-if="applyData.apply_money"
:src="img('static/resource/images/member/apply_withdrawal/close.png')" class="w-[40rpx] h-[40rpx]"
mode="widthFix" />
</view>
<view class="pt-[20rpx] flex items-center justify-between">
<view class="text-[26rpx] text-[#626779] leading-[36rpx]">
<text>{{t('money')}}{{ t('currency') }}{{ moneyFormat(cashOutMoney) }}</text>
<text>{{t('commissionTo')}}{{ config.rate + '%' }}</text>
</view>
<view class="text-[26rpx] text-primary leading-[36rpx]" @click="allMoney">{{t('allTx')}}</view>
</view>
</view>
<view class="mt-[20rpx] card-template">
<view class="font-500 text-[32rpx] text-[#333] leading-[44rpx] mb-[30rpx]">到账方式</view>
<!-- 提现到微信 -->
<view class="p-[20rpx] mb-[20rpx] flex items-center rounded-[16rpx] border-[1rpx] border-solid border-[#eee]" v-if="config.transfer_type.includes('wechatpay') && memberStore.info && (memberStore.info.wx_openid || memberStore.info.weapp_openid)" @click="applyData.transfer_type = 'wechatpay'" :class="{'border-[#089C98] bg-[#F6FFFF]': applyData.transfer_type == 'wechatpay'}">
<view>
<image class="h-[60rpx] w-[60rpx]" :src="img('static/resource/images/member/apply_withdrawal/wechat.png')" mode="widthFix" />
</view>
<view class="flex-1 px-[20rpx]">
<view class="text-[28rpx] text-[#333] leading-[40rpx] mb-[6rpx]">{{ t('cashOutToWechat') }}</view>
<view class="text-[#999] text-[24rpx] leading-[34rpx]">{{ t('cashOutToWechatTips') }}</view>
</view>
</view>
<!-- 提现到支付宝 -->
<view class="p-[20rpx] mb-[20rpx] flex items-center rounded-[16rpx] border-[1rpx] border-solid border-[#eee]" v-if="config.transfer_type.includes('alipay')" :class="{'border-[#089C98] bg-[#F6FFFF]': applyData.transfer_type == 'alipay' && alipayAccountInfo}" >
<view @click="transferAlipay" >
<image class="h-[60rpx] w-[60rpx] align-middle" :src="img('static/resource/images/member/apply_withdrawal/alipay-icon.png')" mode="widthFix" />
</view>
<view class="flex-1 px-[22rpx]" @click="transferAlipay" >
<view class="text-[28rpx] text-[#333] leading-[40rpx] mb-[6rpx]">{{ t('cashOutToAlipay') }}</view>
<view class="text-[#999] text-[24rpx] leading-[34rpx]">
<view v-if="alipayAccountInfo" class="truncate max-w-[440rpx]">
<text>{{ t('cashOutTo') }}{{ t('alipayAccountNo') }}</text>
<text class="text-[#333]">{{ alipayAccountInfo.account_no }}</text>
</view>
<view v-else>{{ t('cashOutToAlipayTips') }}</view>
</view>
</view>
<view class="flex items-center">
<u-button :plain="true" :text="t('toAdd')" type="primary" shape="circle" :custom-style="{height: '54rpx'}" v-if="!alipayAccountInfo && !alipayLoading" @click="redirect({ url: '/app/pages/member/account', param: { type: 'alipay', mode: 'select' } , mode: 'redirectTo'})"></u-button>
<text v-else class="nc-iconfont nc-icon-youV6xx text-[40rpx] text-[#999] p-[10rpx]" @click.stop="redirect({ url: '/app/pages/member/account', param: { type: 'alipay', mode: 'select' } , mode: 'redirectTo'})"></text>
</view>
</view>
<!-- 提现到银行卡 -->
<view class="p-[20rpx] flex items-center rounded-[16rpx] border-[1rpx] border-solid border-[#eee]" v-if="config.transfer_type.includes('bank')" :class="{'border-[#089C98] bg-[#F6FFFF]': applyData.transfer_type == 'bank' && bankAccountInfo}">
<view @click="transferBank" >
<image class="h-[42rpx] w-[60rpx] align-middle" :src="img('static/resource/images/member/apply_withdrawal/bank-icon.png')" mode="widthFix" />
</view>
<view class="flex-1 px-[20rpx]" @click="transferBank" >
<view class="text-[28rpx] text-[#333] leading-[40rpx] mb-[6rpx]">{{ t('cashOutToBank') }}</view>
<view class="text-[#999] text-[24rpx] leading-[34rpx]">
<view v-if="bankAccountInfo" class="truncate max-w-[440rpx]">
<text>{{ t('cashOutTo') }}{{ bankAccountInfo.bank_name }}{{ t('debitCard') }}</text>
<text class="text-[#333]">{{ bankAccountInfo.account_no.substring(bankAccountInfo.account_no.length - 4) }} </text>
</view>
<view v-else>
{{ t('cashOutToBankTips') }}
</view>
</view>
</view>
<view class="flex items-center">
<button hover-class="none" class="h-[54rpx] leading-[50rpx] rounded-full p-[0] w-[100rpx] text-[var(--primary-color)] bg-transparent border-[2rpx] border-solid border-[var(--primary-color)] text-[28rpx]" v-if="!bankAccountInfo && !bankLoading" @click="redirect({ url: '/app/pages/member/account', param: { type: 'bank', mode: 'select' }, mode: 'redirectTo' })">{{t('toAdd')}}</button>
<text v-else class="nc-iconfont nc-icon-youV6xx text-[40rpx] text-[#999] p-[10rpx]" @click.stop="redirect({ url: '/app/pages/member/account', param: { type: 'bank', mode: 'select' }, mode: 'redirectTo' })"></text>
</view>
</view>
</view>
<view class="fixed bottom-[60rpx] left-0 right-0 px-[30rpx]">
<button class="mt-[100rpx] h-[80rpx] !text-[#fff] leading-[80rpx] primary-btn-bg rounded-[50rpx] text-[32rpx]" :disabled="applyData.apply_money == '' || applyData.apply_money == 0" :loading="loading" @click="cashOut">{{t('cashOutNow')}}</button>
<view class="mt-[20rpx] text-center text-[26rpx] text-primary" @click.stop="redirect({ url: '/app/pages/member/cash_out'})">
{{t('cashOutList')}}
</view>
</view>
</view>
</scroll-view>
<view class="h-[100vh] w-[100vw] flex justify-center items-center" v-if="config.is_open == 0 && !pageLoading">
<u-empty :text="t('isOpenApply')" width="320rpx" height="244rpx" :icon="img('static/resource/images/empty.png')"/>
</view>
<u-loading-page :loading="pageLoading" bg-color="#e8e8e8" loading-text=""></u-loading-page>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, computed } from 'vue'
import { t } from '@/locale'
import { moneyFormat, redirect, getToken ,img, deepClone } from '@/utils/common'
import useMemberStore from '@/stores/member'
import { cashOutConfig, cashOutApply, getFirstCashoutAccountInfo, getCashoutAccountInfo } from '@/app/api/member'
import { onLoad } from '@dcloudio/uni-app'
const pageLoading = ref(true)
const loading = ref(false)
const memberStore = useMemberStore()
//
const applyData = reactive({
apply_money: '',
transfer_type: '',
account_type: 'money',
account_id: 0
})
//
const cashOutMoney = computed(() => {
return memberStore.info ? memberStore.info[ applyData.account_type ] : 0
})
watch(() => applyData.transfer_type, (nval) => {
switch (nval) {
case 'bank':
applyData.account_id = bankAccountInfo.value ? bankAccountInfo.value.account_id : 0
break;
case 'alipay':
applyData.account_id = alipayAccountInfo.value ? alipayAccountInfo.value.account_id : 0
break;
default:
applyData.account_id = 0
}
}, { immediate: true })
const config = reactive<AnyObject>({
is_auto_transfer: 0, //
is_auto_verify: 0, //
is_open: 0, //
min: 0, //
rate: 0, //
transfer_type: [] //
})
let query:AnyObject | undefined = {}
onLoad(async (data) => {
query = data
uni.getStorageSync('cashOutAccountType') && (applyData.account_type = uni.getStorageSync('cashOutAccountType'))
if(getToken()){
memberStore.getMemberInfo()
}
if (!['money', 'commission'].includes(applyData.account_type)) {
uni.showToast({
title: t('abnormalOperation'),
icon: 'none',
success() {
setTimeout(() => {
if(getCurrentPages().length > 1){
uni.navigateBack({
delta: 1
});
}else{
redirect({
url: '/app/pages/member/index',
mode: 'reLaunch'
});
}
}, 1500)
}
})
return
}
//
await cashOutConfig().then((res : any) => {
for (let key in deepClone(res.data)) {
config[key] = deepClone(res.data[key]);
}
if (config.transfer_type.includes('wechatpay') && memberStore.info && (!memberStore.info.wx_openid && !memberStore.info.weapp_openid)) config.transfer_type.splice(config.transfer_type.indexOf('wechatpay'),1)
config.transfer_type.includes('bank') && getBankAccountInfo()
config.transfer_type.includes('alipay') && getAlipayAccountInfo()
applyData.transfer_type = config.transfer_type[0]
if(query.type){
applyData.transfer_type = query.type
}
pageLoading.value = false
})
})
//
const allMoney = () => {
if(parseFloat(cashOutMoney.value)) applyData.apply_money = moneyFormat(cashOutMoney.value)
}
//
const clearMoney = () => {
applyData.apply_money = '';
}
const verify = () => {
if (!applyData.transfer_type) {
uni.showToast({ title: t('noAvailableCashOutType'), icon: 'none' })
return false
}
if (uni.$u.test.isEmpty(applyData.apply_money)) {
uni.showToast({ title: t('applyMoneyPlaceholder'), icon: 'none' })
return false
}
if (!uni.$u.test.amount(applyData.apply_money)) {
uni.showToast({ title: t('moneyformatError'), icon: 'none' })
return false
}
if (parseFloat(applyData.apply_money) > parseFloat(cashOutMoney.value)) {
uni.showToast({ title: t('applyMoneyExceed'), icon: 'none' })
return false
}
if (parseFloat(applyData.apply_money) < parseFloat(config.min)) {
uni.showToast({ title: t('applyMoneyBelow'), icon: 'none' })
return false
}
return true;
}
/**
* 获取支付宝提现账号信息
*/
const alipayLoading = ref(false)
const alipayAccountInfo:any = ref(null)
const getAlipayAccountInfo = () => {
const data = { account_type: 'alipay', account_id: 0 }
let request = getFirstCashoutAccountInfo
if (query.type && query.type == 'alipay' && query.account_id) {
request = getCashoutAccountInfo
data.account_id = query.account_id
}
alipayLoading.value = true
request(data).then(res => {
if (res.data && res.data.account_id) {
alipayAccountInfo.value = res.data
//
if(applyData.transfer_type == 'alipay' && !applyData.account_id){
applyData.account_id = alipayAccountInfo.value.account_id;
}
}
alipayLoading.value = false
})
}
/**
* 获取银行卡提现账号信息
*/
const bankLoading = ref(false)
const bankAccountInfo = ref(null)
const getBankAccountInfo = () => {
const data = { account_type: 'bank', account_id: 0 }
let request = getFirstCashoutAccountInfo
if (query.type && query.type == 'bank' && query.account_id) {
request = getCashoutAccountInfo
data.account_id = query.account_id
}
bankLoading.value = true
request(data).then(res => {
if (res.data && res.data.account_id) {
bankAccountInfo.value = res.data
//
if(applyData.transfer_type == 'bank' && !applyData.account_id){
applyData.account_id = bankAccountInfo.value.account_id;
}
}
bankLoading.value = false
})
}
/**
* 申请提现
*/
const cashOut = ()=> {
if (verify()) {
if (loading.value) return
loading.value = true
cashOutApply(applyData).then(res => {
loading.value = false
redirect({ url: '/app/pages/member/cash_out' })
}).catch(() => {
loading.value = false
})
}
}
//
const transferAlipay = () => {
if(!alipayAccountInfo.value){
uni.showToast({ title: t('cashOutToAlipayTips'), icon: 'none' })
return false
}
applyData.transfer_type = 'alipay'
}
//
const transferBank = () => {
if(!bankAccountInfo.value){
uni.showToast({ title: t('cashOutToBankTips'), icon: 'none' })
return false
}
applyData.transfer_type = 'bank'
}
</script>
<style lang="scss" scoped>
:deep(.apply-price){
color: #8288A2;
font-size: 26rpx;
font-weight: normal;
line-height: 76rpx;
}
</style>

View File

@ -0,0 +1,253 @@
<template>
<view class="min-h-[100vh] !bg-[#F6F6F6]" :style="themeColor()" v-if="memberStore.info">
<view class="fixed w-full z-2 !bg-[#F6F6F6]">
<view class="pb-[203rpx] text-[#fff] w-full" :style="headerStyle">
<!-- #ifdef MP-WEIXIN -->
<top-tabbar :data="param" class="top-header"/>
<!-- #endif -->
<view class="leading-[39rpx] text-[28rpx] pl-[53rpx] pt-[79rpx]">{{t('accountBalance')}}</view>
<view class="flex items-baseline pl-[53rpx]">
<text class="text-[40rpx] leading-[56rpx]"></text>
<text class="text-[70rpx] leading-[98rpx]">{{ memberStore.info ? moneyFormat((parseFloat(memberStore.info.balance) + parseFloat(memberStore.info.money)).toString()) : '0.00' }}</text>
</view>
</view>
<view class="sidebar-marign py-[30rpx] bg-[#fff] rounded-[16rpx] px-[40rpx] box-border w-[calc(100% - 60rpx)] mt-[-112rpx]">
<view class="flex flex-col items-center w-full" @click="redirect({ url: '/app/pages/member/detailed_account', param: { type : 'money' } })" :class="{'pt-[12rpx]': !Object.keys(cashOutConfigObj).length || (Object.keys(cashOutConfigObj).length && !systemStore.siteAddons.includes('recharge') && cashOutConfigObj.is_open != 1)}">
<view class=" text-[#999] text-[24rpx] leading-[34rpx] font-400">{{t('money')}}</view>
<view class="flex items-baseline text-[#333]">
<text class="text-[26rpx] leading-[36rpx]"></text>
<text class="text-[44rpx] leading-[62rpx] font-bold">{{ moneyFormat(memberStore.info?.money)|| '0.00' }}</text>
</view>
</view>
<view class="mt-[50rpx] flex justify-between" v-if="Object.keys(cashOutConfigObj).length && (systemStore.siteAddons.includes('recharge') || cashOutConfigObj.is_open == 1)">
<button :class="{'!w-[630rpx]': cashOutConfigObj.is_open != 1}" class="w-[280rpx] h-[66rpx] rounded-[40rpx] text-[30rpx] !bg-[#fff] !text-[var(--primary-color)] leading-[64rpx] !m-0 border-[2rpx] border-[var(--primary-color)] border-solid box-border" shape="circle" v-if="systemStore.siteAddons.includes('recharge')"
@click="redirect({url: '/addon/recharge/pages/recharge'})">充值</button>
<view v-if="cashOutConfigObj.is_open == 1" :class="{'!w-[630rpx]': !systemStore.siteAddons.includes('recharge')}" class="text-center w-[280rpx] h-[66rpx] rounded-[40rpx] text-[30rpx] !text-[#fff] leading-[66rpx] !m-0"
style="background: linear-gradient( 94deg, #FB7939 0%, #FE120E 99%), #EF000C;" @click="applyCashOut">{{t('cashOut')}}</view>
</view>
</view>
<view class="px-[var(--sidebar-m)] box-border mt-[20rpx] flex justify-between items-center">
<scroll-view :scroll-x="true" scroll-with-animation :scroll-into-view="'id' + (subActive>3 ? subActive - 2 : 0)" class="!h-[100%] flex-1">
<view class="flex whitespace-nowrap">
<view :id="'id' + index" class="text-[26rpx] leading-[70rpx] text-[#666] font-400" :class="{ 'class-select': fromType === item.key,'ml-30rpx':index}" @click="fromTypeFn(item.key,index)" v-for="(item, index) in accountTypeList">{{ item.name }}</view>
</view>
</scroll-view>
<view class="flex items-center" @click="handleSelect">
<view class="text-[26rpx] text-[#333] mr-[10rpx]">日期</view>
<view class="nc-iconfont nc-icon-riliV6xx !text-[28rpx] leading-[36rpx]"></view>
</view>
</view>
</view>
<mescroll-body ref="mescrollRef" @init="mescrollInit" :down="{ use: false }" height="auto" @up="getListFn" :top="mescrollTop">
<view class="sidebar-marign pt-[20rpx] body-bottom" v-if="list.length">
<view v-for="(item,index) in list" :key="item.id" class="w-full h-[120rpx] flex justify-between items-center bg-[#fff] box-border p-[20rpx] rounded-[16rpx]" :class="{'mt-[20rpx]':index>0}">
<view class="flex items-center">
<view class="w-[80rpx] h-[80rpx] text-center rounded-[40rpx] text-[40rpx] font-bold leading-[80rpx] text-[#fff]"
:class="{'bg-[#EF000C]' :item.account_data > 0&&item.account_type!='money', 'bg-[#03B521]':item.account_data <= 0&&item.account_type!='money','bg-[#1379FF]':item.account_type=='money'}">
{{item.account_type=='money'?'提':item.account_data > 0?'收':'支'}}
</view>
<view class="flex flex-col ml-[20rpx]">
<view class="text-[#000] text-[26rpx] leading-[36rpx]">{{item.from_type_name}}</view>
<view class="text-[#999] text-[24rpx] leading-[34rpx] mt-[4rpx] font-400">{{item.create_time}}</view>
</view>
</view>
<view class="text-right">
<view class="text-[36rpx] leading-[50rpx]"
:class="{'text-[#EF000C]' :item.account_data > 0&&item.account_type!='money', 'text-[#03B521]':item.account_data <= 0&&item.account_type!='money'}">{{ item.account_data > 0 ? '+' + item.account_data : item.account_data }}</view>
<view class="text-[#999] text-[24rpx] leading-[34rpx] mt-[4rpx] font-400">
<text class="mr-[15rpx]">剩余余额</text>
<text>{{item.account_sum}}</text>
</view>
</view>
</view>
</view>
<view class="box-border pt-[20rpx] px-[30rpx] body-bottom" :style="{'height':'calc(100vh - '+mescrollTop +')'}" v-if="!list.length && !loading &&!listLoading">
<view class="h-full rounded-[16rpx] flex items-center justify-center">
<mescroll-empty></mescroll-empty>
</view>
</view>
</mescroll-body>
<u-loading-page bg-color="rgb(248,248,248)" :loading="loading" loadingText="" fontSize="16" color="#303133"></u-loading-page>
<!-- <pay ref="payRef" @close="rechargeLoading = false"></pay> -->
<!-- 时间选择 -->
<select-date ref="selectDateRef" @confirm="confirmFn" />
</view>
</template>
<script setup lang="ts">
import { ref, reactive,computed } from 'vue'
import { t } from '@/locale'
import { moneyFormat, redirect, img,pxToRpx } from '@/utils/common';
import { cashOutConfig,getBalanceListAll } from '@/app/api/member';
import MescrollBody from '@/components/mescroll/mescroll-body/mescroll-body.vue';
import MescrollEmpty from '@/components/mescroll/mescroll-empty/mescroll-empty.vue';
import useMescroll from '@/components/mescroll/hooks/useMescroll.js';
import { onShow, onPageScroll, onReachBottom } from '@dcloudio/uni-app';
import useMemberStore from '@/stores/member'
import useSystemStore from '@/stores/system'
import { topTabar } from '@/utils/topTabbar'
import selectDate from '@/components/select-date/select-date.vue';
const { downCallback,mescrollInit, getMescroll } = useMescroll(onPageScroll, onReachBottom);
const memberStore = useMemberStore()
const systemStore = useSystemStore()
/********* 自定义头部 - start ***********/
const topTabarObj = topTabar()
let param = topTabarObj.setTopTabbarParam({title:'我的余额'})
/********* 自定义头部 - end ***********/
const cashOutConfigObj = reactive({})
onShow(() => {
cashOutConfig().then((res) => {
for (let key in res.data) {
cashOutConfigObj[key] = res.data[key];
}
})
})
//
let menuButtonInfo:any = {};
// (API)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
const headerStyle = computed(()=>{
return {
backgroundImage: 'url(' + img('static/resource/images/member/balance_bg.png') + ') ',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'bottom',
}
})
const mescrollTop = computed(()=>{
if(Object.keys(cashOutConfigObj).length && (systemStore.siteAddons.includes('recharge') || cashOutConfigObj.is_open == 1)){
if(Object.keys(menuButtonInfo).length){
return (pxToRpx(Number(menuButtonInfo.height)) + pxToRpx(menuButtonInfo.top) +pxToRpx(8)+669)+'rpx'
}else{
return '669rpx'
}
}else {
if(Object.keys(menuButtonInfo).length){
return (pxToRpx(Number(menuButtonInfo.height)) + pxToRpx(menuButtonInfo.top) +pxToRpx(8)+566)+'rpx'
}else{
return '566rpx'
}
}
})
//
const accountTypeList = ref([
{name:'全部',key:''},
{name:'收入',key:'income'},
{name:'支出',key:'disburse'},
{name:'提现',key:'cash_out'},
])
const fromType = ref('')
const create_time = ref([])
//
const subActive = ref(0)
const fromTypeFn = (key,index)=>{
fromType.value = key
subActive.value = index
getMescroll().resetUpScroll();
}
const applyCashOut = () => {
uni.setStorageSync('cashOutAccountType', 'money')
redirect({ url: '/app/pages/member/apply_cash_out' })
}
const list = ref<Array<any>>([]),
loading = ref<boolean>(true),
listLoading = ref<boolean>(true),
mescrollRef = ref(null);
interface mescrollStructure {
num : number,
size : number,
endSuccess : Function,
[propName : string] : any
}
const getListFn = (mescroll : mescrollStructure) => {
listLoading.value = true;
let data : Object = {
page: mescroll.num,
limit: mescroll.size,
trade_type:fromType.value,
create_time: create_time.value
};
interface acceptingDataStructure {
data : acceptingDataItemStructure,
msg : string,
code : number
}
interface acceptingDataItemStructure {
data : [],
[propName : string] : number | string | object
}
getBalanceListAll(data).then((res : acceptingDataStructure) => {
let newArr = res.data.data;
mescroll.endSuccess(newArr.length);
//
if (mescroll.num == 1) {
list.value = []; //
}
list.value = list.value.concat(newArr);
listLoading.value = false;
loading.value = false;
}).catch(() => {
listLoading.value = false;
loading.value = false;
mescroll.endErr(); // ,
})
}
//
const selectDateRef = ref()
const handleSelect = () =>{
selectDateRef.value.show = true
}
//
const confirmFn = (data) =>{
create_time.value = data;
list.value = []
getMescroll().resetUpScroll();
}
</script>
<style lang="scss" scoped>
.class-select {
position: relative;
font-weight: 500;
color: var(--primary-color);
&::before {
content: "";
position: absolute;
bottom: 0;
height: 4rpx;
border-radius: 4rpx;
background-color: var(--primary-color);
width: 40rpx;
left: 50%;
transform: translateX(-50%);
}
}
:deep(.uni-scroll-view){
overflow: auto hidden !important;
}
.body-bottom{
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
}
.pl-20rpx{
padding-left: 20rpx;
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<view class="member-record-list" :style="themeColor()">
<mescroll-body ref="mescrollRef" @init="mescrollInit" :down="{ use: false }" @up="getCashOutListFn">
<view v-for="(item,index) in cashOutList" :key="item.id" class="member-record-item" @click="toDetailFn(item)">
<view class="name">{{item.transfer_type_name}}</view>
<view class="desc">{{t('applyTime')}}: {{item.create_time}}</view>
<view class="desc">{{ item.status != -1 ? currentStatusDesc(item.status) : item.refuse_reason}}</view>
<view class="money" :class="item.apply_money > 0 ? 'text-active' : ''">
{{ item.apply_money > 0 ? '+' + item.apply_money : item.apply_money }}
</view>
<view class="state">{{ item.status_name }}</view>
</view>
<mescroll-empty v-if="!cashOutList.length && loading" :option="{tip : t('emptyTip')}"></mescroll-empty>
</mescroll-body>
</view>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { t } from '@/locale'
import { redirect, img } from '@/utils/common';
import { getCashOutList } from '@/app/api/member';
import MescrollBody from '@/components/mescroll/mescroll-body/mescroll-body.vue';
import MescrollEmpty from '@/components/mescroll/mescroll-empty/mescroll-empty.vue';
import useMescroll from '@/components/mescroll/hooks/useMescroll.js';
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app';
const { mescrollInit, downCallback, getMescroll } = useMescroll(onPageScroll, onReachBottom);
const cashOutList = ref<Array<any>>([]);
const mescrollRef = ref(null);
const loading = ref<boolean>(false);
let account_type = uni.getStorageSync('cashOutAccountType')
const currentStatusDesc = (status) =>{
switch(status){
case 1:
return t('toBeReviewed')
case 2:
return t('toBeTransfer')
case 3:
return t('transfer')
case -2:
return t('cancelApply')
}
}
const getCashOutListFn = (mescroll)=>{
let data:any = {}
loading.value = false;
data.page = mescroll.num;
data.page_size = mescroll.size;
data.account_type = account_type;
getCashOutList(data).then((res) => {
let newArr = res.data.data;
mescroll.endSuccess(newArr.length);
//
if (mescroll.num == 1){
cashOutList.value = []; //
}
cashOutList.value = cashOutList.value.concat(newArr);
loading.value = true;
}).catch(()=>{
loading.value = true;
mescroll.endErr(); // ,
})
}
const toDetailFn = (data)=>{
redirect({ url: '/app/pages/member/cash_out_detail', param: { id: data.id }});
}
</script>
<style lang="scss">
@import '@/styles/member_record_list.scss';
</style>

View File

@ -0,0 +1,80 @@
<template>
<view class="member-record-detail" :style="themeColor()">
<block v-if="!loading">
<view class="money-wrap">
<text>-{{ cashOutInfo.apply_money }}</text>
<text>{{ cashOutInfo.status_name }}</text>
</view>
<!-- 状态0待审核1.待转账2已转账 -1拒绝' -->
<view class="item">
<view class="line-wrap">
<text class="label w-[200rpx]">{{t('cashOutNo')}}</text>
<text class="value">{{ cashOutInfo.cash_out_no }}</text>
</view>
<view class="line-wrap">
<text class="label w-[200rpx]">{{t('serviceMoney')}}</text>
<text class="value">{{ cashOutInfo.service_money }}</text>
</view>
<view class="line-wrap">
<text class="label w-[200rpx]">{{t('createTime')}}</text>
<text class="value">{{ cashOutInfo.create_time }}</text>
</view>
<view class="line-wrap" v-if="cashOutInfo.status && cashOutInfo.audit_time">
<text class="label w-[200rpx]">{{t('auditTime')}}</text>
<text class="value">{{ cashOutInfo.audit_time }}</text>
</view>
<view class="line-wrap" v-if="cashOutInfo.transfer_bank">
<text class="label w-[200rpx]">{{t('transferBank')}}</text>
<text class="value truncate">{{ cashOutInfo.transfer_bank }}</text>
</view>
<view class="line-wrap">
<text class="label w-[200rpx]">{{t('transferAccount')}}</text>
<text class="value truncate">{{ cashOutInfo.transfer_type == 'wechatpay' ? '微信' : cashOutInfo.transfer_account }}</text>
</view>
<view class="line-wrap" v-if="cashOutInfo.status == -1 && cashOutInfo.refuse_reason">
<text class="label w-[200rpx]">{{t('refuseReason')}}</text>
<text class="value truncate">{{ cashOutInfo.refuse_reason }}</text>
</view>
<view class="line-wrap" v-if="cashOutInfo.status == 2">
<text class="label w-[200rpx]">{{t('transferTypeName')}}</text>
<text class="value truncate">{{ cashOutInfo.transfer_type_name }}</text>
</view>
<view class="line-wrap" v-if="cashOutInfo.status == 2 && cashOutInfo.transfer_time">
<text class="label w-[200rpx]">{{t('transferTime')}}</text>
<text class="value truncate">{{ cashOutInfo.transfer_time }}</text>
</view>
</view>
</block>
<u-loading-page bg-color="rgb(248,248,248)" :loading="loading" loadingText="" fontSize="16" color="#303133"></u-loading-page>
</view>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { t } from '@/locale'
import { redirect, img } from '@/utils/common';
import { getCashOutDetail } from '@/app/api/member';
const cashOutInfo = ref({});
const loading = ref<boolean>(true);
onLoad((option: any) => {
let id = option.id || "";
getCashoutAccountListFn(id)
})
const getCashoutAccountListFn = (id) => {
loading.value = true;
getCashOutDetail(id).then((res) => {
cashOutInfo.value = res.data;
loading.value = false;
}).catch(() => {
loading.value = false;
})
}
</script>
<style lang="scss">
@import '@/styles/member_record_detail.scss';
</style>

View File

@ -0,0 +1,192 @@
<template>
<view class="bg-[#F6F6F6] min-h-[100vh] w-full" :style="themeColor()" v-if="memberStore.info">
<view class="fixed w-full z-2 !bg-[#F6F6F6]">
<view class="pb-[272rpx]" :style="headerStyle">
<!-- #ifdef MP-WEIXIN -->
<top-tabbar :data="param" class="top-header"/>
<!-- #endif -->
</view>
<view class="mt-[-232rpx] sidebar-marign" :style="{ backgroundImage: 'url(' + img('static/resource/images/member/commission/account_bg.png') + ')',backgroundRepeat:'no-repeat',backgroundSize:'100% 100%'}">
<view class="pt-[40rpx]">
<view class="flex items-center justify-between px-[30rpx]">
<view>
<view class="text-[26rpx] font-400 text-[#fff] leading-[36rpx] mb-[16rpx]">{{t('accountCommission')}}</view>
<view class="font-bold text-[#fff] flex items-baseline">
<text class="text-[56rpx] h-[72rpx] price-font">{{ memberStore.info ? (moneyFormat(memberStore.info.commission).split(".")[0]+'.') : '0.' }}</text>
<text class="text-[44rpx] h-[56rpx] price-font">{{ memberStore.info ? moneyFormat(memberStore.info.commission).split(".")[1] : '00' }}</text>
</view>
</view>
<button @click="applyCashOut" hover-class="none" class="bg-[#fff] rounded-[100rpx] w-[150rpx] h-[66rpx] leading-[66rpx] text-[#EF000C] m-[0] border-[0] text-[32rpx]">{{t('transferMoney')}}</button>
</view>
<view class="flex items-center mt-[68rpx] px-[30rpx] border-[0] border-t-[2rpx] border-solid border-[rgba(255,255,255,.3)] h-[126rpx]">
<view class="flex-1">
<view class="font-bold text-[#fff] text-[36rpx] leading-[44rpx] mb-[6rpx] price-font">
{{ moneyFormat(memberStore.info?.commission_get)|| '0.00' }}
</view>
<view class="text-[26rpx] text-[#fff] leading-[36rpx]">{{ t('commission') }}</view>
</view>
<view class="flex-1">
<view class="font-bold text-[#fff] text-[36rpx] leading-[44rpx] mb-[6rpx] price-font">
{{ moneyFormat(memberStore.info?.commission_cash_outing)|| '0.00' }}
</view>
<view class="text-[26rpx] text-[#fff] leading-[36rpx]">{{ t('money') }}</view>
</view>
</view>
</view>
</view>
<view class="mt-[40rpx] tab-style-1">
<view class="tab-left">
<view class="tab-left-item" :class="{ 'class-select': fromType.from_type === item.from_type && fromType.account_data_gt === item.account_data_gt }" @click="fromTypeFn(item,index)" v-for="(item, index) in accountTypeList">{{ item.name }}</view>
</view>
<view class="tab-right" @click="handleSelect">
<view class="tab-right-date">日期</view>
<view class="nc-iconfont nc-icon-riliV6xx tab-right-icon"></view>
</view>
</view>
</view>
<mescroll-body ref="mescrollRef" @init="mescrollInit" :down="{ use: false }" @up="geCommissionListFn" :top="mescrollTop">
<view class="px-[var(--sidebar-m)] pt-[20rpx] body-bottom" v-if="list.length">
<view v-for="(item,index) in list" :key="item.id" class="w-full h-[140rpx] flex justify-between items-center card-template" :class="{'mt-[20rpx]':index}">
<view class="flex items-center">
<view class="w-[80rpx] h-[80rpx] text-center rounded-[40rpx] text-[40rpx] font-500 leading-[80rpx] text-[#fff]"
:class="{'bg-[#EF000C]' :item.account_data > 0, 'bg-[#1379FF]':item.account_data <= 0 }">
{{item.account_data > 0?'收':'提'}}
</view>
<view class="flex flex-col ml-[20rpx]">
<view class="text-[#333] text-[26rpx] leading-[36rpx]">{{item.from_type_name}}</view>
<view class="text-[#8288A2] text-[24rpx] leading-[34rpx] mt-[4rpx] font-400">{{item.create_time}}</view>
</view>
</view>
<view class="text-[36rpx] leading-[50rpx]" :class="{'text-[#EF000C]' :item.account_data > 0, 'text-[#333]':item.account_data <= 0 }">{{ item.account_data > 0 ? '+' + item.account_data : item.account_data }}</view>
</view>
</view>
<view class="box-border pt-[20rpx] px-[30rpx] body-bottom" :style="{'height':'calc(100vh - '+ mescrollTop +')'}" v-if="!list.length && !loading &&!listLoading">
<view class="h-full rounded-[16rpx] flex items-center justify-center">
<mescroll-empty></mescroll-empty>
</view>
</view>
</mescroll-body>
<u-loading-page bg-color="rgb(248,248,248)" :loading="loading" loadingText="" fontSize="16" color="#303133"></u-loading-page>
<!-- 时间选择 -->
<select-date ref="selectDateRef" @confirm="confirmFn" />
</view>
</template>
<script setup lang="ts">
import { computed, ref, reactive } from 'vue'
import { t } from '@/locale'
import { moneyFormat, redirect, img } from '@/utils/common';
import useMemberStore from '@/stores/member'
import { getMemberCommission } from '@/app/api/member';
import MescrollBody from '@/components/mescroll/mescroll-body/mescroll-body.vue';
import MescrollEmpty from '@/components/mescroll/mescroll-empty/mescroll-empty.vue';
import useMescroll from '@/components/mescroll/hooks/useMescroll.js';
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app';
import { topTabar } from '@/utils/topTabbar'
import selectDate from '@/components/select-date/select-date.vue';
const { downCallback,mescrollInit, getMescroll } = useMescroll(onPageScroll, onReachBottom);
const memberStore = useMemberStore();
const info = computed(() => memberStore.info)
//
const applyCashOut = ()=> {
uni.setStorageSync('cashOutAccountType', 'commission')
redirect({ url: '/app/pages/member/apply_cash_out' })
}
/********* 自定义头部 - start ***********/
const topTabarObj = topTabar()
let param = topTabarObj.setTopTabbarParam({title:'我的佣金',topStatusBar:{bgColor: '#fff',textColor: '#333'}})
/********* 自定义头部 - end ***********/
//
let menuButtonInfo:any = {};
// (API)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
const headerStyle = computed(()=>{
return {
backgroundImage: 'url(' + img('static/resource/images/member/commission/commission_bg.png') + ') ',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'bottom',
}
})
// 16padding-bottom
const mescrollTop = computed(()=>{
return Object.keys(menuButtonInfo).length? (Number(menuButtonInfo.height) * 2 + menuButtonInfo.top * 2 + 486 + 16)+'rpx':'504rpx'
})
//
const fromType = ref({
from_type:'',
account_data_gt:''
})
const accountTypeList = ref([
{name:'全部',from_type:'',account_data_gt: ''},
{name:'佣金',from_type:'',account_data_gt: 0},
{name:'提现',from_type:'cash_out',account_data_gt: ''},
])
const create_time = ref([])
const list = ref<Array<any>>([])
const loading = ref<boolean>(true)
const listLoading = ref<boolean>(true)
const mescrollRef = ref(null);
const fromTypeFn = (data : any ,index : number)=>{
fromType.value.from_type = data.from_type
fromType.value.account_data_gt = data.account_data_gt
getMescroll().resetUpScroll();
}
const geCommissionListFn = (mescroll) => {
listLoading.value = true;
let data : Object = {
page: mescroll.num,
limit: mescroll.size,
from_type:fromType.value.from_type,
account_data_gt: fromType.value.account_data_gt,
create_time:create_time.value
}
getMemberCommission(data).then((res:any) => {
let newArr = res.data.data;-
mescroll.endSuccess(newArr.length);
//
if (mescroll.num == 1) {
list.value = []; //
}
list.value = list.value.concat(newArr);
listLoading.value = false;
loading.value = false;
}).catch(() => {
listLoading.value = false;
loading.value = false;
mescroll.endErr(); // ,
})
}
//
const selectDateRef = ref()
const handleSelect = () =>{
selectDateRef.value.show = true
}
//
const confirmFn = (data) =>{
create_time.value = data;
list.value = []
getMescroll().resetUpScroll();
}
</script>
<style lang="scss">
.member-level{
background: linear-gradient( 360deg, #F23621 11%, #FF7F71 100%), #D9D9D9;
border-radius: 0rpx 20rpx 20rpx 0rpx;
}
:deep(.uni-scroll-view){
overflow: auto hidden !important;
}
.body-bottom{
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
}
</style>

View File

@ -0,0 +1,192 @@
<template>
<u-popup :show="show" @close="show = false" mode="bottom" :round="10" >
<view>
<view class="py-[30rpx] px-[40rpx] flex items-center justify-between">
<view class="text-center flex-1">选择时间</view>
<view class="nc-iconfont nc-icon-guanbiV6xx text-[36rpx] text-primary" @click="show = false"></view>
</view>
<view class="px-[30rpx] mb-[20rpx]">
<view class="flex items-center justify-between mb-[30rpx]">
<view class="w-[160rpx] h-[60rpx] leading-[60rpx] rounded-[30rpx] bg-[#F4F6FA] text-center text-[26rpx] text-[#666] border-[2rpx] border-solid border-[#F4F6FA]" v-for="(item,index) in curselectDate" :key="'a'+index" :class="{'text-primary !border-[var(--primary-color)] !bg-[rgba(239,0,12,0.04)]': currentValue.type == item.type}" @click="loadDateFn(item)">{{item.name}}</view>
</view>
<view class="flex items-center justify-between">
<view class="w-[316rpx] h-[60rpx] leading-[60rpx] rounded-[30rpx] bg-[#F4F6FA] text-center text-[26rpx] text-[#666] border-[2rpx] border-solid border-[#F4F6FA]" :class="{'text-primary !border-[var(--primary-color)] !bg-[rgba(239,0,12,0.04)]': currentValue.type == 'first'}" @click="currentValue.type = 'first'">{{dateList.nowDate[0]}}</view>
<view class="nc-iconfont nc-icon-jianV6xx"></view>
<view class="w-[316rpx] h-[60rpx] leading-[60rpx] rounded-[30rpx] bg-[#F4F6FA] text-center text-[26rpx] text-[#666] border-[2rpx] border-solid border-[#F4F6FA]" :class="{'text-primary !border-[var(--primary-color)] !bg-[rgba(239,0,12,0.04)]': currentValue.type == 'second'}" @click="currentValue.type = 'second'">{{dateList.nowDate[1]}}</view>
</view>
</view>
<view class="h-[396rpx]">
<picker-view :indicator-style="{height:'70rpx',backgroundColor:'#F4F6FA'}" :value="dateList.curIndex" @change="bindChange" class="px-[60rpx] h-full">
<picker-view-column>
<view class="text-center leading-[70rpx] !h-[70rpx] text-[28rpx]" v-for="(item,index) in dateList.years" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="text-center leading-[70rpx] !h-[70rpx] text-[28rpx]" v-for="(item,index) in dateList.months" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="text-center leading-[70rpx] !h-[70rpx] text-[28rpx]" v-for="(item,index) in dateList.days" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
</view>
<view class="px-[30rpx] pb-[30rpx] pt-[20rpx] flex justify-between">
<button class="w-[330rpx] h-[88rpx] text-[var(--primary-color)] text-[32rpx] leading-[84rpx] border-[2rpx] border-solid border-[var(--primary-color)] rounded-[100rpx] bg-transparent" @click="reset">重置</button>
<button class="w-[330rpx] h-[88rpx] text-[#fff] text-[32rpx] leading-[88rpx] border-[0] rounded-[100rpx] primary-btn-bg" shape="circle" @click="save">确定</button>
</view>
</view>
</u-popup>
</template>
<script setup lang="ts">
import { ref,reactive } from 'vue'
const emits = defineEmits(['confirm'])
//
const show = ref(false)
const create_time = ref([])
//
const init = () =>{
const date = new Date();
const years = []
const months = []
const days = []
const year = date.getFullYear()
const month = date.getMonth();
const day = date.getDate()
for (let i = 1990; i <= date.getFullYear()+2; i++) {
years.push(i)
}
for (let i = 1; i <= 12; i++) {
months.push(i)
}
let dayCount = getDaysInMonth(year, month+1)
for (let i = 1; i <= dayCount; i++) {
days.push(i)
}
let yearIndex = years.indexOf(year)
let curIndex = [yearIndex, month, day-1]
let nowDate = `${year}-${month + 1 < 10 ? '0' + (month + 1) :(month + 1) }-${day < 10 ? '0' + day :day }`
let lastMonthDate = new Date(date.getFullYear(),month - 1,date.getDate())
let lastThreeMonthDate = new Date(date.getFullYear(),month - 3,date.getDate())
let halfYear = new Date(date.getFullYear(), month - 6, date.getDate())
let lastYear =new Date(date.getFullYear() - 1, month, date.getDate())
const formatDate = (dateTime:any) => {
const yearTime = dateTime.getFullYear()
const monthTime = dateTime.getMonth() + 1
const dayTime = dateTime.getDate()
return `${yearTime}-${monthTime < 10 ? '0' + monthTime : monthTime}-${dayTime < 10 ? '0' + dayTime :dayTime }`
}
return {
years,
months,
days,
curIndex,
nowDate,
lastMonth: formatDate(lastMonthDate),
lastThreeMonth: formatDate(lastThreeMonthDate),
halfYear: formatDate(halfYear),
lastYear: formatDate(lastYear)
}
}
const getDaysInMonth = (year, month) => {
let date = new Date(year, month, 0).getDate()
return date
}
const getDaysCount = (year, month) =>{
let count = getDaysInMonth(year, month)
let days = []
for (let i = 1; i <= count; i++) {
days.push(i)
}
return days
}
const dateList = reactive({
years: init().years,
months: init().months,
days: init().days,
curIndex:init().curIndex, //
nowDate:[init().nowDate,init().nowDate] //
})
const bindChange = (e) =>{
const val = e.detail.value
let year = dateList.years[val[0]]
let month= dateList.months[val[1]]
let day = dateList.days[val[2]]
// ,
dateList.days = getDaysCount(year, month)
if(currentValue.value.type == 'first'){
dateList.nowDate[0] = `${year}-${month < 10 ? '0' + month :month }-${day < 10 ? '0' + day :day }`
}else if(currentValue.value.type == 'second'){
dateList.nowDate[1] = `${year}-${month < 10 ? '0' + month :month }-${day < 10 ? '0' + day :day }`
}
}
const curselectDate = reactive([
{
time:[init().lastMonth,init().nowDate],
type:'lastMonth',
name:'近1个月'
},
{
time:[init().lastThreeMonth,init().nowDate],
type:'lastThreeMonth',
name:'近3个月'
},
{
time:[init().halfYear,init().nowDate],
type:'halfYear',
name:'近半年'
},
{
time:[init().lastYear,init().nowDate],
type:'lastYear',
name:'近一年'
}
])
//
const currentValue = ref({
type: 'first',
time: []
})
const loadDateFn = (data) =>{
currentValue.value.type = data.type
currentValue.value.time = data.time
}
const save = () =>{
if(currentValue.value.type == 'first' ||currentValue.value.type == 'second'){
create_time.value = dateList.nowDate
let start = new Date(create_time.value[0]).getTime()
let end = new Date(create_time.value[1]).getTime()
if( start > end){
uni.showToast({ title: '开始时间不能大于结束时间', icon: 'none' })
return
}
}else{
create_time.value = currentValue.value.time
}
emits('confirm',create_time.value)
show.value = false
}
const reset = () =>{
currentValue.value.type = 'first'
dateList.curIndex = init().curIndex
dateList.nowDate = [init().nowDate,init().nowDate]
}
defineExpose({
show
})
</script>
<style scoped>
:deep(.uni-picker-view-content){
z-index: 10;
}
:deep(.uni-picker-view-indicator::before){
border: none !important;
}
:deep(.uni-picker-view-indicator::after){
border: none !important;
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<view class="flex items-center justify-center min-h-[100vh]" :style="themeColor()">
<view class="contact-wrap pt-[5%]">
<image :src="img('static/resource/images/member/contact_service.png')" mode="widthFix" />
<view class="mt-[40rpx] text-[28rpx]">
欢迎您联系我们提供您宝贵的意见
</view>
<nc-contact
:send-message-title="sendMessageTitle"
:send-message-path="sendMessagePath"
:send-message-img="sendMessageImg">
<button type="primary" class="btn-wrap primary-btn-bg">{{ t('customerService') }}</button>
</nc-contact>
</view>
</view>
</template>
<script setup lang="ts">
import { t } from '@/locale'
import { img } from '@/utils/common';
import { ref } from 'vue';
const sendMessageTitle = ref('')
const sendMessagePath = ref('')
const sendMessageImg = ref('')
sendMessageImg.value = img('static/resource/images/member/contact_service.png')
</script>
<style lang="scss" scoped>
.contact-wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
image {
width: 674rpx;
}
.btn-wrap {
margin-top: 120rpx;
width: 530rpx;
height: 88rpx;
font-size: 32rpx;
border-radius: 50rpx;
line-height: 88rpx;
}
}
</style>

View File

@ -0,0 +1,162 @@
<template>
<view class="min-h-[100vh] bg-[#F6F6F6] overflow-hidden" :style="themeColor()">
<view class="fixed left-0 right-0 top-0 z-99">
<view class="bg-[#fff] px-[var(--sidebar-m)] h-[88rpx] flex items-center">
<view class="flex-1 flex items-center h-[60rpx] bg-[#F8F9FD] rounded-[30rpx] px-[20rpx]">
<u-input class="flex-1" maxlength="50" v-model="keyword" @confirm="searchTypeFn()" placeholder="请输入搜索关键词" placeholderClass="text-[#999] text-[24rpx]" fontSize="26rpx" clearable border="none"></u-input>
<text class="nc-iconfont nc-icon-sousuo-duanV6xx1 text-[28rpx] ml-[32rpx] !text-[#999]" @click="searchTypeFn()"></text>
</view>
</view>
<view class="tab-style-1 pt-[14rpx] bg-[#fff]">
<view class="tab-left">
<view class="tab-left-item" :class="{'!text-primary class-select':fromType.from_type === item.from_type && fromType.account_data_gt === item.account_data_gt}" v-for="(item,index) in accountTypeList" :key="index" @click="fromTypeFn(item,index)">{{ item.name }}</view>
</view>
<view class="tab-right" @click="handleSelect">
<view class="tab-right-date">日期</view>
<view class="nc-iconfont nc-icon-riliV6xx tab-right-icon"></view>
</view>
</view>
</view>
<mescroll-body ref="mescrollRef" @init="mescrollInit" :down="{ use: false }" @up="getListFn" top="196rpx">
<view v-for="(item,index) in list" :key="item.id" class="sidebar-marign mb-[20rpx] card-template relative">
<view class="flex items-center justify-between mb-[10rpx]">
<view class="text-[28rpx] font-500 text-[#333] leading-[40rpx]">{{item.from_type_name}}</view>
<view class="absolute right-[30rpx] top-[30rpx] text-[36rpx] font-500 text-[#03B521] leading-[50rpx]" :class="{'!text-[var(--price-text-color)]':item.account_data > 0}">{{ item.account_data > 0 ? '+' + item.account_data : item.account_data }}</view>
</view>
<view class="text-[24rpx] leading-[34rpx] text-[#8288A2] mb-[10rpx]" v-if="item.memo">{{item.memo}}</view>
<view class="text-[24rpx] leading-[34rpx] text-[#8288A2]">{{item.create_time}}</view>
</view>
<view class="mx-[30rpx] rounded-[16rpx] noData flex items-center justify-center" v-if="!list.length && loading">
<mescroll-empty :option="{tip : tip}"></mescroll-empty>
</view>
</mescroll-body>
<!-- 时间选择 -->
<select-date ref="selectDateRef" @confirm="confirmFn" />
</view>
</template>
<script setup lang="ts">
import { ref,nextTick, reactive } from 'vue'
import { t } from '@/locale'
import MescrollBody from '@/components/mescroll/mescroll-body/mescroll-body.vue';
import MescrollEmpty from '@/components/mescroll/mescroll-empty/mescroll-empty.vue';
import useMescroll from '@/components/mescroll/hooks/useMescroll.js';
import { getBalanceList, getMoneyList, getCommissionList} from '@/app/api/member';
import { onPageScroll, onReachBottom, onLoad, onShow } from '@dcloudio/uni-app';
import selectDate from '@/components/select-date/select-date.vue';
const { mescrollInit, downCallback, getMescroll } = useMescroll(onPageScroll, onReachBottom);
const type = ref('')
const tip = ref('')
onLoad((options: any) => {
type.value = options.type || 'balance';
nextTick(()=>{
setTimeout(()=>{
if(type.value == 'balance'){
tip.value = t('balanceEmptyTip')
}else if(type.value == 'money'){
tip.value = t('moneyEmptyTip')
}else if(type.value == 'commission'){
tip.value = t('commissionEmptyTip')
}
},100);
})
});
const keyword = ref<string>('')
const create_time = ref([])
//
const fromType = ref({
from_type:'',
account_data_gt:''
})
const accountTypeList = ref([
{name:'全部',from_type:'',account_data_gt: ''},
{name:'佣金',from_type:'',account_data_gt: 0},
{name:'提现',from_type:'cash_out',account_data_gt: ''},
])
const list = ref<Array<any>>([]),
loading = ref<boolean>(false),
mescrollRef = ref(null);
interface mescrollStructure {
num : number,
size : number,
endSuccess : Function,
[propName : string] : any
}
const getListFn = (mescroll : mescrollStructure) => {
loading.value = false;
let data : Object = {
page: mescroll.num,
page_size: mescroll.size,
keyword:keyword.value,
create_time:create_time.value,
from_type:fromType.value.from_type,
account_data_gt: fromType.value.account_data_gt
};
interface acceptingDataStructure {
data : acceptingDataItemStructure,
msg : string,
code : number
}
interface acceptingDataItemStructure {
data : object,
[propName : string] : number | string | object
}
let fnList = (params : any) => { };
if (type.value == 'balance') fnList = getBalanceList;
else if (type.value == 'money') fnList = getMoneyList;
else if (type.value == 'commission') fnList = getCommissionList;
fnList(data).then((res : acceptingDataStructure) => {
let newArr = res.data.data;
mescroll.endSuccess(newArr.length);
//
if (mescroll.num == 1) {
list.value = []; //
}
list.value = list.value.concat(newArr);
loading.value = true;
}).catch(() => {
loading.value = true;
mescroll.endErr(); // ,
})
}
//
const searchTypeFn = () =>{
list.value = [];
getMescroll().resetUpScroll();
}
//
const fromTypeFn = (data : any ,index : number)=>{
fromType.value.from_type = data.from_type
fromType.value.account_data_gt = data.account_data_gt
list.value = []
getMescroll().resetUpScroll();
}
//
const selectDateRef = ref()
const handleSelect = () =>{
selectDateRef.value.show = true
}
//
const confirmFn = (data) =>{
create_time.value = data;
list.value = []
getMescroll().resetUpScroll();
}
</script>
<style lang="scss">
.noData{
height: calc(100vh - 210rpx - constant(safe-area-inset-bottom));
height: calc(100vh - 210rpx - env(safe-area-inset-bottom));
}
</style>

View File

@ -0,0 +1,93 @@
<template>
<view :style="themeColor()">
<u-loading-page :loading="diy.getLoading()" loadingText="" bg-color="#f7f7f7" />
<view v-show="!diy.getLoading()">
<!-- 自定义模板渲染 -->
<view class="diy-template-wrap bg-index" v-if="diy.data.pageMode != 'fixed'" :style="diy.pageStyle()">
<diy-group ref="diyGroupRef" :data="diy.data" :pullDownRefreshCount="diy.pullDownRefreshCount" />
</view>
<!-- 固定模板渲染 -->
<view class="fixed-template-wrap" v-if="diy.data.pageMode == 'fixed'">
<fixed-group :data="diy.data" :pullDownRefreshCount="diy.pullDownRefreshCount" />
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<!-- 小程序隐私协议 -->
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
<!-- #endif -->
</view>
</template>
<script setup lang="ts">
import {ref, computed} from 'vue';
import {useDiy} from '@/hooks/useDiy'
import {redirect} from '@/utils/common';
import diyGroup from '@/addon/components/diy/group/index.vue'
import fixedGroup from '@/addon/components/fixed/group/index.vue'
import useMemberStore from '@/stores/member'
//
const memberStore = useMemberStore()
const userInfo = computed(() => memberStore.info)
const diy = useDiy({
name: 'DIY_MEMBER_INDEX'
})
const diyGroupRef = ref(null)
//
diy.onLoad();
//
diy.onShow((data: any) => {
if (data.value) {
// uni.setNavigationBarTitle({
// title: diyData.title
// })
} else if (data.page) {
//
redirect({url: data.page, mode: 'reLaunch'})
}
diyGroupRef.value?.refresh();
if (userInfo.value) {
useMemberStore().getMemberInfo()
}
});
//
diy.onUnload();
//
diy.onPullDownRefresh()
//
diy.onPageScroll()
</script>
<style lang="scss" scoped>
@import '@/styles/diy.scss';
</style>
<style lang="scss">
.diy-template-wrap {
/* #ifdef MP */
.child-diy-template-wrap {
::v-deep .diy-group {
> .draggable-element.top-fixed-diy {
display: block !important;
}
}
}
/* #endif */
}
</style>

View File

@ -0,0 +1,321 @@
<template>
<view :style="themeColor()">
<u-loading-page :loading="loading && memberInfo" loadingText="" bg-color="#f7f7f7"></u-loading-page>
<view v-if="!loading && memberInfo && list && list.length" class=" min-h-[100vh] overflow-hidden flex flex-col" :style="{backgroundColor: currLevelInfo.level_style.bg_color }">
<!-- #ifdef MP -->
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()"/>
<!-- #endif -->
<view>
<view class="pt-[40rpx] mb-[20rpx]">
<!-- 轮播图 -->
<view class="relative">
<swiper class="swiper ns-indicator-dots relative" :style="{ height: '300rpx' }" @change="swiperChange" :current = "swiperIndex" previous-margin="48rpx" next-margin="48rpx">
<swiper-item class="swiper-item" v-for="(item,index) in list" :key="item.id">
<view class="h-[300rpx] relative">
<view v-if="memberInfo.member_level == item.level_id && swiperIndex == index" class="text-[24rpx] absolute top-0 left-0 z-10 h-[66rpx] !bg-contain w-[150rpx] flex pt-[12rpx] pl-[16rpx] box-border" :style="{ background: 'url(' + img(currLevelInfo.level_tag) + ') no-repeat',color: currLevelInfo.level_style.level_color}">
当前等级
</view>
<view class="absolute top-0 left-0 right-0 bottom-0 z-20 px-[30rpx] pt-[76rpx] box-border" :class="{'px-[50rpx]': swiperIndex != index}">
<view class="flex items-center leading-[50rpx] mb-[70rpx]">
<image class="h-[32rpx] w-[34rpx] align-middle" :src="img(item.level_icon ? item.level_icon : '')" mode="aspectFill" />
<view class="text-[36rpx] font-bold ml-[10rpx] max-w-[340rpx] truncate" :style="{color:currLevelInfo.level_style.level_color}">{{item.level_name}}</view>
</view>
<view class="flex items-center" :style="{color: currLevelInfo.level_style.level_color}">
<view class="text-[28rpx] font-bold leading-[38rpx]">{{ memberInfo.growth }}</view>
<view class="text-[24rpx] leading-[34rpx] font-500">/{{list[index].growth}}成长值</view>
</view>
<view class="flex justify-between items-center mt-[10rpx]">
<view class="flex flex-col flex-1">
<view>
<progress :percent="progress(index)" :border-radius="100" :activeColor="currLevelInfo.level_style.level_color" backgroundColor="#fff" stroke-width="6" />
</view>
</view>
</view>
</view>
<view class="relatvie h-full w-full">
<image class="h-full w-full" :src="img(item.level_bg)" mode="aspectFit" :class="{'swiper-animation': swiperIndex != index}" :show-menu-by-longpress="true"/>
</view>
</view>
</swiper-item>
</swiper>
</view>
</view>
<view class="mb-[30rpx] relative">
<view class="bg-[#fff] opacity-15 h-[2rpx] w-full absolute top-[15rpx]"></view>
<view :style="lineStyle" class="bg-[#fff] opacity-60 h-[2rpx] absolute top-[15rpx] z-4 left-[50%]"></view>
<view class="mx-[86rpx]">
<scroll-view :scroll-x="true" scroll-with-animation :scroll-into-view="'id' + ( levelIndex ? levelIndex - 1 : 0)">
<view class="flex flex-nowrap py-[10rpx]">
<block v-for="(item,index) in list" :key="item.id">
<view :style="levelStyle" class=" flex-shrink-0 flex flex-col items-center justify-center" @click="changeLevel(index)" :id="'id' + index">
<view class="w-[14rpx] h-[14rpx] level-class" :class="{'level-select': levelIndex == (index)}"></view>
<view :style="maxWidth" class="text-[24rpx] text-[#aaa] mt-[10rpx] truncate">{{item.level_name}}</view>
</view>
</block>
</view>
</scroll-view>
</view>
</view>
<view class="flex mx-[30rpx] pt-[30rpx] pb-[46rpx] items-center flex-col level_benefits" v-if="currLevelInfo.benefits_arr && currLevelInfo.benefits_arr.length" :style="{ backgroundImage: 'url(' + img(currLevelInfo.member_bg) + ')'}">
<view class="flex items-center justify-center">
<text class="text-[#fff] text-[32rpx] font-bold leading-[44rpx]">会员权益</text>
</view>
<view class="flex flex-wrap w-[690rpx] mt-[40rpx] justify-between">
<view class="flex flex-col w-[25%] items-center" v-for="(item,index) in currLevelInfo.benefits_arr" :key="index">
<image class="h-[100rpx] w-[100rpx]" :src="img(item.icon)" mode="heightFix" />
<text class="text-[rgba(255,255,255,0.9)] mt-[10rpx] text-[24rpx] leading-[34rpx]">{{item.title}}</text>
</view>
</view>
</view>
</view>
<view class="flex-1 rounded-t-[40rpx] px-[30rpx] pt-[30rpx] mt-[-16rpx] relative tab-bar" :style="{background: `linear-gradient( 180deg, ${currLevelInfo.level_style.gift} 0%, #FFFFFF 20%)`}">
<!-- 升级礼包 -->
<view v-if="currLevelInfo.gifts_arr && currLevelInfo.gifts_arr.length">
<view class="pt-[10rpx] pb-[30rpx] flex items-center">
<text class="text-[32rpx] text-[#333] font-bold leading-[44rpx]">升级礼包</text>
</view>
<view class="flex flex-wrap">
<view v-for="(item,index) in currLevelInfo.gifts_arr" :key="index" class="mb-[20rpx]" :class="{'mr-[20rpx]': (index+1) % 3 != 0}">
<view class="relative box-border mb-[12rpx] w-[216rpx] h-[180rpx] !bg-contain" :style="{ background: 'url(' + img(item.background) + ') no-repeat'}">
</view>
<view class="text-center text-[#333] text-[28rpx] font-500 truncate leading-[40rpx] max-w-[216rpx]">{{item.text}}</view>
</view>
</view>
</view>
<!-- 升级技巧 -->
<view v-if="upgradeSkills && upgradeSkills.length">
<view class="pt-[10rpx] pb-[30rpx] flex items-center">
<text class="text-[32rpx] text-[#333] font-bold leading-[44rpx]">升级技巧</text>
</view>
<view>
<view class="flex items-center mb-[30rpx]" v-for="(item,index) in upgradeSkills" :key="index">
<image class="h-[100rpx] w-[100rpx] mr-[20rpx]" :src="img(item.icon)" mode="heightFix" />
<view class="flex flex-col">
<view class="text-[#3A3945] text-[28rpx] font-bold leading-[38rpx] mb-[8rpx]">{{item.title}}</view>
<view class="text-[24rpx] text-[#3A3945] leading-[34rpx]">{{item.desc}}</view>
</view>
<text class="skill-btn" @click="redirect({ url: item.button.wap_redirect, param: {} , mode: 'redirectTo'})">{{item.button.text}}</text>
</view>
</view>
</view>
</view>
</view>
<view v-if="!loading && (!list || !list.length)" class="h-[100vh] w-full flex items-center justify-center">
<u-empty :icon="img('static/resource/images/empty.png')" text="暂无会员等级" />
</view>
</view>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getMemberLevel, getTaskGrowth } from '@/app/api/member';
import { img,redirect, deepClone, getToken } from '@/utils/common';
import useMemberStore from '@/stores/member'
import { topTabar } from '@/utils/topTabbar'
const memberStore = useMemberStore()
const loading = ref(false)
const list = ref([]) //
const upgradeSkills = ref([]) //
const swiperIndex = ref(0); //
const levelIndex = ref(0); //
/********* 自定义头部 - start ***********/
const topTabarObj = topTabar()
let topTabbarData = topTabarObj.setTopTabbarParam({title:'会员等级'})
/********* 自定义头部 - end ***********/
onShow(() => {
//
if (getToken()) {
getTaskGrowthFn();
getMemberLevelFn();
}
})
//
const memberInfo = computed(() => {
return memberStore.info
})
//
const progress = (index:any) => {
let indent = index;
let num = 100
if(list.value[indent] && list.value[indent].growth) {
num = memberInfo.value.growth / list.value[indent].growth * 100
}
return num
}
//
const upgradeGrowth = (index:any) => {
let indent = index;
let num = 0;
if(list.value[indent] && list.value[indent].growth) {
num = list.value[indent].growth - memberInfo.value.growth;
}
return num
}
const levelStyle = ref(''); //
const maxWidth = ref(''); //
const lineStyle = ref(''); // 线
const getMemberLevelFn = ()=>{
loading.value = true;
getMemberLevel().then((res) => {
list.value = res.data || [];
//
let bool = true;
if(memberInfo.value && list.value && list.value.length){
list.value.forEach((item,index)=>{
if(item.level_id == memberInfo.value.member_level){
bool = false;
swiperIndex.value = index;
levelIndex.value = swiperIndex.value;
infoStructureFn(index);
}
})
}
if(bool) infoStructureFn(0);
if(list.value && list.value.length >= 5){
levelStyle.value ='width:115rpx;'
maxWidth.value = 'max-width:115rpx;'
lineStyle.value = `width:460rpx;transform: translateX(-235rpx);`
}else if(list.value && list.value.length == 4){
levelStyle.value ='width:144rpx;'
maxWidth.value = 'max-width:144rpx;'
lineStyle.value = `width:436rpx;transform: translateX(-218rpx);`
}else if(list.value && list.value.length == 3){
levelStyle.value ='width:192rpx;'
maxWidth.value = 'max-width:192rpx;'
lineStyle.value = `width:388rpx;transform: translateX(-194rpx);`
}else if(list.value && list.value.length == 2){
levelStyle.value ='width:289rpx;'
maxWidth.value = 'max-width:289rpx;'
lineStyle.value = `width:289rpx;transform: translateX(-144rpx);`
}else{
maxWidth.value = 'max-width:578rpx;'
levelStyle.value ='width:100%;'
}
loading.value = false;
}).catch(()=>{
loading.value = false;
})
}
const getTaskGrowthFn = ()=>{
getTaskGrowth().then((res) => {
upgradeSkills.value = res.data
})
}
const swiperChange = (e) => {
swiperIndex.value = e.detail.current;
levelIndex.value = swiperIndex.value
infoStructureFn(e.detail.current);
};
//
const currLevelInfo = ref<any>({});
const infoStructureFn = (index:number)=>{
let data:any = deepClone(list.value[index]);
//
if(data && data.level_benefits){
data.benefits_arr = [];
Object.values(data.level_benefits).forEach((item,index,Array)=>{
if(item.content){
data.benefits_arr.push(item.content)
}
})
}
//
if(data && data.level_gifts){
data.gifts_arr = [];
for(let key in data.level_gifts){
if(data.level_gifts[key].content){
//
data.level_gifts[key].content.forEach((item,index,Array)=>{
Array[index].type = key
})
data.gifts_arr = data.gifts_arr.concat(data.level_gifts[key].content);
}
}
}
currLevelInfo.value = data;
}
//
const changeLevel = (index : any) =>{
levelIndex.value = index;
swiperIndex.value = index;
infoStructureFn(index);
}
</script>
<style lang="scss" scoped>
.skill-btn{
padding: 0 20rpx;
height: 54rpx;
line-height: 56rpx;
color: #333;
background: linear-gradient( 180deg, #FEE8AC 0%, #F5D36E 85%);
border-radius: 30rpx;
margin-left: auto;
font-size: 24rpx;
}
.swiper-animation{
transform: scale(0.92, 0.92);
transition-duration: 0.3s;
transition-timing-function: ease;
}
:deep(.uni-progress) .uni-progress-bar, :deep(.uni-progress) .uni-progress-inner-bar{
border-radius: 10rpx;
}
.tab-bar {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.level-class{
position: relative;
&::before {
content: "";
position: absolute;
width: 14rpx;
height: 14rpx;
background-color: #aaa;
border-radius: 14rpx;
top:50%;
left: 50%;
transform: translate(-50%,-50%);
z-index: 10;
}
}
.level-select{
position: relative;
&::after {
content: "";
position: absolute;
width: 26rpx;
height: 26rpx;
background-color: #F6F6F6;
opacity: 0.4;
border-radius: 26rpx;
top:50%;
left: 50%;
transform: translate(-50%,-50%);
z-index: 1;
}
}
.level_benefits{
background-repeat: no-repeat;
background-size: 100% 100%;
}
</style>

View File

@ -0,0 +1,247 @@
<template>
<view :style="themeColor()">
<scroll-view scroll-y="true" class="bg-page h-screen">
<!-- <view class="h-[88rpx]">
<u-navbar title="添加地址" @rightClick="rightClick" :autoBack="true"></u-navbar>
</view> -->
<view class="h-[30rpx]"></view>
<view class="m-[30rpx] mt-0 p-[30rpx] pt-[10rpx] rounded-md bg-white">
<u-form labelPosition="left" :model="formData" labelWidth="200rpx" errorType='toast' :rules="rules" ref="formRef">
<view class="mt-[10rpx]">
<u-form-item :label="t('name')" prop="name" :border-bottom="true">
<u-input v-model.trim="formData.name" border="none" clearable maxlength="25" :placeholder="t('namePlaceholder')"/>
</u-form-item>
</view>
<view class="mt-[10rpx]">
<u-form-item :label="t('mobile')" prop="mobile" :border-bottom="true">
<u-input v-model="formData.mobile" border="none" clearable :placeholder="t('mobilePlaceholder')"/>
</u-form-item>
</view>
<view class="mt-[10rpx]">
<u-form-item :label="t('deliveryAddress')" prop="address_name" :border-bottom="true">
<view class="flex justify-between flex-1" @click="chooseLocation">
<view class="text-[15px]" :class="{ 'text-[#303133]': formData.area,'text-[#c3c4d5]':!formData.area }">{{formData.area ? formData.address_name : t('selectAddressPlaceholder')}}</view>
<u-icon name="arrow-right" color="#c3c4d5"></u-icon>
</view>
</u-form-item>
</view>
<view class="mt-[10rpx]">
<u-form-item :label="t('address')" prop="address" :border-bottom="true">
<u-input v-model.trim="formData.address" border="none" clearable maxlength="120" :placeholder="t('addressPlaceholder')"/>
</u-form-item>
</view>
<view class="mt-[10rpx]">
<u-form-item :label="t('defaultAddress')" prop="name" :border-bottom="true" >
<u-switch v-model="formData.is_default" size="20" :activeValue="1" :inactiveValue="0" activeColor="var(--primary-color)"/>
</u-form-item>
</view>
<view class="mt-[40rpx]">
<button class="!bg-[var(--primary-color)] !text-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[28rpx]" :class="{'opacity-50': btnDisabled}" @click="save" :disabled="btnDisabled" :loading="operateLoading">{{t('save')}}</button>
</view>
</u-form>
</view>
</scroll-view>
<!-- #ifdef MP-WEIXIN -->
<!-- 小程序隐私协议 -->
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
<!-- #endif -->
</view>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { redirect } from '@/utils/common'
import { t } from '@/locale'
import { addAddress, editAddress, getAddressInfo } from '@/app/api/member'
import manifestJson from '@/manifest.json'
import { getAddressByLatlng } from '@/app/api/system'
const type = ref('')
const source = ref('')
const btnDisabled = ref(false)
const formData = ref({
id: 0,
name: '',
mobile: '',
province_id: 0,
city_id: 0,
district_id: 0,
lat: '',
lng: '',
address: '',
address_name: '',
full_address: '',
is_default: 0,
area: '',
type: 'location_address'
})
onLoad((option:any) => {
if (option.id) {
getAddressInfo(option.id).then(({ data }) => {
if (data) {
Object.assign(formData.value, data)
formData.value.area = formData.value.full_address.replace(formData.value.address, '').replace(formData.value.address_name, '')
}
}).catch()
} else if (option.name) {
if (uni.getStorageSync('addressInfo')) {
Object.assign(formData.value, uni.getStorageSync('addressInfo'))
}
formData.value.address = option.name;
getAddress(option.latng);
var tempArr = getQueryVariable('latng').split(',');
formData.value.lat = tempArr[0];
formData.value.lng = tempArr[1];
}
type.value = option.type || ''
source.value = option.source || ''
})
const formRef = ref(null)
const rules = computed(() => {
return {
'address': {
type: 'string',
required: true,
message: t('addressError'),
trigger: ['blur', 'change'],
},
'name': {
type: 'string',
required: true,
message: t('namePlaceholder'),
trigger: ['blur', 'change'],
},
'mobile': [
{
type: 'string',
required: true,
message: t('mobilePlaceholder'),
trigger: ['blur', 'change'],
},
{
validator(rule, value, callback) {
let mobile = /^1[3-9]\d{9}$/;
if (!mobile.test(value)){
callback(new Error(t('mobileError')))
} else {
callback()
}
}
}
]
}
})
const getQueryVariable = (variable:any)=> {
var query = window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (pair[0] == variable) {
return pair[1];
}
}
return false;
}
//
const getAddress = (latlng:any)=> {
getAddressByLatlng({latlng}).then((res: any) => {
if (res.data) {
formData.value.full_address = '';
formData.value.full_address += res.data.province != undefined ? res.data.province : '';
formData.value.full_address += res.data.city != undefined ? '' + res.data.city : '';
formData.value.full_address += res.data.district != undefined ? '' + res.data.district : '';
formData.value.address_name = formData.value.full_address.replace(/-/g,'');
formData.value.area = res.data.full_address;
formData.value.province_id = res.data.province_id != undefined ? res.data.province_id : 0;
formData.value.city_id = res.data.city_id != undefined ? res.data.city_id : 0;
formData.value.district_id = res.data.district_id != undefined ? res.data.district_id : 0;
} else {
uni.showToast({title: res.msg, icon: 'none'})
}
})
}
const operateLoading = ref(false)
const save = ()=> {
if (uni.$u.test.isEmpty(formData.value.area)) {
uni.showToast({title: t('selectAddressPlaceholder'), icon: 'none'})
return
}
const save = formData.value.id ? editAddress : addAddress
formRef.value.validate().then(() => {
if (operateLoading.value) return
operateLoading.value = true
btnDisabled.value = true
formData.value.full_address = `${formData.value.area}${formData.value.address_name}${formData.value.address}`
save(formData.value).then((res) => {
operateLoading.value = false
uni.removeStorageSync('addressInfo');
setTimeout(() => {
btnDisabled.value = false
redirect({
url: '/app/pages/member/address',
mode: 'redirectTo',
param: {type: type.value, source: source.value}
})
}, 1000)
}).catch(() => {
operateLoading.value = false
btnDisabled.value = false
})
})
}
const chooseLocation = ()=> {
// #ifdef MP
uni.chooseLocation({
success: (res) => {
res.latitude && (formData.value.lat = res.latitude)
res.longitude && (formData.value.lng = res.longitude)
res.address && (formData.value.area = res.address)
res.name && (formData.value.address_name = res.name)
},
fail: (res)=>{
// chooseLocation:fail api
if(res.errMsg && res.errno) {
if(res.errno == 104){
let msg = '用户未授权隐私权限,选择位置失败';
uni.showToast({title: msg, icon: 'none'})
}else if(res.errno == 112){
let msg = '隐私协议中未声明,打开地图选择位置失败';
uni.showToast({title: msg, icon: 'none'})
}else {
uni.showToast({title: res.errMsg, icon: 'none'})
}
}
}
});
// #endif
// #ifdef H5
var urlencode = formData.value;
uni.setStorageSync('addressInfo', urlencode);
let backurl = location.origin + location.pathname + '?type=' + type.value + '&source=' + source.value;
window.location.href = 'https://apis.map.qq.com/tools/locpicker?search=1&type=0&backurl=' + encodeURIComponent(backurl) + '&key=' + manifestJson.h5.sdkConfigs.maps.qqmap.key + '&referer=myapp';
// #endif
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,209 @@
<template>
<view class="w-full h-screen bg-page personal-wrap" v-if="info" :style="themeColor()">
<view class="flex flex-col items-center pt-[30rpx]">
<!-- #ifdef MP-WEIXIN -->
<button open-type="chooseAvatar" @chooseavatar="onChooseAvatar" :plain="true" class="border-0" @click="checkWxPrivacy">
<u-avatar :src="img(info.headimg)" :default-url="img('static/resource/images/default_headimg.png')" size="60" leftIcon="none"></u-avatar>
<view class="text-primary text-sm mt-[10rpx]">{{ t('updateHeadimg') }}</view>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<u-upload @afterRead="afterRead" :maxCount="1">
<u-avatar :src="img(info.headimg)" :default-url="img('static/resource/images/default_headimg.png')" size="60" leftIcon="none"></u-avatar>
<view class="text-primary text-sm mt-[10rpx]">{{ t('updateHeadimg') }}</view>
</u-upload>
<!-- #endif -->
</view>
<view class="m-[30rpx] bg-white rounded-md overflow-hidden px-[20rpx] py-[10rpx]">
<u-cell-group :border="false">
<u-cell :title="t('nickname')" :is-link="true" :value="info.nickname" @click="updateNickname.modal = true"></u-cell>
<u-cell :title="t('sex')" :is-link="true" :value="info.sex_name || t('unknown')" @click="sexSheetShow = true"></u-cell>
<u-cell :title="t('mobile')">
<template #value>
<view v-if="info.mobile" class="mr-[10rpx]">{{ mobileConceal(info.mobile) }}</view>
<view v-else @click="redirect({ url: '/app/pages/auth/bind' })">
<button class="bg-transparent w-[132rpx] p-[0] rounded-[100rpx] text-[var(--primary-color)] !border-[2rpx] !border-solid border-[var(--primary-color)] text-[20rpx] h-[44rpx] leading-[40rpx]">{{t('bindMobile')}}</button>
</view>
</template>
</u-cell>
<u-cell :title="t('birthday')" :is-link="true" :value="formatDate(info.birthday) || t('unknown')" @click="birthdayPicker = true"></u-cell>
</u-cell-group>
</view>
<u-modal :show="updateNickname.modal" :closeOnClickOverlay="true" @close="updateNickname.modal = false"
:show-cancel-button="true"
@cancel="updateNickname.modal = false" :title="t('updateNickname')" confirmColor="var(--primary-color)">
<view class="w-full mt-[20rpx] border-0 border-b border-gray-300 border-solid py-[20rpx]">
<input type="nickname" v-model="updateNickname.value" :placeholder="t('nicknamePlaceholder')" @blur="bindNickname">
</view>
<template #confirmButton>
<view class="mt-[10rpx]">
<button class="bg-[var(--primary-color)] text-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[28rpx]" @click="updateNicknameConfirm">{{t('confirm')}}</button>
</view>
</template>
</u-modal>
<u-action-sheet :actions="sexList" :show="sexSheetShow" :closeOnClickOverlay="true"
:safeAreaInsetBottom="true"
@close="sexSheetShow = false" @select="updateSex"></u-action-sheet>
<u-datetime-picker v-model="info.birthday" :show="birthdayPicker" mode="date" :confirm-text="t('confirm')"
:maxDate="new Date().valueOf()" :minDate="0"
:cancel-text="t('cancel')" @cancel="birthdayPicker = false" @confirm="updateBirthday"></u-datetime-picker>
<!-- #ifdef MP-WEIXIN -->
<!-- 小程序隐私协议 -->
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
<!-- #endif -->
</view>
</template>
<script setup lang="ts">
import { ref, computed, reactive } from 'vue'
import { t } from '@/locale'
import useMemberStore from '@/stores/member'
import { img, redirect,mobileConceal } from '@/utils/common'
import { modifyMember } from '@/app/api/member'
import { fetchBase64Image, uploadImage } from '@/app/api/system'
const memberStore = useMemberStore()
const info = computed(() => memberStore.info)
const wxPrivacyPopupRef:any = ref(null)
//
const checkWxPrivacy = ()=>{
wxPrivacyPopupRef.value.proactive();
}
/**
* 修改昵称
*/
const updateNickname = reactive({
modal: false,
value: info.nickname || ''
})
const bindNickname = (e) => {
updateNickname.value = e.detail.value
}
const updateNicknameConfirm = () => {
if (uni.$u.test.isEmpty(updateNickname.value)) { uni.showToast({ title: t('nicknamePlaceholder'), icon: 'none' }); return }
modifyMember({
field: 'nickname',
value: updateNickname.value
}).then(res => {
memberStore.info.nickname = updateNickname.value
updateNickname.modal = false
})
}
/**
* 修改性别
*/
const sexSheetShow = ref(false)
const sexList = computed(() => {
return [
{ name: t('man'), value: 1 },
{ name: t('woman'), value: 2 }
]
})
const updateSex = (e) => {
modifyMember({
field: 'sex',
value: e.value
}).then(res => {
memberStore.info.sex_name = e.name
})
}
/**
* 修改用户头像
*/
const onChooseAvatar = (e) => {
uni.getFileSystemManager().readFile({
filePath: e.detail.avatarUrl, //
encoding: 'base64', //
success: res => {
fetchBase64Image({ content: res.data }).then(uploadRes => {
modifyMember({
field: 'headimg',
value: uploadRes.data.url
}).then(res => {
memberStore.info.headimg = uploadRes.data.url
})
})
}
})
}
const afterRead = (event) => {
uploadImage({
filePath: event.file.url,
name: 'file'
}).then(res => {
modifyMember({
field: 'headimg',
value: res.data.url
}).then(() => {
memberStore.info.headimg = res.data.url
})
}).catch(() => {
})
}
/**
* 编辑生日
*/
const birthdayPicker = ref(false)
const updateBirthday = (e) => {
modifyMember({
field: 'birthday',
value: uni.$u.date(e.value, 'yyyy-mm-dd')
}).then(() => {
memberStore.info.birthday = uni.$u.date(e.value || e.value + 1, 'yyyy-mm-dd')
birthdayPicker.value = false
})
}
const formatDate=(date:any)=>{
return date ? uni.$u.date(new Date(date), 'yyyy-mm-dd') : ''
}
</script>
<style lang="scss" scoped>
page {
background: var(--page-bg-color);
}
:deep(.u-upload__wrap ){
>view{
justify-content: center;
align-items: center;
}
}
:deep(.u-cell-group__wrapper) {
.u-cell__body {
padding-left: 0;
padding-right: 0;
}
.u-cell {
&:last-child .u-line {
display: none;
}
}
}
:deep(button, button:after) {
border: none;
}
</style>
<style lang="scss">
.personal-wrap .u-cell--clickable{
background-color: transparent;
}
</style>

View File

@ -0,0 +1,170 @@
<template>
<view class="bg-[#F6F6F6] min-h-[100vh]" :style="themeColor()">
<template v-if="!loading">
<view class="w-full bg-[#F6F6F6]">
<view class="pb-[210rpx] relative" :style="headerStyle">
<!-- #ifdef MP-WEIXIN -->
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header"/>
<!-- #endif -->
<view class="text-[70rpx] leading-[98rpx] text-[#fff] pl-[60rpx] font-600 pt-[77rpx]">{{pointInfo.point||0}}</view>
<view class="flex items-center pl-[60rpx]">
<image class="h-[36rpx] w-[36rpx]" :src="img('static/resource/images/member/point/icon.png')" mode="heightFix" />
<view class="text-[26rpx] leading-[36rpx] text-[#fff] ml-[10rpx]">我的积分</view>
</view>
<view class="flex items-center absolute right-0 px-[14rpx] bg-color rounded-l-[35rpx] text-[#fff] text-[24rpx] h-[50rpx] z-10" :style="{top: topStyle}" @click="toLink('/app/pages/member/point_detail')">
<text class="nc-iconfont nc-icon-jifenduihuanV6xx1 text-[28rpx] text-[#fff] mr-[10rpx]"></text>
<text class="text-[24rpx]">积分明细</text>
</view>
</view>
<view class="sidebar-marign flex flex-col mt-[-178rpx] relative">
<view class="w-[322rpx] h-[80rpx] leading-[80rpx] text-[26rpx] text-[#333] font-bold box-border pl-[30rpx]"
:style="{backgroundImage: 'url(' + img('static/resource/images/member/point/top_bg.png') + ') ',backgroundSize: '100% 100%',backgroundRepeat: 'no-repeat'}">
积分详情
</view>
<view class="flex items-center px-[30rpx] rounded-[16rpx] !rounded-tl-none bg-[#fff] h-[173rpx] box-border">
<view class="w-[196rpx] flex-shrink-0 text-center">
<view class="text-[#333] text-[42rpx] leading-[59rpx] font-bold">{{pointInfo.point_get||0}}</view>
<view class="mt-[8rpx] text-[#666] text-[26rpx] leading-[36rpx] font-400">累计积分</view>
</view>
<view class="w-[1rpx] h-[50rpx] flex-shrink-0 bg-[#EBEBEB] mx-[10rpx]"></view>
<view class="w-[196rpx] flex-shrink-0 text-center">
<view class="text-[#333] text-[42rpx] leading-[59rpx] font-bold">{{pointInfo.use||0}}</view>
<view class="mt-[8rpx] text-[#666] text-[26rpx] leading-[36rpx] font-400">累计消费</view>
</view>
<view class="w-[1rpx] h-[50rpx] flex-shrink-0 bg-[#EBEBEB] mx-[10rpx]"></view>
<view class="w-[196rpx] min-w-[209.33rpx] flex-shrink-0 text-center">
<view class="text-[#333] text-[42rpx] leading-[59rpx] font-bold">{{pointInfo.point||0}}</view>
<view class="mt-[8rpx] text-[#666] text-[26rpx] leading-[36rpx] font-400">可用积分</view>
</view>
</view>
</view>
</view>
<view class="mt-[20rpx] sidebar-marign p-[30rpx] pb-[70rpx] box-border rounded-[16rpx] bg-[#fff]">
<view class="text-[32rpx] leading-[45rpx] font-bold">热门活动</view>
<view class="mt-[50rpx] flex justify-between">
<view class="w-[200rpx] h-[253rpx] box-border pt-[69rpx] relative text-center"
:style="{backgroundImage: 'url(' + img('static/resource/images/member/point/activity_1.png') + ') ',
backgroundSize: '100% 100%',
backgroundRepeat: 'no-repeat'}">
<image class="h-[78rpx] w-[78rpx] absolute left-[65rpx] top-[-21rpx]" :src="img('static/resource/images/member/point/activity_icon_1.png')" mode="heightFix" />
<view class="text-[28rpx] leading-[39rpx] text-[#333]">每日赚积分</view>
<view class="mt-[10rpx] text-[24rpx] leading-[34rpx] text-[#999] font-500">每日签到</view>
<view class="w-full flex justify-center mt-[20rpx]">
<button class="h-[40rpx] !m-0 rounded-[40rpx] text-[26rpx] leading-[40rpx] !text-[#fff] !" shape="circle"
:style="{background: 'linear-gradient( 94deg, #FB7939 0%, #FE120E 99%), #EF000C'}" @click="toLink('/app/pages/member/sign_in')">去签到</button>
</view>
</view>
<view class="w-[200rpx] h-[253rpx] box-border pt-[69rpx] relative text-center"
:style="{backgroundImage: 'url(' + img('static/resource/images/member/point/activity_2.png') + ') ',
backgroundSize: '100% 100%',
backgroundRepeat: 'no-repeat'}">
<image class="h-[78rpx] w-[78rpx] absolute left-[65rpx] top-[-21rpx]" :src="img('static/resource/images/member/point/activity_icon_2.png')" mode="heightFix" />
<view class="text-[28rpx] leading-[39rpx] text-[#333]">积分当钱花</view>
<view class="mt-[10rpx] text-[24rpx] leading-[34rpx] text-[#999] font-500">抵扣部分费用</view>
<view class="w-full flex justify-center mt-[20rpx]">
<button class="h-[40rpx] !m-0 rounded-[40rpx] text-[26rpx] leading-[40rpx] !text-[#fff] !" shape="circle"
:style="{background: 'linear-gradient( 94deg, #FB7939 0%, #FE120E 99%), #EF000C'}" @click="toLink('/addon/shop/pages/point/index')">去兑换</button>
</view>
</view>
<view class="w-[200rpx] h-[253rpx] box-border pt-[69rpx] relative text-center"
:style="{backgroundImage: 'url(' + img('static/resource/images/member/point/activity_3.png') + ') ',
backgroundSize: '100% 100%',
backgroundRepeat: 'no-repeat'}">
<image class="h-[78rpx] w-[78rpx] absolute left-[65rpx] top-[-21rpx]" :src="img('static/resource/images/member/point/icon.png')" mode="heightFix" />
<view class="text-[28rpx] leading-[39rpx] text-[#333]">购物返积分</view>
<view class="mt-[10rpx] text-[24rpx] leading-[34rpx] text-[#999] font-500">下单得积分</view>
<view class="w-full flex justify-center mt-[20rpx]">
<button class="h-[40rpx] !m-0 rounded-[40rpx] text-[26rpx] leading-[40rpx] !text-[#fff] !" shape="circle"
:style="{background: 'linear-gradient( 94deg, #FB7939 0%, #FE120E 99%), #EF000C'}" @click="toLink('/addon/shop/pages/goods/list')">去逛逛</button>
</view>
</view>
</view>
</view>
<view class="mt-[20rpx] mx-[30rpx] p-[30rpx] box-border rounded-[16rpx] bg-[#fff]" v-if="pointList.length">
<view class="flex justify-between items-center">
<view class="text-[32rpx] leading-[45rpx] font-bold">做任务领积分</view>
<!-- <view class="flex items-center text-[#666]">
<text class=" text-[26rpx] leading-[36rpx] mr-[10rpx]">更多</text>
<text class=" text-[18rpx] leading-[36rpx] iconfont iconxiayibu1 text-[#999]"></text>
</view> -->
</view>
<block v-for="(item,index) in pointList">
<view class="flex items-center justify-between mt-[30rpx]">
<view class="flex items-center flex-1">
<image class="h-[62rpx] w-[62rpx]" :src="img(item.icon||'')" mode="heightFix" />
<view class="flex flex-col ml-[20rpx]">
<view class="flex">
<text class="text-[28rpx] leading-[39rpx]">{{item.title}}</text>
<!-- <image class="h-[28rpx] w-[28rpx] ml-[10rpx] mr-[6rpx]" :src="img('static/resource/images/member/point/icon.png')" mode="heightFix" /> -->
<!-- <text class="text-[#EF000C] text-[28rpx] leading-[39rpx]">+10</text> -->
</view>
<view class="mt-[10rpx] text-[#999] text-[24rpx] leading-[34rpx] font-400">{{item.desc}}</view>
</view>
</view>
<button v-if="item.button" class="h-[56rpx] !m-0 rounded-[40rpx] text-[26rpx] leading-[56rpx] !text-[#fff] !" shape="circle"
:style="{background: 'linear-gradient( 94deg, #FB7939 0%, #FE120E 99%), #EF000C'}" @click="toLink(item.button.wap_redirect)">{{item.button.text}}</button>
</view>
</block>
</view>
</template>
<u-loading-page bg-color="rgb(248,248,248)" :loading="loading" loadingText="" fontSize="16" color="#303133"></u-loading-page>
</view>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { t } from '@/locale'
import { redirect, img,pxToRpx } from '@/utils/common';
import { getMemberAccountPointcount,getTaskPoint } from '@/app/api/member';
import { topTabar } from '@/utils/topTabbar'
/********* 自定义头部 - start ***********/
const topTabarObj = topTabar()
let param = topTabarObj.setTopTabbarParam({title:'我的积分'})
/********* 自定义头部 - end ***********/
//
let menuButtonInfo:any = {};
// (API)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
const headerStyle = computed(()=>{
return {
backgroundImage: 'url(' + img('static/resource/images/member/point/point_bg.png') + ') ',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'bottom',
// paddingTop:Object.keys(menuButtonInfo).length?(Number(menuButtonInfo.height) * 2 + menuButtonInfo.top * 2 + 77)+'rpx':'77rpx',
}
})
const topStyle = computed(() => {
let style = ''
style = Object.keys(menuButtonInfo).length?(pxToRpx(Number(menuButtonInfo.height)) + pxToRpx(menuButtonInfo.top) + 38) + 'rpx;':'38rpx'
return style
})
//
const pointInfo = ref({});
//
const pointList = ref([]);
const loading = ref(true)
onLoad(async()=>{
let pointInfoRes :any = await getMemberAccountPointcount()
let pointListRes :any = await getTaskPoint()
pointInfo.value = pointInfoRes.data
pointList.value = pointListRes.data
loading.value = false
})
const toLink=(url='',param={})=>{
redirect({ url, param })
}
</script>
<style lang="scss" scoped>
.bg-color{
background-color: rgba(102,102,102,0.4);
}
</style>

View File

@ -0,0 +1,165 @@
<template>
<view class="bg-[#F6F6F6] min-h-[100vh]" :style="themeColor()">
<view class="fixed left-0 right-0 top-0 z-10085">
<view class="bg-[#fff] px-[var(--sidebar-m)] py-[14rpx] relative z-10084">
<view class="flex items-center h-[60rpx] bg-[#F8F9FD] rounded-[30rpx] px-[20rpx]">
<view class="flex-1 text-[26rpx] leading-[60rpx] text-[#8288A2]" :class="{'!text-[#333]':from_type}" @click="typePopup = true">{{from_type_name || '请选择来源用途'}}</view>
<text class="nc-iconfont nc-icon-shangV6xx-1 text-[32rpx] ml-[18rpx] !text-[#626779]" v-if="typePopup" @click="typePopup = false"></text>
<text class="nc-iconfont nc-icon-xiaV6xx text-[32rpx] ml-[18rpx] !text-[#626779]" v-else @click="typePopup = true"></text>
</view>
</view>
<u-popup :show="typePopup" mode="top" @close="typePopup = false" class="type-class">
<view @touchmove.prevent.stop class="py-[22rpx]">
<view class="leading-[80rpx] text-[26rpx] text-[#333] px-[50rpx]" :class="{'bg-[#FDF8F8] !text-primary font-500' : from_type == ''}" @click="searchTypeFn()">全部</view>
<view class="leading-[80rpx] text-[26rpx] text-[#333] px-[50rpx]" :class="{'bg-[#FDF8F8] !text-primary font-500' : from_type == index}" v-for="(item,index) in pointType" @click="searchTypeFn(index,item)">{{ item.name }}</view>
</view>
</u-popup>
<view class="px-[var(--sidebar-m)] pt-[30rpx] pb-[20rpx] flex items-center justify-between bg-[#F6F6F6]">
<view class="flex items-center">
<view class="px-[20rpx] py-[6rpx] bg-[#fff] rounded-[30rpx] text-[26rpx] leading-[36rpx] mr-[20rpx] text-[#333]" :class="{'!text-[var(--primary-color)] font-500':amount_type == item.status}" v-for="(item,index) in typeList" :key="index" @click="loadTypeFn(item.status)">{{ item.name }}</view>
</view>
<view class="flex items-center" @click="handleSelect">
<view class="text-[26rpx] text-[#333] mr-[10rpx]">日期</view>
<view class="nc-iconfont nc-icon-riliV6xx !text-[28rpx] leading-[36rpx]"></view>
</view>
</view>
</view>
<mescroll-body ref="mescrollRef" @init="mescrollInit" :down="{ use: false }" @up="getPointListFn" top="186rpx">
<view v-for="(item,index) in pointList" :key="index" class="bg-white sidebar-marign rounded-[16rpx] px-[30rpx] mb-[20rpx] py-[12rpx]">
<view class="flex justify-between items-center">
<view class="text-[20rpx] text-[#333] font-500">
<text>{{item.month_info.year}}</text>
<text class="text-[36rpx] font-bold ml-[10rpx] mr-[4rpx]">{{item.month_info.month}}</text>
<text></text>
</view>
<view>
<text class="nc-iconfont nc-icon-xiaV6xx !text-[32rpx] text-[#626779]" v-if="item.flag" @click="item.flag = false"></text>
<text class="nc-iconfont nc-icon-shangV6xx-1 !text-[32rpx] text-[#626779]" v-else @click="item.flag = true"></text>
</view>
</view>
<view v-show="item.flag">
<block v-for="(subItem,subIndex) in item.month_data" :key="subItem.id">
<view class="flex items-center ">
<view class="w-[60rpx] h-[60rpx]">
<image v-if="subItem.account_data > 0" :src="img('static/resource/images/member/point/detail/point_add.png')" class="w-[60rpx] h-[60rpx]"></image>
<image v-else :src="img('static/resource/images/member/point/detail/point_min.png')" class="w-[60rpx] h-[60rpx]"></image>
</view>
<view class="flex-1 flex items-center ml-[20rpx] box-border py-[20rpx] border-0" :class="{'border-solid border-t-[2rpx] border-[#F0F2F8]' : subIndex}">
<view class="flex-1">
<view class="text-[26rpx] font-500 leading-[36rpx] text-[#333]">{{subItem.from_type_name}}</view>
<view class="text-[24rpx] text-[#8288A2] leading-[34rpx] mt-[4rpx]">{{subItem.create_time}}</view>
</view>
<view class="text-[36rpx] font-500 text-[#03B521]" :class="{ '!text-primary' : subItem.account_data > 0 }">{{subItem.account_data > 0 ? '+' + subItem.account_data : subItem.account_data}}</view>
</view>
</view>
</block>
</view>
</view>
<view class="mx-[30rpx] rounded-[16rpx] noData flex items-center justify-center" v-if="!pointList.length && loading">
<mescroll-empty :option="{tip : '暂无积分明细'}"></mescroll-empty>
</view>
</mescroll-body>
<!-- 时间选择 -->
<select-date ref="selectDateRef" @confirm="confirmFn" />
</view>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { t } from '@/locale'
import { redirect, img } from '@/utils/common';
import { getPointList, getPointType } from '@/app/api/member';
import MescrollBody from '@/components/mescroll/mescroll-body/mescroll-body.vue';
import MescrollEmpty from '@/components/mescroll/mescroll-empty/mescroll-empty.vue';
import useMescroll from '@/components/mescroll/hooks/useMescroll.js';
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app';
import selectDate from '@/components/select-date/select-date.vue';
const { mescrollInit, downCallback, getMescroll } = useMescroll(onPageScroll, onReachBottom);
const from_type = ref('');
const from_type_name = ref('')
const amount_type = ref('all');
const create_time = ref([])
const pointList = ref<Array<any>>([]);
const mescrollRef = ref(null);
const loading = ref<boolean>(false);
const typeList = ref([
{name:'全部',status:'all'},
{name:'收入',status:'income'},
{name:'支出',status:'disburse'}
])
const getPointListFn = (mescroll)=> {
let data = {
page: mescroll.num,
page_size: mescroll.size,
from_type:from_type.value,
amount_type:amount_type.value,
create_time: create_time.value
};
loading.value = false;
getPointList(data).then((res) => {
let newArr = res.data.data;
mescroll.endSuccess(newArr.length);
//
if (mescroll.num == 1) {
pointList.value = []; //
}
pointList.value = pointList.value.concat(newArr);
pointList.value = pointList.value.map(item =>{
item.flag = true
return item
})
loading.value = true;
}).catch(() => {
loading.value = true;
mescroll.endErr(); // ,
})
}
const pointType = ref({})
const getPointTypeFn = () =>{
getPointType('point').then(res =>{
pointType.value = res.data
})
}
getPointTypeFn()
//
const typePopup = ref(false)
const searchTypeFn = (index:string = '',item:any = {}) =>{
from_type.value = index
from_type_name.value = item.name
typePopup.value = false
pointList.value = [];
getMescroll().resetUpScroll();
}
//
const loadTypeFn = (type:string) =>{
amount_type.value = type;
pointList.value = [];
getMescroll().resetUpScroll();
}
//
const selectDateRef = ref()
const handleSelect = () =>{
selectDateRef.value.show = true
}
//
const confirmFn = (data:any) =>{
create_time.value = data;
pointList.value = []
getMescroll().resetUpScroll();
}
</script>
<style lang="scss" scoped>
:deep(.u-popup.type-class .u-transition) {
top: 88rpx !important;
box-shadow: 1rpx 1rpx 6rpx 2rpx rgba(184,201,212,0.25);
}
.noData{
height: calc(100vh - 206rpx - constant(safe-area-inset-bottom));
height: calc(100vh - 206rpx - env(safe-area-inset-bottom));
}
</style>

View File

@ -0,0 +1,488 @@
<template>
<view :style="themeColor()">
<view class="bg-[#F6F6F6] min-h-screen overflow-hidden" v-if="Object.values(info).length">
<view v-if="info.is_use">
<view class="sigin-header">
<!-- #ifdef MP-WEIXIN -->
<view class="flex items-center absolute right-0 px-[14rpx] bg-color rounded-l-[35rpx] text-[#fff] text-[24rpx] h-[40rpx] z-10" :style="{top: topStyle}" @click="signPopup = true">
<text class="nc-iconfont nc-icon-meiriqiandaoV6xx text-[28rpx] text-[#fff] mr-[6rpx]"></text>
<text class="text-[24rpx]">签到规则</text>
</view>
<view :style="{height: headStyle, backgroundImage: 'url(' + img('static/resource/images/app/sigin_uniapp.png') + ')',backgroundSize: '100% 100%', backgroundRepeat: 'no-repeat'}">
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view v-if="info.rule_explain" class="flex items-center absolute top-[44rpx] right-0 px-[14rpx] bg-color rounded-l-[35rpx] text-[#fff] text-[24rpx] h-[40rpx] z-10" @click="signPopup = true">
<text class="nc-iconfont nc-icon-meiriqiandaoV6xx text-[28rpx] text-[#fff] mr-[6rpx]"></text>
<text class="text-[24rpx]">签到规则</text>
</view>
<view class="h-[382rpx]" :style="{ backgroundImage: 'url(' + img('static/resource/images/app/sigin_h5.png') + ')',backgroundSize: '100%', backgroundRepeat: 'no-repeat'}">
</view>
<!-- #endif -->
</view>
<view>
<view class="sidebar-marign bg-[#fff] rounded-[16rpx] -mt-[85rpx]">
<view class="card-template">
<view class=" mb-[30rpx] flex justify-between items-center" v-if="flag">
<view class="flex items-center">
<text class="iconfont iconshangyibu text-[#333] text-[24rpx]" @click="changeMonth('prev')"></text>
<view class="mx-[30rpx] font-bold text-[32rpx] text-[#333] leading-[45rpx]">{{ state.curYear }}{{ state.curMonth+1 }}</view>
<text class="iconfont iconxiayibu1 text-[#333] text-[24rpx]" @click="changeMonth('next')"></text>
</view>
<view class="flex items-center">
<text class="nc-iconfont nc-icon-shangV6xx-1 text-[#626779] text-[26rpx]" @click="handleChange"></text>
</view>
</view>
<view class="mb-[30rpx] flex justify-between items-center" v-else>
<view class="flex items-center">
<view class="font-500 text-[32rpx] text-[#333] leading-[45rpx]">已连续签到<text class="text-[#EF000C] mx-[4rpx]">{{ info.days }}</text></view>
</view>
<text class="nc-iconfont nc-icon-xiaV6xx text-[#626779] text-[26rpx]" v-if="!flag" @click="flag = !flag"></text>
</view>
<view class="relative z-9 bg-[#fff] rounded-[18rpx]">
<view>
<view class="flex items-center justify-between text-[#626779] text-[24rpx] mb-[16rpx]">
<text class="w-[14.28%] leading-[36rpx] text-center">周一</text>
<text class="w-[14.28%] leading-[36rpx] text-center">周二</text>
<text class="w-[14.28%] leading-[36rpx] text-center">周三</text>
<text class="w-[14.28%] leading-[36rpx] text-center">周四</text>
<text class="w-[14.28%] leading-[36rpx] text-center">周五</text>
<text class="w-[14.28%] leading-[36rpx] text-center">周六</text>
<text class="w-[14.28%] leading-[36rpx] text-center">周日</text>
</view>
<view class="flex flex-wrap items-center justify-start" v-if="!flag">
<block v-for="(item,index) in state.weekCount" :key="index">
<view class="w-[14.28%] flex flex-col justify-center items-center">
<view v-if="filteredDate(item)" class="w-[74rpx] h-[92rpx] bg-[#F1F2F5] text-[#626779] box-border py-[10rpx] rounded-[8rpx] flex flex-col items-center" :class="{'sigin-bg !text-[#fff]': isVerDate(item),'!bg-[#F6FAFF] border-[1rpx] border-[#F0F4FA] border-solid': !isVerDate(item) && item < state.curDate && (state.curMonth + 1) == (new Date().getMonth() + 1) ,'mb-[20rpx]':isCurrentDate(item),'mb-[30rpx]':!isCurrentDate(item)}" @click="getDayPackFn(item)">
<text class="text-[24rpx] leading-[28rpx] mb-[6rpx]">{{ filteredDate(item) }}</text>
<view v-if="filteredDate(item)" class="flex items-center justufy-center">
<image v-if="isPackDate(item)" :src="img('static/resource/images/app/package.png')" class="w-[40rpx] h-[40rpx]"></image>
<image v-else-if="isVerDate(item)" :src="img('static/resource/images/app/hassigin.png')" class="w-[34rpx] h-[34rpx]"></image>
<template v-else>
<image v-if="!isVerDate(item) && item < state.curDate && (state.curMonth + 1) == (new Date().getMonth() + 1)" :src="img('static/resource/images/app/nosigin.png')" class="w-[34rpx] h-[34rpx]"></image>
<image v-else :src="img('static/resource/images/app/nosigin1.png')" class="w-[34rpx] h-[34rpx]"></image>
</template>
</view>
</view>
<view class="w-[10rpx] h-[10rpx] rounded-[50%] bg-[#FF5527]" v-if="isCurrentDate(item)"></view>
</view>
</block>
</view>
<view class="flex flex-wrap items-center justify-start" v-else>
<block v-for="(item,index) in state.dataCount" >
<view class="w-[14.28%] flex flex-col justify-center items-center mb-[30rpx]">
<view v-if="filteredDate(item)" class="w-[74rpx] h-[92rpx] bg-[#F6FAFF] text-[#626779] box-border py-[10rpx] rounded-[8rpx] flex flex-col items-center" :class="{'sigin-bg !text-[#fff]': isVerDate(item) && active ,'!bg-[#FDFDFD] border-[1rpx] border-[#F0F4FA] border-solid': !isVerDate(item) && item < state.curDate && (state.curMonth + 1) == (new Date().getMonth() + 1) && state.curYear == new Date().getFullYear() ,'mb-[20rpx]':isCurrentDate(item),'mb-[30rpx]':!isCurrentDate(item)}" @click="getDayPackFn(item)">
<text class="text-[24rpx] leading-[28rpx] mb-[6rpx]">{{ filteredDate(item) }}</text>
<view v-if="filteredDate(item)" class="flex items-center justufy-center">
<image v-if="isPackDate(item)" :src="img('static/resource/images/app/package.png')" class="w-[40rpx] h-[40rpx]"></image>
<image v-else-if="isVerDate(item) && active " :src="img('static/resource/images/app/hassigin.png')" class="w-[34rpx] h-[34rpx]"></image>
<template v-else>
<image v-if="!isVerDate(item) && item < state.curDate && (state.curMonth + 1) == (new Date().getMonth() + 1)" :src="img('static/resource/images/app/nosigin.png')" class="w-[34rpx] h-[34rpx]"></image>
<image v-else :src="img('static/resource/images/app/nosigin1.png')" class="w-[34rpx] h-[34rpx]"></image>
</template>
</view>
</view>
<view class="w-[10rpx] h-[10rpx] rounded-[50%] bg-[#FF5527]" v-if="isCurrentDate(item)"></view>
</view>
</block>
</view>
<view class="mt-[40rpx] flex justify-center" v-if="state.curMonth + 1 == (new Date().getMonth() + 1) && state.curYear == new Date().getFullYear() ">
<button v-if="!info.is_sign" class="rounded-[40rpx] !bg-transparent" :style="{width:'490rpx',height:'88rpx',border:'none', color:'#fff', fontSize:'32rpx',lineHeight:'84rpx',backgroundImage: `url(${img('static/resource/images/app/button_bg2.png')})`,backgroundSize: '100%', backgroundRepeat: 'no-repeat'}" shape="circle" @click="setSignFn">
<text class="nc-iconfont nc-icon-meiriqiandaoV6xx text-[32rpx] text-[#fff] mr-[8rpx]"></text>
<text>立即签到</text>
</button>
<button v-else class="rounded-[40rpx] !bg-transparent" :style="{width:'490rpx',height:'88rpx',border:'none',color:'#fff', fontSize:'32rpx',lineHeight:'84rpx',backgroundImage: `url(${img('static/resource/images/app/button_bg1.png')})`,backgroundSize: '100%', backgroundRepeat: 'no-repeat'}" shape="circle">
<text class="nc-iconfont nc-icon-meiriqiandaoV6xx text-[32rpx] text-[#fff] mr-[8rpx]"></text>
<text>已签到</text>
</button>
</view>
</view>
</view>
</view>
</view>
<view class="mt-[20rpx] mb-[30rpx] sidebar-marign card-template" v-if="info && info.continue_award && Object.keys(info.continue_award).length">
<view class="mb-[30rpx] flex items-center">
<view class="font-500 text-[32rpx] text-[#333] leading-[44rpx]">签到奖励</view>
<!-- <view class="text-[#666] text-[26rpx] leading-[30rpx]">
<text>签到记录</text>
<image :src="img('static/resource/images/app/more.png')" class="w-[12rpx] h-[18rpx] ml-[8rpx]"></image>
</view> -->
</view>
<view>
<view v-for="(item,index) in info.continue_award" :key="index" class="flex items-center border-box" :class="{'mt-[40rpx]':index}">
<view class="w-[90rpx] h-[90rpx] rounded-[50%] bg-[#E7F6FF] flex items-center justify-center flex-shrink-0" v-if="(index + 1) % 4 == 1">
<image :src="img('static/resource/images/app/icon_02.png')" class="w-[40rpx] h-[40rpx]"></image>
</view>
<view class="w-[90rpx] h-[90rpx] rounded-[50%] bg-[#ffefef] flex items-center justify-center flex-shrink-0" v-else-if="(index + 1) % 4 == 2">
<image :src="img('static/resource/images/app/icon_03.png')" class="w-[40rpx] h-[40rpx]"></image>
</view>
<view class="w-[90rpx] h-[90rpx] rounded-[50%] bg-[#d3feeb] flex items-center justify-center flex-shrink-0" v-else-if="(index + 1) % 4 == 3">
<image :src="img('static/resource/images/app/icon_04.png')" class="w-[40rpx] h-[40rpx]"></image>
</view>
<view class="w-[90rpx] h-[90rpx] rounded-[50%] bg-[#ffeddd] flex items-center justify-center flex-shrink-0" v-else-if="(index + 1) % 4 == 0">
<image :src="img('static/resource/images/app/icon_05.png')" class="w-[40rpx] h-[40rpx]"></image>
</view>
<view class="flex-1 mx-[20rpx]">
<view class="font-400 text-[28rpx] text-[#333] leading-[38rpx] mb-[8rpx]">连续签到{{item.continue_sign}}</view>
<view class="flex flex-wrap" v-if="item.gift">
<view class="flex">
<image :src="img(item.gift.total.icon)" class="w-[30rpx] h-[30rpx] flex-shrink-0"></image>
<view class="text-[24rpx] ml-[8rpx] text-[#FF9000] leading-[34rpx] max-w-[330rpx]">{{item.gift.total.text}}</view>
</view>
</view>
</view>
<view class="flex-shrink-0">
<view v-if="Number(info.days) < Number(item.continue_sign) " class="w-[130rpx] h-[56rpx] text-center bg-[#FFECE9] rounded-[28rpx] font-400 text-[26rpx] text-[#FF343E] leading-[56rpx]">待完成</view>
<view v-else class="w-[130rpx] h-[56rpx] text-center rounded-[28rpx] font-400 text-[26rpx] text-[#fff] leading-[56rpx]" style="background:linear-gradient( 90deg, #FB7939 0%, #FE120E 100%) ;">已完成</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="h-[100vh] w-[100vw] flex justify-center items-center" v-else>
<!-- #ifdef MP-WEIXIN -->
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" class="top-header"/>
<!-- #endif -->
<u-empty text="签到未开启" width="347rpx" height="265rpx" :icon="img('static/resource/images/system/empty.png')"/>
</view>
<!-- 签到规则-->
<u-popup :show="signPopup" :round="16" mode="bottom" :closeable="true" @close="signPopup = false">
<view class="pt-[30rpx] px-[32rpx] pb-[20rpx]">
<view class="text-center text-[32rpx] font-700 text-[#323233]">签到规则</view>
<scroll-view :scroll-y="true" class="my-[20rpx] h-[360rpx] overflow-auto">
<block v-for="(item) in info.rule_explain.split('\n')">
<view class="text-[28rpx] leading-[40rpx] mb-[20rpx]">{{ item }}</view>
</block>
</scroll-view>
<view>
<button class="primary-btn-bg h-[66rpx] text-[#fff] text-[28rpx] border-[0] leading-[66rpx] rounded-[50rpx]" shape="circle" @click="signPopup = false">知道了</button>
</view>
</view>
</u-popup>
<!-- 签到奖励 -->
<u-popup :show="awardShow" class="award-popup" :customStyle="{backgroundColor:'transparent'}" @close="awardShow = false" mode="center" :round="5" :safeAreaInsetBottom="false" >
<view class="w-[550rpx] -mt-[124rpx]" v-if="Object.values(signAward).length">
<view class="flex justify-center">
<image :src="img('static/resource/images/app/award.png')" class="w-[484rpx] h-[480rpx] z-10" mode="aspectFill"></image>
</view>
<view class="-mt-[265rpx] bg-award rounded-[30rpx] pt-[100rpx] pb-[40rpx] mb-[50rpx] relative">
<view class="px-[32rpx]">
<view class="text-[48rpx] text-[#EF000C] font-bold leading-[68rpx] mb-[6rpx] text-center">{{ signAward.title }}</view>
<view class="text-[24rpx] text-[#F05F66] leading-[34rpx] text-center mb-[60rpx]" v-if="signAward.info">{{ signAward.info }}</view>
<view class="px-[68rpx] mb-[80rpx]">
<block v-for="(item,index) in signAward.awards">
<view class="flex items-center mb-[30rpx]" v-if="item.content">
<image :src="img(item.content.icon)" class="w-[42rpx] h-[42rpx]"></image>
<view class="ml-[20rpx] text-[28rpx] text-[#333] leading-[38rpx]">{{item.content.text }}</view>
</view>
</block>
</view>
<view class="flex justify-center relative z-30">
<view class="w-[370rpx] h-[88rpx] primary-btn-bg rounded-[50rpx] text-[#ffffff] text-center leading-[88rpx] text-[32rpx]" @click="awardShow = false">我知道了</view>
</view>
</view>
</view>
<view class="flex justify-center">
<text class="nc-iconfont nc-icon-cuohaoV6xx text-[#fff] text-[60rpx]" @click="awardShow = false"></text>
</view>
</view>
</u-popup>
<!-- 查看当日或连续签到奖励 -->
<u-popup :show="packShow" class="award-popup" :customStyle="{backgroundColor:'transparent'}" @close="packShow = false" mode="center" :round="5" :safeAreaInsetBottom="false">
<view class="w-[550rpx] -mt-[124rpx]" v-if="Object.values(packInfo).length">
<view class="flex justify-center">
<image :src="img('static/resource/images/app/award.png')" class="w-[484rpx] h-[480rpx] z-10" mode="aspectFill"></image>
</view>
<view class="-mt-[265rpx] bg-award rounded-[30rpx] pt-[100rpx] pb-[40rpx] mb-[50rpx] relative">
<view class="px-[32rpx]">
<view class="text-[48rpx] text-[#333] font-bold leading-[68rpx] mb-[6rpx] text-center relative z-20">
{{ packInfo.title }}
</view>
<view class="text-[24rpx] text-[#F05F66] leading-[34rpx] text-center mb-[60rpx]">{{ packInfo.info }}</view>
<view class="px-[68rpx] mb-[80rpx]">
<block v-for="(item,index) in packInfo.awards">
<template v-if="item.content">
<image :src="img(item.content.icon)" class="w-[42rpx] h-[42rpx]"></image>
<view class="ml-[20rpx] text-[28rpx] text-[#333] leading-[38rpx]">{{ item.content.text }}</view>
</template>
</block>
</view>
<view class="flex justify-center relative z-30">
<view class="w-[370rpx] h-[88rpx] border-[4rpx] border-[#EF000C] border-solid rounded-[50rpx] text-[#EF000C] text-center leading-[88rpx] text-[32rpx] font-500 box-border" @click="packShow = false">我知道了</view>
</view>
</view>
</view>
<view class="flex justify-center">
<text class="nc-iconfont nc-icon-cuohaoV6xx text-[#fff] text-[60rpx]" @click="packShow = false"></text>
</view>
</view>
</u-popup>
</view>
<u-loading-page bg-color="rgb(248,248,248)" :loading="loading" loadingText="" fontSize="16" color="#303133"></u-loading-page>
</view>
</template>
<script setup lang="ts">
import { reactive, ref, toRefs, toRaw, computed } from 'vue'
import { redirect, img,pxToRpx } from '@/utils/common'
import { onLoad } from '@dcloudio/uni-app'
import { getSignInfo,getSignConfig, setSign,getDayPack } from '@/app/api/member'
import useMemberStore from '@/stores/member'
import { topTabar } from '@/utils/topTabbar'
const state = reactive({
dataCount:[], //
weekCount:[], //7
curYear:0, //
curMonth:0, //
curDate:0, //
curWeek:0, //
signInList:[], //
packList:[] //
})
const week = reactive({
weekDay:0, //
week:0 //
})
const loading = ref(false)
const flag = ref(false)
const info = ref({}) //
const signPopup = ref(false)//
const signAward = ref({}) //
const awardShow = ref(false) //
const packShow = ref(false) //
const packInfo = ref({}) //
const active = ref(false)
let currentYear: any = null
let currentMonth: any = null
onLoad(() =>{
let date=new Date()
state.curYear=date.getFullYear()
state.curMonth=date.getMonth()
state.curDate=date.getDate()
state.curWeek=date.getDay()
if(state.curWeek==0) state.curWeek = 7
currentYear=toRaw(state.curYear)
currentMonth=toRaw(state.curMonth)
//
getDayCounts()
getWeekCounts()
getSignInfoFn({year:state.curYear,month:state.curMonth+1})
getSignConfigFn()
})
//
const getSignConfigFn = () =>{
loading.value = true
getSignConfig().then((res:any) =>{
info.value = res.data
if(!info.value.is_use){
topTabbarData = topTabarObj.setTopTabbarParam({title:'我的签到',topStatusBar:{textColor:'#333',bgColor:'#fff'}})
}
loading.value = false
})
}
//
const getSignInfoFn = (data:any) =>{
getSignInfo(data).then((res:any) =>{
state.signInList = []
state.packList = []
state.packList = res.data.period
if(res.data.length){
state.signInList = res.data.days.map((el:any) =>{
return Number(el)
})
}
active.value = true
})
}
//
const getDayCounts= () => {
let counts = new Date(state.curYear,state.curMonth+1,0).getDate()
//
let firstWeekDay = new Date(state.curYear,state.curMonth,1).getDay()
state.dataCount = []
for(let i=1;i<counts+firstWeekDay;i++){
let val=i-firstWeekDay+ 1
state.dataCount.push(val)
}
}
// 7
const getWeekCounts = () =>{
let now = `${state.curYear}-${state.curMonth+1 > 10 ? state.curMonth+1 : '0'+(state.curMonth+1)}-${state.curDate > 10 ? state.curDate : '0'+state.curDate }`
for (let i = state.curWeek - 1; i >= 0; i --) {
const day = new Date(now).getDate() - i
state.weekCount.push(day)
}
for (let i = 1; i <= 7 - state.curWeek; i++) {
const day = new Date(now).getDate() + i
state.weekCount.push(day)
}
}
//
const handleChange = () =>{
let nowDate = new Date().getMonth()
if(state.curMonth == nowDate){
flag.value = !flag.value
}else{
state.curMonth = new Date().getMonth()
state.curYear = new Date().getFullYear()
getSignInfoFn({year:state.curYear,month:state.curMonth+1})
flag.value = !flag.value
}
}
//
const changeMonth=(type: string)=>{
state.dataCount=[]
if(type == 'prev'){
state.curMonth--
if(state.curMonth < 0){
state.curMonth = 11
state.curYear--
}
week.weekDay = 1
active.value = false
}else{
state.curMonth++
if(state.curMonth > 11){
state.curMonth = 0
state.curYear++
}
week.weekDay = 1
active.value = false
}
let data = {year:state.curYear,month:state.curMonth + 1}
getSignInfoFn(data)
getDayCounts()
}
const memberStore = useMemberStore()
//
const setSignFn = () =>{
setSign().then(res =>{
if(Object.values(res.data).length){
signAward.value = res.data
//
let isShowInfo = 0
Object.values(signAward.value.awards).forEach((item,index)=>{
if(!item.content){
isShowInfo++;
}
})
if(isShowInfo == Object.values(signAward.value.awards).length){
signAward.value.info = "";
}
getSignInfoFn({year:state.curYear,month:state.curMonth+1})
getSignConfigFn()
memberStore.getMemberInfo()
awardShow.value = true
}
})
}
//
const curPickDay = ref(null)
const getDayPackFn = (date:number) =>{
let {curYear,curMonth}=toRefs(state)
let itemDate=`${curYear.value}-${(curMonth.value+1) < 10 ? '0' + (curMonth.value+1) : (curMonth.value+1)}-${date < 10 ? '0'+date : date}`
let flag = state.packList.some(el =>{
return el.day == itemDate
})
if(!flag) return
curPickDay.value = date
let obj = {
year: state.curYear,
month:state.curMonth + 1,
day: date
}
getDayPack(obj).then((res:any) => {
if(JSON.stringify(res.data) != '[]'){
packInfo.value = res.data
packShow.value = true
}
})
}
//
const isVerDate = (val:any) => {
return state.signInList.includes(val)
}
//
const isCurrentDate=(date)=>{
if(date> 0 && date <= state.dataCount.length){
if(date == state.curDate && currentYear == state.curYear && currentMonth == state.curMonth){
return true
}
}else{
return false
}
}
const isPackDate = (date:any) =>{
let {curYear,curMonth}=toRefs(state)
let itemDate=`${curYear.value}-${(curMonth.value+1) < 10 ? '0' + (curMonth.value+1) : (curMonth.value+1)}-${date < 10 ? '0'+date : date}`
let flag = state.packList.some((el:any) =>{
return el.day == itemDate && el.award
})
return flag
}
//
const filteredDate=(date :any)=>{
return date > 0 ? date : ''
}
//
let menuButtonInfo = {};
// (API)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
/********* 自定义头部 - start ***********/
const topTabarObj = topTabar()
let topTabbarData = topTabarObj.setTopTabbarParam({title:'我的签到'})
/********* 自定义头部 - end ***********/
const headStyle = computed(() => {
let style = pxToRpx(Number(menuButtonInfo.height) + menuButtonInfo.top + 8)+ 382 + 'rpx;'
return style
})
const topStyle = computed(() => {
let style = pxToRpx(Number(menuButtonInfo.height) + menuButtonInfo.top + 8)+38 + 'rpx;'
return style
})
</script>
<style lang="scss" scoped>
.bg-color{
background-color: rgba(102,102,102,0.4);
}
.sigin-bg{
background: linear-gradient( 90deg, #FFA359 0%, #FF5426 100%), #F2F2F2;
}
:deep(.award-popup .u-popup__content){
background-color: transparent;
}
.bg-award{
background: linear-gradient( 51deg, #FFFBFC 0%, #FFFCF9 59%, #FFE7E7 100%);
}
.bg-button{
background: linear-gradient( 180deg, #FFEAE1 0%, #FFCDD0 34%, #E0052C 100%);
border-radius: 40rpx;
}
</style>

View File

@ -0,0 +1,20 @@
<template>
<view class="w-screen h-screen bg-[#424040] text-right" :style="themeColor()">
<image :src="img('static/resource/images/pay/invite_friends_share.png')" mode="heightFix" class="pt-[30rpx] pr-[30rpx] h-[200rpx]"/>
<view class="text-white font-bold pt-[30rpx] pr-[30rpx]">点击右上角跳转到浏览器打开</view>
</view>
</template>
<script setup lang="ts">
import { img, isWeixinBrowser } from '@/utils/common'
import { onLoad } from '@dcloudio/uni-app'
onLoad((data : any) => {
if (!isWeixinBrowser() && data.alipay) {
uni.setStorageSync('paymenting', { trade_type: data.trade_type, trade_id: data.trade_id })
location.href = data.alipay
}
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,74 @@
<template>
<view :style="themeColor()">
<view class="w-screen h-screen flex flex-col items-center" v-if="payInfo">
<view class="flex-1 flex flex-col items-center w-full pt-[100rpx]">
<text class="iconfont text-2xl" :class="payInfo.status==2 ? 'text-primary iconduigou' : 'iconzhifushibai text-red'"></text>
<view class=" text-sm">{{ payInfo.status == 2 ? t('pay.paySuccess') : t('pay.payFail') }}</view>
<view class="text-xl font-bold pt-[30rpx]">
<text class="text-base">{{ t('currency') }}</text>
<text>{{ moneyFormat(payInfo.money) }}</text>
</view>
</view>
<view class="pb-[200rpx] w-[240rpx]">
<u-button type="primary" :text="payInfo.status == 2 ? t('complete') : t('close')" :plain="true" @click="complete"></u-button>
</view>
</view>
<u-modal :show="loading" :showCancelButton="true" :confirmText="t('pay.completePay')" :cancelText="t('pay.incompletePay')" @cancel="complete" confirmColor="var(--primary-color)">
<view class="py-[20rpx]">
<u-loading-icon :text="t('pay.getting')" textSize="16" mode="circle" :vertical="true"></u-loading-icon>
</view>
</u-modal>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getPayInfo as getPayInfoApi } from '@/app/api/pay'
import { t } from '@/locale'
import { redirect, moneyFormat } from '@/utils/common'
import { getFirstPage } from '@/utils/pages'
const payInfo = ref<AnyObject | null>(null)
const loading = ref(false)
let tradeType = ''
let tradeId = 0
let requestNum = 0
onLoad((data : any) => {
tradeType = data.trade_type
tradeId = data.trade_id
getPayInfo()
})
/**
* 获取支付信息
*/
const getPayInfo = () => {
getPayInfoApi(tradeType, tradeId).then((res: responseResult) => {
if (!uni.$u.test.isEmpty(res.data)) {
if (res.data.status == 1 && requestNum < 5) {
loading.value = true
requestNum++
setTimeout(() => {
getPayInfo()
}, 1000)
return
}
payInfo.value = res.data
loading.value = false
uni.setNavigationBarTitle({
title: payInfo.value.status == 2 ? t('pay.paySuccess') : t('pay.payFail')
})
}
})
}
const complete = () => {
const payReturn = decodeURIComponent(uni.getStorageSync('payReturn'))
if (payReturn) redirect({ url: payReturn, mode: 'reLaunch' })
else redirect({ url: getFirstPage(), param: { code: payInfo.value?.out_trade_no }, mode: 'reLaunch' })
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,86 @@
<template>
<view class="w-full h-screen box-border pt-[30rpx] bg-page setting-wrap" :style="themeColor()">
<view class="m-[30rpx] mt-0 bg-white rounded-md overflow-hidden px-[20rpx] py-[10rpx]">
<u-cell-group :border="false">
<u-cell :title="t('personalSettings')" :is-link="true" url="/app/pages/member/personal"></u-cell>
<u-cell :title="t('switchLang')" :is-link="true" :value="lang" @click="langSheetShow = true"></u-cell>
<u-cell :title="t('version')" :value="version"></u-cell>
</u-cell-group>
</view>
<view class="m-[30rpx] bg-white rounded-md overflow-hidden px-[20rpx] py-[10rpx]">
<u-cell-group :border="false">
<u-cell :title="t('userAgreement')" :is-link="true" url="/app/pages/auth/agreement?key=service"></u-cell>
<u-cell :title="t('privacyAgreement')" :is-link="true" url="/app/pages/auth/agreement?key=privacy"></u-cell>
</u-cell-group>
</view>
<view class="m-[30rpx] bg-white rounded-md overflow-hidden px-[20rpx]">
<u-cell-group :border="false">
<view class="text-center py-[20rpx] text-sm" @click="memberStore.logout(true)">{{ t('logout') }}</view>
</u-cell-group>
</view>
<u-action-sheet :actions="langList" :show="langSheetShow" :closeOnClickOverlay="true"
:safeAreaInsetBottom="true"
@close="langSheetShow = false" @select="switchLang"></u-action-sheet>
</view>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import useMemberStore from '@/stores/member'
import { t, language } from '@/locale'
import { currRoute, isWeixinBrowser } from '@/utils/common'
const memberStore = useMemberStore()
const version = ref(import.meta.env.VITE_APP_VERSION)
/**
* 支持的语言列表
*/
const langList = reactive({
'zh-Hans': { name: '简体中文', fontSize: '14', value: 'zh-Hans' },
'en': { name: 'English', fontSize: '14', value: 'en' }
})
const langSheetShow = ref(false)
//
const lang = computed(() => {
const lang = uni.getLocale()
return langList[lang].name
})
/**
* 切换语言
*/
const switchLang = (lang) => {
language.loadAllLocaleMessages('app', lang.value)
}
</script>
<style lang="scss" scoped>
page {
background: var(--page-bg-color);
}
:deep(.u-cell-group__wrapper) {
.u-cell__body {
padding-left: 0;
padding-right: 0;
}
.u-cell__value{
line-height: 1;
}
.u-cell {
&:last-child .u-line {
display: none;
}
}
}
</style>
<style>
.setting-wrap .u-cell--clickable{
background-color: transparent;
}
</style>

View File

@ -0,0 +1,81 @@
<template>
<view :style="themeColor()" class="bg-[#f8f8f8] min-h-[100vh] overflow-hidden">
<block v-if="!loading">
<view class="pt-[20rpx] sidebar-marign">
<view class="flex flex-col card-template">
<view class="flex" :class="{'mb-[20rpx]': verifyInfo.value.list.length-1 != index}" v-for="(item,index) in verifyInfo.value.list" :key="index">
<image class="w-[150rpx] h-[150rpx] rounded-[8rpx]" mode="aspectFill" v-if="item.cover" :src="img(item.cover)"></image>
<image class="w-[150rpx] h-[150rpx] rounded-[8rpx]" mode="aspectFill" v-else :src="img('addon/tourism/tourism/member/hotel.png')"></image>
<view class="flex flex-col flex-1 ml-[20rpx] py-[4rpx]">
<view class="leading-[1.3] text-[28rpx] multi-hidden">{{item.name}}</view>
<view class="self-end text-[#626779] text-[26rpx] mt-[20rpx]">x1</view>
</view>
</view>
</view>
<view class="flex flex-col card-template mt-[20rpx]">
<view class="text-[32rpx] text-[#333333] font-500 leading-[1.2] mb-[30rpx]">核销信息</view>
<view class="flex justify-between h-[36rpx] items-center">
<text class="text-[26rpx] text-[#626779]">核销类型</text>
<view class="text-[26rpx] text-[#333]">{{verifyInfo.type_name}}</view>
</view>
<view class="flex justify-between h-[36rpx] items-center mt-[20rpx]">
<text class="text-[26rpx] text-[#626779]">核销状态</text>
<view class="text-[26rpx] text-[#333]">已核销</view>
</view>
<view class="flex justify-between h-[36rpx] items-center mt-[20rpx]">
<text class="text-[26rpx] text-[#626779]">核销时间</text>
<view class="text-[#333333] text-[26rpx]">{{verifyInfo.create_time}}</view>
</view>
<view class="flex justify-between h-[36rpx] items-center mt-[20rpx]">
<text class="text-[26rpx] text-[#626779]">核销人员</text>
<view class="text-[#333333] text-[26rpx]">{{verifyInfo.member ? verifyInfo.member.nickname : '--'}}</view>
</view>
<view class="flex items-center h-[36rpx] justify-between mt-[20rpx]" v-for="(item,index) in verifyInfo.value.content.fixed">
<text class="text-[26rpx] text-[#626779]">{{item.title}}</text>
<view class="text-[26rpx] text-[#333]">{{item.value}}</view>
</view>
</view>
<view v-for="(item,index) in verifyInfo.value.content.diy" :key="index" class="card-template mt-[20rpx]">
<view class="text-[32rpx] text-[#333333] font-500 leading-[1.2] mb-[30rpx]">{{item.title}}</view>
<view class="flex items-center h-[36rpx] justify-between mt-[20rpx]" v-for="(subItem,subIndex) in item.list" :key="subIndex" :class="{'mt-30rpx' : subIndex == '0'}">
<text class="text-[26rpx] text-[#626779]">{{subItem.title}}</text>
<view class="text-[26rpx] text-[#333]">{{subItem.value}}</view>
</view>
</view>
</view>
</block>
<u-loading-page :loading="loading" loading-text="" loadingColor="var(--primary-color)" iconSize="35"></u-loading-page>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { onLoad,onShow } from '@dcloudio/uni-app'
import { img,redirect, getToken } from '@/utils/common';
import { getVerifyDetail } from '@/app/api/verify'
import { t } from '@/locale'
const loading = ref(true)
const code = ref('');
onLoad((option:any)=> {
if (option.code) code.value = option.code;
})
onShow(() => {
if(getToken()){
getVerifyDetailFn();
}
})
const verifyInfo = ref({})
const getVerifyDetailFn = ()=>{
loading.value = true;
getVerifyDetail(code.value).then((res:any) =>{
verifyInfo.value = res.data;
loading.value = false;
})
}
</script>

View File

@ -0,0 +1,216 @@
<template>
<view :style="themeColor()">
<view class="w-[100vw] min-h-[100vh] bg-[#f8f8f8]" v-if="!loading">
<view class="w-full bg-[#fff] verify-box h-[760rpx]">
<view class="text-[var(--primary-color)] fixed top-[40rpx] right-[30rpx] flex items-center" @click="redirect({url:'/app/pages/verify/record'})">
<image class="w-[26rpx] h-[28rpx]" :src="img('static/resource/images/verify/history.png')"/>
<text class="text-[26rpx] ml-[10rpx]">核销记录</text>
</view>
<view v-show="operationType == 'sweepCode'" class="flex flex-col items-center justify-center">
<view class="sweep-code flex items-center justify-center" @click="scanCode">
<image class="w-[354rpx] h-[354rpx]" :src="img('static/resource/images/verify/saoma.png')"/>
</view>
<view class="mt-[40rpx] text-[32rpx]">点击扫描二维码</view>
<view class="mt-[20rpx] text-[#8288A2] text-[26rpx] font-400 pb-[142rpx]">扫描二维码进行核销</view>
</view>
<view v-show="operationType == 'manualInput'">
<view class="flex pt-[126rpx] items-center justify-center">
<view class="flex justify-center items-center flex-col pr-[30rpx] w-[130rpx]">
<image class="w-[100rpx] h-[100rpx]" :src="img('static/resource/images/verify/shuruhexiaoma.png')"/>
<view class="text-[26rpx] h-[36rpx] leading-[36rpx] mt-[12rpx]">验证核销码</view>
</view>
<image class="w-[74rpx] h-[12rpx] mb-[50rpx]" :src="img('static/resource/images/verify/youjiantou.png')"/>
<view class="flex justify-center items-center flex-col pl-[30rpx] w-[130rpx]">
<image class="w-[100rpx] h-[100rpx]" :src="img('static/resource/images/verify/hexiao1.png')"/>
<view class="text-[26rpx] h-[36rpx] leading-[36rpx] mt-[12rpx]">核销</view>
</view>
</view>
<view class="mt-[50rpx]">
<view class="h-[90rpx] border-[2rpx] border-solid border-[#DCE0EF] rounded-[16rpx] box-border p-[20rpx] mx-[60rpx] flex items-center" >
<text class="nc-iconfont nc-icon-saotiaoxingmaV6xx text-[44rpx] text-[#EF000C]"></text>
<input type="text" placeholder="请输入核销码" class="h-[90rpx] border-none text-start ml-[30rpx] text-[28rpx] flex-1" placeholder-class="_placeholder" v-model="verify_code" :focus="isFocus" ref="input"/>
</view>
<view class="h-[88rpx] min-w-[630rpx] text-[#fff] flex items-center justify-center !text-[32rpx] save-btn rounded-[50rpx] h-[88rpx] mx-[60rpx] mt-[146rpx] relative z-1" @click="confirm">确认</view>
</view>
</view>
</view>
<view class="w-[630rpx] h-[100rpx] bg-[#fff] mx-[auto] mt-[220rpx] rounded-[90rpx] flex relative action-type-wrap">
<view class="relative w-[51%] pr-[50rpx] box-border rounded-[50rpx] z-0 flex flex-col items-center justify-center" @click="changeOperationType('sweepCode')" :class="{'xuanZhong1': operationType == 'sweepCode'}">
<text class="nc-iconfont nc-icon-saoyisaoV6xx !text-[40rpx]"></text>
<view class="text-[24rpx] leading-[1] mt-[10rpx]">扫码核销</view>
</view>
<view class="flex flex-col items-center flex-col w-[120rpx] h-[120rpx] bg-[#FF7354] rounded-[50%] absolute top-[-10rpx] left-[255rpx] heXiao text-white z-10 shrink-0">
<view class="nc-iconfont nc-icon-saotiaoxingmaV6xx ns-gradient-otherpages-member-balance-balance-rechange !text-[44rpx] mt-[19rpx]"></view>
<view class="text-[24rpx] mt-[8rpx] leading-[34rpx] h-[34rpx]">核销台</view>
</view>
<view class="relative w-[51%] pl-[50rpx] box-border rounded-[50rpx] z-0 flex flex-col items-center justify-center" :class="{'xuanZhong': operationType == 'manualInput'}" @click="changeOperationType('manualInput')">
<text class="iconfont iconVector-77 !text-[40rpx]"></text>
<view class="ml-[20rpx] text-[24rpx] leading-[1] mt-[10rpx]" @click="focus">手动输入</view>
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<!-- 小程序隐私协议 -->
<privacy-popup ref="privacyPopup"></privacy-popup>
<!-- #endif -->
</view>
<u-loading-page :loading="loading" loading-text="" loadingColor="var(--primary-color)" iconSize="35"></u-loading-page>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { img,redirect, isWeixinBrowser, getToken } from '@/utils/common';
import {onShow} from '@dcloudio/uni-app'
import { getVerifierInfo, getCheckVerifier } from '@/app/api/verify'
import { t } from '@/locale'
import wechat from '@/utils/wechat'
const operationType = ref('manualInput'); //
// #ifdef H5
operationType.value = 'manualInput';
// #endif
// #ifndef H5
operationType.value = 'sweepCode';
// #endif
const isFocus = ref(false)
const verify_code = ref('');
const loading = ref(true)
onShow(() => {
if(getToken()) checkIsVerifier();
})
//
const checkIsVerifier = () => {
getCheckVerifier().then((res:any) =>{
if(!res.data){
uni.showToast({
title: '非核销员无此权限',
icon: 'none'
});
setTimeout(() => {
if(getCurrentPages().length > 1){
uni.navigateBack({
delta: 1
});
}else{
redirect({
url: '/app/pages/member/index',
mode: 'reLaunch'
});
}
}, 1000);
}else{
loading.value = false;
}
})
}
const scanCode = () => {
// #ifdef MP
uni.scanCode({
onlyFromCamera: true,
success: res => {
if (res.errMsg == 'scanCode:ok') {
let code = res.result;
redirect({ url: '/app/pages/verify/verify', param: { code} })
} else {
uni.showToast({
title: res.errorMsg,
icon: 'none'
});
}
}
});
// #endif
// #ifdef H5
if (isWeixinBrowser()) {
wechat.init();
wechat.scanQRCode(res => {
if (res.resultStr) {
let code = res.resultStr;
redirect({ url: '/app/pages/verify/verify', param: { code} })
}
});
}
// #endif
}
let isLoading = false;
const confirm = () => {
var reg = /[\S]+/;
if (!reg.test(verify_code.value)) {
uni.showToast({
title: '请输入核销码',
icon: 'none'
});
return false;
}
if(isLoading) return false;
isLoading = true;
getVerifierInfo(verify_code.value).then((res:any) =>{
isLoading = false;
redirect({ url: '/app/pages/verify/verify', param: { code: verify_code.value} })
}).catch(() => {
isLoading = false;
})
}
const focus = () => {
isFocus.value = !isFocus.value;
}
const changeOperationType = (type: string) => {
// #ifdef H5
if (type == 'sweepCode' && !isWeixinBrowser()) {
uni.showToast({
title: 'H5端不支持扫码核销',
icon: 'none'
});
return;
}
// #endif
operationType.value = type;
}
</script>
<style lang="scss" scoped>
.action-type-wrap {
box-shadow: 0 6px 6px 0 rgba(0, 0, 0, 0.03), 0 4px 2px 0 rgba(0, 0, 0, 0.04);;
}
.heXiao{
background: linear-gradient( 180deg, #FF7354 0%, #FF020F 100%), #EF000C;
}
.xuanZhong{
background: linear-gradient( 270deg, #FFD1D1 0%, rgba(255,209,209,0.2) 100%), #FFFFFF;
color:#EF000C;
}
.xuanZhong1{
background: linear-gradient( 90deg, #FFD1D1 0%, rgba(255,209,209,0.2) 100%), #FFFFFF;
color:#EF000C;
}
.sweep-code {
width: 354rpx;
height: 354rpx;
box-shadow: 0 8px 8px 0 rgba(0, 0, 0, 0.03), 0 6px 3px 0 rgba(0, 0, 0, 0.02);
border-radius: 50%;
margin-top: 146rpx;
}
.verify-box {
border-bottom-left-radius: 400rpx 60rpx;
border-bottom-right-radius: 400rpx 60rpx;
}
.save-btn{
background: linear-gradient( 94deg, #FB7939 0%, #FE120E 99%), #EF000C;
}
._placeholder{
color: #8288A2;
font-size: 26rpx;
}
</style>

View File

@ -0,0 +1,129 @@
<template>
<view class="bg-[#f8f8f8] min-h-screen overflow-hidden" :style="themeColor()">
<view class="fixed left-0 right-0 top-0 z-99 bg-[#fff] px-[var(--sidebar-m)]">
<view class="py-[14rpx] flex items-center justify-between">
<view class="flex-1 flex items-center h-[60rpx] bg-[#F8F9FD] rounded-[30rpx] px-[20rpx] mr-[30rpx]">
<u-input class="flex-1" maxlength="50" v-model="keyword" @confirm="searchTypeFn()" placeholder="请输入搜索关键词" placeholderClass="text-[#8288A2] text-[24rpx]" fontSize="26rpx" clearable border="none"></u-input>
<text class="nc-iconfont nc-icon-sousuo-duanV6xx1 text-[32rpx] ml-[18rpx] !text-[#999]" @click="searchTypeFn()"></text>
</view>
<view class="nc-iconfont nc-icon-riliV6xx !text-[30rpx] leading-[36rpx]" @click="handleSelect"></view>
</view>
</view>
<mescroll-body ref="mescrollRef" top="108rpx" @init="mescrollInit" :down="{ use: false }" @up="geVerifyRecordFn">
<view class="sidebar-marign">
<block v-for="(item,index) in list" :key="item.id">
<view class="w-full flex flex-col mb-[20rpx] card-template" @click="toLink(item)">
<view class="flex items-center mb-[30rpx] leading-[1]">
<view class="nc-iconfont nc-icon-hexiaotaiV6xx !text-[26rpx] pr-[10rpx]"></view>
<text class="truncate text-[#333333] text-[26rpx]">核销码{{ item.code }}</text>
</view>
<view class="flex flex-1" v-for="(dataItem,dataIndex) in item.value.list" :key="dataIndex">
<u--image class="rounded-[8rpx] overflow-hidden" width="120rpx" height="120rpx" :src="img(dataItem.cover ? dataItem.cover : '')" model="aspectFill">
<template #error>
<image class="w-[120rpx] h-[120rpx] rounded-[8rpx] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
<view class="flex flex-col flex-1 justify-between ml-[20rpx] py-[4rpx]" >
<view class="leading-[1.3] multi-hidden text-[28rpx] text-[#333]">{{dataItem.name}}</view>
<view class="self-end text-[26rpx] mt-[10rpx] text-[#626779]">x1</view>
</view>
</view>
<view class="flex bg-[#F8F9FD] py-[20rpx] px-[20rpx] rounded-[12rpx] mt-[20rpx]">
<view class="flex-1">
<view class="text-[22rpx] text-[#8288A2] mb-[10rpx] leading-[30rpx]">核销时间</view>
<view class="text-[26rpx] text-[#333] leading-[36rpx]">{{ item.create_time }}</view>
</view>
<view class="flex-1">
<view class="text-[22rpx] text-[#8288A2] mb-[10rpx] leading-[30rpx]">核销员</view>
<view class="text-[26rpx] text-[#333] leading-[36rpx]">{{ item.member ? item.member.nickname : '--' }}</view>
</view>
</view>
</view>
</block>
</view>
<view class="mx-[30rpx] rounded-[16rpx] noData flex items-center justify-center" v-if="!list.length && loading">
<mescroll-empty :option="{tip : '暂无核销记录'}"></mescroll-empty>
</view>
</mescroll-body>
<!-- 时间选择 -->
<select-date ref="selectDateRef" @confirm="confirmFn" />
</view>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import MescrollBody from '@/components/mescroll/mescroll-body/mescroll-body.vue'
import MescrollEmpty from '@/components/mescroll/mescroll-empty/mescroll-empty.vue'
import useMescroll from '@/components/mescroll/hooks/useMescroll.js'
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import { getVerifyRecords } from '@/app/api/verify'
import { img, redirect } from '@/utils/common'
import selectDate from '@/components/select-date/select-date.vue';
const keyword = ref<string>('')
const create_time = ref([])
const list = ref<Array<Object>>([])
const loading = ref<boolean>(false)
const { mescrollInit, downCallback, getMescroll } = useMescroll(onPageScroll, onReachBottom)
const geVerifyRecordFn = (mescroll) => {
loading.value = false;
let data : object = {
page: mescroll.num,
limit: mescroll.size,
keyword:keyword.value,
create_time: create_time.value
};
getVerifyRecords(data).then((res) => {
let newArr = (res.data.data as Array<Object>);
//
if (mescroll.num == 1) {
list.value = []; //
}
list.value = list.value.concat(newArr);
mescroll.endSuccess(newArr.length);
loading.value = true;
}).catch(() => {
loading.value = true;
mescroll.endErr(); // ,
})
}
const toLink = (data: AnyObject)=> {
redirect({ url: '/app/pages/verify/detail', param: { code: data.code } })
}
//
const searchTypeFn = () =>{
getMescroll().resetUpScroll();
}
//
const selectDateRef = ref()
const handleSelect = () =>{
selectDateRef.value.show = true
}
//
const confirmFn = (data) =>{
create_time.value = data;
list.value = []
getMescroll().resetUpScroll();
}
</script>
<style lang="scss" scoped>
:deep(.uni-picker-view-content){
z-index: 10;
}
:deep(.uni-picker-view-indicator::before){
border: none !important;
}
:deep(.uni-picker-view-indicator::after){
border: none !important;
}
.noData{
height: calc(100vh - 132rpx - constant(safe-area-inset-bottom));
height: calc(100vh - 132rpx - env(safe-area-inset-bottom));
}
</style>

View File

@ -0,0 +1,150 @@
<template>
<view :style="themeColor()" class="bg-[#f8f8f8] min-h-[100vh] overflow-hidden">
<block v-if="!loading && verifyInfo && verifyInfo.value">
<view class="w-full bg-[#fff] flex justify-center">
<view class="text-[var(--primary-color)] absolute top-[40rpx] right-[30rpx] flex items-center" @click="redirect({url:'/app/pages/verify/record'})">
<image class="w-[26rpx] h-[28rpx]" :src="img('static/resource/images/verify/history.png')"/>
<text class="text-[26rpx] ml-[10rpx]">核销记录</text>
</view>
<view class="flex pt-[120rpx] pb-[30rpx] items-center">
<view class="flex justify-center items-center flex-col pr-[30rpx] w-[130rpx]">
<image class="w-[100rpx] h-[100rpx]" :src="img('static/resource/images/verify/yanzhenghexiaoma.png')"/>
<view class="text-[26rpx] mt-[12rpx] h-[36rpx] leading-[36rpx]">验证核销码</view>
</view>
<image class="w-[74rpx] h-[12rpx] mb-[50rpx]" :src="img('static/resource/images/verify/youjiantou.png')"/>
<view class="flex justify-center items-center flex-col pl-[30rpx] w-[130rpx]">
<image class="w-[100rpx] h-[100rpx]" :src="img('static/resource/images/verify/hexiao.png')"/>
<view class="text-[26rpx] mt-[12rpx] h-[36rpx] leading-[36rpx]">确定核销</view>
</view>
</view>
</view>
<view class="card-template mt-[20rpx] sidebar-marign">
<view class="flex" :class="{'mb-[20rpx]': (verifyInfo.value.list.length - 1 != index)}" v-for="(item,index) in verifyInfo.value.list" :key="index">
<image class="w-[150rpx] h-[150rpx] rounded-[8rpx]" mode="aspectFill" v-if="item.cover" :src="img(item.cover)"></image>
<image class="w-[150rpx] h-[150rpx] rounded-[8rpx]" mode="aspectFill" v-else :src="img('addon/tourism/tourism/member/hotel.png')"></image>
<view class="flex flex-col flex-1 ml-[20rpx] py-[4rpx]">
<view class="leading-[1.3] multi-hidden">{{item.name}}</view>
<view class="self-end text-[#626779] text-[28rpx] mt-[10rpx]">x1</view>
</view>
</view>
</view>
<view class="card-template mt-[20rpx] sidebar-marign">
<view class="text-[32rpx] font-500 leading-[1.3]">核销信息</view>
<view class="flex pt-[30rpx] items-center justify-between min-h-[36rpx]">
<text class="text-[26rpx] text-[#626779]">核销类型</text>
<view class="text-[26rpx] text-[#333333]">{{verifyInfo.type_name}}</view>
</view>
<view class="flex pt-[20rpx] items-center justify-between min-h-[36rpx]" v-for="(item,index) in verifyInfo.value.content.fixed">
<text class="text-[26rpx] text-[#626779]">{{item.title}}</text>
<view class="text-[26rpx] text-[#333333]">{{item.value}}</view>
</view>
</view>
<view v-for="(item,index) in verifyInfo.value.content.diy" :key="index" class="card-template mt-[20rpx] sidebar-marign">
<view class="text-[32rpx] font-500 leading-[1.3]">{{item.title}}</view>
<view class="flex items-center justify-between min-h-[36rpx]" :class="{'pt-[30rpx]': subIndex==0, 'pt-[20rpx]': subIndex!=0}" v-for="(subItem,subIndex) in item.list" :key="subIndex">
<text class="text-[26rpx] text-[#626779]">{{subItem.title}}</text>
<text class="text-[26rpx] text-[#333333]">{{ subItem.value }}</text>
</view>
</view>
<text class=" min-w-[630rpx] fixed bottom-[60rpx] confirmBtn text-[#fff] flex items-center justify-center !text-[32rpx] rounded-[50rpx] h-[88rpx] ml-[60rpx] mr-[60rpx]" @click="verifyFn">确定</text>
</block>
<u-loading-page :loading="loading" loading-text="" loadingColor="var(--primary-color)" iconSize="35"></u-loading-page>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { onLoad ,onShow} from '@dcloudio/uni-app'
import { img,redirect, getToken } from '@/utils/common';
import { getVerifierInfo, getCheckVerifier, verify } from '@/app/api/verify'
import { t } from '@/locale'
const loading = ref(true)
const code = ref('');
onLoad((option:any)=> {
if (option.code) code.value = option.code;
//
if (option.scene) {
let sceneParams: any = decodeURIComponent(option.scene).split('&');
if (sceneParams.length) {
sceneParams.forEach((item: any) => {
if (item.indexOf('code') != -1) code.value = item.split('-')[1];
});
}
}
})
onShow(() => {
if(getToken()){
checkIsVerifier();
getVerifierInfoFn();
}
})
//
const checkIsVerifier = () => {
getCheckVerifier().then((res:any) =>{
if(!res.data){
uni.showToast({
title: '非核销员无此权限',
icon: 'none'
});
setTimeout(() => {
if(getCurrentPages().length > 1){
uni.navigateBack({
delta: 1
});
}else{
redirect({
url: '/app/pages/member/index',
mode: 'reLaunch'
});
}
}, 1000);
}else{
loading.value = false;
}
})
}
const verifyInfo = ref({})
const getVerifierInfoFn = ()=>{
loading.value = true;
getVerifierInfo(code.value).then((res:any) =>{
verifyInfo.value = res.data;
loading.value = false;
}).catch(() => {
setTimeout(() => {
loading.value = false;
redirect({ url: '/app/pages/verify/index', param: {}, mode: 'redirectTo' })
}, 1000);
})
}
let isLoading = false;
const verifyFn = ()=>{
if(isLoading) return false;
isLoading = true;
verify(code.value).then((res:any) =>{
uni.showToast({
title: '核销成功',
icon: 'none'
});
setTimeout(() => {
isLoading = false;
redirect({ url: '/app/pages/verify/index', param: {}, mode: 'redirectTo' })
}, 1000);
}).catch(() => {
isLoading = false;
})
}
</script>
<style lang="scss" scoped>
.confirmBtn{
background: linear-gradient( 94deg, #FB7939 0%, #FE120E 99%), #EF000C;
}
</style>

View File

@ -0,0 +1,37 @@
<template>
<view :style="themeColor()">
<view class="error-msg">{{errorMsg}}</view>
</view>
</template>
<script setup lang="ts">
import {ref} from 'vue';
import {redirect} from '@/utils/common';
import {onLoad} from '@dcloudio/uni-app'
import {getMsgJumpPath} from '@/app/api/system'
const outTradeNo = ref('')
const errorMsg = ref('')
onLoad((options: any) => {
if (options.merchant_trade_no) {
outTradeNo.value = options.merchant_trade_no;
getMsgJumpPathFn();
} else {
errorMsg.value = '缺少merchant_trade_no参数';
}
});
const getMsgJumpPathFn = () => {
getMsgJumpPath({
out_trade_no: outTradeNo.value,
}).then((res: any) => {
if(res.data && res.data.path){
//
redirect({url: '/' + res.data.path, mode: 'reLaunch'})
}
});
}
</script>
<style lang="scss" scoped>
</style>

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