新增全局 YjyfTabBar 组件,替换各页面自定义导航,实现一致的 tab 切换逻辑与图标样式 新增全局设计令牌与 SCSS 变量系统,统一色彩、间距、字阶、圆角与投影规范 新增 POST 请求防并发锁,避免重复提交 重构多个页面,添加骨架屏、空状态引导、点击反馈类,修复硬编码样式与假数据 删除废弃的后台入口文件与 nginx 配置,清理冗余代码
167 行
8.4 KiB
Vue
167 行
8.4 KiB
Vue
<template>
|
||
<view class="page">
|
||
<view class="top">
|
||
<view class="deco d1"></view>
|
||
<view class="deco d2"></view>
|
||
<view class="back clickable-hover" :style="{ top: (statusBarHeight + 10) + 'px' }" v-if="showBack" @tap="goBack"><text class="back-t">‹</text></view>
|
||
<text class="brand">优匠优服</text>
|
||
<text class="slogan">专业师傅 · 上门到家</text>
|
||
</view>
|
||
|
||
<view class="card">
|
||
<view class="role">
|
||
<view class="role-item" v-for="r in roles" :key="r.key" :class="{ on: role === r.key }" @tap="role = r.key">
|
||
<text class="role-name">{{ r.name }}</text>
|
||
<text class="role-desc">{{ r.desc }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="wx-btn clickable-hover" :class="{ disabled: loading }" @tap="wxLogin">
|
||
<view class="wx-icon"><image class="wx-icon-img" src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%232FA84F'><path d='M8.5 2C4.36 2 1 4.91 1 8.5c0 1.94.98 3.69 2.53 4.88l-.64 2.37 2.76-1.44c.87.26 1.83.4 2.85.4.15 0 .3 0 .44-.01C9.64 12.39 11.58 11 14 11c.21 0 .42.01.62.03C14.16 6.06 11.56 2 8.5 2zm-2.25 4.5c.69 0 1.25.56 1.25 1.25S6.94 9 6.25 9 5 8.44 5 7.75 5.56 6.5 6.25 6.5zm4.5 0c.69 0 1.25.56 1.25 1.25S11.44 9 10.75 9 9.5 8.44 9.5 7.75 10.06 6.5 10.75 6.5zM15.5 12c-3.59 0-6.5 2.46-6.5 5.5 0 3.04 2.91 5.5 6.5 5.5.76 0 1.48-.11 2.14-.31l2.08 1.09-.48-1.79c1.38-.97 2.26-2.43 2.26-4.49 0-3.04-2.91-5.5-6.5-5.5zm-2 3c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm4 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1z'/></svg>" mode="aspectFit"></image></view>
|
||
<text class="wx-txt">{{ loading ? '微信授权中...' : '微信一键登录' }}</text>
|
||
</view>
|
||
|
||
<view class="fallback">
|
||
<view class="fb-toggle" @tap="showPhone = !showPhone">
|
||
<text class="fb-toggle-t">{{ showPhone ? '收起手机号登录' : '微信授权失败?改用手机号登录' }}</text>
|
||
</view>
|
||
<view class="fb-body" v-if="showPhone">
|
||
<input class="fb-input" v-model="mobile" type="number" maxlength="11" placeholder="请输入手机号" />
|
||
<input class="fb-input" v-model="code" type="number" maxlength="6" placeholder="验证码(联调固定 123456)" />
|
||
<view class="fb-btn clickable-hover" @tap="phoneLogin">手机号登录 / 注册</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="tip">授权仅用于账号识别,不会获取你的其他信息</view>
|
||
<view class="agree">登录即代表同意《用户服务协议》与《隐私政策》</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from 'vue';
|
||
import { onLoad } from '@dcloudio/uni-app';
|
||
import api from '@/utils/api.js';
|
||
import { setToken, setUser } from '@/utils/request.js';
|
||
import { wxAuthorizeLogin } from '@/utils/wxlogin.js';
|
||
import { setRole } from '@/utils/role.js';
|
||
|
||
const role = ref('user');
|
||
const loading = ref(false);
|
||
const showBack = ref(false);
|
||
const showPhone = ref(false);
|
||
const mobile = ref('');
|
||
const code = ref('');
|
||
|
||
const roles = [
|
||
{ key: 'user', name: '我是业主', desc: '下单找师傅' },
|
||
{ key: 'master', name: '我是师傅', desc: '接单赚收入' },
|
||
{ key: 'merchant', name: '我是商家', desc: '企业批量下单' }
|
||
];
|
||
|
||
function toast(t) { uni.showToast({ title: t, icon: 'none' }); }
|
||
|
||
/** 从「我的」进入时才显示返回键(作为启动页时不显示) */
|
||
onLoad(() => {
|
||
showBack.value = getCurrentPages().length > 1;
|
||
});
|
||
|
||
function goBack() { uni.navigateBack(); }
|
||
|
||
/** 微信授权登录(单飞防抢占 code + 40029 自动换 code 重试) */
|
||
async function wxLogin() {
|
||
if (loading.value) return;
|
||
loading.value = true;
|
||
try {
|
||
const data = await wxAuthorizeLogin({});
|
||
setRole(role.value);
|
||
toast(data.is_new ? '登录成功,欢迎加入' : '登录成功');
|
||
setTimeout(() => routeAfterLogin(), 600);
|
||
} catch (e) {
|
||
toast(e.message || '登录失败');
|
||
showPhone.value = true;
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
function phoneLogin() {
|
||
if (loading.value) return;
|
||
if (!/^1\d{10}$/.test(mobile.value)) { toast('请输入正确的手机号'); return; }
|
||
if (!code.value) { toast('请输入验证码'); return; }
|
||
loading.value = true;
|
||
api.login({ mobile: mobile.value, code: code.value }).then((data) => {
|
||
loading.value = false;
|
||
setToken(data.token);
|
||
setUser(data.userinfo);
|
||
setRole(role.value);
|
||
toast('登录成功');
|
||
setTimeout(() => routeAfterLogin(), 500);
|
||
}).catch((e) => {
|
||
loading.value = false;
|
||
toast(e.message || '登录失败');
|
||
});
|
||
}
|
||
|
||
function routeAfterLogin() {
|
||
if (role.value === 'user') {
|
||
uni.reLaunch({ url: '/pages/index/index' });
|
||
return;
|
||
}
|
||
if (role.value === 'merchant') {
|
||
api.merchantInfo().then((res) => {
|
||
if (!res.applied || res.merchant.verify_status !== 'pass') {
|
||
uni.redirectTo({ url: '/pages/merchant/apply' });
|
||
} else {
|
||
uni.redirectTo({ url: '/pages/merchant/workbench' });
|
||
}
|
||
}).catch(() => uni.redirectTo({ url: '/pages/merchant/apply' }));
|
||
return;
|
||
}
|
||
api.masterInfo().then((res) => {
|
||
if (!res.applied || res.master.verify_status !== 'pass') {
|
||
uni.redirectTo({ url: '/pages/master/apply' });
|
||
} else {
|
||
uni.redirectTo({ url: '/pages/master/workbench' });
|
||
}
|
||
}).catch(() => {
|
||
uni.redirectTo({ url: '/pages/master/apply' });
|
||
});
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page { min-height: 100vh; background: #F5F6F8; }
|
||
.top { position: relative; height: 420rpx; padding: 120rpx 0 0 48rpx; overflow: hidden;
|
||
background: linear-gradient(160deg, #FFB85C 0%, #F98D25 46%, #ED6F0E 100%); border-radius: 0 0 48rpx 48rpx; }
|
||
.deco { position: absolute; border-radius: 50%; }
|
||
.back { position: absolute; left: 26rpx; top: 92rpx; width: 64rpx; height: 64rpx; border-radius: 50%; background: rgba(255,255,255,.22); display: flex; align-items: center; justify-content: center; }
|
||
.back-t { font-size: 44rpx; color: #fff; line-height: 44rpx; margin-top: -6rpx; }
|
||
.d1 { width: 360rpx; height: 360rpx; right: -120rpx; top: -160rpx; background: rgba(255,255,255,.14); }
|
||
.d2 { width: 220rpx; height: 220rpx; left: -90rpx; bottom: -80rpx; background: rgba(255,255,255,.10); }
|
||
.brand { position: relative; display: block; font-size: 52rpx; font-weight: 700; color: #fff; letter-spacing: 6rpx; text-shadow: 0 3rpx 12rpx rgba(180,80,10,.3); }
|
||
.slogan { position: relative; display: block; margin-top: 14rpx; font-size: 26rpx; color: rgba(255,255,255,.88); letter-spacing: 2rpx; }
|
||
.card { position: relative; margin: -140rpx 40rpx 0; padding: 40rpx 34rpx 34rpx; background: #fff; border-radius: 24rpx; box-shadow: 0 16rpx 40rpx rgba(226,120,26,.16); }
|
||
.role { display: flex; margin-bottom: 34rpx; }
|
||
.role-item { flex: 1; margin-right: 12rpx; padding: 18rpx 0; border-radius: 16rpx; background: #F7F7F7; display: flex; flex-direction: column; align-items: center; border: 2rpx solid transparent; }
|
||
.role-item:last-child { margin-right: 0; }
|
||
.role-item.on { background: linear-gradient(135deg, #FFF8EE 0%, #FFE9D2 100%); border-color: #F9A65B; }
|
||
.role-name { font-size: 26rpx; font-weight: 600; color: #333; }
|
||
.role-item.on .role-name { color: #C4580A; }
|
||
.role-desc { margin-top: 4rpx; font-size: 22rpx; color: #888888; }
|
||
.fallback { margin-top: 20rpx; }
|
||
.fb-toggle { text-align: center; padding: 12rpx 0; }
|
||
.fb-toggle-t { font-size: 22rpx; color: #9A9A9A; text-decoration: underline; }
|
||
.fb-body { margin-top: 10rpx; }
|
||
.fb-input { height: 88rpx; margin-bottom: 14rpx; padding: 0 24rpx; background: #F7F7F7; border-radius: 14rpx; font-size: 28rpx; color: #222; }
|
||
.fb-btn { height: 84rpx; display: flex; align-items: center; justify-content: center; border-radius: 42rpx; background: #FFF3E6; color: #E36B0C; font-size: 28rpx; font-weight: 600; }
|
||
.wx-btn { height: 96rpx; display: flex; align-items: center; justify-content: center; border-radius: 48rpx; background: linear-gradient(135deg, #4BC86A 0%, #2FA84F 100%); box-shadow: 0 10rpx 24rpx rgba(47,168,79,.28); }
|
||
.wx-btn.disabled { opacity: .7; }
|
||
.wx-icon { width: 52rpx; height: 52rpx; border-radius: 50%; background: #fff; display: flex; align-items: center; justify-content: center; margin-right: 14rpx; box-shadow: 0 4rpx 10rpx rgba(0,0,0,0.06); }
|
||
.wx-icon-img { width: 34rpx; height: 34rpx; }
|
||
.wx-icon-t { font-size: 24rpx; font-weight: 700; color: #2FA84F; }
|
||
.wx-txt { font-size: 32rpx; font-weight: 600; color: #fff; letter-spacing: 1rpx; }
|
||
.tip { display: block; margin-top: 24rpx; text-align: center; font-size: 24rpx; color: #888888; line-height: 34rpx; }
|
||
.agree { display: block; margin-top: 12rpx; text-align: center; font-size: 22rpx; color: #AAAAAA; line-height: 32rpx; }
|
||
</style>
|