Compare commits
No commits in common. "0f9a78b64ccbadf711093564e2f209456a31a3e8" and "70d088a4ba62e5cb257dbba96955b4805d3498e8" have entirely different histories.
0f9a78b64c
...
70d088a4ba
61
api/login.js
61
api/login.js
@ -46,7 +46,6 @@ export function logout() {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 获取验证码
|
||||
export function getCodeImg() {
|
||||
return request({
|
||||
@ -57,64 +56,4 @@ export function getCodeImg() {
|
||||
method: 'get',
|
||||
timeout: 20000
|
||||
})
|
||||
}
|
||||
|
||||
export function sendEmailCode(data, type = 'register') {
|
||||
return request({
|
||||
url: `/auth/mail/send/${type}`,
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'post',
|
||||
timeout: 20000,
|
||||
data,
|
||||
params: {
|
||||
autoRegister: data.autoRegister
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function verifyEmailCode(data, type = 'register') {
|
||||
return request({
|
||||
url: `/auth/mail/verify/${type}`,
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'post',
|
||||
timeout: 20000,
|
||||
data,
|
||||
params: {
|
||||
autoRegister: data.autoRegister
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function sendPhoneCode(data, type = 'register') {
|
||||
return request({
|
||||
url: `/auth/dySms/send/${type}`,
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'post',
|
||||
timeout: 20000,
|
||||
data,
|
||||
params: {
|
||||
autoRegister: data.autoRegister
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function verifyPhoneCode(data, type = 'register') {
|
||||
return request({
|
||||
url: `/auth/dySms/verify/${type}`,
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'post',
|
||||
timeout: 20000,
|
||||
data,
|
||||
params: {
|
||||
autoRegister: data.autoRegister
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -1,50 +1,6 @@
|
||||
import upload from '@/utils/upload'
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询用户列表
|
||||
export function listUser(query) {
|
||||
return request({
|
||||
url: '/system/user/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询用户详细
|
||||
export function getUser(userId) {
|
||||
return request({
|
||||
url: '/system/user/' + parseStrEmpty(userId),
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增用户
|
||||
export function addUser(data) {
|
||||
return request({
|
||||
url: '/system/user',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改用户
|
||||
export function updateUser(data) {
|
||||
return request({
|
||||
url: '/system/user',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
export function delUser(userId) {
|
||||
return request({
|
||||
url: '/system/user/' + userId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 用户密码重置
|
||||
export function updateUserPwd(oldPassword, newPassword) {
|
||||
const data = {
|
||||
@ -83,29 +39,3 @@ export function uploadAvatar(data) {
|
||||
filePath: data.filePath
|
||||
})
|
||||
}
|
||||
|
||||
// 用户密码重置
|
||||
export function resetUserPwd(userId, password) {
|
||||
const data = {
|
||||
userId,
|
||||
password
|
||||
}
|
||||
return request({
|
||||
url: '/system/user/resetPwd',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 用户状态修改
|
||||
export function changeUserStatus(userId, status) {
|
||||
const data = {
|
||||
userId,
|
||||
status
|
||||
}
|
||||
return request({
|
||||
url: '/system/user/changeStatus',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
36
pages.json
36
pages.json
@ -256,42 +256,6 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
"root": "pages_system/pages",
|
||||
"pages": [{
|
||||
"path": "dict/index"
|
||||
},
|
||||
{
|
||||
"path": "dict/data",
|
||||
"style": {
|
||||
"navigationBarTitleText": "计算工具"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "register/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "注册",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "forgot/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "忘记密码",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}, {
|
||||
"path": "bind/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "账号绑定",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<ng-cal-tools></ng-cal-tools>
|
||||
<NG-cal-tools></NG-cal-tools>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
@ -8,7 +8,7 @@
|
||||
ref,
|
||||
onMounted
|
||||
} from 'vue';
|
||||
import ngCalTools from '@/pages_caltools/pages/index'
|
||||
import NGCalTools from '@/pages_caltools/pages/index'
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.content {
|
||||
|
||||
298
pages/index.vue
298
pages/index.vue
@ -1,159 +1,179 @@
|
||||
<template>
|
||||
<view class="work-container">
|
||||
<!-- 轮播图 -->
|
||||
<uni-swiper-dot class="uni-swiper-dot-box" :info="data" :current="current" field="content">
|
||||
<swiper class="swiper-box" :current="swiperDotIndex" @change="changeSwiper">
|
||||
<swiper-item v-for="(item, index) in data" :key="index">
|
||||
<view class="swiper-item" @click="clickBannerItem(item)">
|
||||
<image :src="item.image" mode="aspectFill" :draggable="false" />
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</uni-swiper-dot>
|
||||
<view class="work-container">
|
||||
<!-- 轮播图 -->
|
||||
<uni-swiper-dot class="uni-swiper-dot-box" :info="data" :current="current" field="content">
|
||||
<swiper class="swiper-box" :current="swiperDotIndex" @change="changeSwiper">
|
||||
<swiper-item v-for="(item, index) in data" :key="index">
|
||||
<view class="swiper-item" @click="clickBannerItem(item)">
|
||||
<image :src="item.image" mode="aspectFill" :draggable="false" />
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</uni-swiper-dot>
|
||||
|
||||
<!-- 宫格组件 -->
|
||||
<view class="grid-body" v-for="(group, groupIndex) in moudlesGroups">
|
||||
<uni-section :title="group.name" type="line"></uni-section>
|
||||
<uni-grid :column="4" :showBorder="false">
|
||||
<uni-grid-item v-for="(item, itemIndex) in group.items" :key="itemIndex"
|
||||
@click="navigateToMoudles(item)" class="grid-item">
|
||||
<view class="grid-item-box">
|
||||
<uni-icons :type="item.icon" size="30"></uni-icons>
|
||||
<text class="text">{{item.name}}</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
</uni-grid>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 宫格组件 -->
|
||||
<uni-section title="系统管理" type="line"></uni-section>
|
||||
<view class="grid-body">
|
||||
<uni-grid :column="4" :showBorder="false" @change="changeGrid">
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="person-filled" size="30"></uni-icons>
|
||||
<text class="text">用户管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="staff-filled" size="30"></uni-icons>
|
||||
<text class="text">角色管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="color" size="30"></uni-icons>
|
||||
<text class="text">菜单管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="settings-filled" size="30"></uni-icons>
|
||||
<text class="text">部门管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="heart-filled" size="30"></uni-icons>
|
||||
<text class="text">岗位管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="bars" size="30"></uni-icons>
|
||||
<text class="text">字典管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="gear-filled" size="30"></uni-icons>
|
||||
<text class="text">参数设置</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="chat-filled" size="30"></uni-icons>
|
||||
<text class="text">通知公告</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="wallet-filled" size="30"></uni-icons>
|
||||
<text class="text">日志管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
</uni-grid>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
onMounted
|
||||
} from "vue";
|
||||
import modal from "@/plugins/modal"
|
||||
import { ref } from "vue";
|
||||
import modal from "@/plugins/modal"
|
||||
const current = ref(0);
|
||||
const swiperDotIndex = ref(0);
|
||||
const data = ref([
|
||||
{ image: '/static/images/banner/banner01.jpg' },
|
||||
{ image: '/static/images/banner/banner02.jpg' },
|
||||
{ image: '/static/images/banner/banner03.jpg' }
|
||||
]);
|
||||
|
||||
import {
|
||||
extractModuleData
|
||||
} from '@/utils/moudlesData.ts';
|
||||
function clickBannerItem(item) {
|
||||
console.info(item)
|
||||
};
|
||||
function changeSwiper(e) {
|
||||
current.value = e.detail.current
|
||||
}
|
||||
function changeGrid(e) {
|
||||
modal.showToast({
|
||||
title: '模块建设中',
|
||||
mask: false,
|
||||
icon: 'loading',
|
||||
duration: 1000
|
||||
});
|
||||
}
|
||||
|
||||
const current = ref(0);
|
||||
const swiperDotIndex = ref(0);
|
||||
const data = ref([{
|
||||
image: '/static/images/banner/banner01.jpg'
|
||||
},
|
||||
{
|
||||
image: '/static/images/banner/banner02.jpg'
|
||||
},
|
||||
{
|
||||
image: '/static/images/banner/banner03.jpg'
|
||||
}
|
||||
]);
|
||||
|
||||
// 核心数据:计算分组和功能项定义
|
||||
const moudlesGroups = ref([]);
|
||||
|
||||
onMounted(() => {
|
||||
moudlesGroups.value = extractModuleData(['系统管理'], false) })
|
||||
|
||||
function navigateToMoudles(item) {
|
||||
console.log(item)
|
||||
uni.navigateTo({
|
||||
url: item.path
|
||||
});
|
||||
};
|
||||
|
||||
function clickBannerItem(item) {
|
||||
console.info(item)
|
||||
};
|
||||
|
||||
function changeSwiper(e) {
|
||||
current.value = e.detail.current
|
||||
}
|
||||
|
||||
function changeGrid(e) {
|
||||
modal.showToast({
|
||||
title: '模块建设中',
|
||||
mask: false,
|
||||
icon: 'loading',
|
||||
duration: 1000
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
/* #endif */
|
||||
|
||||
.text {
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
.text {
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.grid-item-box {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 15px 0;
|
||||
}
|
||||
.grid-item-box {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.uni-margin-wrap {
|
||||
width: 690rpx;
|
||||
width: 100%;
|
||||
;
|
||||
}
|
||||
.uni-margin-wrap {
|
||||
width: 690rpx;
|
||||
width: 100%;
|
||||
;
|
||||
}
|
||||
|
||||
.swiper {
|
||||
height: 300rpx;
|
||||
}
|
||||
.swiper {
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.swiper-box {
|
||||
height: 150px;
|
||||
}
|
||||
.swiper-box {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.swiper-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
height: 300rpx;
|
||||
line-height: 300rpx;
|
||||
}
|
||||
.swiper-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
height: 300rpx;
|
||||
line-height: 300rpx;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 500px) {
|
||||
.uni-swiper-dot-box {
|
||||
width: 400px;
|
||||
/* #ifndef APP-NVUE */
|
||||
margin: 0 auto;
|
||||
/* #endif */
|
||||
margin-top: 8px;
|
||||
}
|
||||
@media screen and (min-width: 500px) {
|
||||
.uni-swiper-dot-box {
|
||||
width: 400px;
|
||||
/* #ifndef APP-NVUE */
|
||||
margin: 0 auto;
|
||||
/* #endif */
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
.image {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,266 +0,0 @@
|
||||
<script setup>
|
||||
import modal from '@/plugins/modal'
|
||||
import { getCodeImg } from '@/api/login'
|
||||
import { ref } from "vue";
|
||||
import config from '@/config.js'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { getWxCode } from '@/utils/geek';
|
||||
import { wxLogin } from '@/api/oauth';
|
||||
import { setToken } from '@/utils/auth';
|
||||
const userStore = useUserStore()
|
||||
const codeUrl = ref("");
|
||||
const captchaEnabled = ref(true); // 是否开启验证码
|
||||
const useWxLogin = ref(false); // 是否使用微信登录
|
||||
// #if MP-WEIXIN
|
||||
useWxLogin.value = true
|
||||
// #endif
|
||||
const globalConfig = ref(config);
|
||||
const loginForm = ref({
|
||||
username: "admin",
|
||||
password: "admin123",
|
||||
code: "",
|
||||
uuid: ''
|
||||
});
|
||||
|
||||
function handleLoginByWx() {
|
||||
getWxCode("__UNI__A6541FF").then(res => {
|
||||
console.log(res);
|
||||
wxLogin('miniapp', res).then(res => {
|
||||
if (res.token != null) {
|
||||
setToken(res.token);
|
||||
loginSuccess()
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 获取图形验证码
|
||||
function getCode() {
|
||||
getCodeImg().then(res => {
|
||||
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
|
||||
if (captchaEnabled.value) {
|
||||
codeUrl.value = 'data:image/gif;base64,' + res.img
|
||||
loginForm.value.uuid = res.uuid
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
async function handleLogin() {
|
||||
if (loginForm.value.username === "") {
|
||||
modal.msgError("请输入您的账号")
|
||||
} else if (loginForm.value.password === "") {
|
||||
modal.msgError("请输入您的密码")
|
||||
} else if (loginForm.value.code === "" && captchaEnabled.value) {
|
||||
modal.msgError("请输入验证码")
|
||||
} else {
|
||||
modal.loading("登录中,请耐心等待...")
|
||||
pwdLogin()
|
||||
}
|
||||
};
|
||||
// 密码登录
|
||||
async function pwdLogin() {
|
||||
userStore.login(loginForm.value).then(() => {
|
||||
modal.closeLoading()
|
||||
loginSuccess()
|
||||
}).catch(() => {
|
||||
if (captchaEnabled.value) {
|
||||
modal.closeLoading()
|
||||
getCode()
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
function loginSuccess(result) {
|
||||
// 设置用户信息
|
||||
userStore.getInfo().then(res => {
|
||||
uni.switchTab({
|
||||
url: '/pages/index'
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 隐私协议
|
||||
function handlePrivacy() {
|
||||
let site = globalConfig.value.appInfo.agreements[0];
|
||||
uni.navigateTo({
|
||||
url: `/pages/common/webview/index?title=${site.title}&url=${site.url}`
|
||||
});
|
||||
};
|
||||
// 用户协议
|
||||
function handleUserAgrement() {
|
||||
let site = globalConfig.value.appInfo.agreements[1]
|
||||
uni.navigateTo({
|
||||
url: `/pages/common/webview/index?title=${site.title}&url=${site.url}`
|
||||
});
|
||||
};
|
||||
|
||||
getCode();
|
||||
</script>
|
||||
<template>
|
||||
<view class="normal-login-container">
|
||||
<view class="logo-content align-center justify-center flex">
|
||||
<image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
|
||||
</image>
|
||||
<text class="title">登录</text>
|
||||
</view>
|
||||
<view class="login-form-content">
|
||||
<view class="input-item flex align-center">
|
||||
<view class="iconfont icon-user icon"></view>
|
||||
<input v-model="loginForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
|
||||
</view>
|
||||
<view class="input-item flex align-center">
|
||||
<view class="iconfont icon-password icon"></view>
|
||||
<input v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
|
||||
</view>
|
||||
<view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled">
|
||||
<view class="iconfont icon-code icon"></view>
|
||||
<input v-model="loginForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" />
|
||||
<view class="login-code">
|
||||
<image :src="codeUrl" @click="getCode" class="login-code-img"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-btn">
|
||||
<button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
|
||||
<button @click="handleLoginByWx" v-if="useWxLogin"
|
||||
class="login-btn cu-btn block bg-green lg round">微信一键登录</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="register-link" >
|
||||
<text class="question-text">没有账号?</text>
|
||||
<navigator class="link-type" url="/pages_mine/pages/register/index">立即注册</navigator>
|
||||
</view>
|
||||
|
||||
<view class="xieyi text-center">
|
||||
<text class="text-grey1">登录即代表同意</text>
|
||||
<text @click="handleUserAgrement" class="text-blue">《用户协议》</text>
|
||||
<text @click="handlePrivacy" class="text-blue">《隐私协议》</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.normal-login-container {
|
||||
width: 100%;
|
||||
|
||||
.logo-content {
|
||||
width: 100%;
|
||||
font-size: 21px;
|
||||
text-align: center;
|
||||
padding-top: 15%;
|
||||
|
||||
image {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form-content {
|
||||
text-align: center;
|
||||
margin: 20px auto;
|
||||
margin-top: 15%;
|
||||
width: 80%;
|
||||
|
||||
.input-item {
|
||||
margin: 20px auto;
|
||||
background-color: #f5f6f7;
|
||||
height: 45px;
|
||||
border-radius: 20px;
|
||||
|
||||
.icon {
|
||||
font-size: 38rpx;
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
margin-top: 40px;
|
||||
|
||||
.login-btn {
|
||||
height: 45px;
|
||||
|
||||
&+.login-btn {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.xieyi {
|
||||
color: #333;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.login-code {
|
||||
height: 38px;
|
||||
float: right;
|
||||
|
||||
.login-code-img {
|
||||
height: 38px;
|
||||
position: absolute;
|
||||
margin-left: 10px;
|
||||
width: 200rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.register-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.question-text {
|
||||
color: #606266;
|
||||
margin-right: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.link-type {
|
||||
color: #409EFF;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background-color: #409EFF;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #66b1ff;
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
809
pages/login.vue
809
pages/login.vue
@ -1,640 +1,219 @@
|
||||
<script setup>
|
||||
import modal from '@/plugins/modal'
|
||||
import { getCodeImg } from '@/api/login'
|
||||
import { ref } from "vue";
|
||||
import config from '@/config.js'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { getWxCode } from '@/utils/geek';
|
||||
import { wxLogin } from '@/api/oauth';
|
||||
import { setToken } from '@/utils/auth';
|
||||
const userStore = useUserStore()
|
||||
const codeUrl = ref("");
|
||||
const captchaEnabled = ref(true); // 是否开启验证码
|
||||
const useWxLogin = ref(false); // 是否使用微信登录
|
||||
// #if MP-WEIXIN
|
||||
useWxLogin.value = true
|
||||
// #endif
|
||||
const globalConfig = ref(config);
|
||||
const loginForm = ref({
|
||||
username: "admin",
|
||||
password: "admin123",
|
||||
code: "",
|
||||
uuid: ''
|
||||
});
|
||||
|
||||
function handleLoginByWx() {
|
||||
getWxCode().then(res => {
|
||||
console.log(res);
|
||||
wxLogin('miniapp', res).then(res => {
|
||||
if (res.token != null) {
|
||||
setToken(res.token);
|
||||
loginSuccess()
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 获取图形验证码
|
||||
function getCode() {
|
||||
getCodeImg().then(res => {
|
||||
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
|
||||
if (captchaEnabled.value) {
|
||||
codeUrl.value = 'data:image/gif;base64,' + res.img
|
||||
loginForm.value.uuid = res.uuid
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
async function handleLogin() {
|
||||
if (loginForm.value.username === "") {
|
||||
modal.msgError("请输入您的账号")
|
||||
} else if (loginForm.value.password === "") {
|
||||
modal.msgError("请输入您的密码")
|
||||
} else if (loginForm.value.code === "" && captchaEnabled.value) {
|
||||
modal.msgError("请输入验证码")
|
||||
} else {
|
||||
modal.loading("登录中,请耐心等待...")
|
||||
pwdLogin()
|
||||
}
|
||||
};
|
||||
// 密码登录
|
||||
async function pwdLogin() {
|
||||
userStore.login(loginForm.value).then(() => {
|
||||
modal.closeLoading()
|
||||
loginSuccess()
|
||||
}).catch(() => {
|
||||
if (captchaEnabled.value) {
|
||||
modal.closeLoading()
|
||||
getCode()
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
function loginSuccess(result) {
|
||||
// 设置用户信息
|
||||
userStore.getInfo().then(res => {
|
||||
uni.switchTab({
|
||||
url: '/pages/index'
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 隐私协议
|
||||
function handlePrivacy() {
|
||||
let site = globalConfig.value.appInfo.agreements[0];
|
||||
uni.navigateTo({
|
||||
url: `/pages/common/webview/index?title=${site.title}&url=${site.url}`
|
||||
});
|
||||
};
|
||||
// 用户协议
|
||||
function handleUserAgrement() {
|
||||
let site = globalConfig.value.appInfo.agreements[1]
|
||||
uni.navigateTo({
|
||||
url: `/pages/common/webview/index?title=${site.title}&url=${site.url}`
|
||||
});
|
||||
};
|
||||
|
||||
getCode();
|
||||
</script>
|
||||
<template>
|
||||
<view class="login-container">
|
||||
<view class="login-header">
|
||||
<image class="logo" src="/static/logo.png" mode="aspectFit"></image>
|
||||
<text class="app-name">{{ appName }}</text>
|
||||
<view class="normal-login-container">
|
||||
<view class="logo-content align-center justify-center flex">
|
||||
<image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
|
||||
</image>
|
||||
<text class="title">若依移动端登录</text>
|
||||
</view>
|
||||
<view class="login-form-content">
|
||||
<view class="input-item flex align-center">
|
||||
<view class="iconfont icon-user icon"></view>
|
||||
<input v-model="loginForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
|
||||
</view>
|
||||
<view class="input-item flex align-center">
|
||||
<view class="iconfont icon-password icon"></view>
|
||||
<input v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
|
||||
</view>
|
||||
<view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled">
|
||||
<view class="iconfont icon-code icon"></view>
|
||||
<input v-model="loginForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" />
|
||||
<view class="login-code">
|
||||
<image :src="codeUrl" @click="getCode" class="login-code-img"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-btn">
|
||||
<button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
|
||||
<button @click="handleLoginByWx" v-if="useWxLogin"
|
||||
class="login-btn cu-btn block bg-green lg round">微信一键登录</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="login-content">
|
||||
<!-- 登录方式切换 -->
|
||||
<view class="login-tabs">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'account' }"
|
||||
@click="switchTab('account')"
|
||||
>
|
||||
账号登录
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'phone' }"
|
||||
@click="switchTab('phone')"
|
||||
>
|
||||
手机登录
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账号密码登录 -->
|
||||
<view v-if="activeTab === 'account'" class="login-form">
|
||||
<view class="form-item">
|
||||
<text class="label">账号</text>
|
||||
<input
|
||||
v-model="accountForm.username"
|
||||
class="input"
|
||||
placeholder="请输入用户名/手机号/邮箱"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">密码</text>
|
||||
<input
|
||||
v-model="accountForm.password"
|
||||
class="input"
|
||||
password
|
||||
placeholder="请输入密码"
|
||||
placeholder-class="placeholder"
|
||||
@confirm="handleAccountLogin"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item" v-if="accountForm.showCaptcha">
|
||||
<text class="label">验证码</text>
|
||||
<view class="captcha-input">
|
||||
<input
|
||||
v-model="accountForm.code"
|
||||
class="input"
|
||||
placeholder="请输入验证码"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
<image
|
||||
:src="accountForm.captchaImg"
|
||||
class="captcha-img"
|
||||
@click="getCaptcha"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-options">
|
||||
<text class="forget-pwd" @click="goForgetPwd">忘记密码?</text>
|
||||
</view>
|
||||
|
||||
<button class="login-btn" @click="handleAccountLogin" :disabled="accountLogining">
|
||||
{{ accountLogining ? '登录中...' : '登录' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 手机验证码登录 -->
|
||||
<view v-if="activeTab === 'phone'" class="login-form">
|
||||
<view class="form-item">
|
||||
<text class="label">手机号</text>
|
||||
<input
|
||||
v-model="phoneForm.phone"
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">验证码</text>
|
||||
<view class="code-input">
|
||||
<input
|
||||
v-model="phoneForm.code"
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
<button
|
||||
class="send-code-btn"
|
||||
:disabled="phoneForm.countdown > 0"
|
||||
@click="handlesendPhoneCode"
|
||||
>
|
||||
{{ phoneForm.countdown > 0 ? `${phoneForm.countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="login-btn" @click="handlePhoneLogin" :disabled="phoneLogining">
|
||||
{{ phoneLogining ? '登录中...' : '登录' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 快捷登录方式 -->
|
||||
<view class="quick-login">
|
||||
<view class="divider">
|
||||
<text class="divider-text">快捷登录</text>
|
||||
</view>
|
||||
|
||||
<view class="login-methods">
|
||||
<view class="method-item" @click="handleOneClickLogin">
|
||||
<view class="method-icon one-click">
|
||||
<text class="iconfont">⚡</text>
|
||||
</view>
|
||||
<text class="method-text">本机一键登录</text>
|
||||
</view>
|
||||
|
||||
<view class="method-item" @click="handleWechatLogin">
|
||||
<view class="method-icon wechat">
|
||||
<text class="iconfont">💬</text>
|
||||
</view>
|
||||
<text class="method-text">微信登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 注册链接 -->
|
||||
<view class="register-link">
|
||||
<text>还没有账号? </text>
|
||||
<text class="link" @click="goRegister">立即注册</text>
|
||||
</view>
|
||||
<view class="xieyi text-center">
|
||||
<text class="text-grey1">登录即代表同意</text>
|
||||
<text @click="handleUserAgrement" class="text-blue">《用户协议》</text>
|
||||
<text @click="handlePrivacy" class="text-blue">《隐私协议》</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import modal from '@/plugins/modal'
|
||||
import { getCodeImg, login, sendPhoneCode, verifyPhoneCode } from '@/api/login'
|
||||
import { getWxCode } from '@/utils/geek'
|
||||
import { wxLogin } from '@/api/oauth'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { setToken } from '@/utils/auth'
|
||||
import config from '@/config.js'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const appName = config.appName || '系统名称'
|
||||
|
||||
// 响应式数据
|
||||
const activeTab = ref('account')
|
||||
const accountLogining = ref(false)
|
||||
const phoneLogining = ref(false)
|
||||
|
||||
// 账号登录表单
|
||||
const accountForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
code: '',
|
||||
captchaImg: '',
|
||||
uuid: '',
|
||||
showCaptcha: false
|
||||
})
|
||||
|
||||
// 手机登录表单
|
||||
const phoneForm = reactive({
|
||||
phone: '',
|
||||
code: '',
|
||||
countdown: 0,
|
||||
timer: null
|
||||
})
|
||||
|
||||
// 切换登录方式
|
||||
const switchTab = (tab) => {
|
||||
activeTab.value = tab
|
||||
if (tab === 'account' && !accountForm.captchaImg) {
|
||||
getCaptcha()
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
// 获取验证码
|
||||
const getCaptcha = async () => {
|
||||
try {
|
||||
const res = await getCodeImg()
|
||||
accountForm.captchaImg = 'data:image/gif;base64,' + res.img
|
||||
accountForm.uuid = res.uuid
|
||||
accountForm.showCaptcha = true
|
||||
} catch (error) {
|
||||
modal.alert('验证码获取失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 发送手机验证码
|
||||
const handlesendPhoneCode = async () => {
|
||||
if (!phoneForm.phone) {
|
||||
modal.alert('请输入手机号')
|
||||
return
|
||||
}
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(phoneForm.phone)) {
|
||||
modal.alert('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await sendPhoneCode({ phone: phoneForm.phone }, 'login')
|
||||
modal.alert('验证码已发送')
|
||||
|
||||
// 开始倒计时
|
||||
phoneForm.countdown = 60
|
||||
phoneForm.timer = setInterval(() => {
|
||||
phoneForm.countdown--
|
||||
if (phoneForm.countdown <= 0) {
|
||||
clearInterval(phoneForm.timer)
|
||||
}
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
modal.alert(error.message || '验证码发送失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 账号密码登录
|
||||
const handleAccountLogin = async () => {
|
||||
if (!accountForm.username) {
|
||||
modal.alert('请输入账号')
|
||||
return
|
||||
}
|
||||
|
||||
if (!accountForm.password) {
|
||||
modal.alert('请输入密码')
|
||||
return
|
||||
}
|
||||
|
||||
if (accountForm.showCaptcha && !accountForm.code) {
|
||||
modal.alert('请输入验证码')
|
||||
return
|
||||
}
|
||||
|
||||
accountLogining.value = true
|
||||
|
||||
try {
|
||||
const params = {
|
||||
username: accountForm.username,
|
||||
password: accountForm.password
|
||||
}
|
||||
|
||||
if (accountForm.showCaptcha) {
|
||||
params.code = accountForm.code
|
||||
params.uuid = accountForm.uuid
|
||||
}
|
||||
|
||||
const res = await login(params.username, params.password, params.code, params.uuid)
|
||||
|
||||
if (res.token) {
|
||||
setToken(res.token)
|
||||
await userStore.getInfo()
|
||||
modal.alert('登录成功', () => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index'
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果需要验证码,重新获取
|
||||
if (error.code === 401 || error.message?.includes('验证码')) {
|
||||
getCaptcha()
|
||||
}
|
||||
modal.alert(error.message || '登录失败')
|
||||
} finally {
|
||||
accountLogining.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 手机验证码登录
|
||||
const handlePhoneLogin = async () => {
|
||||
if (!phoneForm.phone) {
|
||||
modal.alert('请输入手机号')
|
||||
return
|
||||
}
|
||||
|
||||
if (!phoneForm.code) {
|
||||
modal.alert('请输入验证码')
|
||||
return
|
||||
}
|
||||
|
||||
phoneLogining.value = true
|
||||
|
||||
try {
|
||||
// 验证验证码
|
||||
await verifyPhoneCode({
|
||||
phone: phoneForm.phone,
|
||||
code: phoneForm.code
|
||||
}, 'login')
|
||||
|
||||
// 验证成功后执行登录
|
||||
const res = await login(phoneForm.phone, '', '', '')
|
||||
|
||||
if (res.token) {
|
||||
setToken(res.token)
|
||||
await userStore.getInfo()
|
||||
modal.alert('登录成功', () => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index'
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
modal.alert(error.message || '登录失败')
|
||||
} finally {
|
||||
phoneLogining.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 本机一键登录
|
||||
const handleOneClickLogin = async () => {
|
||||
try {
|
||||
// 这里需要调用uni的一键登录API
|
||||
// 由于不同平台实现不同,这里使用模拟实现
|
||||
modal.confirm('是否使用本机号码一键登录?', async () => {
|
||||
try {
|
||||
// 获取本机号码(实际项目中需要调用运营商SDK)
|
||||
const phoneNumber = '' // 从SDK获取
|
||||
|
||||
if (!phoneNumber) {
|
||||
modal.alert('无法获取本机号码')
|
||||
return
|
||||
}
|
||||
|
||||
// 执行一键登录
|
||||
const res = await login(phoneNumber, '', '', '')
|
||||
|
||||
if (res.token) {
|
||||
setToken(res.token)
|
||||
await userStore.getInfo()
|
||||
modal.alert('登录成功', () => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index'
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
modal.alert(error.message || '一键登录失败')
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
modal.alert('一键登录功能暂不可用')
|
||||
}
|
||||
}
|
||||
|
||||
// 微信登录
|
||||
const handleWechatLogin = async () => {
|
||||
try {
|
||||
const code = await getWxCode()
|
||||
const res = await wxLogin('miniapp', code)
|
||||
|
||||
if (res.token) {
|
||||
setToken(res.token)
|
||||
await userStore.getInfo()
|
||||
modal.alert('登录成功', () => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index'
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 400) {
|
||||
// 需要绑定微信
|
||||
modal.confirm('首次使用微信登录,需要绑定账号', () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_system/pages/login/bind-account'
|
||||
})
|
||||
})
|
||||
} else {
|
||||
modal.alert(error.message || '微信登录失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转注册页面
|
||||
const goRegister = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_system/pages/register/index'
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转忘记密码页面
|
||||
const goForgetPwd = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_system/pages/forget/index'
|
||||
})
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
getCaptcha()
|
||||
})
|
||||
|
||||
onLoad(() => {
|
||||
// 检查是否有token,如果有则直接跳转
|
||||
const token = uni.getStorageSync('token')
|
||||
if (token) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index'
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 60rpx 40rpx;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 80rpx;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 36rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.login-content {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx;
|
||||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.login-tabs {
|
||||
display: flex;
|
||||
margin-bottom: 40rpx;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #007aff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60rpx;
|
||||
height: 4rpx;
|
||||
background: #007aff;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
.normal-login-container {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 10rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #ccc;
|
||||
}
|
||||
.logo-content {
|
||||
width: 100%;
|
||||
font-size: 21px;
|
||||
text-align: center;
|
||||
padding-top: 15%;
|
||||
|
||||
.captcha-input, .code-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
image {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.captcha-img {
|
||||
width: 200rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 10rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
}
|
||||
.title {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.send-code-btn {
|
||||
width: 200rpx;
|
||||
height: 80rpx;
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10rpx;
|
||||
font-size: 24rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.login-form-content {
|
||||
text-align: center;
|
||||
margin: 20px auto;
|
||||
margin-top: 15%;
|
||||
width: 80%;
|
||||
|
||||
.send-code-btn[disabled] {
|
||||
background: #ccc;
|
||||
}
|
||||
.input-item {
|
||||
margin: 20px auto;
|
||||
background-color: #f5f6f7;
|
||||
height: 45px;
|
||||
border-radius: 20px;
|
||||
|
||||
.form-options {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
.icon {
|
||||
font-size: 38rpx;
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.forget-pwd {
|
||||
color: #007aff;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.input {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.login-btn[disabled] {
|
||||
background: #ccc;
|
||||
}
|
||||
.action-btn {
|
||||
margin-top: 40px;
|
||||
|
||||
.quick-login {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
.login-btn {
|
||||
height: 45px;
|
||||
|
||||
.divider {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin: 40rpx 0;
|
||||
}
|
||||
&+.login-btn {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.divider::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1rpx;
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
.divider-text {
|
||||
background: #fff;
|
||||
padding: 0 20rpx;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.xieyi {
|
||||
color: #333;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.login-methods {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 80rpx;
|
||||
}
|
||||
.login-code {
|
||||
height: 38px;
|
||||
float: right;
|
||||
|
||||
.method-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.login-code-img {
|
||||
height: 38px;
|
||||
position: absolute;
|
||||
margin-left: 10px;
|
||||
width: 200rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.method-icon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10rpx;
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.method-icon.one-click {
|
||||
background: #ff9500;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.method-icon.wechat {
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.method-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.register-link {
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #007aff;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
48
pages/template - 副本.vue
Normal file
48
pages/template - 副本.vue
Normal file
@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import tab from "@/plugins/tab";
|
||||
import list from "./template.config.js";
|
||||
|
||||
interface ListItem {
|
||||
groupName: string;
|
||||
list: FieldItem[];
|
||||
}
|
||||
|
||||
interface FieldItem {
|
||||
title: string;
|
||||
icon: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
const listData = list as ListItem[];
|
||||
const getIcon = (path: string) => `../static/uview/demo/${path}.png`;
|
||||
const openPage = (path: string) => tab.navigateTo(path)
|
||||
const getGroupTitle = (item: ListItem) => item.groupName;
|
||||
const getFieldTitle = (item: FieldItem) => item.title;
|
||||
</script>
|
||||
<template>
|
||||
<view class="wrap">
|
||||
<view class="list-wrap">
|
||||
<u-cell-group title-bg-color="rgb(243, 244, 246)" :title="getGroupTitle(item)" v-for="(item, index) in listData"
|
||||
:key="index">
|
||||
<u-cell :titleStyle="{ fontWeight: 500 }" @click="openPage(item1.path)" :title="getFieldTitle(item1)"
|
||||
v-for="(item1, index1) in item.list" :key="index1">
|
||||
<template v-slot:icon>
|
||||
<image class="u-cell-icon" :src="getIcon(item1.icon)" mode="widthFix"></image>
|
||||
</template>
|
||||
</u-cell>
|
||||
</u-cell-group>
|
||||
</view>
|
||||
<u-gap height="70"></u-gap>
|
||||
</view>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: rgb(240, 242, 244);
|
||||
}
|
||||
|
||||
.u-cell-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
</style>
|
||||
@ -192,7 +192,7 @@
|
||||
description: '展示不同天然气组分与热值之间的对应关系曲线',
|
||||
images: [{
|
||||
url: '/static/charts/heat-value-chart.jpg',
|
||||
thumbnail: '',
|
||||
thumbnail: '/static/charts/heat-value-thumb.jpg',
|
||||
description: '热值关系图表'
|
||||
}],
|
||||
tables: [],
|
||||
|
||||
179
pages/work.vue
179
pages/work.vue
@ -1,179 +0,0 @@
|
||||
<template>
|
||||
<view class="work-container">
|
||||
<!-- 轮播图 -->
|
||||
<uni-swiper-dot class="uni-swiper-dot-box" :info="data" :current="current" field="content">
|
||||
<swiper class="swiper-box" :current="swiperDotIndex" @change="changeSwiper">
|
||||
<swiper-item v-for="(item, index) in data" :key="index">
|
||||
<view class="swiper-item" @click="clickBannerItem(item)">
|
||||
<image :src="item.image" mode="aspectFill" :draggable="false" />
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</uni-swiper-dot>
|
||||
|
||||
<!-- 宫格组件 -->
|
||||
<uni-section title="系统管理" type="line"></uni-section>
|
||||
<view class="grid-body">
|
||||
<uni-grid :column="4" :showBorder="false" @change="changeGrid">
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="person-filled" size="30"></uni-icons>
|
||||
<text class="text">用户管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="staff-filled" size="30"></uni-icons>
|
||||
<text class="text">角色管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="color" size="30"></uni-icons>
|
||||
<text class="text">菜单管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="settings-filled" size="30"></uni-icons>
|
||||
<text class="text">部门管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="heart-filled" size="30"></uni-icons>
|
||||
<text class="text">岗位管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="bars" size="30"></uni-icons>
|
||||
<text class="text">字典管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="gear-filled" size="30"></uni-icons>
|
||||
<text class="text">参数设置</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="chat-filled" size="30"></uni-icons>
|
||||
<text class="text">通知公告</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="wallet-filled" size="30"></uni-icons>
|
||||
<text class="text">日志管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
</uni-grid>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import modal from "@/plugins/modal"
|
||||
const current = ref(0);
|
||||
const swiperDotIndex = ref(0);
|
||||
const data = ref([
|
||||
{ image: '/static/images/banner/banner01.jpg' },
|
||||
{ image: '/static/images/banner/banner02.jpg' },
|
||||
{ image: '/static/images/banner/banner03.jpg' }
|
||||
]);
|
||||
|
||||
function clickBannerItem(item) {
|
||||
console.info(item)
|
||||
};
|
||||
function changeSwiper(e) {
|
||||
current.value = e.detail.current
|
||||
}
|
||||
function changeGrid(e) {
|
||||
modal.showToast({
|
||||
title: '模块建设中',
|
||||
mask: false,
|
||||
icon: 'loading',
|
||||
duration: 1000
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
.text {
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.grid-item-box {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.uni-margin-wrap {
|
||||
width: 690rpx;
|
||||
width: 100%;
|
||||
;
|
||||
}
|
||||
|
||||
.swiper {
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.swiper-box {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.swiper-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
height: 300rpx;
|
||||
line-height: 300rpx;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 500px) {
|
||||
.uni-swiper-dot-box {
|
||||
width: 400px;
|
||||
/* #ifndef APP-NVUE */
|
||||
margin: 0 auto;
|
||||
/* #endif */
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -467,7 +467,7 @@
|
||||
}
|
||||
|
||||
:deep(.uni-forms-item__label) {
|
||||
font-size: 22rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
:deep(.uni-input-input) {
|
||||
|
||||
@ -27,24 +27,54 @@
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
onBeforeMount,
|
||||
watch
|
||||
ref
|
||||
} from 'vue';
|
||||
import {
|
||||
extractModuleData
|
||||
} from '@/utils/moudlesData.ts';
|
||||
|
||||
// 响应式数据:当前激活的 Swiper 标签索引
|
||||
const currentTab = ref(0);
|
||||
|
||||
// 核心数据:计算分组和功能项定义
|
||||
const calcGroups = ref([]);
|
||||
|
||||
onMounted(() => {
|
||||
calcGroups.value = extractModuleData(['流量计算', '参数计算'], false)
|
||||
console.log(calcGroups.value);
|
||||
})
|
||||
const calcGroups = [{
|
||||
name: '流量计算',
|
||||
color: '#007AFF',
|
||||
items: [{
|
||||
name: '差压式流量计算',
|
||||
icon: 'smallcircle',
|
||||
dMeterType: 0
|
||||
},
|
||||
{
|
||||
name: '速度式流量计算',
|
||||
icon: 'paperplane',
|
||||
dMeterType: 1
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '参数计算',
|
||||
color: '#5AC8FA',
|
||||
items: [{
|
||||
name: '压缩因子',
|
||||
icon: 'pyq',
|
||||
dMeterType: 4
|
||||
},
|
||||
{
|
||||
name: '声速计算',
|
||||
icon: 'sound',
|
||||
dMeterType: 5
|
||||
},
|
||||
{
|
||||
name: '发热量',
|
||||
icon: 'fire',
|
||||
dMeterType: 6
|
||||
},
|
||||
{
|
||||
name: '其他参数',
|
||||
icon: 'more',
|
||||
dMeterType: 7
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* 处理 Swiper 滑动切换事件
|
||||
@ -59,10 +89,12 @@
|
||||
* @param {Object} item 点击的项
|
||||
*/
|
||||
const navigateToCalc = (item) => {
|
||||
const dMeterType = item.params;
|
||||
const dMeterType = item.dMeterType;
|
||||
console.log(`导航到计算页面,dMeterType: ${dMeterType}`);
|
||||
|
||||
// 使用 UniApp 原生导航 API
|
||||
uni.navigateTo({
|
||||
url: item.path + `?dMeterType=${dMeterType}`
|
||||
url: `/pages_caltools/pages/main?dMeterType=${dMeterType}`
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -1,277 +1,273 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getCodeImg, sendEmailCode, sendPhoneCode } from "@/api/login";
|
||||
import useUserStore from "@/store/modules/user";
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps<{
|
||||
register: boolean;
|
||||
captchaEnabled: boolean;
|
||||
method: 'password' | 'phone' | 'email';
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const registerForm = ref({
|
||||
username: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
email: '',
|
||||
phonenumber: '',
|
||||
code: "",
|
||||
uuid: "",
|
||||
});
|
||||
|
||||
const codeUrl = ref("");
|
||||
|
||||
// 分开控制发送验证码和注册的 loading 状态
|
||||
const sendCodeLoading = ref(false);
|
||||
const registerLoading = ref(false);
|
||||
|
||||
const registerRef = ref<any>(null);
|
||||
|
||||
// 表单校验规则
|
||||
const registerRules = {
|
||||
username: [
|
||||
{ required: true, message: "请输入您的账号", trigger: "blur" },
|
||||
{ min: 2, max: 20, message: "用户账号长度必须介于 2 和 20 之间", trigger: "blur" }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: "请输入您的密码", trigger: "blur" },
|
||||
{ min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" },
|
||||
{ pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: "请再次输入您的密码", trigger: "blur" },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (registerForm.value.password !== value) {
|
||||
callback(new Error("两次输入的密码不一致"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
code: [{ required: true, message: "请输入验证码", trigger: "change" }]
|
||||
};
|
||||
|
||||
// 用户仓库
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 获取图形验证码
|
||||
function getCode() {
|
||||
if (!props.captchaEnabled) return;
|
||||
getCodeImg().then((res: any) => {
|
||||
codeUrl.value = "data:image/gif;base64," + res.img;
|
||||
registerForm.value.uuid = res.uuid;
|
||||
});
|
||||
}
|
||||
|
||||
// 发送验证码
|
||||
function sendCode() {
|
||||
if (props.method === 'email') {
|
||||
sendEmailCode(registerForm.value, 'register');
|
||||
} else if (props.method === 'phone') {
|
||||
sendPhoneCode(registerForm.value, 'register');
|
||||
}
|
||||
}
|
||||
|
||||
// 注册提交
|
||||
function handleRegister() {
|
||||
registerRef.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
userStore.register(registerForm.value, props.method).then(() => {
|
||||
uni.showModal({
|
||||
title: "系统提示",
|
||||
content: `<font color='red'>恭喜你,您的账号 ${registerForm.value.username} 注册成功!</font>`,
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
uni.redirectTo({
|
||||
url: "/pages/login/login" // 跳转到登录页
|
||||
});
|
||||
}
|
||||
});
|
||||
}).catch(() => {
|
||||
getCode();
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载时获取验证码
|
||||
onMounted(() => {
|
||||
getCode();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="register-container">
|
||||
<uni-forms
|
||||
ref="registerRef"
|
||||
:model="registerForm"
|
||||
:rules="registerRules"
|
||||
class="register-form"
|
||||
>
|
||||
<!-- 邮箱输入 -->
|
||||
<uni-forms-item prop="email" v-if="method === 'email'">
|
||||
<uni-easyinput
|
||||
v-model="registerForm.email"
|
||||
type="text"
|
||||
placeholder="邮箱"
|
||||
prefixIcon="email"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<view class="register-container">
|
||||
<!-- 背景图建议在 pages.json 中配置,这里作为备用 -->
|
||||
<view class="register-form-wrapper">
|
||||
<uni-forms ref="registerFormRef" :modelValue="registerForm" :rules="registerRules" label-width="0">
|
||||
|
||||
<!-- 手机号输入 -->
|
||||
<uni-forms-item prop="phonenumber" v-else-if="method === 'phone'">
|
||||
<uni-easyinput
|
||||
v-model="registerForm.phonenumber"
|
||||
type="text"
|
||||
placeholder="手机号"
|
||||
prefixIcon="phone"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<view class="title">天然气工具平台</view>
|
||||
|
||||
<!-- 用户名输入 -->
|
||||
<uni-forms-item prop="username" v-else>
|
||||
<uni-easyinput
|
||||
v-model="registerForm.username"
|
||||
type="text"
|
||||
placeholder="账号"
|
||||
prefixIcon="user"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item name="username">
|
||||
<uni-easyinput v-model="registerForm.username" type="text" placeholder="账号" prefixIcon="person"
|
||||
@confirm="handleRegister" />
|
||||
</uni-forms-item>
|
||||
|
||||
<!-- 密码输入 -->
|
||||
<uni-forms-item prop="password">
|
||||
<uni-easyinput
|
||||
v-model="registerForm.password"
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
prefixIcon="lock"
|
||||
@keyup.enter="handleRegister"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item name="password">
|
||||
<uni-easyinput v-model="registerForm.password" type="password" placeholder="密码" prefixIcon="locked"
|
||||
@confirm="handleRegister" />
|
||||
</uni-forms-item>
|
||||
|
||||
<!-- 确认密码输入 -->
|
||||
<uni-forms-item prop="confirmPassword">
|
||||
<uni-easyinput
|
||||
v-model="registerForm.confirmPassword"
|
||||
type="password"
|
||||
placeholder="确认密码"
|
||||
prefixIcon="lock"
|
||||
@keyup.enter="handleRegister"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item name="confirmPassword">
|
||||
<uni-easyinput v-model="registerForm.confirmPassword" type="password" placeholder="确认密码"
|
||||
prefixIcon="locked" @confirm="handleRegister" />
|
||||
</uni-forms-item>
|
||||
|
||||
<!-- 验证码输入 -->
|
||||
<uni-forms-item prop="code">
|
||||
<view class="code-input">
|
||||
<uni-easyinput
|
||||
v-model="registerForm.code"
|
||||
type="text"
|
||||
placeholder="验证码"
|
||||
prefixIcon="code"
|
||||
@keyup.enter="handleRegister"
|
||||
/>
|
||||
|
||||
<!-- 图形验证码或发送按钮 -->
|
||||
<view class="code-action">
|
||||
<uni-image
|
||||
v-if="captchaEnabled && method === 'password'"
|
||||
:src="codeUrl"
|
||||
class="code-img"
|
||||
@click="getCode"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<uni-button
|
||||
v-else
|
||||
type="primary"
|
||||
size="mini"
|
||||
@click="sendCode"
|
||||
:loading="loading"
|
||||
>
|
||||
发送验证码
|
||||
</uni-button>
|
||||
</view>
|
||||
</view>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item name="code" v-if="captchaEnabled">
|
||||
<view class="code-input-wrapper">
|
||||
<uni-easyinput v-model="registerForm.code" type="text" placeholder="验证码" prefixIcon="code"
|
||||
@confirm="handleRegister" />
|
||||
<view class="code-img-wrapper">
|
||||
<image :src="codeUrl" class="code-img" @tap="getCode"></image>
|
||||
</view>
|
||||
</view>
|
||||
</uni-forms-item>
|
||||
|
||||
<!-- 注册按钮 -->
|
||||
<uni-forms-item>
|
||||
<button
|
||||
type="primary"
|
||||
size="default"
|
||||
class="register-btn"
|
||||
:loading="loading"
|
||||
@click="handleRegister"
|
||||
>
|
||||
注册
|
||||
</button>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item>
|
||||
<uni-button type="primary" size="default" :loading="loading" @click="handleRegister"
|
||||
class="register-btn">
|
||||
注册
|
||||
</uni-button>
|
||||
<view class="login-link">
|
||||
<text>已有账号?</text>
|
||||
<navigator url="/pages/login/login" class="link-text">立即登录</navigator>
|
||||
</view>
|
||||
</uni-forms-item>
|
||||
|
||||
<!-- 已有账号跳转 -->
|
||||
<view class="login-link">
|
||||
<text class="question-text">已有账号?</text>
|
||||
<navigator url="/pages/login" class="link-type">立即登录</navigator>
|
||||
</view>
|
||||
</uni-forms>
|
||||
</view>
|
||||
</uni-forms>
|
||||
</view>
|
||||
|
||||
<view class="el-register-footer">
|
||||
<text>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.register-container {
|
||||
padding: 40rpx;
|
||||
}
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
onReady
|
||||
} from 'vue';
|
||||
import {
|
||||
getCodeImg,
|
||||
register
|
||||
} from '@/api/login'; // 假设 API 适配了 Uniapp
|
||||
|
||||
.register-form {
|
||||
background-color: #fff;
|
||||
padding: 60rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
// 表单引用
|
||||
const registerFormRef = ref(null);
|
||||
|
||||
.code-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
// 表单数据
|
||||
const registerForm = ref({
|
||||
username: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
code: "",
|
||||
uuid: ""
|
||||
});
|
||||
|
||||
.code-action {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
// 校验规则
|
||||
const registerRules = {
|
||||
username: [{
|
||||
required: true,
|
||||
errorMessage: '请输入您的账号'
|
||||
},
|
||||
{
|
||||
minLength: 2,
|
||||
maxLength: 20,
|
||||
errorMessage: '用户账号长度必须介于 2 和 20 之间'
|
||||
}
|
||||
],
|
||||
password: [{
|
||||
required: true,
|
||||
errorMessage: '请输入您的密码'
|
||||
},
|
||||
{
|
||||
minLength: 5,
|
||||
maxLength: 20,
|
||||
errorMessage: '用户密码长度必须介于 5 和 20 之间'
|
||||
},
|
||||
{
|
||||
pattern: /^[^<>"'|\\]+$/,
|
||||
errorMessage: '不能包含非法字符:< > " \' \\ |'
|
||||
}
|
||||
],
|
||||
confirmPassword: [{
|
||||
required: true,
|
||||
errorMessage: '请再次输入您的密码'
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback, source, options) => {
|
||||
if (value !== registerForm.value.password) {
|
||||
callback(new Error('两次输入的密码不一致'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
errorMessage: '两次输入的密码不一致'
|
||||
}
|
||||
],
|
||||
code: [{
|
||||
required: true,
|
||||
errorMessage: '请输入验证码'
|
||||
}]
|
||||
};
|
||||
|
||||
.code-img {
|
||||
width: 180rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
// 状态变量
|
||||
const codeUrl = ref("");
|
||||
const loading = ref(false);
|
||||
const captchaEnabled = ref(true);
|
||||
|
||||
.register-btn {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
font-size: 30rpx;
|
||||
border-radius: 40rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
// 获取验证码
|
||||
const getCode = () => {
|
||||
getCodeImg().then(res => {
|
||||
// 假设 res.data 结构为 { img: 'base64...', uuid: '...', captchaEnabled: true }
|
||||
captchaEnabled.value = res.data.captchaEnabled !== false;
|
||||
if (captchaEnabled.value) {
|
||||
codeUrl.value = res.data.img; // Uniapp 的 image 组件可以直接显示 base64
|
||||
registerForm.value.uuid = res.data.uuid;
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error("获取验证码失败:", err);
|
||||
uni.showToast({
|
||||
title: '获取验证码失败',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
.login-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 40rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
// 处理注册
|
||||
const handleRegister = () => {
|
||||
// 使用 uni-forms 的 validate 方法
|
||||
registerFormRef.value.validate().then(() => {
|
||||
loading.value = true;
|
||||
register(registerForm.value).then(res => {
|
||||
uni.showModal({
|
||||
title: '系统提示',
|
||||
content: `<font color='red'>恭喜你,您的账号 ${registerForm.value.username} 注册成功!</font>`,
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
// 注册成功跳转到登录页,关闭当前页
|
||||
uni.redirectTo({
|
||||
url: '/pages/login/login'
|
||||
});
|
||||
}
|
||||
});
|
||||
}).catch(err => {
|
||||
// 错误处理,如用户名已存在等
|
||||
uni.showToast({
|
||||
title: err.message || '注册失败',
|
||||
icon: 'none'
|
||||
});
|
||||
if (captchaEnabled.value) {
|
||||
getCode(); // 刷新验证码
|
||||
}
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}).catch(errors => {
|
||||
// 表单校验失败,由 uni-forms 自动提示错误信息
|
||||
console.log('表单校验失败:', errors);
|
||||
});
|
||||
};
|
||||
|
||||
.link-type {
|
||||
color: #007aff;
|
||||
margin-left: 10rpx;
|
||||
text-decoration: underline;
|
||||
}
|
||||
// 页面加载时获取验证码
|
||||
onReady(() => {
|
||||
getCode();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面整体背景,建议在 pages.json 中配置 "style": { "backgroundImage": "url('/static/login-background.jpg')" } */
|
||||
.register-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f5f5;
|
||||
/* 备用背景色 */
|
||||
}
|
||||
|
||||
/* 表单外层容器 */
|
||||
.register-form-wrapper {
|
||||
width: 100%;
|
||||
max-width: 400rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 验证码输入框和图片一行显示 */
|
||||
.code-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.code-img-wrapper {
|
||||
flex-shrink: 0;
|
||||
width: 140rpx;
|
||||
height: 72rpx;
|
||||
/* 与输入框高度对齐 */
|
||||
}
|
||||
|
||||
.code-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
/* 注册按钮 */
|
||||
.register-btn {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
border-radius: 40rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
/* 登录链接 */
|
||||
.login-link {
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.link-text {
|
||||
color: #007aff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.el-register-footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
font-size: 20rpx;
|
||||
color: #ccc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@ -1,314 +0,0 @@
|
||||
<template>
|
||||
<view class="forget-container">
|
||||
<view class="forget-header">
|
||||
<text class="back-btn" @click="goBack">‹</text>
|
||||
<text class="title">忘记密码</text>
|
||||
<view class="placeholder"></view>
|
||||
</view>
|
||||
|
||||
<view class="forget-content">
|
||||
<view class="forget-form">
|
||||
<view class="form-item">
|
||||
<text class="label">手机号</text>
|
||||
<input
|
||||
v-model="forgetForm.phone"
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="请输入注册手机号"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">验证码</text>
|
||||
<view class="code-input">
|
||||
<input
|
||||
v-model="forgetForm.code"
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
<button
|
||||
class="send-code-btn"
|
||||
:disabled="countdown > 0"
|
||||
@click="sendCode"
|
||||
>
|
||||
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">新密码</text>
|
||||
<input
|
||||
v-model="forgetForm.newPassword"
|
||||
class="input"
|
||||
password
|
||||
placeholder="请输入新密码"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">确认密码</text>
|
||||
<input
|
||||
v-model="forgetForm.confirmPassword"
|
||||
class="input"
|
||||
password
|
||||
placeholder="请再次输入新密码"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<button class="confirm-btn" @click="handleResetPassword" :disabled="resetting">
|
||||
{{ resetting ? '重置中...' : '重置密码' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import modal from '@/plugins/modal'
|
||||
import { sendPhoneCode, verifyPhoneCode } from '@/api/login'
|
||||
import { resetUserPwd } from '@/api/system/user'
|
||||
|
||||
// 响应式数据
|
||||
const resetting = ref(false)
|
||||
const countdown = ref(0)
|
||||
let countdownTimer = null
|
||||
|
||||
// 忘记密码表单
|
||||
const forgetForm = reactive({
|
||||
phone: '',
|
||||
code: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
// 发送验证码
|
||||
const sendCode = async () => {
|
||||
if (!forgetForm.phone) {
|
||||
modal.alert('请输入手机号')
|
||||
return
|
||||
}
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(forgetForm.phone)) {
|
||||
modal.alert('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await sendPhoneCode({ phone: forgetForm.phone }, 'reset')
|
||||
modal.alert('验证码已发送')
|
||||
|
||||
// 开始倒计时
|
||||
countdown.value = 60
|
||||
countdownTimer = setInterval(() => {
|
||||
countdown.value--
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(countdownTimer)
|
||||
}
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
modal.alert(error.message || '验证码发送失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理密码重置
|
||||
const handleResetPassword = async () => {
|
||||
if (!validateForm()) return
|
||||
|
||||
resetting.value = true
|
||||
|
||||
try {
|
||||
// 验证验证码
|
||||
await verifyPhoneCode({
|
||||
phone: forgetForm.phone,
|
||||
code: forgetForm.code
|
||||
}, 'reset')
|
||||
|
||||
// 执行密码重置
|
||||
// 这里需要先找到用户ID,实际项目中可能需要根据手机号查询用户信息
|
||||
// 假设我们已经获得了userId
|
||||
const userId = await getUserIdByPhone(forgetForm.phone)
|
||||
|
||||
if (!userId) {
|
||||
modal.alert('未找到该手机号对应的用户')
|
||||
return
|
||||
}
|
||||
|
||||
await resetUserPwd(userId, forgetForm.newPassword)
|
||||
|
||||
modal.alert('密码重置成功', () => {
|
||||
uni.redirectTo({
|
||||
url: '/pages/login'
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
modal.alert(error.message || '密码重置失败')
|
||||
} finally {
|
||||
resetting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 根据手机号获取用户ID(模拟实现)
|
||||
const getUserIdByPhone = async (phone) => {
|
||||
// 实际项目中需要调用API查询用户信息
|
||||
// 这里返回一个模拟的userId
|
||||
return '123' // 实际应该从API获取
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
const validateForm = () => {
|
||||
if (!forgetForm.phone) {
|
||||
modal.alert('请输入手机号')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(forgetForm.phone)) {
|
||||
modal.alert('请输入正确的手机号')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!forgetForm.code) {
|
||||
modal.alert('请输入验证码')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!forgetForm.newPassword) {
|
||||
modal.alert('请输入新密码')
|
||||
return false
|
||||
}
|
||||
|
||||
if (forgetForm.newPassword.length < 6) {
|
||||
modal.alert('密码长度不能少于6位')
|
||||
return false
|
||||
}
|
||||
|
||||
if (forgetForm.newPassword !== forgetForm.confirmPassword) {
|
||||
modal.alert('两次输入的密码不一致')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 返回登录页面
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
// 清理计时器
|
||||
import { onUnmounted } from 'vue'
|
||||
onUnmounted(() => {
|
||||
if (countdownTimer) {
|
||||
clearInterval(countdownTimer)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.forget-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.forget-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 40rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
font-size: 50rpx;
|
||||
color: #333;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.forget-content {
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.forget-form {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 10rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.send-code-btn {
|
||||
width: 200rpx;
|
||||
height: 80rpx;
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10rpx;
|
||||
font-size: 24rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.send-code-btn[disabled] {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.confirm-btn[disabled] {
|
||||
background: #ccc;
|
||||
}
|
||||
</style>
|
||||
@ -1,473 +0,0 @@
|
||||
<template>
|
||||
<view class="login-container">
|
||||
<!-- 顶部标题 -->
|
||||
<view class="login-header">
|
||||
<text class="login-title">欢迎登录</text>
|
||||
</view>
|
||||
|
||||
<!-- 登录方式切换 -->
|
||||
<view class="login-tabs">
|
||||
<view
|
||||
class="login-tab"
|
||||
:class="{ 'active': loginType === 'account' }"
|
||||
@click="loginType = 'account'"
|
||||
>
|
||||
账号密码
|
||||
</view>
|
||||
<view
|
||||
class="login-tab"
|
||||
:class="{ 'active': loginType === 'phone' }"
|
||||
@click="loginType = 'phone'"
|
||||
>
|
||||
手机验证码
|
||||
</view>
|
||||
<view
|
||||
class="login-tab"
|
||||
:class="{ 'active': loginType === 'wx' }"
|
||||
@click="loginType = 'wx'"
|
||||
>
|
||||
微信登录
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<view class="login-form">
|
||||
<!-- 账号密码登录 -->
|
||||
<view v-if="loginType === 'account'">
|
||||
<view class="form-item">
|
||||
<input
|
||||
v-model="form.username"
|
||||
type="text"
|
||||
placeholder="请输入用户名/手机号"
|
||||
class="input"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
class="input"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item captcha">
|
||||
<input
|
||||
v-model="form.code"
|
||||
type="text"
|
||||
placeholder="验证码"
|
||||
class="input"
|
||||
/>
|
||||
<image
|
||||
:src="captchaUrl"
|
||||
@click="refreshCaptcha"
|
||||
class="captcha-img"
|
||||
/>
|
||||
</view>
|
||||
<button
|
||||
class="login-btn"
|
||||
:disabled="isLogining"
|
||||
@click="handleLogin"
|
||||
>
|
||||
{{ isLogining ? '登录中...' : '登录' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 手机验证码登录 -->
|
||||
<view v-else-if="loginType === 'phone'">
|
||||
<view class="form-item">
|
||||
<input
|
||||
v-model="form.phone"
|
||||
type="tel"
|
||||
placeholder="请输入手机号"
|
||||
class="input"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<input
|
||||
v-model="form.code"
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
class="input"
|
||||
/>
|
||||
<text
|
||||
v-if="countdown > 0"
|
||||
class="countdown"
|
||||
>{{ countdown }}s</text>
|
||||
<text
|
||||
v-else
|
||||
class="send-code"
|
||||
@click="sendCode"
|
||||
>发送验证码</text>
|
||||
</view>
|
||||
<button
|
||||
class="login-btn"
|
||||
:disabled="isLogining"
|
||||
@click="handleLogin"
|
||||
>
|
||||
{{ isLogining ? '登录中...' : '登录' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 微信登录 -->
|
||||
<view v-else-if="loginType === 'wx'">
|
||||
<button
|
||||
class="wx-login-btn"
|
||||
@click="wxLogin"
|
||||
:disabled="isLogining"
|
||||
>
|
||||
<image src="/static/icon-wechat.png" class="wx-icon" />
|
||||
微信一键登录
|
||||
</button>
|
||||
<view class="one-click-login" @click="getPhone">
|
||||
<image src="/static/icon-phone.png" class="phone-icon" />
|
||||
本机一键登录
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部链接 -->
|
||||
<view class="login-footer">
|
||||
<text class="link" @click="toRegister">注册新账号</text>
|
||||
<text class="link" @click="toForget">忘记密码</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { login, getCodeImg, sendPhoneCode, verifyPhoneCode } from '@/api/login';
|
||||
import { register } from '@/api/system/user';
|
||||
import { getWxCode } from '@/utils/geek';
|
||||
import { wxLogin, wxRegister } from '@/api/oauth';
|
||||
import { setToken } from '@/utils/auth';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import modal from '@/plugins/modal';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
// 当前登录方式 (account/phone/wx)
|
||||
const loginType = ref('account');
|
||||
// 表单数据
|
||||
const form = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
phone: '',
|
||||
code: '',
|
||||
captcha: ''
|
||||
});
|
||||
// 验证码图片
|
||||
const captchaUrl = ref('');
|
||||
// 倒计时(用于发送验证码)
|
||||
const countdown = ref(0);
|
||||
// 登录中状态
|
||||
const isLogining = ref(false);
|
||||
// 用户store
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 初始化验证码
|
||||
const initCaptcha = () => {
|
||||
getCodeImg().then(res => {
|
||||
captchaUrl.value = res;
|
||||
}).catch(() => {
|
||||
modal.toast('验证码加载失败');
|
||||
});
|
||||
};
|
||||
|
||||
// 刷新验证码
|
||||
const refreshCaptcha = () => {
|
||||
initCaptcha();
|
||||
};
|
||||
|
||||
// 发送手机验证码
|
||||
const sendCode = () => {
|
||||
if (!form.value.phone) {
|
||||
modal.toast('请输入手机号');
|
||||
return;
|
||||
}
|
||||
sendPhoneCode({ phone: form.value.phone }).then(() => {
|
||||
modal.toast('验证码已发送');
|
||||
countdown.value = 60;
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) clearInterval(timer);
|
||||
}, 1000);
|
||||
}).catch(err => {
|
||||
modal.toast(err.message || '发送失败');
|
||||
});
|
||||
};
|
||||
|
||||
// 处理登录
|
||||
const handleLogin = async () => {
|
||||
isLogining.value = true;
|
||||
try {
|
||||
if (loginType.value === 'account') {
|
||||
// 账号密码登录
|
||||
if (!form.value.username || !form.value.password || !form.value.code) {
|
||||
modal.toast('请填写完整信息');
|
||||
return;
|
||||
}
|
||||
const res = await login(
|
||||
form.value.username,
|
||||
form.value.password,
|
||||
form.value.code,
|
||||
''
|
||||
);
|
||||
handleLoginSuccess(res);
|
||||
} else if (loginType.value === 'phone') {
|
||||
// 手机验证码登录
|
||||
if (!form.value.phone || !form.value.code) {
|
||||
modal.toast('请填写完整信息');
|
||||
return;
|
||||
}
|
||||
// 1. 验证手机验证码
|
||||
await verifyPhoneCode({ phone: form.value.phone, code: form.value.code });
|
||||
// 2. 检查用户是否存在(自动注册逻辑)
|
||||
try {
|
||||
// 尝试用手机号登录(后端会自动注册新用户)
|
||||
const res = await login(
|
||||
form.value.phone,
|
||||
'123456', // 默认密码(首次登录自动注册时使用)
|
||||
'',
|
||||
''
|
||||
);
|
||||
handleLoginSuccess(res);
|
||||
} catch (err) {
|
||||
// 用户不存在,自动注册
|
||||
await register({
|
||||
username: form.value.phone,
|
||||
phonenumber: form.value.phone,
|
||||
password: '123456'
|
||||
});
|
||||
// 注册后重新登录
|
||||
const res = await login(
|
||||
form.value.phone,
|
||||
'123456',
|
||||
'',
|
||||
''
|
||||
);
|
||||
handleLoginSuccess(res);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
modal.toast(err.message || '登录失败');
|
||||
} finally {
|
||||
isLogining.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理登录成功
|
||||
const handleLoginSuccess = (res) => {
|
||||
setToken(res.token);
|
||||
userStore.setUserInfo(res.user);
|
||||
uni.switchTab({ url: '/pages/index' });
|
||||
};
|
||||
|
||||
// 微信登录
|
||||
const wxLogin = async () => {
|
||||
isLogining.value = true;
|
||||
try {
|
||||
const code = await getWxCode();
|
||||
const res = await wxLogin('pub', code); // pub: 公众号/小程序
|
||||
if (res.token) {
|
||||
handleLoginSuccess(res);
|
||||
} else {
|
||||
modal.toast('微信登录失败,请重试');
|
||||
}
|
||||
} catch (err) {
|
||||
modal.toast('微信登录异常: ' + err.message);
|
||||
} finally {
|
||||
isLogining.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 本机一键登录(获取手机号)
|
||||
const getPhone = async () => {
|
||||
try {
|
||||
const res = await uni.getPhoneNumber();
|
||||
if (res.code === 0) {
|
||||
form.value.phone = res.code;
|
||||
modal.toast('手机号获取成功');
|
||||
} else {
|
||||
modal.toast('获取手机号失败');
|
||||
}
|
||||
} catch (err) {
|
||||
modal.toast('获取手机号异常: ' + err.message);
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到注册页面
|
||||
const toRegister = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_system/pages/login/register'
|
||||
});
|
||||
};
|
||||
|
||||
// 跳转到忘记密码页面
|
||||
const toForget = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_system/pages/login/forget'
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initCaptcha();
|
||||
});
|
||||
|
||||
return {
|
||||
loginType,
|
||||
form,
|
||||
captchaUrl,
|
||||
countdown,
|
||||
isLogining,
|
||||
handleLogin,
|
||||
sendCode,
|
||||
refreshCaptcha,
|
||||
wxLogin,
|
||||
getPhone,
|
||||
toRegister,
|
||||
toForget
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
padding: 40rpx;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 80rpx;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.login-tabs {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.login-tab {
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 40rpx;
|
||||
border: 1px solid #ddd;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.login-tab.active {
|
||||
background: #007AFF;
|
||||
color: white;
|
||||
border-color: #007AFF;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
background: white;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.captcha {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.captcha-img {
|
||||
width: 200rpx;
|
||||
height: 80rpx;
|
||||
margin-left: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
color: #007AFF;
|
||||
font-size: 26rpx;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.send-code {
|
||||
color: #007AFF;
|
||||
font-size: 26rpx;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
background: #007AFF;
|
||||
color: white;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
}
|
||||
|
||||
.wx-login-btn {
|
||||
background: #07C160;
|
||||
color: white;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.wx-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.one-click-login {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
color: #666;
|
||||
font-size: 28rpx;
|
||||
margin-top: 30rpx;
|
||||
padding: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.phone-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
margin-top: 60rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #007AFF;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
</style>
|
||||
@ -1,524 +0,0 @@
|
||||
<template>
|
||||
<view class="register-container">
|
||||
<view class="register-header">
|
||||
<text class="back-btn" @click="goBack">‹</text>
|
||||
<text class="title">用户注册</text>
|
||||
<view class="placeholder"></view>
|
||||
</view>
|
||||
|
||||
<view class="register-content">
|
||||
<view class="register-form">
|
||||
<view class="form-item">
|
||||
<text class="label">用户名</text>
|
||||
<input
|
||||
v-model="registerForm.username"
|
||||
class="input"
|
||||
placeholder="请输入用户名"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">手机号</text>
|
||||
<input
|
||||
v-model="registerForm.phone"
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">验证码</text>
|
||||
<view class="code-input">
|
||||
<input
|
||||
v-model="registerForm.code"
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
<button
|
||||
class="send-code-btn"
|
||||
:disabled="countdown > 0"
|
||||
@click="sendCode"
|
||||
>
|
||||
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">密码</text>
|
||||
<input
|
||||
v-model="registerForm.password"
|
||||
class="input"
|
||||
password
|
||||
placeholder="请输入密码"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">确认密码</text>
|
||||
<input
|
||||
v-model="registerForm.confirmPassword"
|
||||
class="input"
|
||||
password
|
||||
placeholder="请再次输入密码"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item" v-if="registerForm.showEmail">
|
||||
<text class="label">邮箱</text>
|
||||
<input
|
||||
v-model="registerForm.email"
|
||||
class="input"
|
||||
type="email"
|
||||
placeholder="请输入邮箱(选填)"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="agreement">
|
||||
<checkbox-group @change="toggleAgreement">
|
||||
<label class="checkbox-label">
|
||||
<checkbox :checked="agreed" color="#007aff" />
|
||||
<text class="agreement-text">我已阅读并同意</text>
|
||||
</label>
|
||||
</checkbox-group>
|
||||
<text class="agreement-link" @click="showAgreement">《用户协议》</text>
|
||||
<text class="agreement-link" @click="showPrivacy">《隐私政策》</text>
|
||||
</view>
|
||||
|
||||
<button class="register-btn" @click="handleRegister" :disabled="registering">
|
||||
{{ registering ? '注册中...' : '注册' }}
|
||||
</button>
|
||||
|
||||
<view class="login-link">
|
||||
<text>已有账号? </text>
|
||||
<text class="link" @click="goLogin">立即登录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 第三方注册 -->
|
||||
<view class="third-register">
|
||||
<view class="divider">
|
||||
<text class="divider-text">第三方注册</text>
|
||||
</view>
|
||||
|
||||
<view class="register-methods">
|
||||
<view class="method-item" @click="handleWechatRegister">
|
||||
<view class="method-icon wechat">
|
||||
<text class="iconfont">💬</text>
|
||||
</view>
|
||||
<text class="method-text">微信注册</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import modal from '@/plugins/modal'
|
||||
import { getCodeImg, login, sendPhoneCode, verifyPhoneCode } from '@/api/login'
|
||||
import { getWxCode } from '@/utils/geek'
|
||||
import { wxLogin } from '@/api/oauth'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { setToken } from '@/utils/auth'
|
||||
import config from '@/config.js'
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 响应式数据
|
||||
const registering = ref(false)
|
||||
const agreed = ref(false)
|
||||
const countdown = ref(0)
|
||||
let countdownTimer = null
|
||||
|
||||
// 注册表单
|
||||
const registerForm = reactive({
|
||||
username: '',
|
||||
phone: '',
|
||||
code: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
email: '',
|
||||
showEmail: false
|
||||
})
|
||||
|
||||
// 发送验证码
|
||||
const sendCode = async () => {
|
||||
if (!registerForm.phone) {
|
||||
modal.alert('请输入手机号')
|
||||
return
|
||||
}
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(registerForm.phone)) {
|
||||
modal.alert('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await sendPhoneCode({ phone: registerForm.phone }, 'register')
|
||||
modal.alert('验证码已发送')
|
||||
|
||||
// 开始倒计时
|
||||
countdown.value = 60
|
||||
countdownTimer = setInterval(() => {
|
||||
countdown.value--
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(countdownTimer)
|
||||
}
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
modal.alert(error.message || '验证码发送失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理注册
|
||||
const handleRegister = async () => {
|
||||
if (!validateForm()) return
|
||||
|
||||
registering.value = true
|
||||
|
||||
try {
|
||||
// 验证验证码
|
||||
await verifyPhoneCode({
|
||||
phone: registerForm.phone,
|
||||
code: registerForm.code
|
||||
}, 'register')
|
||||
|
||||
// 执行注册
|
||||
const params = {
|
||||
username: registerForm.username,
|
||||
password: registerForm.password,
|
||||
phonenumber: registerForm.phone
|
||||
}
|
||||
|
||||
if (registerForm.email) {
|
||||
params.email = registerForm.email
|
||||
}
|
||||
|
||||
const res = await register(params)
|
||||
|
||||
if (res.token) {
|
||||
setToken(res.token)
|
||||
await userStore.getInfo()
|
||||
modal.alert('注册成功', () => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index'
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
modal.alert(error.message || '注册失败')
|
||||
} finally {
|
||||
registering.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
const validateForm = () => {
|
||||
if (!registerForm.username) {
|
||||
modal.alert('请输入用户名')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!registerForm.phone) {
|
||||
modal.alert('请输入手机号')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(registerForm.phone)) {
|
||||
modal.alert('请输入正确的手机号')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!registerForm.code) {
|
||||
modal.alert('请输入验证码')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!registerForm.password) {
|
||||
modal.alert('请输入密码')
|
||||
return false
|
||||
}
|
||||
|
||||
if (registerForm.password.length < 6) {
|
||||
modal.alert('密码长度不能少于6位')
|
||||
return false
|
||||
}
|
||||
|
||||
if (registerForm.password !== registerForm.confirmPassword) {
|
||||
modal.alert('两次输入的密码不一致')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!agreed.value) {
|
||||
modal.alert('请同意用户协议和隐私政策')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 微信注册
|
||||
const handleWechatRegister = async () => {
|
||||
try {
|
||||
const code = await getWxCode()
|
||||
const res = await wxRegister('miniapp', code)
|
||||
|
||||
if (res.token) {
|
||||
setToken(res.token)
|
||||
await userStore.getInfo()
|
||||
modal.alert('注册成功', () => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index'
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
modal.alert(error.message || '微信注册失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 切换协议同意状态
|
||||
const toggleAgreement = (e) => {
|
||||
agreed.value = e.detail.value.length > 0
|
||||
}
|
||||
|
||||
// 显示用户协议
|
||||
const showAgreement = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_system/pages/login/agreement?type=user'
|
||||
})
|
||||
}
|
||||
|
||||
// 显示隐私政策
|
||||
const showPrivacy = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_system/pages/login/agreement?type=privacy'
|
||||
})
|
||||
}
|
||||
|
||||
// 返回登录页面
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const goLogin = () => {
|
||||
uni.redirectTo({
|
||||
url: '/pages/login'
|
||||
})
|
||||
}
|
||||
|
||||
// 清理计时器
|
||||
import { onUnmounted } from 'vue'
|
||||
onUnmounted(() => {
|
||||
if (countdownTimer) {
|
||||
clearInterval(countdownTimer)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.register-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.register-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 40rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
font-size: 50rpx;
|
||||
color: #333;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.register-content {
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.register-form {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx;
|
||||
margin-bottom: 40rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 10rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.send-code-btn {
|
||||
width: 200rpx;
|
||||
height: 80rpx;
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10rpx;
|
||||
font-size: 24rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.send-code-btn[disabled] {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.agreement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin: 40rpx 0;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
margin-left: 10rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: #007aff;
|
||||
margin: 0 5rpx;
|
||||
}
|
||||
|
||||
.register-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.register-btn[disabled] {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.login-link {
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.third-register {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.divider {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.divider::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1rpx;
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
.divider-text {
|
||||
background: #fff;
|
||||
padding: 0 20rpx;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.register-methods {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.method-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.method-icon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10rpx;
|
||||
font-size: 40rpx;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.method-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@ -4,6 +4,5 @@ const constant = {
|
||||
roles: 'vuex_roles',
|
||||
permissions: 'vuex_permissions'
|
||||
}
|
||||
|
||||
|
||||
export default constant
|
||||
|
||||
export default constant
|
||||
|
||||
@ -1,133 +0,0 @@
|
||||
const moudleGroups = [{
|
||||
name: '系统管理',
|
||||
color: '#007AFF',
|
||||
items: [{
|
||||
name: '用户管理',
|
||||
icon: 'person-filled',
|
||||
path: '',
|
||||
params: ''
|
||||
|
||||
},
|
||||
{
|
||||
name: '角色管理',
|
||||
icon: 'staff-filled',
|
||||
path: '',
|
||||
params: ''
|
||||
|
||||
}, {
|
||||
name: '菜单管理',
|
||||
icon: 'color',
|
||||
path: '',
|
||||
params: ''
|
||||
|
||||
}, {
|
||||
name: '部门管理',
|
||||
icon: 'settings-filled',
|
||||
path: '',
|
||||
params: ''
|
||||
|
||||
}, {
|
||||
name: '岗位管理',
|
||||
icon: 'heart-filled',
|
||||
path: '',
|
||||
params: ''
|
||||
|
||||
}, {
|
||||
name: '字典管理',
|
||||
icon: 'bars',
|
||||
path: `/pages_system/pages/dict/index`
|
||||
, params: ''
|
||||
|
||||
}, {
|
||||
name: '参数设置',
|
||||
icon: 'gear-filled',
|
||||
path: '',
|
||||
params: ''
|
||||
},
|
||||
{
|
||||
name: '通知公告',
|
||||
icon: 'chat-filled',
|
||||
path: '',
|
||||
params: ''
|
||||
}
|
||||
, {
|
||||
name: '日志管理',
|
||||
icon: 'wallet-filled',
|
||||
path: '',
|
||||
params: ''
|
||||
},
|
||||
]
|
||||
}, {
|
||||
name: '流量计算',
|
||||
color: '#007AFF',
|
||||
items: [{
|
||||
name: '差压式流量计算',
|
||||
icon: 'smallcircle',
|
||||
path: `/pages_caltools/pages/main`,
|
||||
params: 0
|
||||
},
|
||||
{
|
||||
name: '速度式流量计算',
|
||||
icon: 'paperplane',
|
||||
path: `/pages_caltools/pages/main`,
|
||||
params: 1
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '参数计算',
|
||||
color: '#5AC8FA',
|
||||
items: [{
|
||||
name: '压缩因子',
|
||||
icon: 'pyq',
|
||||
path: `/pages_caltools/pages/main`,
|
||||
params: 4
|
||||
},
|
||||
{
|
||||
name: '声速计算',
|
||||
icon: 'sound',
|
||||
path: `/pages_caltools/pages/main`,
|
||||
params: 5
|
||||
},
|
||||
{
|
||||
name: '发热量',
|
||||
icon: 'fire',
|
||||
path: `/pages_caltools/pages/main`,
|
||||
params: 6
|
||||
},
|
||||
{
|
||||
name: '其他参数',
|
||||
icon: 'more',
|
||||
path: `/pages_caltools/pages/main`,
|
||||
params: 7
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export default moudleGroups
|
||||
|
||||
/**
|
||||
* 通用方法:从模块组中提取数据
|
||||
* @param {Array} groupNames - 要提取的模块组名称数组
|
||||
* @param {boolean} mergeItems - 是否合并items,true为合并,false为保持原结构
|
||||
* @param {Function} transformFn - 可选的转换函数,用于自定义输出格式
|
||||
* @returns {Array} 提取后的数据
|
||||
*/
|
||||
export function extractModuleData(groupNames, mergeItems = false, transformFn = null) {
|
||||
if (!Array.isArray(groupNames) || groupNames.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 过滤出指定的模块组
|
||||
const filteredGroups = moudleGroups.filter(group => groupNames.includes(group.name));
|
||||
|
||||
if (mergeItems) {
|
||||
// 合并所有items
|
||||
const mergedItems = filteredGroups.flatMap(group => group.items || []);
|
||||
return transformFn ? transformFn(mergedItems) : mergedItems;
|
||||
} else {
|
||||
// 保持原结构
|
||||
return transformFn ? transformFn(filteredGroups) : filteredGroups;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user