Compare commits

..

No commits in common. "0f9a78b64ccbadf711093564e2f209456a31a3e8" and "70d088a4ba62e5cb257dbba96955b4805d3498e8" have entirely different histories.

18 changed files with 709 additions and 3091 deletions

View File

@ -46,7 +46,6 @@ export function logout() {
})
}
// 获取验证码
export function getCodeImg() {
return request({
@ -58,63 +57,3 @@ export function getCodeImg() {
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
},
})
}

View File

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

View File

@ -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"
}
}
]
}

View File

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

View File

@ -12,14 +12,61 @@
</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">
<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="item.icon" size="30"></uni-icons>
<text class="text">{{item.name}}</text>
<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>
@ -28,50 +75,22 @@
</template>
<script setup>
import {
ref,
onMounted
} from "vue";
import { ref } from "vue";
import modal from "@/plugins/modal"
import {
extractModuleData
} from '@/utils/moudlesData.ts';
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 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: '模块建设中',
@ -80,6 +99,7 @@
duration: 1000
});
}
</script>
<style lang="scss">

View File

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

View File

@ -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-content">
<!-- 登录方式切换 -->
<view class="login-tabs">
<view
class="tab-item"
:class="{ active: activeTab === 'account' }"
@click="switchTab('account')"
>
账号登录
<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="tab-item"
:class="{ active: activeTab === 'phone' }"
@click="switchTab('phone')"
>
手机登录
<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 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('验证码获取失败')
}
}
.normal-login-container {
width: 100%;
//
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 {
// uniAPI
// 使
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;
.logo-content {
width: 100%;
font-size: 21px;
text-align: center;
padding: 20rpx;
font-size: 32rpx;
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;
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 {
width: 100%;
height: 80rpx;
border: 2rpx solid #e0e0e0;
border-radius: 10rpx;
padding: 0 20rpx;
font-size: 28rpx;
box-sizing: border-box;
font-size: 14px;
line-height: 20px;
text-align: left;
padding-left: 15px;
}
.placeholder {
color: #ccc;
}
.captcha-input, .code-input {
display: flex;
align-items: center;
gap: 20rpx;
}
.captcha-img {
width: 200rpx;
height: 80rpx;
border-radius: 10rpx;
border: 2rpx solid #e0e0e0;
}
.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;
}
.form-options {
display: flex;
justify-content: flex-end;
margin-bottom: 40rpx;
}
.forget-pwd {
color: #007aff;
font-size: 26rpx;
}
.action-btn {
margin-top: 40px;
.login-btn {
width: 100%;
height: 88rpx;
background: #007aff;
color: #fff;
border: none;
border-radius: 44rpx;
font-size: 32rpx;
margin-bottom: 40rpx;
height: 45px;
&+.login-btn {
margin-top: 20px;
}
}
}
.login-btn[disabled] {
background: #ccc;
.xieyi {
color: #333;
margin-top: 20px;
}
.quick-login {
margin-bottom: 40rpx;
}
.login-code {
height: 38px;
float: right;
.divider {
position: relative;
text-align: center;
margin: 40rpx 0;
}
.divider::before {
content: '';
.login-code-img {
height: 38px;
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1rpx;
background: #e0e0e0;
margin-left: 10px;
width: 200rpx;
}
.divider-text {
background: #fff;
padding: 0 20rpx;
color: #999;
font-size: 24rpx;
}
.login-methods {
display: flex;
justify-content: center;
gap: 80rpx;
}
.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;
}
.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>

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

View File

@ -192,7 +192,7 @@
description: '展示不同天然气组分与热值之间的对应关系曲线',
images: [{
url: '/static/charts/heat-value-chart.jpg',
thumbnail: '',
thumbnail: '/static/charts/heat-value-thumb.jpg',
description: '热值关系图表'
}],
tables: [],

View File

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

View File

@ -467,7 +467,7 @@
}
:deep(.uni-forms-item__label) {
font-size: 22rpx;
font-size: 26rpx;
}
:deep(.uni-input-input) {

View File

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

View File

@ -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";
<template>
<view class="register-container">
<!-- 背景图建议在 pages.json 中配置这里作为备用 -->
<view class="register-form-wrapper">
<uni-forms ref="registerFormRef" :modelValue="registerForm" :rules="registerRules" label-width="0">
// props
const props = defineProps<{
register: boolean;
captchaEnabled: boolean;
method: 'password' | 'phone' | 'email';
}>();
<view class="title">天然气工具平台</view>
//
<uni-forms-item name="username">
<uni-easyinput v-model="registerForm.username" type="text" placeholder="账号" prefixIcon="person"
@confirm="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 name="confirmPassword">
<uni-easyinput v-model="registerForm.confirmPassword" type="password" placeholder="确认密码"
prefixIcon="locked" @confirm="handleRegister" />
</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>
<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>
</uni-forms>
</view>
<view class="el-register-footer">
<text>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</text>
</view>
</view>
</template>
<script setup>
import {
ref,
onReady
} from 'vue';
import {
getCodeImg,
register
} from '@/api/login'; // API Uniapp
//
const registerFormRef = ref(null);
//
const registerForm = ref({
username: "",
password: "",
confirmPassword: "",
email: '',
phonenumber: '',
code: "",
uuid: "",
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" },
username: [{
required: true,
errorMessage: '请输入您的账号'
},
{
validator: (rule: any, value: any, callback: any) => {
if (registerForm.value.password !== value) {
callback(new Error("两次输入的密码不一致"));
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();
}
},
trigger: "blur"
errorMessage: '两次输入的密码不一致'
}
],
code: [{ required: true, message: "请输入验证码", trigger: "change" }]
code: [{
required: true,
errorMessage: '请输入验证码'
}]
};
//
const userStore = useUserStore();
//
const codeUrl = ref("");
const loading = ref(false);
const captchaEnabled = ref(true);
//
function getCode() {
if (!props.captchaEnabled) return;
getCodeImg().then((res: any) => {
codeUrl.value = "data:image/gif;base64," + res.img;
registerForm.value.uuid = res.uuid;
//
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'
});
}
});
};
//
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) {
//
const handleRegister = () => {
// 使 uni-forms validate
registerFormRef.value.validate().then(() => {
loading.value = true;
userStore.register(registerForm.value, props.method).then(() => {
register(registerForm.value).then(res => {
uni.showModal({
title: "系统提示",
title: '系统提示',
content: `<font color='red'>恭喜你,您的账号 ${registerForm.value.username} 注册成功!</font>`,
showCancel: false,
success: () => {
//
uni.redirectTo({
url: "/pages/login/login" //
url: '/pages/login/login'
});
}
});
}).catch(() => {
getCode();
}).catch(err => {
//
uni.showToast({
title: err.message || '注册失败',
icon: 'none'
});
if (captchaEnabled.value) {
getCode(); //
}
}).finally(() => {
loading.value = false;
});
}
}).catch(errors => {
// uni-forms
console.log('表单校验失败:', errors);
});
}
};
//
onMounted(() => {
onReady(() => {
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>
<!-- 手机号输入 -->
<uni-forms-item prop="phonenumber" v-else-if="method === 'phone'">
<uni-easyinput
v-model="registerForm.phonenumber"
type="text"
placeholder="手机号"
prefixIcon="phone"
/>
</uni-forms-item>
<!-- 用户名输入 -->
<uni-forms-item prop="username" v-else>
<uni-easyinput
v-model="registerForm.username"
type="text"
placeholder="账号"
prefixIcon="user"
/>
</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 prop="confirmPassword">
<uni-easyinput
v-model="registerForm.confirmPassword"
type="password"
placeholder="确认密码"
prefixIcon="lock"
@keyup.enter="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>
<button
type="primary"
size="default"
class="register-btn"
:loading="loading"
@click="handleRegister"
>
注册
</button>
</uni-forms-item>
<!-- 已有账号跳转 -->
<view class="login-link">
<text class="question-text">已有账号</text>
<navigator url="/pages/login" class="link-type">立即登录</navigator>
</view>
</uni-forms>
</view>
</template>
<style scoped lang="scss">
<style scoped>
/* 页面整体背景,建议在 pages.json 中配置 "style": { "backgroundImage": "url('/static/login-background.jpg')" } */
.register-container {
padding: 40rpx;
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 {
background-color: #fff;
padding: 60rpx;
/* 表单外层容器 */
.register-form-wrapper {
width: 100%;
max-width: 400rpx;
padding: 40rpx 30rpx;
background-color: #ffffff;
border-radius: 16rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
}
.code-input {
.title {
text-align: center;
font-size: 32rpx;
font-weight: 500;
color: #333;
margin-bottom: 40rpx;
}
/* 验证码输入框和图片一行显示 */
.code-input-wrapper {
display: flex;
align-items: center;
gap: 20rpx;
gap: 16rpx;
}
.code-action {
.code-img-wrapper {
flex-shrink: 0;
width: 140rpx;
height: 72rpx;
/* 与输入框高度对齐 */
}
.code-img {
width: 180rpx;
height: 70rpx;
width: 100%;
height: 100%;
border-radius: 8rpx;
}
/* 注册按钮 */
.register-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
font-size: 30rpx;
font-size: 28rpx;
border-radius: 40rpx;
margin-top: 20rpx;
margin-bottom: 20rpx;
}
/* 登录链接 */
.login-link {
display: flex;
justify-content: center;
align-items: center;
margin-top: 40rpx;
font-size: 26rpx;
color: #666;
text-align: center;
font-size: 24rpx;
color: #999;
}
.link-type {
.link-text {
color: #007aff;
margin-left: 10rpx;
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>

View File

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

View File

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

View File

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

View File

@ -5,5 +5,4 @@ const constant = {
permissions: 'vuex_permissions'
}
export default constant

View File

@ -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 - itemstrue为合并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;
}
}