This commit is contained in:
zpc 2025-06-21 23:03:06 +08:00
parent 4a24619622
commit 73cfc42a38
26 changed files with 2753 additions and 188 deletions

View File

@ -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
View 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;
}

View File

@ -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;
}

View File

@ -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
View 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');
}

View File

@ -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
View 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
View File

@ -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']

View File

@ -34,6 +34,7 @@ export default {
justify-content: center;
animation: emptyFadeIn 0.5s ease-out;
// background-color: #F7F7F7;
}
.loading-image {

View File

@ -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,

View 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({
// indexswiper
tabIndex: {
type: Number,
default: 0
},
// swiperindex
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(() => {
// (pageNorefreshreload)
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>

View 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>

View File

@ -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);

View 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>

View 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>

View 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
});
// Promiseresolvereject
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>

View File

@ -0,0 +1,4 @@
// z-tabs全局配置文件注意避免更新时此文件被覆盖若被覆盖可在此文件中右键->点击本地历史记录,找回覆盖前的配置
export default {
}

View 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标签全平台兼容支持nvuevue3
* @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:当前切换到的indexvalue:当前切换到的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)
},
//listscrollCount()
scrollCount: {
type: [Number, String],
default: _gc('scrollCount',5)
},
//z-tabs
tabsStyle: {
type: Object,
default: function() {
return _gc('tabsStyle',{})
}
},
//tab0rpx100"100px""100rpx"
tabWidth: {
type: [Number, String],
default: _gc('tabWidth',0)
},
//rpx100"100px""100rpx"
barWidth: {
type: [Number, String],
default: _gc('barWidth',45)
},
//rpx100"100px""100rpx"
barHeight: {
type: [Number, String],
default: _gc('barHeight',8)
},
//swiperrpx100"100px""100rpx""750rpx"
swiperWidth: {
type: [Number, String],
default: _gc('swiperWidth',750)
},
//widthheightbarWidthbarHeight
barStyle: {
type: Object,
default: function() {
return _gc('barStyle',{});
}
},
//tabsrpx100"100px""100rpx"
bottomSpace: {
type: [Number, String],
default: _gc('bottomSpace',8)
},
//tabswipertabsetDxlinetab线worm
barAnimateMode: {
type: String,
default: _gc('barAnimateMode','line')
},
//listitemname()key
nameKey: {
type: String,
default: _gc('nameKey','name')
},
//listitemvaluekey
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-tabsrpx
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@transitiondot
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@animationfinishz-tabssetDxsetDxunlockDx
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;
},
//dxswipersetDx
_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]);
}
}
},
//nodebottomX
_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);
});
});
},
//badgecount
_formatCount(count) {
if (!count) return '';
if (count > this.badgeMaxCount) {
return this.badgeMaxCount + '+';
}
return count.toString();
},
//pxrpxpx
_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>

View File

@ -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": {

View File

@ -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
View 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(['全部', '待发货', '待收货', '评价']);
// tabsswiper
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>

View 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
View 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 URLURL
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>

View File

@ -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
View 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>

View File

@ -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,
},
},
}
});