用户
This commit is contained in:
parent
4a24619622
commit
73cfc42a38
16
README.md
16
README.md
|
|
@ -11,4 +11,20 @@ let icon = ref("/static/app-plus/icon_108.png");
|
|||
onLoad(() => {
|
||||
console.log('页面加载中');
|
||||
});
|
||||
```
|
||||
# 新文件
|
||||
```vue
|
||||
<!-- 详情 -->
|
||||
<template>
|
||||
<page-no-container ref="pageContainer" title="协议" show-back>
|
||||
|
||||
</page-no-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
let pageContainer = ref(null);
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
```
|
||||
33
common/server/other.js
Normal file
33
common/server/other.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import HttpRequest from "../system/request";
|
||||
/**
|
||||
* 发送短信验证码
|
||||
* @param {String} phone 手机号
|
||||
* @returns {Promise} 发送短信验证码
|
||||
*/
|
||||
export const sendSms = async (phone) => {
|
||||
const res = await HttpRequest.post('v2/account/sendSms', {
|
||||
phone: phone
|
||||
});
|
||||
return res.status == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议内容
|
||||
* @param {String} type 协议类型
|
||||
* @returns {Promise} 获取协议
|
||||
*/
|
||||
export const getAgreement = async (type) => {
|
||||
let type_id = 0;
|
||||
if (type == "user") {
|
||||
type_id = 4;
|
||||
} else if (type == "privacy") {
|
||||
type_id = 5;
|
||||
}
|
||||
const res = await HttpRequest.get('/getAgreement', {
|
||||
type: type_id
|
||||
});
|
||||
if (res.status == 1) {
|
||||
return res.data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -1,11 +1,52 @@
|
|||
import { HttpRequest } from "@/common/utils/request";
|
||||
import HttpRequest from "../system/request";
|
||||
import { decryptRouteMap } from "../system/routeMap";
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns {Promise} 用户信息
|
||||
*/
|
||||
export const getUserInfo = async () => {
|
||||
const res = await HttpRequest.get('/userInfo');
|
||||
return res.data;
|
||||
if (res.status == 1) {
|
||||
let userInfo = res.data;
|
||||
if (userInfo.other != null && userInfo.other != undefined) {
|
||||
userInfo.other = decryptRouteMap(userInfo.other);
|
||||
userInfo['currency1'] = userInfo.other['a'];
|
||||
userInfo['currency2'] = userInfo.other['b'];
|
||||
userInfo['currency3'] = userInfo.other['c'];
|
||||
userInfo['uid'] = userInfo.other['uid'];
|
||||
userInfo['pid'] = userInfo.other['pid'];
|
||||
delete userInfo.other;
|
||||
}
|
||||
console.log("userInfo", userInfo);
|
||||
return userInfo;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请注销账号
|
||||
* @returns {Promise} 是否成功
|
||||
*/
|
||||
export const deleteAccount = async () => {
|
||||
const res = await HttpRequest.post('/deleteAccount');
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号登录
|
||||
* @param {String} phone 手机号
|
||||
* @param {String} code 验证码
|
||||
* @param {String} pid 推广码
|
||||
* @returns {Promise} 是否成功
|
||||
*/
|
||||
export const mobileLogin = async (phone, code, pid = 0) => {
|
||||
const res = await HttpRequest.post('/mobileLogin', {
|
||||
mobile: phone,
|
||||
code: code,
|
||||
pid: pid
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,4 +12,24 @@ export const navigateTo = (url) => {
|
|||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转登录页面
|
||||
* @param {String} page 跳转页面
|
||||
*/
|
||||
export const navigateToAccountLogin = (page = "") => {
|
||||
if (page == "") {
|
||||
const _page = getCurrentPages()[0];
|
||||
page = _page.route;
|
||||
}
|
||||
navigateTo(`/pages/me/account-login?page=${encodeURIComponent(page)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转协议页面
|
||||
* @param {String} type 协议类型
|
||||
*/
|
||||
export const navigateToAgreement = (type) => {
|
||||
navigateTo(`/pages/other/agreement?type=${type}`);
|
||||
};
|
||||
49
common/system/userInfo.js
Normal file
49
common/system/userInfo.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { getCache, getLocalStorage, setLocalStorage, removeCache, removeLocalStorage } from './cacheService';
|
||||
import { getUserInfo } from '@/common/server/user';
|
||||
import { navigateTo, navigateToAccountLogin } from './router';
|
||||
export const isAccountLogin = () => {
|
||||
const token = getCache('token');
|
||||
if (token) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns {Promise} 用户信息
|
||||
*/
|
||||
export const getAccountInfo = async () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const user = getLocalStorage('user');
|
||||
if (user) {
|
||||
resolve(user);
|
||||
return;
|
||||
}
|
||||
if (isAccountLogin()) {
|
||||
const user_res = await getUserInfo();
|
||||
setLocalStorage("user", user_res, 60);
|
||||
resolve(user_res);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否登录,如果用户未登录,会跳转到登录页面
|
||||
* @returns {Boolean} 是否登录
|
||||
*/
|
||||
export const IsUserLogin = () => {
|
||||
if (!isAccountLogin()) {
|
||||
navigateToAccountLogin();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export const logout = () => {
|
||||
removeCache('token');
|
||||
removeCache('userInfo');
|
||||
removeLocalStorage('user');
|
||||
}
|
||||
|
|
@ -30,4 +30,81 @@ export function parseQueryString(urlOrQueryString) {
|
|||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示确认弹窗
|
||||
* @param {Object} options 弹窗选项
|
||||
* @param {String} options.title 弹窗标题
|
||||
* @param {String} options.content 弹窗内容
|
||||
* @param {String} options.confirmText 确认按钮文字
|
||||
* @param {String} options.cancelText 取消按钮文字
|
||||
* @returns {Promise} 返回Promise对象,resolve中返回{confirm: Boolean},表示是否点击了确认按钮
|
||||
*/
|
||||
export function showModal(options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
uni.showModal({
|
||||
title: options.title || '提示',
|
||||
content: options.content || '',
|
||||
confirmText: options.confirmText || '确定',
|
||||
cancelText: options.cancelText || '取消',
|
||||
success(res) {
|
||||
resolve(res);
|
||||
},
|
||||
fail() {
|
||||
resolve({ confirm: false });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示提示信息
|
||||
* @param {*} title 提示信息
|
||||
* @param {*} icon 图标
|
||||
* @param {*} duration 提示时长
|
||||
* @returns
|
||||
*/
|
||||
export function showToast(title, icon = "none", duration = 1500) {
|
||||
return new Promise((resolve) => {
|
||||
uni.showToast({
|
||||
title: title || '',
|
||||
icon: icon || 'none',
|
||||
duration: duration,
|
||||
success: () => {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示加载中
|
||||
* @param {String} title 加载中文字
|
||||
* @returns {Promise} 返回Promise对象,resolve中返回true
|
||||
*/
|
||||
export function showLoading(title = "加载中...") {
|
||||
return new Promise((resolve) => {
|
||||
uni.showLoading({
|
||||
title: title,
|
||||
success: () => {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏加载中
|
||||
* @returns {Promise} 返回Promise对象,resolve中返回true
|
||||
*/
|
||||
export function hideLoading() {
|
||||
return new Promise((resolve) => {
|
||||
uni.hideLoading({
|
||||
success: () => {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
14
common/yds.js
Normal file
14
common/yds.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import * as utils1 from './utils';
|
||||
import * as utils2 from './system/cacheService';
|
||||
import * as utils3 from './system/router';
|
||||
import * as utils4 from './system/request';
|
||||
import * as utils5 from './system/userInfo';
|
||||
// 动态合并所有导出到 yds 对象
|
||||
export const yds = {
|
||||
userInfo: { ...utils5 },
|
||||
...utils1,
|
||||
...utils2,
|
||||
...utils3,
|
||||
...utils4,
|
||||
// 其他文件...
|
||||
};
|
||||
5
components.d.ts
vendored
5
components.d.ts
vendored
|
|
@ -11,7 +11,12 @@ declare module 'vue' {
|
|||
LoadingData: typeof import('./components/youdas-container/loading-data.vue')['default']
|
||||
NewsListItem: typeof import('./components/youdas-container/news-list-item.vue')['default']
|
||||
NoData: typeof import('./components/youdas-container/no-data.vue')['default']
|
||||
OrderListItem: typeof import('./components/youdas-container/order-list-item.vue')['default']
|
||||
PageBaseContainer: typeof import('./components/youdas-container/page-base-container.vue')['default']
|
||||
PageContainer: typeof import('./components/youdas-container/page-container.vue')['default']
|
||||
PageLine: typeof import('./components/youdas-container/page-line.vue')['default']
|
||||
PageNoContainer: typeof import('./components/youdas-container/page-no-container.vue')['default']
|
||||
PagePopup: typeof import('./components/youdas-container/page-popup.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
UniNavBar: typeof import('./components/uni-nav-bar/uni-nav-bar.vue')['default']
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export default {
|
|||
justify-content: center;
|
||||
animation: emptyFadeIn 0.5s ease-out;
|
||||
// background-color: #F7F7F7;
|
||||
|
||||
}
|
||||
|
||||
.loading-image {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ watch(() => props.currentIndex, (newVal) => {
|
|||
});
|
||||
|
||||
const queryList = (pageNo, pageSize) => {
|
||||
if (!props.responseCallback) return;
|
||||
if (!props.responseCallback) { paging.value.complete(false); return; };
|
||||
|
||||
const params = {
|
||||
pageNo: pageNo,
|
||||
|
|
|
|||
153
components/youdas-container/order-list-item.vue
Normal file
153
components/youdas-container/order-list-item.vue
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<!-- 在这个文件对每个tab对应的列表进行渲染 -->
|
||||
<template>
|
||||
<view class="content">
|
||||
<!-- :enable-back-to-top="currentIndex===tabIndex" 在微信小程序上可以多加这一句,因为默认是允许点击返回顶部的,但是这个页面有多个scroll-view,会全部返回顶部,所以需要控制是当前index才允许点击返回顶部 -->
|
||||
<!-- 如果当前页已经加载过数据或者当前切换到的tab是当前页,才展示当前页数据(懒加载) -->
|
||||
<z-paging v-if="firstLoaded || isCurrentPage" ref="paging" v-model="dataList" @query="queryList" :fixed="false">
|
||||
<!-- 如果希望其他view跟着页面滚动,可以放在z-paging标签内 -->
|
||||
<view class="item" v-for="(item, index) in dataList" :key="index" @click="itemClick(item)">
|
||||
<view class="item-title">{{ item.title }}</view>
|
||||
<view class="item-detail">{{ item.detail }}</view>
|
||||
<view class="item-line"></view>
|
||||
</view>
|
||||
|
||||
<template #empty>
|
||||
<no-data />
|
||||
</template>
|
||||
<template #loading>
|
||||
<loading-data />
|
||||
</template>
|
||||
</z-paging>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, nextTick, onMounted } from 'vue';
|
||||
|
||||
// props定义
|
||||
const props = defineProps({
|
||||
// 当前组件的index,也就是当前组件是swiper中的第几个
|
||||
tabIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 当前swiper切换到第几个index
|
||||
currentIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
responseCallback: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
// 数据
|
||||
// v-model绑定的这个变量不要在分页请求结束中自己赋值!!!
|
||||
const dataList = ref([]);
|
||||
// 当前组件是否已经加载过了
|
||||
const firstLoaded = ref(false);
|
||||
// 是否滚动到当前页
|
||||
const isCurrentPage = ref(false);
|
||||
// ref引用
|
||||
const paging = ref(null);
|
||||
|
||||
// 监听currentIndex变化
|
||||
watch(
|
||||
() => props.currentIndex,
|
||||
(newVal) => {
|
||||
if (newVal === props.tabIndex) {
|
||||
// 懒加载,当滑动到当前的item时,才去加载
|
||||
if (!firstLoaded.value) {
|
||||
// 这里需要延迟渲染z-paging的原因是为了避免在一些平台上立即渲染可能引发的底层报错问题
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
isCurrentPage.value = true;
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 接收父组件传过来的刷新列表要求
|
||||
const reload = () => {
|
||||
nextTick(() => {
|
||||
// 刷新列表数据(如果不希望列表pageNo被重置可以用refresh代替reload方法)
|
||||
paging.value && paging.value.reload();
|
||||
});
|
||||
};
|
||||
|
||||
const queryList = (pageNo, pageSize) => {
|
||||
if (!props.responseCallback) { paging.value.complete(false); return; };
|
||||
|
||||
const params = {
|
||||
pageNo: pageNo,
|
||||
pageSize: pageSize,
|
||||
type: props.tabIndex
|
||||
};
|
||||
|
||||
try {
|
||||
props.responseCallback(params).then(res => {
|
||||
if (paging.value) {
|
||||
paging.value.complete(res.list || []);
|
||||
firstLoaded.value = true;
|
||||
}
|
||||
}).catch(err => {
|
||||
if (paging.value) {
|
||||
paging.value.complete(false);
|
||||
}
|
||||
console.error('获取新闻列表失败', err);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('调用responseCallback异常', e);
|
||||
if (paging.value) {
|
||||
paging.value.complete(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 点击项目
|
||||
const itemClick = (item) => {
|
||||
console.log('点击了', item.title);
|
||||
};
|
||||
|
||||
// 暴露方法给父组件调用
|
||||
defineExpose({
|
||||
reload
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 注意:父节点需要固定高度,z-paging的height:100%才会生效 */
|
||||
.content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
height: 150rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0rpx 30rpx;
|
||||
}
|
||||
|
||||
.item-detail {
|
||||
padding: 5rpx 15rpx;
|
||||
border-radius: 10rpx;
|
||||
font-size: 28rpx;
|
||||
color: white;
|
||||
background-color: #007AFF;
|
||||
}
|
||||
|
||||
.item-line {
|
||||
position: absolute;
|
||||
bottom: 0rpx;
|
||||
left: 0rpx;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
</style>
|
||||
100
components/youdas-container/page-base-container.vue
Normal file
100
components/youdas-container/page-base-container.vue
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<!-- 详情 -->
|
||||
<template>
|
||||
|
||||
<view>
|
||||
<z-paging ref="_paging" :refresher-enabled="false">
|
||||
<template #top>
|
||||
<uni-nav-bar :title="title" @clickLeft="onClickLeft" :leftIcon="leftIcon" />
|
||||
</template>
|
||||
<view class="page-container">
|
||||
<view class="page-container__content">
|
||||
<view class="page-container__not-data" v-if="showNotData">
|
||||
<no-data />
|
||||
</view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
<view>
|
||||
<page-popup ref="_pagePopup" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showBack: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showNotData: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
let _paging = ref(null);
|
||||
let _pagePopup = ref(null);
|
||||
const leftIcon = computed(() => {
|
||||
if (props.showBack) {
|
||||
return "/static/back.png";
|
||||
}
|
||||
});
|
||||
const onClickLeft = () => {
|
||||
if (props.showBack) {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
|
||||
const getPaging = () => {
|
||||
return _paging.value;
|
||||
}
|
||||
const getPagePopup = () => {
|
||||
return _pagePopup.value;
|
||||
}
|
||||
defineExpose({
|
||||
getPaging,
|
||||
getPagePopup
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__divider {
|
||||
width: 100%;
|
||||
height: 4rpx;
|
||||
background-color: #e5e5e5;
|
||||
/* 使用较浅的灰色 */
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
margin-bottom: 10rpx;
|
||||
/* 在分割线下方添加一些间距 */
|
||||
}
|
||||
|
||||
&__not-data {
|
||||
width: 100%;
|
||||
height: 80vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,19 +1,20 @@
|
|||
<!-- 详情 -->
|
||||
<template>
|
||||
<z-paging ref="paging" refresher-only @onRefresh="onRefresh">
|
||||
<template #top>
|
||||
<uni-nav-bar :title="title" @clickLeft="onClickLeft" :leftIcon="leftIcon" />
|
||||
</template>
|
||||
<view class="page-container">
|
||||
|
||||
<view class="page-container__content">
|
||||
<view class="page-container__not-data" v-if="showNotData">
|
||||
<no-data />
|
||||
<view>
|
||||
<z-paging ref="paging" refresher-only @onRefresh="onRefresh">
|
||||
<template #top>
|
||||
<uni-nav-bar :title="title" @clickLeft="onClickLeft" :leftIcon="leftIcon" />
|
||||
</template>
|
||||
<view class="page-container">
|
||||
<view class="page-container__content">
|
||||
<view class="page-container__not-data" v-if="showNotData">
|
||||
<no-data />
|
||||
</view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</z-paging>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
|
@ -32,7 +33,7 @@ const props = defineProps({
|
|||
},
|
||||
refresh: {
|
||||
type: Function,
|
||||
default: (res) => { }
|
||||
default: null
|
||||
}
|
||||
});
|
||||
let paging = ref(null);
|
||||
|
|
|
|||
31
components/youdas-container/page-line.vue
Normal file
31
components/youdas-container/page-line.vue
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<view class="item-line">
|
||||
<view class="line" :style="{ width: width }"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.item-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
|
||||
.line {
|
||||
width: 90%;
|
||||
height: 1px;
|
||||
background-color: #eeeeee;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
119
components/youdas-container/page-no-container.vue
Normal file
119
components/youdas-container/page-no-container.vue
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<!-- 详情 -->
|
||||
<template>
|
||||
<view>
|
||||
<z-paging-swiper>
|
||||
<template #top>
|
||||
<uni-nav-bar :title="title" @clickLeft="onClickLeft" :leftIcon="leftIcon" />
|
||||
</template>
|
||||
|
||||
<view class="page-container">
|
||||
<view class="page-container__content">
|
||||
<view class="page-container__not-data" v-if="showNotData">
|
||||
<no-data />
|
||||
</view>
|
||||
<view class="page-container__loading" v-if="showLoading" @click.stop="mengban">
|
||||
<loading-data />
|
||||
</view>
|
||||
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</z-paging-swiper>
|
||||
<view>
|
||||
<page-popup ref="_pagePopup" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showBack: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showNotData: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showLoading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
let _pagePopup = ref(null);
|
||||
const leftIcon = computed(() => {
|
||||
if (props.showBack) {
|
||||
return "/static/back.png";
|
||||
}
|
||||
});
|
||||
const onClickLeft = () => {
|
||||
if (props.showBack) {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
const mengban = () => {
|
||||
|
||||
}
|
||||
const getPagePopup = () => {
|
||||
return _pagePopup.value;
|
||||
}
|
||||
defineExpose({
|
||||
getPagePopup
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__divider {
|
||||
width: 100%;
|
||||
height: 4rpx;
|
||||
background-color: #e5e5e5;
|
||||
/* 使用较浅的灰色 */
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
margin-bottom: 10rpx;
|
||||
/* 在分割线下方添加一些间距 */
|
||||
}
|
||||
|
||||
&__not-data {
|
||||
width: 100%;
|
||||
height: 80vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__loading {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
229
components/youdas-container/page-popup.vue
Normal file
229
components/youdas-container/page-popup.vue
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
<!-- 通用弹出组件 -->
|
||||
<template>
|
||||
<view v-if="state.visible" class="popup-container">
|
||||
<view class="popup-mask"></view>
|
||||
<view class="popup-content">
|
||||
<!-- 标题区域 -->
|
||||
<view v-if="state.title" class="popup-header">
|
||||
<text class="popup-title">{{ state.title }}</text>
|
||||
<!-- <view class="popup-close" @click="close">×</view> -->
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view class="popup-body">
|
||||
<text v-if="state.content" class="popup-content-text">{{ state.content }}</text>
|
||||
<slot></slot>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮区域 -->
|
||||
<view class="popup-footer">
|
||||
<button class="popup-btn popup-btn--cancel" @click="handleCancel">
|
||||
{{ state.cancelText || '取消' }}
|
||||
</button>
|
||||
<button class="popup-btn popup-btn--confirm" @click="handleConfirm">
|
||||
{{ state.confirmText || '确定' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
// 使用reactive状态存储弹窗的各种属性
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
title: '',
|
||||
content: '',
|
||||
confirmText: '确定',
|
||||
cancelText: '取消',
|
||||
onConfirm: null,
|
||||
onCancel: null
|
||||
});
|
||||
|
||||
// 存储Promise的resolve和reject函数
|
||||
const currentResolve = ref(null);
|
||||
const currentReject = ref(null);
|
||||
|
||||
// 打开弹窗方法
|
||||
const open = (options = {}) => {
|
||||
state.title = options.title || '';
|
||||
state.content = options.content || '';
|
||||
state.confirmText = options.confirmText || '确定';
|
||||
state.cancelText = options.cancelText || '取消';
|
||||
state.onConfirm = options.onConfirm || null;
|
||||
state.onCancel = options.onCancel || null;
|
||||
state.visible = true;
|
||||
currentResolve.value = null;
|
||||
currentReject.value = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 异步打开弹窗方法,返回Promise
|
||||
* @param {String} title 标题
|
||||
* @param {String} content 内容
|
||||
* @param {String} confirmText 确定按钮文本
|
||||
* @param {String} cancelText 取消按钮文本
|
||||
* @param {Function} onConfirm 确定回调
|
||||
* @param {Function} onCancel
|
||||
* @returns
|
||||
*/
|
||||
const showModal = (title, content, confirmText = "确定", cancelText = "取消", onConfirm = null, onCancel = null) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
open({ title, content, confirmText, cancelText, onConfirm, onCancel });
|
||||
currentResolve.value = resolve;
|
||||
currentReject.value = reject;
|
||||
});
|
||||
};
|
||||
|
||||
// 关闭弹窗方法
|
||||
const close = () => {
|
||||
state.visible = false;
|
||||
// 如果有未完成的Promise,取消它
|
||||
if (currentReject.value) {
|
||||
currentReject.value('popup-closed');
|
||||
currentResolve.value = null;
|
||||
currentReject.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理确认
|
||||
const handleConfirm = () => {
|
||||
// 处理常规回调
|
||||
if (typeof state.onConfirm === 'function') {
|
||||
state.onConfirm();
|
||||
}
|
||||
|
||||
// 处理Promise
|
||||
if (currentResolve.value) {
|
||||
currentResolve.value({ confirm: true, cancel: false });
|
||||
currentResolve.value = null;
|
||||
currentReject.value = null;
|
||||
}
|
||||
|
||||
close();
|
||||
};
|
||||
|
||||
// 处理取消
|
||||
const handleCancel = () => {
|
||||
// 处理常规回调
|
||||
if (typeof state.onCancel === 'function') {
|
||||
state.onCancel();
|
||||
}
|
||||
|
||||
// 处理Promise
|
||||
if (currentReject.value) {
|
||||
// currentReject.value('cancel');
|
||||
currentResolve.value({ confirm: false, cancel: true });
|
||||
currentResolve.value = null;
|
||||
currentReject.value = null;
|
||||
}
|
||||
|
||||
close();
|
||||
};
|
||||
|
||||
// 将方法暴露给父组件
|
||||
defineExpose({
|
||||
open,
|
||||
showModal,
|
||||
close
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.popup-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.popup-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
max-height: 90%;
|
||||
max-width: 90%;
|
||||
width: 90%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
padding: 32rpx 40rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.popup-title {
|
||||
margin: 0;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup-close {
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
padding: 40rpx;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
|
||||
.popup-content-text {
|
||||
font-size: 28rpx;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-footer {
|
||||
padding: 20rpx 20rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.popup-btn {
|
||||
// padding: 16rpx 20rpx;
|
||||
// margin-left: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
width: 200rpx;
|
||||
text-align: center;
|
||||
|
||||
&--cancel {
|
||||
background-color: #f7f7f7;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&--confirm {
|
||||
background-color: #409eff;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
4
components/z-tabs/config/index.js
Normal file
4
components/z-tabs/config/index.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// z-tabs全局配置文件,注意避免更新时此文件被覆盖,若被覆盖,可在此文件中右键->点击本地历史记录,找回覆盖前的配置
|
||||
export default {
|
||||
|
||||
}
|
||||
791
components/z-tabs/z-tabs.vue
Normal file
791
components/z-tabs/z-tabs.vue
Normal file
|
|
@ -0,0 +1,791 @@
|
|||
<!-- z-tabs v0.3.0 by-ZXLee -->
|
||||
<!-- github地址:https://github.com/SmileZXLee/uni-z-tabs -->
|
||||
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?name=z-tabs -->
|
||||
<!-- 反馈QQ群:371624008 -->
|
||||
|
||||
<template name="z-tabs">
|
||||
<view class="z-tabs-conatiner" :style="[{background:bgColor}, {height: unit==='rpx' ? '80rpx' : '40px'}, tabsStyle]">
|
||||
<view class="z-tabs-left">
|
||||
<slot name="left" />
|
||||
</view>
|
||||
<view ref="z-tabs-scroll-view-conatiner" class="z-tabs-scroll-view-conatiner">
|
||||
<scroll-view ref="z-tabs-scroll-view" class="z-tabs-scroll-view" :scroll-x="true" :scroll-left="scrollLeft" :show-scrollbar="false" :scroll-with-animation="isFirstLoaded" @scroll="scroll">
|
||||
<view class="z-tabs-list-container" :style="[tabsListStyle]">
|
||||
<view class="z-tabs-list" :style="[tabsListStyle, {marginTop: -finalBottomSpace+'px'}]">
|
||||
<view :ref="`z-tabs-item-${index}`" :id="`z-tabs-item-${index}`" class="z-tabs-item" :style="[tabStyle]" v-for="(item,index) in list" :key="index" @click="tabsClick(index,item)">
|
||||
<view class="z-tabs-item-title-container">
|
||||
<text :class="{'z-tabs-item-title-rpx':unit==='rpx','z-tabs-item-title-px':unit==='px','z-tabs-item-title-disabled':item.disabled}"
|
||||
:style="[{color:item.disabled?disabledColor:(currentIndex===index?activeColor:inactiveColor)},item.disabled?disabledStyle:(currentIndex===index?activeStyle:inactiveStyle)]">
|
||||
{{item[nameKey]||item}}
|
||||
</text>
|
||||
<text v-if="item.badge&&_formatCount(item.badge.count).length" class="z-tabs-item-badge" :class="{'z-tabs-item-badge-rpx':unit==='rpx','z-tabs-item-badge-px':unit==='px'}" :style="[badgeStyle]">{{_formatCount(item.badge.count)}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="z-tabs-bottom" :style="[{width: tabsContainerWidth+'px', bottom: finalBottomSpace+'px'}]">
|
||||
<view ref="z-tabs-bottom-dot" class="z-tabs-bottom-dot"
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
:style="[{transform:`translateX(${bottomDotX}px)`,transition:dotTransition,background:activeColor},finalDotStyle]"
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
:style="[{background:activeColor},finalDotStyle]"
|
||||
<!-- #endif -->
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="z-tabs-right">
|
||||
<slot name="right" />
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef APP-NVUE
|
||||
const weexDom = weex.requireModule('dom');
|
||||
const weexAnimation = weex.requireModule('animation');
|
||||
// #endif
|
||||
import zTabsConfig from './config/index'
|
||||
|
||||
// #ifdef APP-HARMONY
|
||||
let screenWidth = 0;
|
||||
// #endif
|
||||
// 获取默认配置信息
|
||||
function _gc(key, defaultValue) {
|
||||
let config = null;
|
||||
if (zTabsConfig && Object.keys(zTabsConfig).length) {
|
||||
config = zTabsConfig;
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
const value = config[_toKebab(key)];
|
||||
return value === undefined ? defaultValue : value;
|
||||
}
|
||||
// 驼峰转短横线
|
||||
function _toKebab(value) {
|
||||
return value.replace(/([A-Z])/g, "-$1").toLowerCase();
|
||||
}
|
||||
// rpx => px,兼容鸿蒙
|
||||
function rpx2px(rpx) {
|
||||
// #ifdef APP-HARMONY
|
||||
if (!screenWidth) {
|
||||
screenWidth = uni.getSystemInfoSync().screenWidth;
|
||||
}
|
||||
return (screenWidth * Number.parseFloat(rpx)) / 750;
|
||||
// #endif
|
||||
// #ifndef APP-HARMONY
|
||||
return uni.upx2px(rpx);
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* z-tabs 标签
|
||||
* @description 一个简单轻量的tabs标签,全平台兼容,支持nvue、vue3
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?name=z-tabs
|
||||
* @property {Array} list 数据源数组,支持形如['tab1','tab2']的格式或[{name:'tab1',value:1}]的格式
|
||||
* @property {Number|String} current 当前选中的index,默认为0
|
||||
* @property {Number|String} scroll-count list数组长度超过scrollCount时滚动显示(不自动铺满全屏),默认为5
|
||||
* @property {Number|String} tab-width 自定义每个tab的宽度,默认为0,即代表根据内容自动撑开,单位rpx,支持传100、"100px"或"100rpx"
|
||||
* @property {Number|String} bar-width 滑块宽度,单位rpx,支持传100、"100px"或"100rpx"
|
||||
* @property {Number|String} bar-height 滑块高度,单位rpx,支持传100、"100px"或"100rpx"
|
||||
* @property {Object} bar-style 滑块样式,其中的width和height将被bar-width和bar-height覆盖
|
||||
* @property {Number|String} bottom-space tabs与底部的间距,单位rpx,支持传100、"100px"或"100rpx"
|
||||
* @property {String} bar-animate-mode 切换tab时滑块动画模式,与swiper联动时有效,点击切换tab时无效,必须调用setDx。默认为line,即切换tab时滑块宽度保持不变,线性运动。可选值为worm,即为类似毛毛虫蠕动效果
|
||||
* @property {String} name-key list中item的name(标题)的key,默认为name
|
||||
* @property {String} value-key list中item的value的key,默认为value
|
||||
* @property {String} active-color 激活状态tab的颜色
|
||||
* @property {String} inactive-color 未激活状态tab的颜色
|
||||
* @property {String} disabled-color 禁用状态tab的颜色
|
||||
* @property {Object} active-style 激活状态tab的样式
|
||||
* @property {Object} inactive-style 未激活状态tab的样式
|
||||
* @property {Object} disabled-style 禁用状态tab的样式
|
||||
* @property {Number|String} badge-max-count 徽标数最大数字限制,超过这个数字将变成badge-max-count+,默认为99
|
||||
* @property {Object} badge-style 徽标样式,例如可自定义背景色,字体等等
|
||||
* @property {String} bg-color z-tabs背景色
|
||||
* @property {Object} tabs-style z-tabs样式
|
||||
* @property {Boolean} init-trigger-change 初始化时是否自动触发change事件
|
||||
* @property {String} unit z-tabs中布局的单位,默认为rpx
|
||||
* @event {Function(index,value)} change tabs改变时触发,index:当前切换到的index;value:当前切换到的value
|
||||
* @example <z-tabs :list="list"></z-tabs>
|
||||
*/
|
||||
export default {
|
||||
name: 'z-tabs',
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 0,
|
||||
currentSwiperIndex: 0,
|
||||
bottomDotX: -1,
|
||||
bottomDotXForIndex: 0,
|
||||
showBottomDot: false,
|
||||
shouldSetDx: true,
|
||||
|
||||
barCalcedWidth: 0,
|
||||
pxBarWidth: 0,
|
||||
scrollLeft: 0,
|
||||
tabsSuperWidth: rpx2px(750),
|
||||
tabsWidth: rpx2px(750),
|
||||
tabsHeight: rpx2px(80),
|
||||
tabsLeft: 0,
|
||||
tabsContainerWidth: 0,
|
||||
itemNodeInfos: [],
|
||||
isFirstLoaded: false,
|
||||
currentScrollLeft: 0,
|
||||
changeTriggerFailed: false,
|
||||
currentChanged: false
|
||||
};
|
||||
},
|
||||
props: {
|
||||
//数据源数组,支持形如['tab1','tab2']的格式或[{name:'tab1',value:1}]的格式
|
||||
list: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
//当前选中的index
|
||||
current: {
|
||||
type: [Number, String],
|
||||
default: _gc('current',0)
|
||||
},
|
||||
//list数组长度超过scrollCount时滚动显示(不自动铺满全屏)
|
||||
scrollCount: {
|
||||
type: [Number, String],
|
||||
default: _gc('scrollCount',5)
|
||||
},
|
||||
//z-tabs样式
|
||||
tabsStyle: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return _gc('tabsStyle',{})
|
||||
}
|
||||
},
|
||||
//自定义每个tab的宽度,默认为0,即代表根据内容自动撑开,单位rpx,支持传100、"100px"或"100rpx"
|
||||
tabWidth: {
|
||||
type: [Number, String],
|
||||
default: _gc('tabWidth',0)
|
||||
},
|
||||
//滑块宽度,单位rpx,支持传100、"100px"或"100rpx"
|
||||
barWidth: {
|
||||
type: [Number, String],
|
||||
default: _gc('barWidth',45)
|
||||
},
|
||||
//滑块高度,单位rpx,支持传100、"100px"或"100rpx"
|
||||
barHeight: {
|
||||
type: [Number, String],
|
||||
default: _gc('barHeight',8)
|
||||
},
|
||||
//swiper的宽度,单位rpx,支持传100、"100px"或"100rpx",默认为"750rpx"
|
||||
swiperWidth: {
|
||||
type: [Number, String],
|
||||
default: _gc('swiperWidth',750)
|
||||
},
|
||||
//滑块样式,其中的width和height将被barWidth和barHeight覆盖
|
||||
barStyle: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return _gc('barStyle',{});
|
||||
}
|
||||
},
|
||||
//tabs与底部的间距,单位rpx,支持传100、"100px"或"100rpx"
|
||||
bottomSpace: {
|
||||
type: [Number, String],
|
||||
default: _gc('bottomSpace',8)
|
||||
},
|
||||
//切换tab时滑块动画模式,与swiper联动时有效,点击切换tab时无效,必须调用setDx。默认为line,即切换tab时滑块宽度保持不变,线性运动。可选值为worm,即为类似毛毛虫蠕动效果
|
||||
barAnimateMode: {
|
||||
type: String,
|
||||
default: _gc('barAnimateMode','line')
|
||||
},
|
||||
//list中item的name(标题)的key
|
||||
nameKey: {
|
||||
type: String,
|
||||
default: _gc('nameKey','name')
|
||||
},
|
||||
//list中item的value的key
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: _gc('valueKey','value')
|
||||
},
|
||||
//激活状态tab的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: _gc('activeColor','#007AFF')
|
||||
},
|
||||
//未激活状态tab的颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: _gc('inactiveColor','#666666')
|
||||
},
|
||||
//禁用状态tab的颜色
|
||||
disabledColor: {
|
||||
type: String,
|
||||
default: _gc('disabledColor','#bbbbbb')
|
||||
},
|
||||
//激活状态tab的样式
|
||||
activeStyle: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return _gc('activeStyle',{});
|
||||
}
|
||||
},
|
||||
//未激活状态tab的样式
|
||||
inactiveStyle: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return _gc('inactiveStyle',{});
|
||||
}
|
||||
},
|
||||
//禁用状态tab的样式
|
||||
disabledStyle: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return _gc('disabledStyle',{});
|
||||
}
|
||||
},
|
||||
//z-tabs背景色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: _gc('bgColor','white')
|
||||
},
|
||||
//徽标数最大数字限制,超过这个数字将变成badgeMaxCount+
|
||||
badgeMaxCount: {
|
||||
type: [Number, String],
|
||||
default: _gc('badgeMaxCount',99)
|
||||
},
|
||||
//徽标样式,例如可自定义背景色,字体等等
|
||||
badgeStyle: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return _gc('badgeStyle',{})
|
||||
}
|
||||
},
|
||||
//初始化时是否自动触发change事件
|
||||
initTriggerChange: {
|
||||
type: Boolean,
|
||||
default: _gc('initTriggerChange',false)
|
||||
},
|
||||
//z-tabs中布局的单位,默认为rpx
|
||||
unit: {
|
||||
type: String,
|
||||
default: _gc('unit', 'rpx')
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.updateSubviewLayout();
|
||||
},
|
||||
watch: {
|
||||
current: {
|
||||
handler(newVal) {
|
||||
this.currentChanged && this._lockDx();
|
||||
this.currentIndex = newVal;
|
||||
this._preUpdateDotPosition(this.currentIndex);
|
||||
if (this.initTriggerChange) {
|
||||
if (newVal < this.list.length) {
|
||||
this.$emit('change', newVal, this.list[newVal][this.valueKey]);
|
||||
}else {
|
||||
this.changeTriggerFailed = true;
|
||||
}
|
||||
}
|
||||
this.currentChanged = true;
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
list: {
|
||||
handler(newVal) {
|
||||
this._handleListChange(newVal);
|
||||
},
|
||||
immediate: false
|
||||
},
|
||||
bottomDotX(newVal) {
|
||||
if(newVal >= 0){
|
||||
// #ifndef APP-NVUE
|
||||
this.showBottomDot = true;
|
||||
// #endif
|
||||
this.$nextTick(() => {
|
||||
// #ifdef APP-NVUE
|
||||
weexAnimation.transition(this.$refs['z-tabs-bottom-dot'], {
|
||||
styles: {
|
||||
transform: `translateX(${newVal}px)`
|
||||
},
|
||||
duration: this.showAnimate ? 200 : 0,
|
||||
delay: 0
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.showBottomDot = true;
|
||||
},10)
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
},
|
||||
finalBarWidth: {
|
||||
handler(newVal) {
|
||||
this.barCalcedWidth = newVal;
|
||||
this.pxBarWidth = this.barCalcedWidth;
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
currentIndex: {
|
||||
handler(newVal) {
|
||||
this.currentSwiperIndex = newVal;
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
shouldScroll(){
|
||||
return this.list.length > this.scrollCount;
|
||||
},
|
||||
finalTabsHeight(){
|
||||
return this.tabsHeight;
|
||||
},
|
||||
tabStyle(){
|
||||
const stl = this.shouldScroll ? {'flex-shrink': 0} : {'flex': 1};
|
||||
if(this.finalTabWidth > 0){
|
||||
stl['width'] = this.finalTabWidth + 'px';
|
||||
}else{
|
||||
delete stl.width;
|
||||
}
|
||||
return stl;
|
||||
},
|
||||
tabsListStyle(){
|
||||
return this.shouldScroll ? {} : {'flex':1};
|
||||
},
|
||||
showAnimate(){
|
||||
return this.isFirstLoaded && !this.shouldSetDx;
|
||||
},
|
||||
dotTransition(){
|
||||
return this.showAnimate ? 'transform .2s linear':'none';
|
||||
},
|
||||
finalDotStyle(){
|
||||
return {...this.barStyle, width: this.barCalcedWidth + 'px', height: this.finalBarHeight + 'px', opacity: this.showBottomDot ? 1 : 0};
|
||||
},
|
||||
finalTabWidth(){
|
||||
return this._convertTextToPx(this.tabWidth);
|
||||
},
|
||||
finalBarWidth(){
|
||||
return this._convertTextToPx(this._addUnit(this.barWidth, this.unit));
|
||||
},
|
||||
finalBarHeight(){
|
||||
return this._convertTextToPx(this._addUnit(this.barHeight, this.unit));
|
||||
},
|
||||
finalSwiperWidth(){
|
||||
return this._convertTextToPx(this.swiperWidth);
|
||||
},
|
||||
finalBottomSpace(){
|
||||
return this._convertTextToPx(this._addUnit(this.bottomSpace, this.unit));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//根据swiper的@transition实时更新底部dot位置
|
||||
setDx(dx) {
|
||||
if (!this.shouldSetDx) return;
|
||||
const isLineMode = this.barAnimateMode === 'line';
|
||||
const isWormMode = this.barAnimateMode === 'worm';
|
||||
let dxRate = dx / this.finalSwiperWidth;
|
||||
this.currentSwiperIndex = this.currentIndex + parseInt(dxRate);
|
||||
const isRight = dxRate > 0;
|
||||
const barWidth = this.pxBarWidth;
|
||||
if(this.currentSwiperIndex !== this.currentIndex){
|
||||
dxRate = dxRate - (this.currentSwiperIndex - this.currentIndex);
|
||||
const currentNode = this.itemNodeInfos[this.currentSwiperIndex];
|
||||
if (!!currentNode){
|
||||
this.bottomDotXForIndex = this._getBottomDotX(currentNode, barWidth);
|
||||
}
|
||||
}
|
||||
const currentIndex = this.currentSwiperIndex;
|
||||
let nextIndex = currentIndex + (isRight ? 1 : -1);
|
||||
nextIndex = Math.max(0, nextIndex);
|
||||
nextIndex = Math.min(nextIndex, this.itemNodeInfos.length - 1);
|
||||
const currentNodeInfo = this.itemNodeInfos[currentIndex];
|
||||
const nextNodeInfo = this.itemNodeInfos[nextIndex];
|
||||
const nextBottomX = this._getBottomDotX(nextNodeInfo, barWidth);
|
||||
if (isLineMode){
|
||||
this.bottomDotX = this.bottomDotXForIndex + (nextBottomX - this.bottomDotXForIndex) * Math.abs(dxRate);
|
||||
} else if (isWormMode) {
|
||||
if ((isRight && currentIndex >= this.itemNodeInfos.length - 1) || (!isRight && currentIndex <= 0)) return;
|
||||
const spaceOffset = isRight ? nextNodeInfo.right - currentNodeInfo.left : currentNodeInfo.right - nextNodeInfo.left;
|
||||
let barCalcedWidth = barWidth + spaceOffset * Math.abs(dxRate);
|
||||
if (isRight) {
|
||||
if (barCalcedWidth > nextBottomX - this.bottomDotX + barWidth) {
|
||||
const barMinusWidth = barWidth + spaceOffset * (1 - dxRate);
|
||||
this.bottomDotX = this.bottomDotXForIndex + (barCalcedWidth - barMinusWidth) / 2;
|
||||
barCalcedWidth = barMinusWidth;
|
||||
}
|
||||
}else if (!isRight) {
|
||||
if (barCalcedWidth > this.bottomDotXForIndex + barWidth - nextBottomX){
|
||||
const barMinusWidth = barWidth + spaceOffset * (1 + dxRate);
|
||||
barCalcedWidth = barMinusWidth;
|
||||
this.bottomDotX = nextBottomX;
|
||||
} else{
|
||||
this.bottomDotX = this.bottomDotXForIndex - (barCalcedWidth - barWidth);
|
||||
}
|
||||
}
|
||||
barCalcedWidth = Math.max(barCalcedWidth, barWidth);
|
||||
this.barCalcedWidth = barCalcedWidth;
|
||||
}
|
||||
},
|
||||
//在swiper的@animationfinish中通知z-tabs结束多setDx的锁定,若在父组件中调用了setDx,则必须调用unlockDx
|
||||
unlockDx() {
|
||||
this.$nextTick(() => {
|
||||
this.shouldSetDx = true;
|
||||
})
|
||||
},
|
||||
//更新z-tabs内部布局
|
||||
updateSubviewLayout(tryCount = 0) {
|
||||
this.$nextTick(() => {
|
||||
let delayTime = 10;
|
||||
// #ifdef APP-NVUE || MP-BAIDU
|
||||
delayTime = 50;
|
||||
// #endif
|
||||
setTimeout(() => {
|
||||
this._getNodeClientRect('.z-tabs-scroll-view-conatiner').then(res=>{
|
||||
if (res){
|
||||
if (!res[0].width && tryCount < 10) {
|
||||
setTimeout(() => {
|
||||
tryCount ++;
|
||||
this.updateSubviewLayout(tryCount);
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
this.tabsWidth = res[0].width;
|
||||
this.tabsHeight = res[0].height;
|
||||
this.tabsLeft = res[0].left;
|
||||
this._handleListChange(this.list);
|
||||
}
|
||||
})
|
||||
this._getNodeClientRect('.z-tabs-conatiner').then(res=>{
|
||||
if(res && res[0].width){
|
||||
this.tabsSuperWidth = res[0].width;
|
||||
}
|
||||
})
|
||||
},delayTime)
|
||||
})
|
||||
},
|
||||
//点击了tabs
|
||||
tabsClick(index,item) {
|
||||
if (item.disabled) return;
|
||||
if (this.currentIndex != index) {
|
||||
this.shouldSetDx = false;
|
||||
this.$emit('change', index, item[this.valueKey]);
|
||||
this.currentIndex = index;
|
||||
this._preUpdateDotPosition(index);
|
||||
} else {
|
||||
this.$emit('secondClick',index, item[this.valueKey]);
|
||||
}
|
||||
},
|
||||
//scroll-view滚动
|
||||
scroll(e){
|
||||
this.currentScrollLeft = e.detail.scrollLeft;
|
||||
},
|
||||
//锁定dx,用于避免在swiper被动触发滚动时候执行setDx中的代码
|
||||
_lockDx() {
|
||||
this.shouldSetDx = false;
|
||||
},
|
||||
//更新底部dot位置之前的预处理
|
||||
_preUpdateDotPosition(index) {
|
||||
// #ifndef APP-NVUE
|
||||
this.$nextTick(() => {
|
||||
uni.createSelectorQuery().in(this).select(".z-tabs-scroll-view").fields({
|
||||
scrollOffset: true
|
||||
}, data => {
|
||||
if (data) {
|
||||
this.currentScrollLeft = data.scrollLeft;
|
||||
this._updateDotPosition(index);
|
||||
} else {
|
||||
this._updateDotPosition(index);
|
||||
}
|
||||
}).exec();
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
this._updateDotPosition(index);
|
||||
// #endif
|
||||
},
|
||||
//更新底部dot位置
|
||||
_updateDotPosition(index){
|
||||
if(index >= this.itemNodeInfos.length) return;
|
||||
this.$nextTick(async ()=>{
|
||||
let node = this.itemNodeInfos[index];
|
||||
let offset = 0;
|
||||
let tabsContainerWidth = this.tabsContainerWidth;
|
||||
if (JSON.stringify(this.activeStyle) !== '{}') {
|
||||
const nodeRes = await this._getNodeClientRect(`#z-tabs-item-${index}`,true);
|
||||
if (nodeRes) {
|
||||
node = nodeRes[0];
|
||||
offset = this.currentScrollLeft;
|
||||
this.tabsHeight = Math.max(node.height + rpx2px(28), this.tabsHeight);
|
||||
tabsContainerWidth = 0;
|
||||
for(let i = 0;i < this.itemNodeInfos.length;i++){
|
||||
let oldNode = this.itemNodeInfos[i];
|
||||
tabsContainerWidth += i === index ? node.width : oldNode.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node) {
|
||||
this.bottomDotX = this._getBottomDotX(node, this.finalBarWidth, offset);
|
||||
}
|
||||
this.bottomDotXForIndex = this.bottomDotX;
|
||||
if (this.tabsWidth) {
|
||||
setTimeout(()=>{
|
||||
let scrollLeft = this.bottomDotX - this.tabsWidth / 2 + this.finalBarWidth / 2;
|
||||
scrollLeft = Math.max(0,scrollLeft);
|
||||
if (tabsContainerWidth) {
|
||||
scrollLeft = Math.min(scrollLeft,tabsContainerWidth - this.tabsWidth + 10);
|
||||
}
|
||||
if (this.shouldScroll && tabsContainerWidth > this.tabsWidth) {
|
||||
this.scrollLeft = scrollLeft;
|
||||
}
|
||||
this.$nextTick(()=>{
|
||||
this.isFirstLoaded = true;
|
||||
})
|
||||
},200)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 处理list改变
|
||||
_handleListChange(newVal) {
|
||||
this.$nextTick(async ()=>{
|
||||
if(newVal.length){
|
||||
let itemNodeInfos = [];
|
||||
let tabsContainerWidth = 0;
|
||||
let delayTime = 0;
|
||||
// #ifdef MP-BAIDU
|
||||
delayTime = 100;
|
||||
// #endif
|
||||
setTimeout(async()=>{
|
||||
for(let i = 0;i < newVal.length;i++){
|
||||
const nodeRes = await this._getNodeClientRect(`#z-tabs-item-${i}`,true);
|
||||
if(nodeRes){
|
||||
const node = nodeRes[0];
|
||||
node.left += this.currentScrollLeft;
|
||||
itemNodeInfos.push(node);
|
||||
tabsContainerWidth += node.width;
|
||||
}
|
||||
if (i === this.currentIndex){
|
||||
this.itemNodeInfos = itemNodeInfos;
|
||||
this.tabsContainerWidth = tabsContainerWidth;
|
||||
this._updateDotPosition(this.currentIndex);
|
||||
}
|
||||
}
|
||||
this.itemNodeInfos = itemNodeInfos;
|
||||
this.tabsContainerWidth = tabsContainerWidth;
|
||||
this._updateDotPosition(this.currentIndex);
|
||||
},delayTime)
|
||||
}
|
||||
})
|
||||
|
||||
if (this.initTriggerChange && this.changeTriggerFailed && newVal.length) {
|
||||
if (this.current < newVal.length) {
|
||||
this.$emit('change', this.current, newVal[this.current][this.valueKey]);
|
||||
}
|
||||
}
|
||||
},
|
||||
//根据node获取bottomX
|
||||
_getBottomDotX(node, barWidth = this.finalBarWidth, offset = 0){
|
||||
return node.left + node.width / 2 - barWidth / 2 + offset - this.tabsLeft;
|
||||
},
|
||||
//获取节点信息
|
||||
_getNodeClientRect(select, withRefArr = false) {
|
||||
// #ifdef APP-NVUE
|
||||
select = select.replace('.', '').replace('#', '');
|
||||
const ref = withRefArr ? this.$refs[select][0] : this.$refs[select];
|
||||
return new Promise((resolve, reject) => {
|
||||
if (ref) {
|
||||
weexDom.getComponentRect(ref, option => {
|
||||
if (option && option.result) {
|
||||
resolve([option.size]);
|
||||
} else resolve(false);
|
||||
})
|
||||
} else resolve(false);
|
||||
});
|
||||
return;
|
||||
// #endif
|
||||
const res = uni.createSelectorQuery().in(this);
|
||||
res.select(select).boundingClientRect();
|
||||
return new Promise((resolve, reject) => {
|
||||
res.exec(data => {
|
||||
resolve((data && data != '' && data != undefined && data.length) ? data : false);
|
||||
});
|
||||
});
|
||||
},
|
||||
//格式化badge中的count
|
||||
_formatCount(count) {
|
||||
if (!count) return '';
|
||||
if (count > this.badgeMaxCount) {
|
||||
return this.badgeMaxCount + '+';
|
||||
}
|
||||
return count.toString();
|
||||
},
|
||||
//将文本的px或者rpx转为px的值
|
||||
_convertTextToPx(text) {
|
||||
const dataType = Object.prototype.toString.call(text);
|
||||
if (dataType === '[object Number]') {
|
||||
return rpx2px(text);
|
||||
}
|
||||
let isRpx = false;
|
||||
if (text.indexOf('rpx') !== -1 || text.indexOf('upx') !== -1) {
|
||||
text = text.replace('rpx', '').replace('upx', '');
|
||||
isRpx = true;
|
||||
} else if (text.indexOf('px') !== -1) {
|
||||
text = text.replace('px', '');
|
||||
} else {
|
||||
text = rpx2px(text);
|
||||
}
|
||||
if (!isNaN(text)) {
|
||||
if (isRpx) return Number(rpx2px(text));
|
||||
return Number(text);
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
// 添加单位
|
||||
_addUnit(value, unit) {
|
||||
if (Object.prototype.toString.call(value) === '[object String]') {
|
||||
let tempValue = value;
|
||||
tempValue = tempValue.replace('rpx', '').replace('upx', '').replace('px', '');
|
||||
if (value.indexOf('rpx') === -1 && value.indexOf('upx') === -1 && value.indexOf('px') !== -1) {
|
||||
tempValue = parseFloat(tempValue) * 2;
|
||||
}
|
||||
value = tempValue;
|
||||
}
|
||||
return unit === 'rpx' ? value + 'rpx' : (value / 2) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.z-tabs-conatiner{
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
width: 750rpx;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.z-tabs-scroll-view-conatiner{
|
||||
flex: 1;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.z-tabs-scroll-view ::-webkit-scrollbar {
|
||||
display: none;
|
||||
-webkit-appearance: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
background: transparent;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.z-tabs-scroll-view{
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.z-tabs-list-container{
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
height: 100%;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.z-tabs-list,.z-tabs-list-container{
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.z-tabs-item{
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0px 20rpx;
|
||||
}
|
||||
|
||||
.z-tabs-item-title-container{
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.z-tabs-item-title-rpx{
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.z-tabs-item-title-px{
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.z-tabs-item-title-disabled{
|
||||
/* #ifndef APP-NVUE */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.z-tabs-item-badge{
|
||||
background-color: #ec5b56;
|
||||
color: white;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.z-tabs-item-badge-rpx{
|
||||
margin-left: 8rpx;
|
||||
font-size: 22rpx;
|
||||
padding: 0rpx 10rpx;
|
||||
}
|
||||
|
||||
.z-tabs-item-badge-px{
|
||||
margin-left: 4px;
|
||||
font-size: 11px;
|
||||
padding: 0px 5px;
|
||||
}
|
||||
|
||||
.z-tabs-bottom{
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.z-tabs-bottom-dot{
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.z-tabs-left,.z-tabs-right{
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
24
pages.json
24
pages.json
|
|
@ -33,6 +33,30 @@
|
|||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/me/account-deletion",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/order-list",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/me/account-login",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/other/agreement",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
// "globalStyle": {
|
||||
|
|
|
|||
|
|
@ -9,62 +9,55 @@
|
|||
<!-- 顶部三个商品 -->
|
||||
<view class="top">
|
||||
|
||||
<view v-for="(item,index) in topDataList" class="item">
|
||||
<view v-for="(item, i) in topDataList" :key="i" class="item" @click="goToDetail(item)">
|
||||
|
||||
<view class="img">
|
||||
|
||||
<image v-if="item.imgUrl" :src="item.imgUrl" mode="aspectFill" class="product-image"></image>
|
||||
</view>
|
||||
|
||||
<view class="" style="width: 100%; height: 109.07rpx; position: relative;">
|
||||
<view class=""
|
||||
style="width: 100%;overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 19.08rpx; color: #333333; text-align: center; margin-top: 10rpx;">
|
||||
<view class="item-info">
|
||||
<view class="item-name">
|
||||
{{item.name}}
|
||||
</view>
|
||||
|
||||
<view class=""
|
||||
style="position: absolute; display: flex; flex-direction: row; left: 13rpx; bottom:24rpx;">
|
||||
<text style="font-size: 15.27rpx;margin-top: 10rpx;">¥</text>
|
||||
<text style="font-size: 22.9rpx;">{{item.price}}</text>
|
||||
</view>
|
||||
<view class="item-bottom">
|
||||
<view class="item-price">
|
||||
<text class="price-symbol-small">¥</text>
|
||||
<text class="price-value-small">{{item.price}}</text>
|
||||
</view>
|
||||
|
||||
<view class=""
|
||||
style="position: absolute; display: flex; flex-direction: row; right: 13rpx; bottom:24rpx;">
|
||||
<text style="font-size: 15.27rpx; color: #6C6C6C;">{{item.num}}/1</text>
|
||||
<image src="/static/ic_box.png" style="width: 17.39rpx; height: 17.39rpx; margin-left: 7rpx;"
|
||||
mode=""></image>
|
||||
<view class="item-count">
|
||||
<text class="count-text-small">{{item.num}}/1</text>
|
||||
<image src="/static/ic_box.png" class="box-icon-small" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="view-list" scroll-y="true">
|
||||
<view class="" v-for="(item,index) in dataList"
|
||||
style="width: 100%; height: 261.45rpx; background-color: #FFFFFF; margin-bottom: 21rpx; border-radius: 15.27rpx; position: relative;">
|
||||
<view class="product-item" v-for="(item, i) in dataList" :key="i" @click="goToDetail(item)">
|
||||
|
||||
<view class=""
|
||||
style="width: 216.31rpx; height: 216.31rpx; background-color: #D8D8D8; border-radius: 15.27rpx; position: absolute; left: 22rpx; top: 22rpx;">
|
||||
<view class="product-image-container">
|
||||
<image v-if="item.imgUrl" :src="item.imgUrl" mode="aspectFill" class="product-image"></image>
|
||||
</view>
|
||||
|
||||
<view class=""
|
||||
style="width: 350rpx;overflow: hidden; text-overflow: ellipsis; white-space: nowrap; position: absolute; left: 269rpx; top: 44rpx; font-size: 26.72rpx;">
|
||||
<view class="product-name">
|
||||
{{item.name}}
|
||||
</view>
|
||||
|
||||
<view class="" style="position: absolute; display: flex; flex-direction: row; left: 269rpx; top:95rpx;">
|
||||
<text style="font-size: 15.27rpx; color: #6C6C6C;">{{item.num}}/1</text>
|
||||
<image src="/static/ic_box.png" style="width: 17.39rpx; height: 17.39rpx; margin-left: 7rpx;"
|
||||
mode=""></image>
|
||||
<view class="product-count">
|
||||
<text class="count-text">{{item.num}}/1</text>
|
||||
<image src="/static/ic_box.png" class="box-icon" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<view class=""
|
||||
style="position: absolute; display: flex; flex-direction: row; left: 269rpx; bottom:44rpx;">
|
||||
<text style="font-size: 19.08rpx;margin-top: 15rpx;">¥</text>
|
||||
<text style="font-size: 34.35rpx">{{item.price}}</text>
|
||||
<view class="product-price">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-value">{{item.price}}</text>
|
||||
</view>
|
||||
|
||||
<view class=""
|
||||
style="width: 89.22rpx; height: 42rpx; border-radius: 20.99rpx; border: 1rpx solid #6C6C6C; display: flex; align-items: center; justify-content: center; position: absolute; right: 38rpx; bottom: 38rpx;">
|
||||
<text style="font-size: 19.08rpx; color: #6C6C6C;">购买</text>
|
||||
<view class="buy-button" @click.stop="buyProduct(item)">
|
||||
<text class="buy-text">购买</text>
|
||||
</view>
|
||||
|
||||
|
||||
|
|
@ -80,57 +73,100 @@
|
|||
data() {
|
||||
return {
|
||||
topDataList: [{
|
||||
imgUrl: "",
|
||||
name: "英雄联盟K/DA系列…",
|
||||
id: '1',
|
||||
imgUrl: "/static/product1.png",
|
||||
name: "英雄联盟K/DA系列阿卡丽手办",
|
||||
num: "1",
|
||||
price: "69"
|
||||
price: "69",
|
||||
stock: 10
|
||||
}, {
|
||||
imgUrl: "",
|
||||
name: "英雄联盟K/DA系列…",
|
||||
id: '2',
|
||||
imgUrl: "/static/product2.png",
|
||||
name: "英雄联盟K/DA系列阿狸手办",
|
||||
num: "1",
|
||||
price: "69"
|
||||
price: "79",
|
||||
stock: 5
|
||||
}, {
|
||||
imgUrl: "",
|
||||
name: "英雄联盟K/DA系列…",
|
||||
id: '3',
|
||||
imgUrl: "/static/product3.png",
|
||||
name: "英雄联盟K/DA系列伊芙琳手办",
|
||||
num: "1",
|
||||
price: "69"
|
||||
price: "89",
|
||||
stock: 8
|
||||
}, ],
|
||||
dataList: [{
|
||||
imgUrl: "",
|
||||
name: "英雄联盟K/DA系列…",
|
||||
id: '4',
|
||||
imgUrl: "/static/product4.png",
|
||||
name: "英雄联盟K/DA系列阿卡丽手办豪华版",
|
||||
num: "1",
|
||||
price: "69"
|
||||
price: "169",
|
||||
stock: 3
|
||||
}, {
|
||||
imgUrl: "",
|
||||
name: "英雄联盟K/DA系列…",
|
||||
id: '5',
|
||||
imgUrl: "/static/product5.png",
|
||||
name: "英雄联盟K/DA系列阿狸手办豪华版",
|
||||
num: "1",
|
||||
price: "69"
|
||||
price: "179",
|
||||
stock: 2
|
||||
}, {
|
||||
imgUrl: "",
|
||||
name: "英雄联盟K/DA系列…",
|
||||
id: '6',
|
||||
imgUrl: "/static/product6.png",
|
||||
name: "英雄联盟K/DA系列伊芙琳手办豪华版",
|
||||
num: "1",
|
||||
price: "69"
|
||||
price: "189",
|
||||
stock: 6
|
||||
}, {
|
||||
imgUrl: "",
|
||||
name: "英雄联盟K/DA系列…",
|
||||
id: '7',
|
||||
imgUrl: "/static/product7.png",
|
||||
name: "英雄联盟K/DA系列卡莎手办",
|
||||
num: "1",
|
||||
price: "69"
|
||||
price: "99",
|
||||
stock: 15
|
||||
}, {
|
||||
imgUrl: "",
|
||||
name: "英雄联盟K/DA系列…",
|
||||
id: '8',
|
||||
imgUrl: "/static/product8.png",
|
||||
name: "英雄联盟K/DA系列莎拉手办",
|
||||
num: "1",
|
||||
price: "69"
|
||||
price: "99",
|
||||
stock: 12
|
||||
}, {
|
||||
imgUrl: "",
|
||||
name: "英雄联盟K/DA系列…",
|
||||
id: '9',
|
||||
imgUrl: "/static/product9.png",
|
||||
name: "英雄联盟K/DA系列套装收藏版",
|
||||
num: "1",
|
||||
price: "69"
|
||||
price: "599",
|
||||
stock: 1
|
||||
}, ]
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
// 跳转到商品详情页
|
||||
goToDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/product-detail?id=${item.id}`
|
||||
});
|
||||
},
|
||||
|
||||
// 购买商品
|
||||
buyProduct(item) {
|
||||
// 阻止事件冒泡
|
||||
event.stopPropagation();
|
||||
|
||||
// 检查库存
|
||||
if (item.stock <= 0) {
|
||||
uni.showToast({
|
||||
title: '商品已售罄',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加到购物车或直接购买的逻辑
|
||||
uni.showToast({
|
||||
title: '已添加到购物车',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -150,6 +186,7 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 32.44rpx;
|
||||
font-weight: 600;
|
||||
position: absolute;
|
||||
bottom: 30rpx;
|
||||
}
|
||||
|
|
@ -162,7 +199,6 @@
|
|||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
|
||||
.item {
|
||||
width: 216.31rpx;
|
||||
height: 100%;
|
||||
|
|
@ -170,15 +206,91 @@
|
|||
border-radius: 15.27rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
.img {
|
||||
width: 100%;
|
||||
height: 216.31rpx;
|
||||
background-color: #D8D8D8;
|
||||
background-color: #F2F2F2;
|
||||
border-radius: 15.27rpx 15.27rpx 0rpx 0rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.item-info {
|
||||
width: 100%;
|
||||
height: 109.07rpx;
|
||||
position: relative;
|
||||
padding: 8rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 19.08rpx;
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.item-bottom {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 12rpx;
|
||||
left: 0;
|
||||
padding: 0 13rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.item-price {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.price-symbol-small {
|
||||
font-size: 15.27rpx;
|
||||
margin-top: 6rpx;
|
||||
color: #FF6A6A;
|
||||
}
|
||||
|
||||
.price-value-small {
|
||||
font-size: 22.9rpx;
|
||||
font-weight: bold;
|
||||
color: #FF6A6A;
|
||||
}
|
||||
|
||||
.item-count {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.count-text-small {
|
||||
font-size: 15.27rpx;
|
||||
color: #6C6C6C;
|
||||
}
|
||||
|
||||
.box-icon-small {
|
||||
width: 17.39rpx;
|
||||
height: 17.39rpx;
|
||||
margin-left: 7rpx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.view-list {
|
||||
|
|
@ -186,4 +298,118 @@
|
|||
height: 850rpx;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
width: 100%;
|
||||
height: 261.45rpx;
|
||||
background-color: #FFFFFF;
|
||||
margin-bottom: 21rpx;
|
||||
border-radius: 15.27rpx;
|
||||
position: relative;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
.product-image-container {
|
||||
width: 216.31rpx;
|
||||
height: 216.31rpx;
|
||||
background-color: #F2F2F2;
|
||||
border-radius: 15.27rpx;
|
||||
position: absolute;
|
||||
left: 22rpx;
|
||||
top: 22rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.product-name {
|
||||
width: 350rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
left: 269rpx;
|
||||
top: 44rpx;
|
||||
font-size: 26.72rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.product-count {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
left: 269rpx;
|
||||
top: 95rpx;
|
||||
align-items: center;
|
||||
|
||||
.count-text {
|
||||
font-size: 15.27rpx;
|
||||
color: #6C6C6C;
|
||||
}
|
||||
|
||||
.box-icon {
|
||||
width: 17.39rpx;
|
||||
height: 17.39rpx;
|
||||
margin-left: 7rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.product-price {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
left: 269rpx;
|
||||
bottom: 44rpx;
|
||||
align-items: center;
|
||||
|
||||
.price-symbol {
|
||||
font-size: 19.08rpx;
|
||||
margin-top: 15rpx;
|
||||
color: #FF6A6A;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 34.35rpx;
|
||||
font-weight: bold;
|
||||
color: #FF6A6A;
|
||||
}
|
||||
}
|
||||
|
||||
.buy-button {
|
||||
width: 89.22rpx;
|
||||
height: 42rpx;
|
||||
border-radius: 20.99rpx;
|
||||
border: 1rpx solid #FF6A6A;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 38rpx;
|
||||
bottom: 38rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
background-color: #FF6A6A;
|
||||
|
||||
.buy-text {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.buy-text {
|
||||
font-size: 19.08rpx;
|
||||
color: #FF6A6A;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
63
pages/mall/order-list.vue
Normal file
63
pages/mall/order-list.vue
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<!-- 滑动切换选项卡演示(标准写法) -->
|
||||
<template>
|
||||
<page-no-container ref="pageContainer" title="我的订单" :showBack="true">
|
||||
<!-- 使用z-paging-swiper为根节点可以免计算高度 -->
|
||||
<z-paging-swiper :fixed="false">
|
||||
<!-- 需要固定在顶部不滚动的view放在slot="top"的view中 -->
|
||||
<!-- 注意!此处的z-tabs为独立的组件,可替换为第三方的tabs,若需要使用z-tabs,请在插件市场搜索z-tabs并引入,否则会报插件找不到的错误 -->
|
||||
<template #top>
|
||||
<z-tabs ref="tabs" :list="tabList" :current="current" @change="tabsChange" />
|
||||
</template>
|
||||
<!-- swiper必须设置height:100%,因为swiper有默认的高度,只有设置高度100%才可以铺满页面 -->
|
||||
<swiper class="swiper" :current="current" @transition="swiperTransition"
|
||||
@animationfinish="swiperAnimationfinish">
|
||||
<swiper-item class="swiper-item" v-for="(item, index) in tabList" :key="index">
|
||||
<order-list-item :tabIndex="index" :currentIndex="current" :responseCallback="responseCallback">
|
||||
|
||||
</order-list-item>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</z-paging-swiper>
|
||||
|
||||
</page-no-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
const paging = ref(null);
|
||||
const tabs = ref(null);
|
||||
|
||||
const current = ref(0);
|
||||
const tabList = ref(['全部', '待发货', '待收货', '评价']);
|
||||
|
||||
|
||||
// tabs通知swiper切换
|
||||
const tabsChange = (index) => {
|
||||
current.value = index;
|
||||
}
|
||||
|
||||
// swiper滑动中
|
||||
const swiperTransition = (e) => {
|
||||
tabs.value.setDx(e.detail.dx);
|
||||
}
|
||||
|
||||
// swiper滑动结束
|
||||
const swiperAnimationfinish = (e) => {
|
||||
current.value = e.detail.current;
|
||||
tabs.value.unlockDx();
|
||||
}
|
||||
const responseCallback = (params) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({ list: [] });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.swiper {
|
||||
height:90vh;
|
||||
}
|
||||
|
||||
.swiper-item {
|
||||
}
|
||||
</style>
|
||||
73
pages/me/account-deletion.vue
Normal file
73
pages/me/account-deletion.vue
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<!-- 详情 -->
|
||||
<template>
|
||||
<page-base-container ref="pageContainer" title="注销账号" show-back>
|
||||
<view class="deletion-info">
|
||||
<text>尊敬的用户,您正式开始有关帐号的注销流程前,我们先为您做出如下特别说明:注销本帐号后,除法律法规另有规定外,您在该帐号下的个人信息将进行删除,且此前已关联该帐号的相关产品与服务将不再关联。
|
||||
该帐号一旦注销完成,将无法恢复,请您谨慎操作。若您经过慎重考虑后仍执意决定注销本帐号的,请您务必先行仔细阅读和充分理解本《帐号注销协议》
|
||||
("本协议"),并同意本协议全部内容。您按照我们的注销操作流程开始注销流程的,或者您勾选本注销协议并开始下一步操作的,均视为您已经同意和遵守本协议全部内容。为确保注销的顺利完成,请您在锁定期内不要登录和使用该帐号,否则视为您撤销注销该帐号。注销流程大概需要7-15个工作日!如您在注销后需要再次使用我们的服务的,欢迎您重新注册登录。</text>
|
||||
</view>
|
||||
<view class="button-container">
|
||||
<button type="primary" @click="onDelete" class="delete-button">申请注销账号</button>
|
||||
</view>
|
||||
</page-base-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { deleteAccount } from '@/common/server/user';
|
||||
let pageContainer = ref(null);
|
||||
|
||||
const onDelete = async () => {
|
||||
const { confirm } = await pageContainer.value.getPagePopup().showModal('确认注销',
|
||||
'你确定要注销当前账户吗? 将在7-15个工作日内注销本账户!');
|
||||
if (confirm) {
|
||||
try {
|
||||
// 调用注销账号接口
|
||||
const result = await deleteAccount();
|
||||
console.log("result", result);
|
||||
if (result.status == 1) {
|
||||
uni.showToast({
|
||||
title: '注销申请已提交',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.message || '注销失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: '操作失败,请稍后重试',
|
||||
icon: 'none'
|
||||
});
|
||||
console.error('账号注销失败:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.deletion-info {
|
||||
margin: 50rpx 40rpx 0;
|
||||
background-color: #f8f8f8;
|
||||
padding: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 40rpx;
|
||||
line-height: 1.6;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background: #ff4d4f;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
}
|
||||
</style>
|
||||
300
pages/me/account-login.vue
Normal file
300
pages/me/account-login.vue
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
<!-- 详情 -->
|
||||
<template>
|
||||
<page-no-container ref="pageContainer" title="登录" show-back :showLoading="pageLoading">
|
||||
<view class="page">
|
||||
<view class="welcome">
|
||||
<image class="welcome__logo" src="/static/app-plus/icon_108.png" mode="aspectFit">
|
||||
</image>
|
||||
</view>
|
||||
<page-line width="90%" />
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<view class="login-form">
|
||||
<!-- 手机号输入 -->
|
||||
<view class="input-item">
|
||||
<input type="number" v-model="phone" placeholder="请输入手机号" maxlength="11" />
|
||||
</view>
|
||||
|
||||
<!-- 验证码输入 -->
|
||||
<view class="input-item code-input">
|
||||
<input type="number" v-model="code" placeholder="请输入验证码" maxlength="4" />
|
||||
<view class="send-code-btn" @click="sendCode">
|
||||
{{ countdown > 0 ? `重新发送(${countdown}s)` : '发送验证码' }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 协议勾选 -->
|
||||
<view class="agreement">
|
||||
<checkbox :checked="isAgree" @click="toggleAgree" color="#999" />
|
||||
<text class="agreement-text" @click="toggleAgree">我已阅读并同意 <text class="link"
|
||||
@click.stop="openAgreement('user')">《用户协议》</text> 和 <text class="link"
|
||||
@click.stop="openAgreement('privacy')">《隐私政策》</text></text>
|
||||
</view>
|
||||
|
||||
<!-- 按钮组 -->
|
||||
<view class="button-group">
|
||||
<button class="btn btn-refuse" @click="handleRefuse">拒绝</button>
|
||||
<button class="btn btn-login" @click="handleLogin">登录</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</page-no-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onUnmounted } from 'vue';
|
||||
import { sendSms } from '@/common/server/other';
|
||||
import { mobileLogin } from '@/common/server/user';
|
||||
import { setCache } from '@/common/system/cacheService';
|
||||
let redirectUrl = "";
|
||||
onLoad((options) => {
|
||||
console.log(options, "optionsoptionsoptionsoptionsoptions");
|
||||
if (options.page) {
|
||||
redirectUrl = decodeURIComponent(options.page);
|
||||
}
|
||||
});
|
||||
let pageLoading = ref(false);
|
||||
let pageContainer = ref(null);
|
||||
let phone = ref('');
|
||||
let code = ref('');
|
||||
let isAgree = ref(false);
|
||||
let countdown = ref(0);
|
||||
let timer = null;
|
||||
// 切换协议勾选状态
|
||||
const toggleAgree = () => {
|
||||
isAgree.value = !isAgree.value;
|
||||
console.log('协议状态已切换:', isAgree.value);
|
||||
};
|
||||
|
||||
// 打开协议页面
|
||||
const openAgreement = (type) => {
|
||||
console.log('打开协议页面:', type);
|
||||
// navigateTo(`/pages/other/agreement?type=${type}`);
|
||||
yds.navigateToAgreement(type)
|
||||
};
|
||||
|
||||
// 发送验证码
|
||||
const sendCode = async () => {
|
||||
if (countdown.value > 0) {
|
||||
return;
|
||||
};
|
||||
|
||||
console.log('发送验证码前检查协议:', isAgree.value);
|
||||
|
||||
if (!isAgree.value) {
|
||||
yds.showToast('请同意用户协议和隐私政策');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!phone.value || phone.value.length !== 11) {
|
||||
yds.showToast('请输入正确的手机号');
|
||||
return;
|
||||
}
|
||||
pageLoading.value = true;
|
||||
// 发送验证码逻辑,这里模拟发送
|
||||
countdown.value = 60;
|
||||
timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const isSuccess = await sendSms(phone.value);
|
||||
if (!isSuccess) {
|
||||
showToast('发送失败');
|
||||
return;
|
||||
}
|
||||
pageLoading.value = false;
|
||||
showToast('验证码已发送!');
|
||||
|
||||
};
|
||||
|
||||
// 登录
|
||||
const handleLogin = async () => {
|
||||
console.log('登录前检查协议:', isAgree.value);
|
||||
|
||||
if (!phone.value || phone.value.length !== 11) {
|
||||
showToast('请输入正确的手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!code.value || code.value.length !== 4) {
|
||||
showToast('请输入正确的验证码');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAgree.value) {
|
||||
showToast('请同意用户协议和隐私政策');
|
||||
return;
|
||||
}
|
||||
pageLoading.value = true;
|
||||
const res = await mobileLogin(phone.value, code.value);
|
||||
if (res.status != 1) {
|
||||
showToast('登录失败');
|
||||
return;
|
||||
}
|
||||
// 登录逻辑
|
||||
showToast('登录成功');
|
||||
setCache('token', res.data);
|
||||
await sleep(500);
|
||||
pageLoading.value = false;
|
||||
if (redirectUrl != "") {
|
||||
if (!redirectUrl.startsWith("/")) {
|
||||
redirectUrl = "/" + redirectUrl;
|
||||
}
|
||||
// 获取当前页面栈
|
||||
const pages = getCurrentPages();
|
||||
|
||||
// 检查是否有上一页,以及redirect URL是否与上一页相同
|
||||
if (pages.length > 1) {
|
||||
const prevPage = pages[pages.length - 2];
|
||||
// 构建完整的上一页路径(带参数)
|
||||
let prevPageUrl = '/' + prevPage.route;
|
||||
if (prevPage.options && Object.keys(prevPage.options).length > 0) {
|
||||
const paramStr = Object.keys(prevPage.options)
|
||||
.map(key => `${key}=${prevPage.options[key]}`)
|
||||
.join('&');
|
||||
prevPageUrl += '?' + paramStr;
|
||||
}
|
||||
|
||||
// 比较redirect URL和上一页URL
|
||||
const redirectUrlPath = redirectUrl.split('?')[0];
|
||||
const prevPageUrlPath = prevPageUrl.split('?')[0];
|
||||
|
||||
if (redirectUrlPath === prevPageUrlPath) {
|
||||
// 如果路径相同,直接返回上一页
|
||||
console.log('重定向URL与上一页相同,直接返回');
|
||||
uni.navigateBack();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
yds.navigateTo(redirectUrl);
|
||||
} else {
|
||||
yds.navigateTo('/pages/me/me');
|
||||
}
|
||||
};
|
||||
|
||||
// 拒绝
|
||||
const handleRefuse = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
// 组件卸载时清除定时器
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
|
||||
&__logo {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
margin-top: 5vh;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&__animation {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
}
|
||||
|
||||
&__tips {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// 登录表单样式
|
||||
.login-form {
|
||||
padding: 20px;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.input-item {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 15px 0;
|
||||
margin-bottom: 15px;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.code-input {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.send-code-btn {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.agreement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 20px 0;
|
||||
|
||||
.agreement-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 40px;
|
||||
|
||||
.btn {
|
||||
height: 45px;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-refuse {
|
||||
flex: 1;
|
||||
background-color: #c0ff00;
|
||||
color: #333;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
flex: 2;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
335
pages/me/me.vue
335
pages/me/me.vue
|
|
@ -1,126 +1,253 @@
|
|||
<template>
|
||||
<view class="content">
|
||||
<view class="" style="width: 100%; height: 225.19rpx; background-color: white; position: relative;">
|
||||
<view class="title">
|
||||
我的
|
||||
</view>
|
||||
<view class="header-container">
|
||||
<view class="title">我的</view>
|
||||
</view>
|
||||
|
||||
<view class="" style="width: 688.93rpx; display: flex; flex-direction: column; margin: 34rpx auto;">
|
||||
|
||||
<view class="" style="width: 100%; height: 80.15rpx; position: relative;">
|
||||
|
||||
<view class="" style="width: 80.15rpx; height: 80.15rpx; background-color: #D8D8D8;"></view>
|
||||
|
||||
<view class=""
|
||||
style="position: absolute; left: 103rpx; top: 11rpx; font-size: 22.9rpx; color: #333333;">
|
||||
666688888
|
||||
<view class="main-container">
|
||||
<!-- 用户信息区域 -->
|
||||
<view class="user-info-section" v-if="userInfo">
|
||||
<view class="avatar">
|
||||
<image :src="userInfo.userIcon" mode="aspectFill"
|
||||
style="width: 80.15rpx;height: 80.15rpx;border-radius: 10rpx;"></image>
|
||||
</view>
|
||||
|
||||
<view class=""
|
||||
style="position: absolute; left: 103rpx; top: 50rpx; font-size: 19.08rpx; color: #8A8A8A;">
|
||||
已陪你走过了100天
|
||||
<view class="user-id">{{ userInfo.username }}</view>
|
||||
<view class="user-days">已陪你走过了{{ userInfo.days }}天</view>
|
||||
</view>
|
||||
<view class="user-info-section" v-else @click="navigateToAccountLogin();">
|
||||
<view class="avatar">
|
||||
</view>
|
||||
<view class="user-id">点击登录</view>
|
||||
<view class="user-days"></view>
|
||||
</view>
|
||||
<!-- 订单状态区域 -->
|
||||
<view class="order-status-section">
|
||||
<view class="status-item" @click="navigateTo('/pages/mall/order-list');">
|
||||
<text class="status-count">0</text>
|
||||
<text class="status-label">待发货</text>
|
||||
</view>
|
||||
<view class="status-item">
|
||||
<text class="status-count">0</text>
|
||||
<text class="status-label">待收货</text>
|
||||
</view>
|
||||
<view class="status-item">
|
||||
<text class="status-count">0</text>
|
||||
<text class="status-label">待评价</text>
|
||||
</view>
|
||||
<view class="status-item">
|
||||
<text class="status-count">0</text>
|
||||
<text class="status-label">退款售后</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class=""
|
||||
style="width: 100%; height: 143.13rpx; background-color: white; border-radius: 15.27rpx; margin-top: 36rpx; display: flex; flex-direction: row; justify-content: space-around;">
|
||||
|
||||
<view class=""
|
||||
style="display: flex; flex-direction: column; width: 100rpx; height: 100%; align-items: center; justify-content: center;">
|
||||
<text style="font-size: 41.98rpx; color: #333333;">0</text>
|
||||
<text style="font-size: 19.08rpx; color: #8A8A8A;">待发货</text>
|
||||
<!-- 功能列表区域 -->
|
||||
<view class="function-list-section">
|
||||
<view v-for="item in itemList" :key="item.id" class="function-item"
|
||||
@click="item.onClick ? item.onClick(item) : null">
|
||||
<text class="function-title">{{ item.title }}</text>
|
||||
<image src="/static/ic_arrow.png" class="arrow-icon"></image>
|
||||
</view>
|
||||
<view class=""
|
||||
style="display: flex; flex-direction: column; width: 100rpx; height: 100%; align-items: center; justify-content: center;">
|
||||
<text style="font-size: 41.98rpx; color: #333333;">0</text>
|
||||
<text style="font-size: 19.08rpx; color: #8A8A8A;">待发货</text>
|
||||
</view>
|
||||
|
||||
<view class=""
|
||||
style="display: flex; flex-direction: column; width: 100rpx; height: 100%; align-items: center; justify-content: center;">
|
||||
<text style="font-size: 41.98rpx; color: #333333;">0</text>
|
||||
<text style="font-size: 19.08rpx; color: #8A8A8A;">待发货</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="" style="width: 100%; margin-top: 36rpx;">
|
||||
|
||||
<view v-for="(item,index) in itemList" class=""
|
||||
style="width: 100%; height: 82.06rpx; background-color: white; border-radius: 15.27rpx;display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 15rpx; align-items: center;">
|
||||
<text style="margin-left: 27rpx; font-size: 22.9rpx; color: #333333;">{{item.title}}</text>
|
||||
<image src="/static/ic_arrow.png" style="width: 10.67rpx; height: 19.66rpx; margin-right: 25rpx;">
|
||||
</image>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
<page-popup ref="_pagePopup" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
itemList: [{
|
||||
id: 1,
|
||||
title: "消费记录",
|
||||
}, {
|
||||
id: 1,
|
||||
title: "我的收藏",
|
||||
}, {
|
||||
id: 1,
|
||||
title: "优惠券",
|
||||
}, {
|
||||
id: 1,
|
||||
title: "加入福利群",
|
||||
}, {
|
||||
id: 1,
|
||||
title: "用户协议",
|
||||
}, {
|
||||
id: 1,
|
||||
title: "退出登录",
|
||||
}, {
|
||||
id: 1,
|
||||
title: "注销账号",
|
||||
},
|
||||
<script setup>
|
||||
import { getUserInfo } from '@/common/server/user';
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { navigateTo, navigateToAccountLogin } from '@/common/system/router';
|
||||
import { removeCache } from '@/common/system/cacheService';
|
||||
let _pagePopup = ref(null);
|
||||
const itemList = ref([
|
||||
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
let userInfo = ref(null);
|
||||
|
||||
onShow(async () => {
|
||||
yds.showLoading();
|
||||
const res = await getUserInfo();
|
||||
console.log("res", res);
|
||||
userInfo.value = res;
|
||||
loadMenu();
|
||||
yds.hideLoading();
|
||||
});
|
||||
const loadMenu = async () => {
|
||||
const menuList = [
|
||||
{
|
||||
id: 1,
|
||||
title: "消费记录",
|
||||
}, {
|
||||
id: 2,
|
||||
title: "我的收藏",
|
||||
}, {
|
||||
id: 3,
|
||||
title: "优惠券",
|
||||
}, {
|
||||
id: 4,
|
||||
title: "加入福利群",
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
{
|
||||
id: 5,
|
||||
title: "用户协议",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: "隐私政策",
|
||||
},
|
||||
];
|
||||
if (yds.userInfo.isAccountLogin()) {
|
||||
menuList.push({
|
||||
id: 7,
|
||||
title: "退出登录",
|
||||
onClick: async (res) => {
|
||||
const { confirm } = await _pagePopup.value.showModal('退出登录', '确定退出登录吗?');
|
||||
if (confirm) {
|
||||
yds.userInfo.logout();
|
||||
userInfo.value = null;
|
||||
loadMenu();
|
||||
navigateTo("/pages/me/account-login");
|
||||
}
|
||||
}
|
||||
}, {
|
||||
id: 8,
|
||||
title: "注销账号",
|
||||
onClick: (res) => {
|
||||
navigateTo("/pages/me/account-deletion");
|
||||
}
|
||||
});
|
||||
}
|
||||
itemList.value = menuList;
|
||||
}
|
||||
const goToPage = (route) => {
|
||||
uni.navigateTo({
|
||||
url: route
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #F7F7F7;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #F7F7F7;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 32.44rpx;
|
||||
position: absolute;
|
||||
bottom: 30rpx;
|
||||
}
|
||||
.header-container {
|
||||
width: 100%;
|
||||
height: 225.19rpx;
|
||||
background-color: white;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 32.44rpx;
|
||||
position: absolute;
|
||||
bottom: 30rpx;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
width: 688.93rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 34rpx auto;
|
||||
}
|
||||
|
||||
// 用户信息区域样式
|
||||
.user-info-section {
|
||||
width: 100%;
|
||||
height: 80.15rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 80.15rpx;
|
||||
height: 80.15rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #D8D8D8;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
position: absolute;
|
||||
left: 103rpx;
|
||||
top: 11rpx;
|
||||
font-size: 22.9rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.user-days {
|
||||
position: absolute;
|
||||
left: 103rpx;
|
||||
top: 50rpx;
|
||||
font-size: 19.08rpx;
|
||||
color: #8A8A8A;
|
||||
}
|
||||
|
||||
// 订单状态区域样式
|
||||
.order-status-section {
|
||||
width: 100%;
|
||||
height: 143.13rpx;
|
||||
background-color: white;
|
||||
border-radius: 15.27rpx;
|
||||
margin-top: 36rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100rpx;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.status-count {
|
||||
font-size: 41.98rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 19.08rpx;
|
||||
color: #8A8A8A;
|
||||
}
|
||||
|
||||
// 功能列表区域样式
|
||||
.function-list-section {
|
||||
width: 100%;
|
||||
margin-top: 36rpx;
|
||||
}
|
||||
|
||||
.function-item {
|
||||
width: 100%;
|
||||
height: 82.06rpx;
|
||||
background-color: white;
|
||||
border-radius: 15.27rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.function-title {
|
||||
margin-left: 27rpx;
|
||||
font-size: 22.9rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
width: 10.67rpx;
|
||||
height: 19.66rpx;
|
||||
margin-right: 25rpx;
|
||||
}
|
||||
</style>
|
||||
41
pages/other/agreement.vue
Normal file
41
pages/other/agreement.vue
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<!-- 详情 -->
|
||||
<template>
|
||||
<page-base-container ref="pageContainer" :title="pageTitle" show-back>
|
||||
<view class="agreement-content">
|
||||
<rich-text :nodes="agreementContent"></rich-text>
|
||||
</view>
|
||||
</page-base-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getAgreement } from '@/common/server/other';
|
||||
let pageContainer = ref(null);
|
||||
onLoad((options) => {
|
||||
if (options.type) {
|
||||
loadData(options.type);
|
||||
}
|
||||
});
|
||||
let pageTitle = ref("");
|
||||
let agreementContent = ref("");
|
||||
const loadData = async (type) => {
|
||||
if (type == "user") {
|
||||
pageTitle.value = "用户协议";
|
||||
} else if (type == "privacy") {
|
||||
pageTitle.value = "隐私政策";
|
||||
}
|
||||
const res = await getAgreement(type);
|
||||
if (res) {
|
||||
agreementContent.value = res.content;
|
||||
if (pageTitle.value == "") {
|
||||
pageTitle.value = res.title;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.agreement-content {
|
||||
width: 95%;
|
||||
padding: 20rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -2,13 +2,32 @@ import { defineConfig } from 'vite';
|
|||
import uni from "@dcloudio/vite-plugin-uni";
|
||||
import AutoImport from 'unplugin-auto-import/vite';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
uni(),
|
||||
// 自动导入 Vue 相关 API(如 ref, reactive)
|
||||
AutoImport({
|
||||
imports: ['vue'],
|
||||
imports: ['vue',
|
||||
{
|
||||
'@dcloudio/uni-app': [
|
||||
'onLoad', // 页面加载时触发
|
||||
'onShow', // 页面显示时触发
|
||||
'onHide', // 页面隐藏时触发
|
||||
'onUnload', // 页面卸载时触发
|
||||
],
|
||||
},
|
||||
{
|
||||
'@/common/utils': [
|
||||
'showToast', // 明确列出方法名(推荐)
|
||||
'sleep',
|
||||
],
|
||||
},
|
||||
{
|
||||
'@/common/yds': [
|
||||
['yds'], // 从 '@/common/index' 导入 yds 对象
|
||||
],
|
||||
}
|
||||
],
|
||||
}),
|
||||
// 自动导入组件
|
||||
Components({
|
||||
|
|
@ -20,4 +39,12 @@ export default defineConfig({
|
|||
dts: true, // 生成类型声明文件(可选)
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user