This commit is contained in:
zpc 2025-09-08 12:47:02 +08:00
parent 53cb4a7b89
commit 4e191d6c39
18 changed files with 1246 additions and 194 deletions

View File

@ -4,8 +4,21 @@ import * as utils3 from './system/router';
import * as utils4 from './system/request';
// 动态合并所有导出到 yds 对象
export const com = {
...utils1,
...utils2,
...utils3,
...utils4,
...utils1,
...utils2,
...utils3,
...utils4,
};
export const useLogin = async () => {
return new Promise((resolve) => {
uni.login({
provider: 'weixin', //使用微信登录
success: function (loginRes) {
// console.log(loginRes);
resolve(loginRes.code)
}
});
});
}

View File

@ -13,9 +13,12 @@ export const preloadConfigData = async () => {
configData.value = {
config: res
};
console.log("configData.value",configData.value);
console.log("configData.value", configData.value);
};
export const getConfigData = () => {
export const getConfigData = async () => {
if (configData.value == null) {
await preloadConfigData();
}
return configData.value;
}

View File

@ -0,0 +1,28 @@
import request from '@/common/system/request';
/**
* 获取单个文章内容
* @param {number} id 文章ID
* @returns {Promise<any>}
*/
export const getArticleDetail = async (id) => {
const res = await request.getOrCache("Article/GetArticleDetail", { id });
if (res.code == 0) {
return res.data;
}
return null;
}
/**
* 获取通知列表
* @param {Object} params 查询参数
* @returns {Promise<any>}
*/
export const noticeList = async (params) => {
const res = await request.post("Article/NoticeList", params);
if (res.code == 0) {
return res.data;
}
return null;
}

View File

@ -1,11 +1,12 @@
import request from '@/common/system/request';
/**
* 查询用户是否可以成为分销商
* 匿名登录
* @returns {Promise<any>}
*/
export const getDistributionInfo = async () => {
const res = await request.post("Distribution/Info");
export const getAnonymousLogin = async (code) => {
const res = await request.post("user/UseAnonymousLogin", { code: code });
if (res.code == 0) {
return res.data;
}
@ -13,12 +14,13 @@ export const getDistributionInfo = async () => {
}
/**
* 申请成为分销商接口
* @param {Object} data 分销商申请数据
* @returns {Promise<any>}
* 微信小程序授权拉取手机号码并登录
* @param {*} code
* @param {*} sessionAuthId
* @returns
*/
export const applyDistribution = async (data) => {
const res = await request.post("Distribution/ApplyDistribution", data);
export const ueWxPhoneNumberLogin = async (code, sessionAuthId) => {
const res = await request.post("user/UseWxPhoneNumberLogin", { code: code, sessionAuthId: sessionAuthId });
if (res.code == 0) {
return res.data;
}
@ -26,12 +28,13 @@ export const applyDistribution = async (data) => {
}
/**
* 获取分销商排行
* @param {Object} params 分页参数
* @returns {Promise<any>}
* 微信小程序匿名授权登录手机号已授权过
* @param {*} code
* @param {*} sessionAuthId
* @returns
*/
export const getDistributionRanking = async (params) => {
const res = await request.post("Distribution/GetDistributionRanking", params);
export const useWxAnonymousLogin = async (sessionAuthId) => {
const res = await request.post("user/UseWxAnonymousLogin", { sessionAuthId: sessionAuthId });
if (res.code == 0) {
return res.data;
}
@ -39,25 +42,13 @@ export const getDistributionRanking = async (params) => {
}
/**
* 获取我的订单统计
* @returns {Promise<any>}
* 获取用户信息
* @returns
*/
export const getOrderSum = async () => {
const res = await request.post("Distribution/GetOrderSum");
export const getUserInfo = async () => {
const res = await request.post("user/GetUserInfo");
if (res.code == 0) {
return res.data;
}
return null;
}
/**
* 获取我的下级用户数量
* @returns {Promise<any>}
*/
export const getTeamSum = async () => {
const res = await request.post("Distribution/GetTeamSum");
if (res.code == 0) {
return res.data;
}
return null;
}
}

0
common/server/login.js Normal file
View File

18
common/server/user.js Normal file
View File

@ -0,0 +1,18 @@
import { getUserInfo } from '@/common/server/interface/user'
import { ref } from 'vue'
export var userInfo = ref(null);
export const loadUserInfo = async () => {
const res = await getUserInfo();
if (res == null) {
userInfo.value = null;
uni.removeStorageSync('tokenInfo');
uni.removeStorageSync('userInfo');
return;
}
userInfo.value = res;
uni.setStorageSync('userInfo', res);
}

View File

@ -101,7 +101,7 @@ class request {
return new Promise((resolve, reject) => {
// 使用传入的method而不是重新声明
const requestMethod = method.toUpperCase();
const token = uni.getStorageSync('token');
let data = { ...fromData }; // 创建数据的深拷贝,避免修改原数据
// 构建请求URL和提取主机名
@ -122,9 +122,10 @@ class request {
const header = {
'content-type': 'application/json'
};
if(token!=null&&token!=""){
header['Authorization']='Bearer ' + token;
}
const tokenInfo = uni.getStorageSync('tokenInfo');
if (tokenInfo != null && tokenInfo != "") {
header['Authorization'] = 'Bearer ' + tokenInfo.token;
}
const startDate = Date.now();

View File

@ -29,5 +29,6 @@ export const navigateToAccountLogin = (page = "") => {
* @param {String} type 协议类型
*/
export const navigateToAgreement = (type) => {
// navigateTo(`/pages/other/agreement?type=${type}`);
navigateTo(`/pages/other/agreement?type=${type}`);
};

4
components.d.ts vendored
View File

@ -8,8 +8,12 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
Container: typeof import('./components/com/page/container.vue')['default']
MahjongCard: typeof import('./components/index/MahjongCard.vue')['default']
NoData: typeof import('./components/com/page/no-data.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']
UniStatusBar: typeof import('./components/uni-nav-bar/uni-status-bar.vue')['default']
}
}

View File

@ -0,0 +1,132 @@
<!-- 详情 -->
<template>
<view>
<z-paging ref="paging" refresher-only @onRefresh="onRefresh">
<template #top>
<uniNavBar :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">
</view>
<view class="page-container__loading" v-if="showLoading" @click.stop="mengban">
</view>
<slot></slot>
</view>
</view>
</z-paging>
</view>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import {
uniNavBar
} from '@/components/uni-nav-bar/uni-nav-bar.vue'
const props = defineProps({
title: {
type: String,
default: ''
},
showBack: {
type: Boolean,
default: false
},
showNotData: {
type: Boolean,
default: false
},
refresh: {
type: Function,
default: function(refresh) {
// refresh.complete();
}
},
showLoading: {
type: Boolean,
default: false
}
});
let paging = ref(null);
const leftIcon = computed(() => {
if (props.showBack) {
return "/static/back.png";
}
});
const onClickLeft = () => {
if (props.showBack) {
uni.navigateBack();
}
}
const onRefresh = () => {
if (props.refresh) {
props.refresh(paging.value);
} else {
paging.value.complete();
}
}
const getPaging = () => {
return paging.value;
}
const mengban = () => {
}
defineExpose({
getPaging
})
</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,58 @@
<template>
<view class="empty-state">
<image class="empty-image" :src="imageSrc" mode="aspectFit"></image>
<text class="empty-text">{{ message }}</text>
</view>
</template>
<script setup>
defineProps({
// 使kong.png
imageSrc: {
type: String,
default: '/static/no_content.png'
},
//
message: {
type: String,
default: '暂无数据'
}
});
</script>
<style lang="scss">
.empty-state {
width: 100%;
height: 100%;
// border: 1px solid red;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
animation: emptyFadeIn 0.5s ease-out;
// background-color: #F7F7F7;
}
.empty-image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
.empty-text {
font-size: 28rpx;
color: #999999;
}
@keyframes emptyFadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -0,0 +1,360 @@
<template>
<view class="uni-navbar" :class="{ 'uni-dark': dark, 'uni-nvue-fixed': fixed }">
<view class="uni-navbar__content"
:class="{ 'uni-navbar--fixed': fixed, 'uni-navbar--shadow': shadow, 'uni-navbar--border': border }"
:style="{ 'background-color': themeBgColor, 'border-bottom-color': themeColor }">
<status-bar v-if="statusBar" />
<view :style="{ color: themeColor, backgroundColor: themeBgColor, height: navbarHeight }"
class="uni-navbar__header">
<view @tap="onClickLeft" class="uni-navbar__header-btns uni-navbar__header-btns-left"
:style="{ width: leftIconWidth }">
<slot name="left">
<view class="uni-navbar__content_view" v-if="leftIcon.length > 0">
<image :src="leftIcon" mode="aspectFill" style="width: 22px; height: 22px;" />
</view>
<view :class="{ 'uni-navbar-btn-icon-left': !leftIcon.length > 0 }" class="uni-navbar-btn-text"
v-if="leftText.length">
<text :style="{ color: themeColor, fontSize: '12px' }">{{ leftText }}</text>
</view>
</slot>
</view>
<view class="uni-navbar__header-container " @tap="onClickTitle">
<slot>
<view class="uni-navbar__header-container-inner" v-if="title.length > 0">
<text class="uni-nav-bar-text uni-ellipsis-1" :style="{ color: themeColor }">{{ title
}}</text>
</view>
</slot>
</view>
<view @click="onClickRight" class="uni-navbar__header-btns uni-navbar__header-btns-right"
:style="{ width: rightIconWidth }">
<slot name="right">
<view v-if="rightIcon.length">
<image :src="rightIcon" mode="aspectFill" style="width: 22px; height: 22px;" />
</view>
<view class="uni-navbar-btn-text" v-if="rightText.length && !rightIcon.length">
<text class="uni-nav-bar-right-text" :style="{ color: themeColor }">{{ rightText }}</text>
</view>
</slot>
</view>
</view>
</view>
<!-- #ifndef APP-NVUE -->
<view class="uni-navbar__placeholder" v-if="fixed">
<status-bar v-if="statusBar" />
<view class="uni-navbar__placeholder-view" :style="{ height: navbarHeight }" />
</view>
<!-- #endif -->
</view>
</template>
<script>
import statusBar from "./uni-status-bar.vue";
const getVal = (val) => typeof val === 'number' ? val + 'px' : val;
/**
*
*
* NavBar 自定义导航栏
* @description 导航栏组件主要用于头部导航
* @tutorial https://ext.dcloud.net.cn/plugin?id=52
* @property {Boolean} dark 开启黑暗模式
* @property {String} title 标题文字
* @property {String} leftText 左侧按钮文本
* @property {String} rightText 右侧按钮文本
* @property {String} leftIcon 左侧按钮图标图标类型参考 [Icon 图标](http://ext.dcloud.net.cn/plugin?id=28) type
* @property {String} rightIcon 右侧按钮图标图标类型参考 [Icon 图标](http://ext.dcloud.net.cn/plugin?id=28) type
* @property {String} color 图标和文字颜色
* @property {String} backgroundColor 导航栏背景颜色
* @property {Boolean} fixed = [true|false] 是否固定顶部
* @property {Boolean} statusBar = [true|false] 是否包含状态栏
* @property {Boolean} shadow = [true|false] 导航栏下是否有阴影
* @property {Boolean} stat 是否开启统计标题上报
* @event {Function} clickLeft 左侧按钮点击时触发
* @event {Function} clickRight 右侧按钮点击时触发
* @event {Function} clickTitle 中间标题点击时触发
*/
export default {
name: "UniNavBar",
components: {
statusBar
},
emits: ['clickLeft', 'clickRight', 'clickTitle'],
props: {
dark: {
type: Boolean,
default: false
},
title: {
type: String,
default: ""
},
leftText: {
type: String,
default: ""
},
rightText: {
type: String,
default: ""
},
leftIcon: {
type: String,
default: ""
},
rightIcon: {
type: String,
default: ""
},
fixed: {
type: [Boolean, String],
default: false
},
color: {
type: String,
default: ""
},
backgroundColor: {
type: String,
default: ""
},
statusBar: {
type: [Boolean, String],
default: true
},
shadow: {
type: [Boolean, String],
default: false
},
border: {
type: [Boolean, String],
default: true
},
height: {
type: [Number, String],
default: 44
},
leftWidth: {
type: [Number, String],
default: 60
},
rightWidth: {
type: [Number, String],
default: 60
},
stat: {
type: [Boolean, String],
default: ''
}
},
computed: {
themeBgColor() {
if (this.dark) {
//
if (this.backgroundColor) {
return this.backgroundColor
} else {
return this.dark ? '#333' : '#FFF'
}
}
return this.backgroundColor || '#FFF'
},
themeColor() {
if (this.dark) {
//
if (this.color) {
return this.color
} else {
return this.dark ? '#fff' : '#333'
}
}
return this.color || '#333'
},
navbarHeight() {
return getVal(this.height)
},
leftIconWidth() {
return getVal(this.leftWidth)
},
rightIconWidth() {
return getVal(this.rightWidth)
}
},
mounted() {
if (uni.report && this.stat && this.title !== '') {
uni.report('title', this.title)
}
},
methods: {
onClickLeft() {
this.$emit("clickLeft");
},
onClickRight() {
this.$emit("clickRight");
},
onClickTitle() {
this.$emit("clickTitle");
}
}
};
</script>
<style lang="scss" scoped>
$nav-height: 44px;
.uni-nvue-fixed {
/* #ifdef APP-NVUE */
position: sticky;
/* #endif */
}
.uni-navbar {
// box-sizing: border-box;
}
.uni-nav-bar-text {
/* #ifdef APP-PLUS */
font-size: 34rpx;
/* #endif */
/* #ifndef APP-PLUS */
font-size: 14px;
/* #endif */
}
.uni-nav-bar-right-text {
font-size: 12px;
}
.uni-navbar__content {
position: relative;
// background-color: #fff;
// box-sizing: border-box;
background-color: transparent;
}
.uni-navbar__content_view {
// box-sizing: border-box;
}
.uni-navbar-btn-text {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: flex-start;
align-items: center;
line-height: 12px;
}
.uni-navbar__header {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
padding: 0 10px;
flex-direction: row;
height: $nav-height;
font-size: 12px;
}
.uni-navbar__header-btns {
/* #ifndef APP-NVUE */
overflow: hidden;
display: flex;
/* #endif */
flex-wrap: nowrap;
flex-direction: row;
width: 120rpx;
// padding: 0 6px;
justify-content: center;
align-items: center;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.uni-navbar__header-btns-left {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
width: 120rpx;
justify-content: flex-start;
align-items: center;
}
.uni-navbar__header-btns-right {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
// width: 150rpx;
// padding-right: 30rpx;
justify-content: flex-end;
align-items: center;
}
.uni-navbar__header-container {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
padding: 0 10px;
overflow: hidden;
}
.uni-navbar__header-container-inner {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
align-items: center;
justify-content: center;
font-size: 12px;
overflow: hidden;
// box-sizing: border-box;
}
.uni-navbar__placeholder-view {
height: $nav-height;
}
.uni-navbar--fixed {
position: fixed;
z-index: 99;
/* #ifdef H5 */
left: var(--window-left);
right: var(--window-right);
/* #endif */
/* #ifndef H5 */
left: 0;
right: 0;
/* #endif */
}
.uni-navbar--shadow {
box-shadow: 0 1px 6px #ccc;
}
.uni-navbar--border {
border-bottom-width: 1rpx;
border-bottom-style: solid;
border-bottom-color: #eee;
}
.uni-ellipsis-1 {
overflow: hidden;
/* #ifndef APP-NVUE */
white-space: nowrap;
text-overflow: ellipsis;
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
text-overflow: ellipsis;
/* #endif */
}
//
.uni-dark {}
</style>

View File

@ -0,0 +1,31 @@
<template>
<view :style="{ height: statusBarHeight }" class="uni-status-bar">
<slot />
</view>
</template>
<script>
export default {
name: 'UniStatusBar',
data() {
return {
// #ifdef MP-WEIXIN
statusBarHeight: uni.getWindowInfo().statusBarHeight + 'px',
// #endif
// #ifndef MP-WEIXIN
statusBarHeight: uni.getSystemInfoSync().statusBarHeight + 'px',
// #endif
}
}
}
</script>
<style lang="scss" >
.uni-status-bar {
// width: 750rpx;
height: 20px;
// height: var(--status-bar-height);
}
</style>

View File

@ -1,4 +1,12 @@
{
"easycom": {
"autoscan": true,
"custom": {
// guyu <>-<>
"^com-([a-z]+)-(.*)": "@/components/com/$1/$2.vue"
}
},
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path" : "pages/index/loading",
@ -55,6 +63,22 @@
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path" : "pages/other/agreement",
"style" :
{
"navigationBarTitleText" : "",
"navigationStyle": "custom"
}
},
{
"path" : "components/com/page/container",
"style" :
{
"navigationBarTitleText" : "",
"navigationStyle": "custom"
}
}
],
"globalStyle": {

View File

@ -9,71 +9,193 @@
<view class="column" style="width: 90%; margin: 80rpx auto 0;">
<view class="row" style="justify-content: space-between; width: 100%; align-items: center;">
<text style="font-size: 26rpx;">我的头像</text>
<image src="" style="width: 90rpx; height: 90rpx; background-color: aquamarine; border-radius: 50%;"
mode=""></image>
<view class="row"
style="justify-content: space-between; width: 100%; align-items: center; margin-top: 40rpx; font-size: 26rpx;">
<text style="">我的UID</text>
<text style="">{{ user.id }}</text>
</view>
<view style="width: 100%; height: 1rpx; background-color: antiquewhite; margin-top: 20rpx;"></view>
<view class="row" style="justify-content: space-between; width: 100%; align-items: center; margin-top: 40rpx; font-size: 26rpx;">
<view class="row"
style="justify-content: space-between; width: 100%; align-items: center;margin-top:20rpx;">
<text style="font-size: 26rpx;">我的头像</text>
<button class="avatar-wrapper" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
<image :src="user.avatarImage"
style="width: 70rpx; height: 70rpx; background-color: aquamarine; border-radius: 50%;" mode="">
</image>
</button>
</view>
<view style="width: 100%; height: 1rpx; background-color: antiquewhite; margin-top: 20rpx;"></view>
<view class="row"
style="justify-content: space-between; width: 100%; align-items: center; margin-top: 40rpx; font-size: 26rpx;">
<text style="">我的昵称</text>
<input type="text" v-model="userName" style="text-align: right;" />
<input type="nickname" v-model="user.nickName" style="text-align: right;" />
</view>
<view style="width: 100%; height: 1rpx; background-color: antiquewhite; margin-top: 20rpx;"></view>
<view class="row" style="justify-content: space-between; width: 100%; align-items: center; margin-top: 40rpx; font-size: 26rpx;">
<view class="row"
style="justify-content: space-between; width: 100%; align-items: center; margin-top: 40rpx; font-size: 26rpx;">
<text style="">我的年龄</text>
<input type="text" v-model="userAge" style="text-align: right;" />
<picker mode="selector" :value="ageIndex" :range="ageOptions" @change="onAgeChange">
<view class="picker-text">
{{ ageOptions[ageIndex] }}
</view>
</picker>
</view>
<view style="width: 100%; height: 1rpx; background-color: antiquewhite; margin-top: 20rpx;"></view>
<view class="row" style="justify-content: space-between; width: 100%; align-items: center; margin-top: 40rpx; font-size: 26rpx;">
<text style="">我的UID</text>
<text style="">{{userId}}</text>
<view class="row"
style="justify-content: space-between; width: 100%; align-items: center; margin-top: 40rpx; font-size: 26rpx;">
<text style="">我的性别</text>
<picker mode="selector" :value="genderIndex" :range="genderOptions" @change="onGenderChange">
<view class="picker-text">
{{ genderOptions[genderIndex] }}
</view>
</picker>
</view>
<view style="width: 100%; height: 1rpx; background-color: antiquewhite; margin-top: 20rpx;"></view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
userName: "树下的胖子",
userAge:"30",
userId:"132546"
}
},
methods: {
goBack() {
//
uni.navigateBack({
delta: 1
});
},
}
<script setup>
import { ref } from 'vue'
import { userInfo } from '@/common/server/user'
//
const genderOptions = ['男', '女']
const genderIndex = ref(0)
// (18-90)
const ageOptions = Array.from({ length: 73 }, (_, i) => i + 18)
const ageIndex = ref(0)
const user = ref({
avatarImage: userInfo.value.avatarImage,
nickName: userInfo.value.nickName,
age: userInfo.value.age ?? 18,
id: userInfo.value.id,
sex: userInfo.value.sex == 1 ? '男' : '女'
})
onLoad(() => {
console.log("userInfo", userInfo.value);
// picker
if (userInfo.value.sex === '女') {
genderIndex.value = 1
} else {
genderIndex.value = 0
}
// picker
const userAge = userInfo.value.age ?? 18
const ageIndexValue = Math.max(0, Math.min(72, userAge - 18))
ageIndex.value = ageIndexValue
})
//
const goBack = () => {
//
uni.navigateBack({
delta: 1
});
}
/**
* 选择头像
*/
const onChooseAvatar = (e) => {
if (e && e.detail && e.detail.avatarUrl) {
// user.value.avatarImage = e.detail.avatarUrl;
convertToBase64(user.value.avatarImage);
}
}
/**
* 性别选择变化
*/
const onGenderChange = (e) => {
genderIndex.value = e.detail.value
user.value.sex = genderOptions[genderIndex.value]
}
/**
* 年龄选择变化
*/
const onAgeChange = (e) => {
ageIndex.value = e.detail.value
user.value.age = ageOptions[ageIndex.value]
}
/**
* 将图片转换为Base64
*/
const convertToBase64 = (filePath) => {
console.log(filePath);
uni.getFileSystemManager().readFile({
filePath: filePath,
encoding: "base64",
success: (res) => {
user.value.avatarImage = "data:image/png;base64," + res.data;
},
fail: (err) => {
console.error("读取文件失败:", err);
uni.showToast({
title: "图片处理失败",
icon: "none",
});
},
});
}
</script>
<style lang="scss">
.content {
width: 100%;
height: 100vh;
.content {
width: 100%;
height: 100vh;
}
.avatar-wrapper {
margin: 0rpx;
float: right;
border-radius: 128rpx;
overflow: hidden;
border: 0rpx solid #F3F3F3;
background-color: transparent;
padding: 0;
line-height: normal;
&::after {
border: none;
}
.avatar {
width: 170rpx;
height: 160rpx;
border-radius: 50%;
}
}
.picker-text {
display: flex;
align-items: center;
color: #333;
font-size: 26rpx;
}
</style>

View File

@ -21,16 +21,26 @@
<view class="login-form">
<!-- 一键登录按钮 -->
<view class="one-click-login">
<view class="quick-login-btn" @click="handleOneClickLogin" :class="{ disabled: loading }">
<button v-if="agreedToTerms && isMobile" class="quick-login-btn" :class="{ disabled: loading }"
open-type="getPhoneNumber" @getphonenumber="mobileClickLogin">
<text>{{ loading ? '登录中...' : '一键登录' }}</text>
</view>
</button>
<button v-if="!agreedToTerms" class="quick-login-btn" :class="{ disabled: loading }"
@click="agreementClickLogin">
<text>{{ loading ? '登录中...' : '一键登录' }}</text>
</button>
<button v-if="agreedToTerms && !isMobile" class="quick-login-btn" :class="{ disabled: loading }"
@click="anonymousLoginClickLogin">
<text>{{ loading ? '登录中...' : '一键登录' }}</text>
</button>
</view>
<!-- 用户协议同意 -->
<view class="agreement-section">
<view class="agreement-checkbox" @click="toggleAgreement">
<checkbox :value="agreedToTerms" :checked="agreedToTerms" style="transform:scale(0.5);margin-top: -8rpx;" />
<checkbox :value="agreedToTerms" :checked="agreedToTerms"
style="transform:scale(0.5);margin-top: -8rpx;" />
<text class="agreement-text">
我已阅读并同意
<text class="agreement-link" @click.stop="showUserAgreement">用户协议</text>
@ -49,8 +59,14 @@
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import {
ref,
reactive,
computed
} from 'vue'
import { useLogin } from '@/common/com'
import { getAnonymousLogin, ueWxPhoneNumberLogin, useWxAnonymousLogin } from '@/common/server/interface/user'
import { userInfo, loadUserInfo } from '@/common/server/user'
//
const loading = ref(false)
const agreedToTerms = ref(false)
@ -60,7 +76,7 @@ const toggleAgreement = () => {
agreedToTerms.value = !agreedToTerms.value
}
const handleOneClickLogin = async () => {
const agreementClickLogin = async () => {
if (loading.value) return
if (!agreedToTerms.value) {
@ -70,37 +86,66 @@ const handleOneClickLogin = async () => {
})
return
}
}
const mobileClickLogin = async (e) => {
console.log(e) //
console.log(e.detail.code) //
var code = e.detail.code;
loading.value = true
try {
// API
// const response = await oneClickLoginApi()
var response = await ueWxPhoneNumberLogin(code, sessionAuthId);
if (response == null) {
uni.showToast({
title: '登录失败,请重试',
icon: 'error'
})
return;
}
uni.setStorageSync("tokenInfo", response);
//
await new Promise(resolve => setTimeout(resolve, 2000))
//
const randomPhone = '138' + Math.floor(Math.random() * 100000000).toString().padStart(8, '0')
//
uni.setStorageSync('userInfo', {
phone: randomPhone,
nickname: '用户' + randomPhone.slice(-4),
uid: 'U' + Date.now(),
avatar: '@@:app/nouser.png',
rating: 4.6,
reputation: 5.0,
cardQuality: 4.5,
cardSkill: 4.5,
pigeonCount: 0
})
await loadUserInfo();
uni.showToast({
title: '登录成功',
icon: 'success'
})
});
//
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
console.error('一键登录失败:', error)
uni.showToast({
title: '登录失败,请重试',
icon: 'error'
})
} finally {
loading.value = false
}
}
const anonymousLoginClickLogin = async (e) => {
console.log('aaa');
loading.value = true
try {
// API
// const response = await oneClickLoginApi()
var response = await useWxAnonymousLogin(sessionAuthId);
if (response == null) {
uni.showToast({
title: '登录失败,请重试',
icon: 'error'
})
return;
}
uni.setStorageSync("tokenInfo", response);
await loadUserInfo();
uni.showToast({
title: '登录成功',
icon: 'success'
});
//
setTimeout(() => {
uni.navigateBack()
@ -122,22 +167,32 @@ const skipLogin = () => {
}
const showUserAgreement = () => {
uni.showModal({
title: '用户协议',
content: '这里是用户协议的内容...\n\n1. 用户权利和义务\n2. 服务条款\n3. 免责声明\n4. 其他条款',
showCancel: false,
confirmText: '我知道了'
})
com.navigateToAgreement('userAgreement');
}
const showPrivacyPolicy = () => {
uni.showModal({
title: '隐私政策',
content: '这里是隐私政策的内容...\n\n1. 信息收集\n2. 信息使用\n3. 信息保护\n4. 信息共享',
showCancel: false,
confirmText: '我知道了'
})
com.navigateToAgreement('privacyPolicy');
}
let isMobile = ref(false);
let sessionAuthId = null;
async function AnonymousLogin() {
var code = await useLogin();
console.log("res", code);
var res = await getAnonymousLogin(code);
if (res.user == 0) {
isMobile.value = true;
}
sessionAuthId = res.sessionAuthId;
}
onLoad(() => {
AnonymousLogin();
/* uni.login({
}) */
})
</script>
<style lang="scss" scoped>
@ -166,7 +221,7 @@ const showPrivacyPolicy = () => {
position: absolute;
background: rgba(255, 255, 255, 0.8);
border-radius: 50rpx;
&::before,
&::after {
content: '';
@ -174,20 +229,20 @@ const showPrivacyPolicy = () => {
background: rgba(255, 255, 255, 0.8);
border-radius: 50%;
}
&.cloud-1 {
width: 120rpx;
height: 40rpx;
top: 15%;
left: 10%;
&::before {
width: 50rpx;
height: 50rpx;
top: -25rpx;
left: 10rpx;
}
&::after {
width: 60rpx;
height: 60rpx;
@ -195,20 +250,20 @@ const showPrivacyPolicy = () => {
right: 10rpx;
}
}
&.cloud-2 {
width: 100rpx;
height: 35rpx;
top: 25%;
right: 15%;
&::before {
width: 40rpx;
height: 40rpx;
top: -20rpx;
left: 8rpx;
}
&::after {
width: 50rpx;
height: 50rpx;
@ -216,20 +271,20 @@ const showPrivacyPolicy = () => {
right: 8rpx;
}
}
&.cloud-3 {
width: 80rpx;
height: 30rpx;
top: 70%;
left: 20%;
&::before {
width: 35rpx;
height: 35rpx;
top: -17rpx;
left: 6rpx;
}
&::after {
width: 40rpx;
height: 40rpx;
@ -246,7 +301,7 @@ const showPrivacyPolicy = () => {
height: 15rpx;
background: #8B4513;
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
&::before {
content: '';
position: absolute;
@ -258,13 +313,13 @@ const showPrivacyPolicy = () => {
left: -5rpx;
transform: rotate(-20deg);
}
&.bird-1 {
top: 20%;
right: 25%;
animation: fly 3s ease-in-out infinite;
}
&.bird-2 {
top: 60%;
left: 15%;
@ -277,7 +332,7 @@ const showPrivacyPolicy = () => {
position: absolute;
width: 30rpx;
height: 30rpx;
&::before {
content: '';
position: absolute;
@ -286,7 +341,7 @@ const showPrivacyPolicy = () => {
background: radial-gradient(circle, #FFB6C1 30%, #FF69B4 70%);
border-radius: 50%;
}
&::after {
content: '';
position: absolute;
@ -298,19 +353,19 @@ const showPrivacyPolicy = () => {
left: 50%;
transform: translateX(-50%);
}
&.flower-1 {
top: 40%;
left: 8%;
animation: sway 2s ease-in-out infinite;
}
&.flower-2 {
top: 80%;
right: 10%;
animation: sway 2.5s ease-in-out infinite reverse;
}
&.flower-3 {
top: 50%;
right: 30%;
@ -320,22 +375,53 @@ const showPrivacyPolicy = () => {
//
@keyframes fly {
0%, 100% { transform: translateX(0) translateY(0); }
25% { transform: translateX(10rpx) translateY(-5rpx); }
50% { transform: translateX(20rpx) translateY(0); }
75% { transform: translateX(10rpx) translateY(5rpx); }
0%,
100% {
transform: translateX(0) translateY(0);
}
25% {
transform: translateX(10rpx) translateY(-5rpx);
}
50% {
transform: translateX(20rpx) translateY(0);
}
75% {
transform: translateX(10rpx) translateY(5rpx);
}
}
@keyframes sway {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(2deg); }
50% { transform: rotate(0deg); }
75% { transform: rotate(-2deg); }
0%,
100% {
transform: rotate(0deg);
}
25% {
transform: rotate(2deg);
}
50% {
transform: rotate(0deg);
}
75% {
transform: rotate(-2deg);
}
}
@keyframes shine {
0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
0% {
transform: translateX(-100%) translateY(-100%) rotate(45deg);
}
100% {
transform: translateX(100%) translateY(100%) rotate(45deg);
}
}
.header {
@ -391,7 +477,7 @@ const showPrivacyPolicy = () => {
border: 4rpx solid #FFE4B5;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
@ -403,7 +489,7 @@ const showPrivacyPolicy = () => {
transform: rotate(45deg);
transition: all 0.5s;
}
&:active::before {
animation: shine 0.6s ease-in-out;
}
@ -443,7 +529,7 @@ const showPrivacyPolicy = () => {
line-height: 1.5;
.agreement-link {
color: #FF69B4;
color: blue;
text-decoration: underline;
font-weight: 500;
}
@ -462,4 +548,4 @@ const showPrivacyPolicy = () => {
font-weight: 500;
}
}
</style>
</style>

View File

@ -6,14 +6,24 @@
<text>加载中...</text>
</view>
<view class="row" style="margin-top: 150rpx; align-items: center; margin-left: 40rpx;">
<image :src="userInfo.avatar" style="width: 80rpx; height: 80rpx; border-radius: 50%;" mode=""></image>
<view v-if="userInfo != null" class="row" style="margin-top: 150rpx; align-items: center; margin-left: 40rpx;">
<image :src="userInfo.avatarImage" style="width: 80rpx; height: 80rpx; border-radius: 50%;" mode="">
</image>
<view class="column" style="margin-left: 50rpx; font-size: 24rpx;">
<view class="row" @click="toEditInfo()" style="align-items: center;">
<text>{{ userInfo.nickname }}</text>
<text>{{ userInfo.nickName }}</text>
</view>
<text v-if="userInfo.uid" style="margin-top: 10rpx;">UID{{ userInfo.uid }}</text>
<text v-else style="margin-top: 10rpx; color: #999;" @click="toLogin()">点击登录</text>
<text style="margin-top: 10rpx;">UID{{ userInfo.id }}</text>
</view>
</view>
<view v-else class="row" style="margin-top: 150rpx; align-items: center; margin-left: 40rpx;">
<image src="@@:app/nouser.png" style="width: 80rpx; height: 80rpx; border-radius: 50%;" mode=""></image>
<view class="column" style="margin-left: 50rpx; font-size: 24rpx;">
<view class="row" @click="toEditInfo()" style="align-items: center;">
<text>未登录</text>
</view>
<text style="margin-top: 10rpx; color: #999;" @click="toLogin()">点击登录</text>
</view>
</view>
<view class="column" @click="currentAppointment ? openPop() : null"
@ -21,7 +31,7 @@
<!-- 无预约状态 -->
<view v-if="!currentAppointment" class="row" style="align-items: center; margin: 20rpx;">
<image src="/static/no_content.png" style="width: 150rpx; height: 150rpx;" mode=""></image>
<image src="@@:app/static/no_content.png" style="width: 150rpx; height: 150rpx;" mode=""></image>
<text style="margin-left: 40rpx;">当前没有预约</text>
</view>
@ -32,7 +42,7 @@
currentAppointment.duration }}
</text>
<text style="font-size: 24rpx; margin-left: 20rpx; margin-top: 10rpx;">{{ currentAppointment.room
}}</text>
}}</text>
<view class="row"
style="font-size: 24rpx; margin-left: 20rpx; margin-top: 20rpx; align-items: center; margin-bottom: 20rpx;">
@ -49,7 +59,8 @@
</view>
<view class="column" style="width: 90%; border-radius: 10rpx; background-color: #F2F3F5; margin: 40rpx auto 0;">
<view v-if="userInfo != null" class="column"
style="width: 90%; border-radius: 10rpx; background-color: #F2F3F5; margin: 40rpx auto 0;">
<view class="row" style="align-items: center; margin-top: 30rpx; margin-left: 20rpx;">
@ -77,6 +88,29 @@
</view>
<view v-else class="column"
style="width: 90%; border-radius: 10rpx; background-color: #F2F3F5; margin: 40rpx auto 0;">
<view class="row" style="align-items: center; margin-top: 30rpx; margin-left: 20rpx;">
<text style="font-size: 24rpx;">我的评分</text>
<text style="font-size: 24rpx; color: #999; margin-left: 10rpx;">未评分</text>
<text style="font-size: 24rpx; margin-left: 180rpx;">我的信誉</text>
<text style="font-size: 24rpx; color: #999; margin-left: 10rpx;">未评级</text>
</view>
<view class="row" style="align-items: center; margin-top: 20rpx; margin-left: 20rpx;">
<text style="font-size: 24rpx;">牌品</text>
<uni-rate style="margin-left: 20rpx;" :readonly="true" />
</view>
<view class="row" style="align-items: center; margin-top: 20rpx; margin-left: 20rpx;">
<text style="font-size: 24rpx;">牌技</text>
<uni-rate style="margin-left: 20rpx;" :readonly="true" />
</view>
<text style="font-size: 24rpx; margin: 20rpx;">鸽子数 0</text>
</view>
<view class="row" style="width: 90%; margin: 40rpx auto 0;">
<view class="column" @click="toAppointment()" style="align-items: center;">
@ -166,7 +200,7 @@
<view class="column"
style="width: 100%; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx; font-size: 22rpx;">
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">房间号{{ appointmentDetail.roomInfo.roomNumber
}}{{ appointmentDetail.roomInfo.price }}</text>
}}{{ appointmentDetail.roomInfo.price }}</text>
<text style="margin: 10rpx 20rpx;">人数{{ appointmentDetail.roomInfo.playerCount }}</text>
<text style="margin: 10rpx 20rpx;">玩法类型{{ appointmentDetail.roomInfo.gameType }}</text>
<text style="margin: 10rpx 20rpx;">具体规则{{ appointmentDetail.roomInfo.rules }}</text>
@ -177,7 +211,7 @@
style="width: 100%; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx; font-size: 22rpx;">
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">是否禁烟{{
appointmentDetail.requirements.smoking
}}</text>
}}</text>
<text style="margin: 10rpx 20rpx;">性别{{ appointmentDetail.requirements.gender }}</text>
<text style="margin: 10rpx 20rpx 20rpx;">信誉{{ appointmentDetail.requirements.reputation }}</text>
</view>
@ -202,6 +236,7 @@
</template>
<script setup>
import { userInfo, loadUserInfo } from '@/common/server/user'
import { ref, reactive, onMounted } from 'vue'
//
@ -210,16 +245,16 @@ const infoPop = ref(null)
const loading = ref(false)
// -
const userInfo = reactive({
nickname: '未登录',
uid: '',
avatar: '@@:app/nouser.png',
rating: 0,
reputation: 0,
cardQuality: 0,
cardSkill: 0,
pigeonCount: 0
})
// const userInfo = reactive({
// nickname: '',
// uid: '',
// avatar: '@@:app/nouser.png',
// rating: 0,
// reputation: 0,
// cardQuality: 0,
// cardSkill: 0,
// pigeonCount: 0
// })
//
const currentAppointment = ref(null)
@ -301,20 +336,12 @@ const toAppointment = () => {
const toEditInfo = () => {
//
if (!userInfo.uid) {
uni.showModal({
title: '提示',
content: '请先登录',
showCancel: false,
success: () => {
uni.navigateTo({
url: '/pages/me/login'
});
}
if (userInfo == null) {
uni.navigateTo({
url: '/pages/me/login'
});
return;
}
uni.navigateTo({
url: '/pages/me/edit-info'
});
@ -396,16 +423,14 @@ onMounted(() => {
})
//
// onShow(() => {
// //
// const savedUserInfo = uni.getStorageSync('userInfo')
// if (savedUserInfo && savedUserInfo.uid) {
// //
// Object.assign(userInfo, savedUserInfo)
// //
// loadCurrentAppointment()
// }
// })
onShow(() => {
//
// getUserInfoData();
})
onLoad(async () => {
console.log('kiad');
await loadUserInfo();
})
</script>
<style lang="scss">

155
pages/other/agreement.vue Normal file
View File

@ -0,0 +1,155 @@
<template>
<view>
<com-page-container :title="title" showBack>
<view class="agreement-container">
<rich-text v-if="article != null" :nodes="article.contentBody" class="agreement-content"></rich-text>
</view>
</com-page-container>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { getArticleDetail } from '@/common/server/interface/article'
import { getConfigData } from '@/common/server/config'
let title = ref('')
const article = ref(null)
let configData = null;
onLoad(async (option) => {
console.log(option);
if (configData == null) {
configData = await getConfigData()
}
console.log("configData", configData.config);
if (option.type != null) {
if (option.type == 'userAgreement') {
title = "用户协议";
article.value = await getArticleDetail(configData.config.userAgreementId)
} else if (option.type == 'privacyPolicy') {
title = "隐私协议";
article.value = await getArticleDetail(configData.config.privacyPolicyId)
}
}
})
</script>
<style scoped>
.agreement-container {
padding: 20rpx;
background-color: #fff;
min-height: 100vh;
}
.agreement-content {
line-height: 1.8;
font-size: 28rpx;
color: #333;
word-break: break-all;
}
/* 富文本内容样式优化 */
.agreement-content :deep(h1),
.agreement-content :deep(h2),
.agreement-content :deep(h3) {
color: #2c3e50;
font-weight: bold;
margin: 30rpx 0 20rpx 0;
line-height: 1.5;
}
.agreement-content :deep(h1) {
font-size: 36rpx;
border-bottom: 2rpx solid #e0e0e0;
padding-bottom: 15rpx;
}
.agreement-content :deep(h2) {
font-size: 32rpx;
color: #34495e;
}
.agreement-content :deep(h3) {
font-size: 30rpx;
color: #34495e;
}
.agreement-content :deep(p) {
margin: 15rpx 0;
text-indent: 0;
line-height: 1.8;
color: #555;
}
.agreement-content :deep(ul),
.agreement-content :deep(ol) {
margin: 15rpx 0;
padding-left: 40rpx;
}
.agreement-content :deep(li) {
margin: 8rpx 0;
line-height: 1.6;
color: #555;
}
.agreement-content :deep(strong),
.agreement-content :deep(b) {
font-weight: bold;
color: #2c3e50;
}
.agreement-content :deep(em),
.agreement-content :deep(i) {
font-style: italic;
color: #7f8c8d;
}
.agreement-content :deep(a) {
color: #3498db;
text-decoration: underline;
}
.agreement-content :deep(blockquote) {
border-left: 6rpx solid #3498db;
padding-left: 20rpx;
margin: 20rpx 0;
background-color: #f8f9fa;
padding: 20rpx;
border-radius: 8rpx;
}
.agreement-content :deep(code) {
background-color: #f1f2f6;
padding: 4rpx 8rpx;
border-radius: 4rpx;
font-family: 'Courier New', monospace;
font-size: 24rpx;
}
.agreement-content :deep(pre) {
background-color: #f8f9fa;
padding: 20rpx;
border-radius: 8rpx;
overflow-x: auto;
margin: 20rpx 0;
}
.agreement-content :deep(table) {
width: 100%;
border-collapse: collapse;
margin: 20rpx 0;
}
.agreement-content :deep(th),
.agreement-content :deep(td) {
border: 1rpx solid #ddd;
padding: 12rpx;
text-align: left;
}
.agreement-content :deep(th) {
background-color: #f8f9fa;
font-weight: bold;
}
</style>