删除不需要的文件及提交移动端的代码
This commit is contained in:
1 parent
86f65df783
commit
134f711349
751 files changed
+3888
-115501
No files matched your search
@@ -7,8 +7,9 @@
|
||||
tabindex="0"
|
||||
>
|
||||
<view class="service-icon">
|
||||
<text class="icon" v-if="icon">{{ icon }}</text>
|
||||
<slot v-else name="icon"></slot>
|
||||
<slot name="icon">
|
||||
<text class="icon" v-if="icon">{{ icon }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
<text class="service-title">{{ title }}</text>
|
||||
<view v-if="badge" class="service-badge">
|
||||
@@ -82,12 +83,33 @@ export default {
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
// 支持u-icon组件
|
||||
:deep(.u-icon) {
|
||||
font-size: 24px !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
// 支持iconfont图标
|
||||
.iconfont {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 24px;
|
||||
color: inherit;
|
||||
filter: brightness(0) saturate(100%) invert(49%) sepia(98%) saturate(3332%)
|
||||
hue-rotate(196deg) brightness(104%) contrast(101%);
|
||||
}
|
||||
}
|
||||
|
||||
.service-item:hover .service-icon {
|
||||
background: $uni-color-primary;
|
||||
color: $uni-text-color-inverse;
|
||||
transform: scale(1.1);
|
||||
|
||||
.iconfont {
|
||||
filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg)
|
||||
brightness(100%) contrast(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.service-title {
|
||||
@@ -160,6 +182,18 @@ export default {
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
// 响应式u-icon支持
|
||||
:deep(.u-icon) {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
|
||||
// 响应式iconfont支持
|
||||
.iconfont {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.service-title {
|
||||
|
||||
+8
-44
@@ -24,20 +24,6 @@
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workbench/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "办公中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/iconfont-demo/index",
|
||||
"style": {
|
||||
@@ -66,6 +52,14 @@
|
||||
"navigationBarTitleText": "我发起的流程",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/uview-icon-test/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "uview图标测试",
|
||||
"navigationBarBackgroundColor": "#5d87ff",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
@@ -73,35 +67,5 @@
|
||||
"navigationBarTitleText": "智慧校园",
|
||||
"navigationBarBackgroundColor": "rgb(93, 135, 255)",
|
||||
"backgroundColor": "#f8f9fa"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#999999",
|
||||
"selectedColor": "rgb(93, 135, 255)",
|
||||
"borderStyle": "white",
|
||||
"backgroundColor": "#ffffff",
|
||||
"height": "60px",
|
||||
"fontSize": "12px",
|
||||
"iconWidth": "22px",
|
||||
"iconHeight": "22px",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "首页",
|
||||
"iconPath": "static/tabbar/home.svg",
|
||||
"selectedIconPath": "static/tabbar/home-active.svg"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/workbench/index",
|
||||
"text": "办公中心",
|
||||
"iconPath": "static/tabbar/workbench.svg",
|
||||
"selectedIconPath": "static/tabbar/workbench-active.svg"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/index",
|
||||
"text": "我的",
|
||||
"iconPath": "static/tabbar/profile.svg",
|
||||
"selectedIconPath": "static/tabbar/profile-active.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+92
-6
@@ -38,9 +38,19 @@
|
||||
<text class="section-title">操作图标</text>
|
||||
<view class="icon-grid">
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-login"></text>
|
||||
<text class="icon-name">登录</text>
|
||||
<text class="icon-code">icon-login</text>
|
||||
<text class="iconfont icon-add"></text>
|
||||
<text class="icon-name">添加</text>
|
||||
<text class="icon-code">icon-add</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-edit"></text>
|
||||
<text class="icon-name">编辑</text>
|
||||
<text class="icon-code">icon-edit</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-delete"></text>
|
||||
<text class="icon-name">删除</text>
|
||||
<text class="icon-code">icon-delete</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-search"></text>
|
||||
@@ -48,9 +58,9 @@
|
||||
<text class="icon-code">icon-search</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-add"></text>
|
||||
<text class="icon-name">添加</text>
|
||||
<text class="icon-code">icon-add</text>
|
||||
<text class="iconfont icon-refresh"></text>
|
||||
<text class="icon-name">刷新</text>
|
||||
<text class="icon-code">icon-refresh</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-close"></text>
|
||||
@@ -60,6 +70,82 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 文件图标 -->
|
||||
<view class="icon-section">
|
||||
<text class="section-title">文件图标</text>
|
||||
<view class="icon-grid">
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-download"></text>
|
||||
<text class="icon-name">下载</text>
|
||||
<text class="icon-code">icon-download</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-upload"></text>
|
||||
<text class="icon-name">上传</text>
|
||||
<text class="icon-code">icon-upload</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 状态图标 -->
|
||||
<view class="icon-section">
|
||||
<text class="section-title">状态图标</text>
|
||||
<view class="icon-grid">
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-success" style="color: #52c41a"></text>
|
||||
<text class="icon-name">成功</text>
|
||||
<text class="icon-code">icon-success</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-warning" style="color: #faad14"></text>
|
||||
<text class="icon-name">警告</text>
|
||||
<text class="icon-code">icon-warning</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-info" style="color: #1890ff"></text>
|
||||
<text class="icon-name">信息</text>
|
||||
<text class="icon-code">icon-info</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-check" style="color: #52c41a"></text>
|
||||
<text class="icon-name">确认</text>
|
||||
<text class="icon-code">icon-check</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 社交图标 -->
|
||||
<view class="icon-section">
|
||||
<text class="section-title">社交图标</text>
|
||||
<view class="icon-grid">
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-star" style="color: #faad14"></text>
|
||||
<text class="icon-name">星星</text>
|
||||
<text class="icon-code">icon-star</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-heart" style="color: #ff4d4f"></text>
|
||||
<text class="icon-name">喜欢</text>
|
||||
<text class="icon-code">icon-heart</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-message"></text>
|
||||
<text class="icon-name">消息</text>
|
||||
<text class="icon-code">icon-message</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-phone"></text>
|
||||
<text class="icon-name">电话</text>
|
||||
<text class="icon-code">icon-phone</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-mail"></text>
|
||||
<text class="icon-name">邮件</text>
|
||||
<text class="icon-code">icon-mail</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 导航图标 -->
|
||||
<view class="icon-section">
|
||||
<text class="section-title">导航图标</text>
|
||||
File renamed without changes.
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<view class="test-page">
|
||||
<view class="test-header">
|
||||
<text class="test-title">uview-plus 图标问题分析与解决</text>
|
||||
</view>
|
||||
|
||||
<!-- 问题演示 -->
|
||||
<view class="test-section">
|
||||
<text class="section-title">❌ 问题演示 - uview-plus图标异常</text>
|
||||
<view class="test-items">
|
||||
<view class="test-item">
|
||||
<u-icon name="logout" size="24" color="#ff4d4f"></u-icon>
|
||||
<text class="test-desc">logout图标 (可能显示文字)</text>
|
||||
</view>
|
||||
<view class="test-item">
|
||||
<u-icon name="power" size="24" color="#ff4d4f"></u-icon>
|
||||
<text class="test-desc">power图标 (可能显示文字)</text>
|
||||
</view>
|
||||
<view class="test-item">
|
||||
<u-icon name="exit" size="24" color="#ff4d4f"></u-icon>
|
||||
<text class="test-desc">exit图标 (可能显示文字)</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 正常工作的uview图标 -->
|
||||
<view class="test-section">
|
||||
<text class="section-title">✅ 正常工作 - uview-plus图标</text>
|
||||
<view class="test-items">
|
||||
<view class="test-item">
|
||||
<u-icon name="account" size="24" color="#52c41a"></u-icon>
|
||||
<text class="test-desc">account图标 (正常)</text>
|
||||
</view>
|
||||
<view class="test-item">
|
||||
<u-icon name="home" size="24" color="#52c41a"></u-icon>
|
||||
<text class="test-desc">home图标 (正常)</text>
|
||||
</view>
|
||||
<view class="test-item">
|
||||
<u-icon name="setting" size="24" color="#52c41a"></u-icon>
|
||||
<text class="test-desc">setting图标 (正常)</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 解决方案 - 使用iconfont -->
|
||||
<view class="test-section">
|
||||
<text class="section-title">🎯 解决方案 - 使用iconfont自定义图标</text>
|
||||
<view class="test-items">
|
||||
<view class="test-item">
|
||||
<text class="iconfont icon-close solution-icon"></text>
|
||||
<text class="test-desc">关闭图标 (退出登录)</text>
|
||||
</view>
|
||||
<view class="test-item">
|
||||
<text class="iconfont icon-user solution-icon"></text>
|
||||
<text class="test-desc">用户图标</text>
|
||||
</view>
|
||||
<view class="test-item">
|
||||
<text class="iconfont icon-setting solution-icon"></text>
|
||||
<text class="test-desc">设置图标</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Unicode字符方案 -->
|
||||
<view class="test-section">
|
||||
<text class="section-title">🔤 备选方案 - Unicode字符</text>
|
||||
<view class="test-items">
|
||||
<view class="test-item">
|
||||
<text class="unicode-icon">⏻</text>
|
||||
<text class="test-desc">电源符号 (退出登录)</text>
|
||||
</view>
|
||||
<view class="test-item">
|
||||
<text class="unicode-icon">❌</text>
|
||||
<text class="test-desc">关闭符号</text>
|
||||
</view>
|
||||
<view class="test-item">
|
||||
<text class="unicode-icon">⚙️</text>
|
||||
<text class="test-desc">设置符号</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 解决方案说明 -->
|
||||
<view class="solution-section">
|
||||
<text class="section-title">💡 解决方案说明</text>
|
||||
<view class="solution-content">
|
||||
<text class="solution-text">
|
||||
1. **根本原因**: uview-plus某些图标名称在当前版本字体文件中不存在
|
||||
</text>
|
||||
<text class="solution-text">
|
||||
2. **推荐方案**: 使用项目中的iconfont自定义图标系统
|
||||
</text>
|
||||
<text class="solution-text">
|
||||
3. **备选方案**: 使用Unicode字符(⏻ ❌ ⚙️),兼容性最好
|
||||
</text>
|
||||
<text class="solution-text">
|
||||
4. **长期方案**: 更新uview-plus字体文件或使用其他图标库
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'UviewIconTest',
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.test-page {
|
||||
min-height: 100vh;
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.test-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.test-title {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.test-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.test-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: #fafbfc;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.test-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.solution-icon {
|
||||
font-size: 24px;
|
||||
color: #1890ff;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.unicode-icon {
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.solution-section {
|
||||
background: #fff7e6;
|
||||
border: 1px solid #ffd591;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.solution-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.solution-text {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
+636
-1137
File diff suppressed because it is too large.
Load diff
@@ -19,7 +19,7 @@
|
||||
v-model="username"
|
||||
placeholder="账号"
|
||||
type="text"
|
||||
:border="true"
|
||||
border="true"
|
||||
:custom-style="inputStyle"
|
||||
placeholder-style="color: #999; font-size: 16px"
|
||||
>
|
||||
@@ -34,7 +34,7 @@
|
||||
v-model="password"
|
||||
placeholder="密码"
|
||||
type="password"
|
||||
:border="true"
|
||||
border="true"
|
||||
:custom-style="inputStyle"
|
||||
placeholder-style="color: #999; font-size: 16px"
|
||||
@confirm="onPasswordConfirm"
|
||||
@@ -187,7 +187,7 @@ export default {
|
||||
|
||||
// 调用登录API
|
||||
const loginResult = await api.login(loginData);
|
||||
|
||||
console.log('登录结果:', loginResult);
|
||||
if (loginResult.code === 200) {
|
||||
// 登录成功,先保存Token
|
||||
console.log('✅ 登录成功,先保存Token');
|
||||
|
||||
@@ -1,674 +0,0 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<!-- 自定义导航栏 -->
|
||||
<NavBar title="我的" :show-settings="true" @settings="handleSettings" />
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<scroll-view class="main-content" scroll-y enhanced :show-scrollbar="false">
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="profile-card">
|
||||
<view class="profile-info">
|
||||
<view class="profile-avatar">
|
||||
<text class="avatar-icon">👤</text>
|
||||
</view>
|
||||
<view class="profile-details">
|
||||
<text class="user-name">{{ userInfo.name }}</text>
|
||||
<text class="user-major">{{ userInfo.major }} · {{ userInfo.grade }}</text>
|
||||
<text class="user-id">学号:{{ userInfo.studentId }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="profile-stats">
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">{{ userInfo.gpa }}</text>
|
||||
<text class="stat-label">GPA</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">{{ userInfo.credits }}</text>
|
||||
<text class="stat-label">学分</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">{{ userInfo.grade }}</text>
|
||||
<text class="stat-label">年级</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">{{ userInfo.ranking }}</text>
|
||||
<text class="stat-label">排名</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学习状态 -->
|
||||
<Card>
|
||||
<template #header>
|
||||
<view class="section-header">
|
||||
<text class="section-icon">📊</text>
|
||||
<text class="card-title">学习状态</text>
|
||||
</view>
|
||||
</template>
|
||||
<GridLayout :columns="2" gap="12px">
|
||||
<StatCard
|
||||
v-for="stat in learningStats"
|
||||
:key="stat.label"
|
||||
:number="stat.number"
|
||||
:label="stat.label"
|
||||
@click="handleStatClick"
|
||||
/>
|
||||
</GridLayout>
|
||||
</Card>
|
||||
|
||||
<!-- 我的服务 -->
|
||||
<view class="menu-section">
|
||||
<view
|
||||
v-for="menu in myServiceMenus"
|
||||
:key="menu.title"
|
||||
class="menu-item touchable"
|
||||
@click="handleMenuClick(menu)"
|
||||
>
|
||||
<view class="menu-icon">
|
||||
<text class="icon">{{ menu.icon }}</text>
|
||||
</view>
|
||||
<view class="menu-content">
|
||||
<text class="menu-title">{{ menu.title }}</text>
|
||||
<text class="menu-desc">{{ menu.description }}</text>
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-divider"></view>
|
||||
|
||||
<!-- 校园生活 -->
|
||||
<view class="menu-section">
|
||||
<view
|
||||
v-for="menu in campusLifeMenus"
|
||||
:key="menu.title"
|
||||
class="menu-item touchable"
|
||||
@click="handleMenuClick(menu)"
|
||||
>
|
||||
<view class="menu-icon">
|
||||
<text class="icon">{{ menu.icon }}</text>
|
||||
</view>
|
||||
<view class="menu-content">
|
||||
<text class="menu-title">{{ menu.title }}</text>
|
||||
<text class="menu-desc">{{ menu.description }}</text>
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-divider"></view>
|
||||
|
||||
<!-- 应用设置 -->
|
||||
<view class="menu-section">
|
||||
<view
|
||||
v-for="menu in settingsMenus"
|
||||
:key="menu.title"
|
||||
class="menu-item touchable"
|
||||
@click="handleMenuClick(menu)"
|
||||
>
|
||||
<view class="menu-icon">
|
||||
<text class="icon">{{ menu.icon }}</text>
|
||||
</view>
|
||||
<view class="menu-content">
|
||||
<text class="menu-title">{{ menu.title }}</text>
|
||||
<text class="menu-desc">{{ menu.description }}</text>
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-divider"></view>
|
||||
|
||||
<!-- 帮助与反馈 -->
|
||||
<view class="menu-section">
|
||||
<view
|
||||
v-for="menu in helpMenus"
|
||||
:key="menu.title"
|
||||
class="menu-item touchable"
|
||||
@click="handleMenuClick(menu)"
|
||||
>
|
||||
<view class="menu-icon">
|
||||
<text class="icon">{{ menu.icon }}</text>
|
||||
</view>
|
||||
<view class="menu-content">
|
||||
<text class="menu-title">{{ menu.title }}</text>
|
||||
<text class="menu-desc">{{ menu.description }}</text>
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 版权信息 -->
|
||||
<view class="footer-section">
|
||||
<text class="app-version">智慧校园 v2.1.0</text>
|
||||
<text class="copyright">© 2024 University. All rights reserved.</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部间距 -->
|
||||
<view class="bottom-spacer"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavBar from '@/components/NavBar.vue';
|
||||
import Card from '@/components/Card.vue';
|
||||
import StatCard from '@/components/StatCard.vue';
|
||||
import GridLayout from '@/components/GridLayout.vue';
|
||||
import { userStorage } from '@/utils/request';
|
||||
import { userManager, userState } from '@/utils/user';
|
||||
import { checkPageAccess } from '@/utils/route-guard';
|
||||
|
||||
export default {
|
||||
name: 'ProfilePage',
|
||||
components: {
|
||||
NavBar,
|
||||
Card,
|
||||
StatCard,
|
||||
GridLayout,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userInfo: {
|
||||
name: '用户',
|
||||
major: '未设置',
|
||||
grade: '未设置',
|
||||
studentId: '未设置',
|
||||
gpa: 0,
|
||||
credits: 0,
|
||||
ranking: 0,
|
||||
},
|
||||
learningStats: [
|
||||
{ number: 5, label: '本周课程' },
|
||||
{ number: 3, label: '待提交作业' },
|
||||
{ number: 2, label: '即将考试' },
|
||||
{ number: 8, label: '借阅图书' },
|
||||
],
|
||||
myServiceMenus: [
|
||||
{
|
||||
title: '我的课表',
|
||||
description: '查看本学期课程安排',
|
||||
icon: '🎓',
|
||||
action: 'schedule',
|
||||
},
|
||||
{
|
||||
title: '成绩单',
|
||||
description: '查看历史成绩记录',
|
||||
icon: '📊',
|
||||
action: 'grades',
|
||||
},
|
||||
{
|
||||
title: '我的待办',
|
||||
description: '管理学习任务和事项',
|
||||
icon: '✅',
|
||||
action: 'todos',
|
||||
},
|
||||
{
|
||||
title: '校园卡',
|
||||
description: '余额:¥128.50',
|
||||
icon: '💳',
|
||||
action: 'card',
|
||||
},
|
||||
],
|
||||
campusLifeMenus: [
|
||||
{
|
||||
title: '图书借阅',
|
||||
description: '当前借阅8本,2本即将到期',
|
||||
icon: '📚',
|
||||
action: 'library',
|
||||
},
|
||||
{
|
||||
title: '宿舍信息',
|
||||
description: '1号楼 308室',
|
||||
icon: '🛏️',
|
||||
action: 'dormitory',
|
||||
},
|
||||
{
|
||||
title: '社团活动',
|
||||
description: '计算机协会、摄影社',
|
||||
icon: '👥',
|
||||
action: 'clubs',
|
||||
},
|
||||
{
|
||||
title: '活动报名',
|
||||
description: '查看可参与的活动',
|
||||
icon: '📅',
|
||||
action: 'events',
|
||||
},
|
||||
],
|
||||
settingsMenus: [
|
||||
{
|
||||
title: '个人信息',
|
||||
description: '编辑个人资料',
|
||||
icon: '👤',
|
||||
action: 'profile',
|
||||
},
|
||||
{
|
||||
title: '消息通知',
|
||||
description: '管理通知设置',
|
||||
icon: '🔔',
|
||||
action: 'notifications',
|
||||
},
|
||||
{
|
||||
title: '账号安全',
|
||||
description: '密码、绑定手机等',
|
||||
icon: '🛡️',
|
||||
action: 'security',
|
||||
},
|
||||
{
|
||||
title: '主题设置',
|
||||
description: '切换应用主题',
|
||||
icon: '🎨',
|
||||
action: 'theme',
|
||||
},
|
||||
],
|
||||
helpMenus: [
|
||||
{
|
||||
title: '帮助中心',
|
||||
description: '常见问题解答',
|
||||
icon: '❓',
|
||||
action: 'help',
|
||||
},
|
||||
{
|
||||
title: '意见反馈',
|
||||
description: '提交建议和问题',
|
||||
icon: '💬',
|
||||
action: 'feedback',
|
||||
},
|
||||
{
|
||||
title: '关于应用',
|
||||
description: '版本 v2.1.0',
|
||||
icon: 'ℹ️',
|
||||
action: 'about',
|
||||
},
|
||||
{
|
||||
title: '退出登录',
|
||||
description: '安全退出当前账号',
|
||||
icon: '🚪',
|
||||
action: 'logout',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 检查登录状态
|
||||
checkLoginStatus() {
|
||||
return userState.autoLoginCheck();
|
||||
},
|
||||
|
||||
// 加载用户信息
|
||||
loadUserInfo() {
|
||||
const displayUser = userManager.formatUserForDisplay();
|
||||
if (displayUser) {
|
||||
this.userInfo.name = displayUser.name;
|
||||
this.userInfo.avatar = displayUser.avatar;
|
||||
this.userInfo.department = displayUser.department;
|
||||
this.userInfo.phone = displayUser.phone;
|
||||
this.userInfo.email = displayUser.email;
|
||||
this.userInfo.studentId = userManager.getCurrentUser()?.userId || '未设置';
|
||||
}
|
||||
},
|
||||
|
||||
handleSettings() {
|
||||
uni.showToast({ title: '设置', icon: 'none' });
|
||||
},
|
||||
handleStatClick(stat) {
|
||||
uni.showToast({ title: `查看${stat.label}详情`, icon: 'none' });
|
||||
},
|
||||
handleMenuClick(menu) {
|
||||
uni.vibrateShort();
|
||||
|
||||
switch (menu.action) {
|
||||
case 'schedule':
|
||||
this.openSchedule();
|
||||
break;
|
||||
case 'grades':
|
||||
this.openGrades();
|
||||
break;
|
||||
case 'todos':
|
||||
this.openTodos();
|
||||
break;
|
||||
case 'card':
|
||||
this.openCampusCard();
|
||||
break;
|
||||
case 'library':
|
||||
this.openLibrary();
|
||||
break;
|
||||
case 'logout':
|
||||
this.handleLogout();
|
||||
break;
|
||||
default:
|
||||
uni.showToast({
|
||||
title: `${menu.title}功能开发中`,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
},
|
||||
openSchedule() {
|
||||
uni.showToast({ title: '打开课程表', icon: 'success' });
|
||||
},
|
||||
openGrades() {
|
||||
uni.showToast({ title: '打开成绩单', icon: 'success' });
|
||||
},
|
||||
openTodos() {
|
||||
uni.showToast({ title: '打开待办事项', icon: 'success' });
|
||||
},
|
||||
openCampusCard() {
|
||||
uni.showToast({ title: '打开校园卡', icon: 'success' });
|
||||
},
|
||||
openLibrary() {
|
||||
uni.showToast({ title: '打开图书借阅', icon: 'success' });
|
||||
},
|
||||
handleLogout() {
|
||||
uni.showModal({
|
||||
title: '确认退出',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '正在退出...' });
|
||||
|
||||
// 清除登录信息
|
||||
userStorage.clearLoginInfo();
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '已退出登录',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 跳转到登录页面
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
});
|
||||
}, 500);
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
// 检查页面访问权限
|
||||
if (!checkPageAccess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.checkLoginStatus()) {
|
||||
this.loadUserInfo();
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 页面显示时再次检查权限
|
||||
if (!checkPageAccess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 刷新用户信息
|
||||
this.loadUserInfo();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: $uni-bg-color-grey;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding-bottom: calc(50px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* 用户信息卡片 */
|
||||
.profile-card {
|
||||
background: linear-gradient(135deg, $uni-color-primary, $uni-color-primary-dark);
|
||||
color: $uni-text-color-inverse;
|
||||
margin: $uni-spacing-col-lg;
|
||||
border-radius: $uni-border-radius-lg;
|
||||
padding: 24px 20px;
|
||||
box-shadow: $uni-shadow;
|
||||
}
|
||||
|
||||
.profile-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: $uni-spacing-col-lg;
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: $uni-border-radius-circle;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: $uni-spacing-row-lg;
|
||||
}
|
||||
|
||||
.avatar-icon {
|
||||
font-size: 24px;
|
||||
color: $uni-text-color-inverse;
|
||||
}
|
||||
|
||||
.profile-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: $uni-font-size-xl;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-major {
|
||||
font-size: $uni-font-size-base;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
font-size: $uni-font-size-sm;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.profile-stats {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: $uni-spacing-col-lg;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: $uni-font-size-xxl;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: $uni-font-size-sm;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 分组标题 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
font-size: $uni-font-size-lg;
|
||||
color: $uni-color-primary;
|
||||
}
|
||||
|
||||
/* 菜单列表 */
|
||||
.menu-section {
|
||||
background: $uni-bg-color;
|
||||
border-radius: $uni-border-radius-lg;
|
||||
margin: $uni-spacing-col-lg;
|
||||
box-shadow: $uni-shadow;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: $uni-spacing-col-lg $uni-spacing-row-xl;
|
||||
transition: $uni-transition;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $uni-color-primary-light;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: $uni-color-primary-light;
|
||||
border-radius: $uni-border-radius-circle;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: $uni-spacing-row-lg;
|
||||
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
color: $uni-color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: $uni-font-size-lg;
|
||||
font-weight: 500;
|
||||
color: $uni-text-color;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.menu-desc {
|
||||
font-size: $uni-font-size-sm;
|
||||
color: $uni-text-color-secondary;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
color: $uni-text-color-grey;
|
||||
font-size: $uni-font-size-xl;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/* 分隔器 */
|
||||
.section-divider {
|
||||
height: 8px;
|
||||
background: $uni-bg-color-grey;
|
||||
margin: 0 $uni-spacing-row-lg;
|
||||
}
|
||||
|
||||
/* 版权信息 */
|
||||
.footer-section {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: $uni-text-color-grey;
|
||||
}
|
||||
|
||||
.app-version {
|
||||
font-size: $uni-font-size-sm;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* 底部间距 */
|
||||
.bottom-spacer {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* 菜单项进入动画 */
|
||||
.menu-item {
|
||||
animation: slideInRight 0.6s ease-out backwards;
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 页面进入动画 */
|
||||
.page-container {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.profile-card,
|
||||
.menu-section {
|
||||
margin: $uni-spacing-col-base;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: $uni-spacing-col-base $uni-spacing-row-lg;
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
||||
.avatar-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: $uni-font-size-lg;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: $uni-font-size-xl;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,499 +0,0 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<!-- 自定义导航栏 -->
|
||||
<NavBar title="办公中心" :show-search="true" @search="handleSearch" />
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<scroll-view class="main-content" scroll-y enhanced :show-scrollbar="false">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-section">
|
||||
<view class="search-bar" @click="focusSearch">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="搜索服务..."
|
||||
v-model="searchQuery"
|
||||
@input="handleSearchInput"
|
||||
@confirm="handleSearchConfirm"
|
||||
:focus="searchFocused"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 热门服务 -->
|
||||
<Card>
|
||||
<template #header>
|
||||
<view class="section-header">
|
||||
<text class="section-icon">🔥</text>
|
||||
<text class="card-title">热门服务</text>
|
||||
</view>
|
||||
</template>
|
||||
<GridLayout :columns="4" gap="12px">
|
||||
<ServiceItem
|
||||
v-for="service in hotServices"
|
||||
:key="service.title"
|
||||
:title="service.title"
|
||||
:icon="service.icon"
|
||||
:badge="service.badge"
|
||||
@click="handleServiceClick"
|
||||
/>
|
||||
</GridLayout>
|
||||
</Card>
|
||||
|
||||
<!-- 学习教务 -->
|
||||
<Card>
|
||||
<template #header>
|
||||
<view class="section-header">
|
||||
<text class="section-icon">🎓</text>
|
||||
<text class="card-title">学习教务</text>
|
||||
</view>
|
||||
</template>
|
||||
<GridLayout :columns="3" gap="12px">
|
||||
<ServiceItem
|
||||
v-for="service in academicServices"
|
||||
:key="service.title"
|
||||
:title="service.title"
|
||||
:icon="service.icon"
|
||||
@click="handleServiceClick"
|
||||
/>
|
||||
</GridLayout>
|
||||
</Card>
|
||||
|
||||
<!-- 生活服务 -->
|
||||
<Card>
|
||||
<template #header>
|
||||
<view class="section-header">
|
||||
<text class="section-icon">🏠</text>
|
||||
<text class="card-title">生活服务</text>
|
||||
</view>
|
||||
</template>
|
||||
<GridLayout :columns="3" gap="12px">
|
||||
<ServiceItem
|
||||
v-for="service in lifeServices"
|
||||
:key="service.title"
|
||||
:title="service.title"
|
||||
:icon="service.icon"
|
||||
@click="handleServiceClick"
|
||||
/>
|
||||
</GridLayout>
|
||||
</Card>
|
||||
|
||||
<!-- 校园资源 -->
|
||||
<Card>
|
||||
<template #header>
|
||||
<view class="section-header">
|
||||
<text class="section-icon">🏢</text>
|
||||
<text class="card-title">校园资源</text>
|
||||
</view>
|
||||
</template>
|
||||
<GridLayout :columns="3" gap="12px">
|
||||
<ServiceItem
|
||||
v-for="service in resourceServices"
|
||||
:key="service.title"
|
||||
:title="service.title"
|
||||
:icon="service.icon"
|
||||
@click="handleServiceClick"
|
||||
/>
|
||||
</GridLayout>
|
||||
</Card>
|
||||
|
||||
<!-- 办事服务 -->
|
||||
<Card>
|
||||
<template #header>
|
||||
<view class="section-header">
|
||||
<text class="section-icon">📋</text>
|
||||
<text class="card-title">办事服务</text>
|
||||
</view>
|
||||
</template>
|
||||
<GridLayout :columns="3" gap="12px">
|
||||
<ServiceItem
|
||||
v-for="service in adminServices"
|
||||
:key="service.title"
|
||||
:title="service.title"
|
||||
:icon="service.icon"
|
||||
@click="handleServiceClick"
|
||||
/>
|
||||
</GridLayout>
|
||||
</Card>
|
||||
|
||||
<!-- 信息查询 -->
|
||||
<Card>
|
||||
<template #header>
|
||||
<view class="section-header">
|
||||
<text class="section-icon">🔍</text>
|
||||
<text class="card-title">信息查询</text>
|
||||
</view>
|
||||
</template>
|
||||
<GridLayout :columns="3" gap="12px">
|
||||
<ServiceItem
|
||||
v-for="service in queryServices"
|
||||
:key="service.title"
|
||||
:title="service.title"
|
||||
:icon="service.icon"
|
||||
@click="handleServiceClick"
|
||||
/>
|
||||
</GridLayout>
|
||||
</Card>
|
||||
|
||||
<!-- 快捷工具 -->
|
||||
<Card>
|
||||
<template #header>
|
||||
<view class="section-header">
|
||||
<text class="section-icon">⚡</text>
|
||||
<text class="card-title">快捷工具</text>
|
||||
</view>
|
||||
</template>
|
||||
<GridLayout :columns="3" gap="12px">
|
||||
<ServiceItem
|
||||
v-for="service in toolServices"
|
||||
:key="service.title"
|
||||
:title="service.title"
|
||||
:icon="service.icon"
|
||||
@click="handleServiceClick"
|
||||
/>
|
||||
</GridLayout>
|
||||
</Card>
|
||||
|
||||
<!-- 底部间距 -->
|
||||
<view class="bottom-spacer"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavBar from '@/components/NavBar.vue';
|
||||
import Card from '@/components/Card.vue';
|
||||
import ServiceItem from '@/components/ServiceItem.vue';
|
||||
import GridLayout from '@/components/GridLayout.vue';
|
||||
import { userStorage } from '@/utils/request';
|
||||
import { checkPageAccess } from '@/utils/route-guard';
|
||||
|
||||
export default {
|
||||
name: 'WorkbenchPage',
|
||||
components: {
|
||||
NavBar,
|
||||
Card,
|
||||
ServiceItem,
|
||||
GridLayout,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchQuery: '',
|
||||
searchFocused: false,
|
||||
hotServices: [
|
||||
{ title: '课程表', icon: '🎓' },
|
||||
{ title: '成绩查询', icon: '📊' },
|
||||
{ title: '校园卡', icon: '💳' },
|
||||
{ title: '图书馆', icon: '📚' },
|
||||
],
|
||||
academicServices: [
|
||||
{ title: '我的课表', icon: '🎓' },
|
||||
{ title: '成绩查询', icon: '📊' },
|
||||
{ title: '选课系统', icon: '👨🎓' },
|
||||
{ title: '考试安排', icon: '📝' },
|
||||
{ title: '课程评教', icon: '📋' },
|
||||
{ title: '学分查询', icon: '🏆' },
|
||||
{ title: '作业提交', icon: '📄' },
|
||||
{ title: '竞赛报名', icon: '🏅' },
|
||||
{ title: '教学评价', icon: '👨🏫' },
|
||||
],
|
||||
lifeServices: [
|
||||
{ title: '食堂菜单', icon: '🍽️' },
|
||||
{ title: '校园卡', icon: '💳' },
|
||||
{ title: '宿舍管理', icon: '🛏️' },
|
||||
{ title: '停车服务', icon: '🚗' },
|
||||
{ title: '校车时刻', icon: '🚌' },
|
||||
{ title: '校园购物', icon: '🛒' },
|
||||
{ title: '医疗服务', icon: '🏥' },
|
||||
{ title: '报修服务', icon: '🔧' },
|
||||
{ title: '空调控制', icon: '🌡️' },
|
||||
],
|
||||
resourceServices: [
|
||||
{ title: '图书馆', icon: '📚' },
|
||||
{ title: '机房预约', icon: '💻' },
|
||||
{ title: '会议室', icon: '🎤' },
|
||||
{ title: '体育馆', icon: '🏃♂️' },
|
||||
{ title: '艺术中心', icon: '🎭' },
|
||||
{ title: '实验室', icon: '🧪' },
|
||||
{ title: '打印服务', icon: '🖨️' },
|
||||
{ title: '网络服务', icon: '📶' },
|
||||
{ title: '设备租借', icon: '🔧' },
|
||||
],
|
||||
adminServices: [
|
||||
{ title: '学费缴费', icon: '💰' },
|
||||
{ title: '证明开具', icon: '📄' },
|
||||
{ title: '学生证', icon: '🆔' },
|
||||
{ title: '学籍管理', icon: '👤' },
|
||||
{ title: '转专业', icon: '🔄' },
|
||||
{ title: '请假申请', icon: '✈️' },
|
||||
{ title: '毕业申请', icon: '🎓' },
|
||||
{ title: '就业指导', icon: '💼' },
|
||||
{ title: '国际交流', icon: '🌍' },
|
||||
],
|
||||
queryServices: [
|
||||
{ title: '校历查询', icon: '📅' },
|
||||
{ title: '校园通讯录', icon: '📞' },
|
||||
{ title: '校园地图', icon: '🗺️' },
|
||||
{ title: '通知公告', icon: '📢' },
|
||||
{ title: '常见问题', icon: '❓' },
|
||||
{ title: '意见反馈', icon: '💬' },
|
||||
{ title: '在线客服', icon: '🎧' },
|
||||
{ title: '数据统计', icon: '📈' },
|
||||
{ title: '校园新闻', icon: '📰' },
|
||||
],
|
||||
toolServices: [
|
||||
{ title: '扫一扫', icon: '📱' },
|
||||
{ title: '计算器', icon: '🧮' },
|
||||
{ title: '作息时间', icon: '⏰' },
|
||||
{ title: '天气查询', icon: '☁️' },
|
||||
{ title: '翻译工具', icon: '🔤' },
|
||||
{ title: '分享', icon: '📤' },
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSearch() {
|
||||
this.focusSearch();
|
||||
},
|
||||
focusSearch() {
|
||||
this.searchFocused = true;
|
||||
},
|
||||
handleSearchInput(e) {
|
||||
this.searchQuery = e.detail.value;
|
||||
// 实现搜索建议逻辑
|
||||
this.showSearchSuggestions();
|
||||
},
|
||||
handleSearchConfirm() {
|
||||
if (this.searchQuery.trim()) {
|
||||
this.performSearch(this.searchQuery);
|
||||
}
|
||||
},
|
||||
showSearchSuggestions() {
|
||||
// 根据输入显示搜索建议
|
||||
if (this.searchQuery.length > 0) {
|
||||
// 这里可以实现搜索建议逻辑
|
||||
}
|
||||
},
|
||||
performSearch(query) {
|
||||
uni.showToast({
|
||||
title: `搜索: ${query}`,
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
this.searchFocused = false;
|
||||
},
|
||||
handleServiceClick(service) {
|
||||
uni.vibrateShort();
|
||||
|
||||
// 根据不同服务类型进行路由跳转或功能调用
|
||||
switch (service.title) {
|
||||
case '课程表':
|
||||
case '我的课表':
|
||||
this.openCourseSchedule();
|
||||
break;
|
||||
case '成绩查询':
|
||||
this.openGradeQuery();
|
||||
break;
|
||||
case '校园卡':
|
||||
this.openCampusCard();
|
||||
break;
|
||||
case '图书馆':
|
||||
this.openLibrary();
|
||||
break;
|
||||
case '扫一扫':
|
||||
this.openScanner();
|
||||
break;
|
||||
default:
|
||||
uni.showToast({
|
||||
title: `${service.title}功能开发中`,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
},
|
||||
openCourseSchedule() {
|
||||
uni.showToast({ title: '打开课程表', icon: 'success' });
|
||||
// 这里可以跳转到课程表页面
|
||||
},
|
||||
openGradeQuery() {
|
||||
uni.showToast({ title: '打开成绩查询', icon: 'success' });
|
||||
// 这里可以跳转到成绩查询页面
|
||||
},
|
||||
openCampusCard() {
|
||||
uni.showToast({ title: '打开校园卡', icon: 'success' });
|
||||
// 这里可以跳转到校园卡页面
|
||||
},
|
||||
openLibrary() {
|
||||
uni.showToast({ title: '打开图书馆', icon: 'success' });
|
||||
// 这里可以跳转到图书馆页面
|
||||
},
|
||||
openScanner() {
|
||||
uni.scanCode({
|
||||
success: (res) => {
|
||||
uni.showToast({
|
||||
title: `扫描结果: ${res.result}`,
|
||||
icon: 'none',
|
||||
});
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '扫描失败',
|
||||
icon: 'error',
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
// 检查登录状态
|
||||
checkLoginStatus() {
|
||||
if (!userStorage.isLoggedIn()) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index',
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
// 检查页面访问权限
|
||||
if (!checkPageAccess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.checkLoginStatus();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 页面显示时再次检查权限
|
||||
if (!checkPageAccess()) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: $uni-bg-color-grey;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding-bottom: calc(50px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-section {
|
||||
padding: $uni-spacing-col-lg;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
position: relative;
|
||||
background: $uni-bg-color;
|
||||
border-radius: $uni-border-radius-lg;
|
||||
box-shadow: $uni-shadow;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 $uni-spacing-row-lg;
|
||||
transition: $uni-transition;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $uni-shadow-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 16px;
|
||||
color: $uni-text-color-grey;
|
||||
margin-right: $uni-spacing-row-base;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
padding: $uni-spacing-col-base 0;
|
||||
font-size: $uni-font-size-lg;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: $uni-text-color;
|
||||
|
||||
&::placeholder {
|
||||
color: $uni-text-color-placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
/* 分组标题 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
font-size: $uni-font-size-lg;
|
||||
}
|
||||
|
||||
/* 服务网格动画 */
|
||||
.service-item {
|
||||
animation-delay: calc(var(--item-index, 0) * 0.1s);
|
||||
animation: slideInUp 0.6s ease-out backwards;
|
||||
}
|
||||
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 搜索焦点效果 */
|
||||
.search-bar:focus-within {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: $uni-shadow-hover;
|
||||
|
||||
.search-icon {
|
||||
color: $uni-color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部间距 */
|
||||
.bottom-spacer {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* 页面进入动画 */
|
||||
.page-container {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.search-section {
|
||||
padding: $uni-spacing-col-base;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
padding: 0 $uni-spacing-row-base;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
font-size: $uni-font-size-base;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -73,3 +73,180 @@
|
||||
.icon-mail {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 新增常用图标 - 2025-01-07 */
|
||||
|
||||
/* 星星图标 */
|
||||
.icon-star {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 心形图标 */
|
||||
.icon-heart {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 日历图标 */
|
||||
.icon-calendar {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 聊天图标 */
|
||||
.icon-chat {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4l4 4 4-4h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 工具图标 */
|
||||
.icon-tools {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 地图图标 */
|
||||
.icon-map {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M20.5 3l-.16.03L15 5.1 9 3 3.36 4.9c-.21.07-.36.25-.36.48V20.5c0 .28.22.5.5.5l.16-.03L9 18.9l6 2.1 5.64-1.9c.21-.07.36-.25.36-.48V3.5c0-.28-.22-.5-.5-.5zM15 19l-6-2.11V5l6 2.11V19z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 播放圆圈图标 */
|
||||
.icon-play-circle {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 列表图标 */
|
||||
.icon-list {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 人民币圆圈图标 */
|
||||
.icon-rmb-circle {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.5 6L12 10.5 10.5 8H9.5l1.75 2.5H9v1h3v1H9v1h3V15h1v-1.5h3v-1h-3v-1h3v-1h-2.25L14.5 8h-1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 商店图标 */
|
||||
.icon-shop {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2l3.09 6.26L22 9.27 16.18 14l1.64 7L12 17.77 6.18 21l1.64-7L2 9.27l6.91-1.01L12 2zm0 0L4 8v12h16V8l-8-6z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 奖杯图标 */
|
||||
.icon-trophy {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M7 4V2c0-1.1.9-2 2-2h6c1.1 0 2 .9 2 2v2h4c1.1 0 2 .9 2 2v4c0 2.21-1.79 4-4 4h-1.28c-.44 2.83-2.82 5-5.72 5s-5.28-2.17-5.72-5H5c-2.21 0-4-1.79-4-4V6c0-1.1.9-2 2-2h4zM9 4h6V2H9v2zM5 6v4c0 1.1.9 2 2 2h1.28c.42-1.82 1.89-3.26 3.72-3.26s3.3 1.44 3.72 3.26H17c1.1 0 2-.9 2-2V6H5zm7 14c1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3 1.34 3 3 3z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 朋友图标 */
|
||||
.icon-friends {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M16 4c0-1.11.89-2 2-2s2 .89 2 2-.89 2-2 2-2-.89-2-2zm4 18v-6h2.5l-2.54-7.63A1.495 1.495 0 0 0 18.5 7.5h-3c-.57 0-1.08.32-1.34.82L11.5 15h2.5v7h6zM12.5 11.5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5S11 9.17 11 10s.67 1.5 1.5 1.5zM5.5 6c1.11 0 2-.89 2-2s-.89-2-2-2-2 .89-2 2 .89 2 2 2zm2 16v-7H9l-1.46-3.12A1.495 1.495 0 0 0 6.12 11h-1.5c-.57 0-1.08.32-1.34.82L1 17h2.5v7h4z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 飞机图标 */
|
||||
.icon-plane {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M21 16v-2l-8-5V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 书签图标 */
|
||||
.icon-bookmark {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 人民币圆圈填充图标 */
|
||||
.icon-rmb-circle-fill {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.5 6L12 10.5 10.5 8H9.5l1.75 2.5H9v1h3v1H9v1h3V15h1v-1.5h3v-1h-3v-1h3v-1h-2.25L14.5 8h-1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 金钱圆圈图标 */
|
||||
.icon-money-circle {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 文件文本图标 */
|
||||
.icon-file-text {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 订单图标 */
|
||||
.icon-order {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 设置填充图标 */
|
||||
.icon-setting-fill {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm8.94 3A8.994 8.994 0 0 0 13 3.06V1h-2v2.06A8.994 8.994 0 0 0 3.06 11H1v2h2.06A8.994 8.994 0 0 0 11 20.94V23h2v-2.06A8.994 8.994 0 0 0 20.94 13H23v-2h-2.06zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 用户圆圈填充图标 */
|
||||
.icon-account-circle-fill {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 列表点图标 */
|
||||
.icon-list-dot {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 首页填充图标 */
|
||||
.icon-home-fill {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* WiFi图标 */
|
||||
.icon-wifi {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M1 9l2 2c4.97-4.97 13.03-4.97 18 0l2-2C16.93 2.93 7.07 2.93 1 9zm8 8l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zm-4-4l2 2c2.76-2.76 7.24-2.76 10 0l2-2C15.14 9.14 8.87 9.14 5 13z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 分享图标 */
|
||||
.icon-share {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 分享方形图标 */
|
||||
.icon-share-square {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M18 2h-3c-1.1 0-2 .9-2 2v3H7c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2v-6h3c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8 15H7v-3h3v3zm0-5H7v-2h3v2zm0-4H7V6h3v2zm5 9h-3v-3h3v3zm0-5h-3v-2h3v2zm0-4h-3V6h3v2zm5-3h-3V4h3v1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 编辑笔图标 */
|
||||
.icon-edit-pen {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 时钟图标 */
|
||||
.icon-clock {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z'/%3E%3C/path%3E%3Cpath d='m12.5 7-1 1v4.25l3.5 2.08.75-1.23-2.75-1.63V8z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 确认圆圈图标 */
|
||||
.icon-checkmark-circle {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 复制图标 */
|
||||
.icon-copy {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 用户圆圈图标 */
|
||||
.icon-account-circle {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 用户填充图标 */
|
||||
.icon-account-fill {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 星星填充图标 */
|
||||
.icon-star-fill {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 包图标 */
|
||||
.icon-bag {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M17 6h-2V3c0-.55-.45-1-1-1h-4c-.55 0-1 .45-1 1v3H7c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zM9.5 3h5v3h-5V3zM9 12v-2h2v2h2v-2h2v2c0 1.1-.9 2-2 2h-2c-1.1 0-2-.9-2-2z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 学工管理填充图标 */
|
||||
.icon-account-fill {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 登出图标 */
|
||||
.icon-logout {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.59L17 17l5-5-5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="7" r="4" fill="rgb(93, 135, 255)"/>
|
||||
<path d="M12 14c-4.42 0-8 3.58-8 8h16c0-4.42-3.58-8-8-8z" fill="rgb(93, 135, 255)"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 256 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="7" r="4" fill="#999999"/>
|
||||
<path d="M12 14c-4.42 0-8 3.58-8 8h16c0-4.42-3.58-8-8-8z" fill="#999999"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 236 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 4h16v2H4V4zm0 4h16v2H4V8zm0 4h16v2H4v-2zm0 4h10v2H4v-2z" fill="rgb(93, 135, 255)"/>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" stroke="rgb(93, 135, 255)" stroke-width="1.5" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 319 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 4h16v2H4V4zm0 4h16v2H4V8zm0 4h16v2H4v-2zm0 4h10v2H4v-2z" fill="#999999"/>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" stroke="#999999" stroke-width="1.5" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 299 B |
@@ -0,0 +1,378 @@
|
||||
# 移动端工具库使用说明
|
||||
|
||||
本文档说明移动端 `utils` 目录下各个工具的使用方法,特别是升级后的请求工具。
|
||||
|
||||
## 📁 文件结构
|
||||
|
||||
```
|
||||
mobile/src/utils/
|
||||
├── request.js # 统一请求工具(已升级)
|
||||
├── crypto.js # 加密解密工具(新增)
|
||||
├── config.js # 配置管理工具(新增)
|
||||
├── encrypt-pure.js # 登录专用加密工具(保留)
|
||||
├── encrypt-*.js # 其他加密实现(保留)
|
||||
└── README.md # 本文档
|
||||
```
|
||||
|
||||
## 🚀 核心升级功能
|
||||
|
||||
### 1. 统一的加密解密支持
|
||||
|
||||
移动端现在与 Web 端保持一致,支持:
|
||||
- ✅ **入参加密**:通过 `isEncrypt: 'true'` 头部控制
|
||||
- ✅ **出参解密**:自动处理后端返回的加密数据
|
||||
- ✅ **AES + RSA 双重加密**:与后端完全兼容
|
||||
- ✅ **防重复提交**:避免用户误操作
|
||||
- ✅ **错误处理优化**:404静默处理、401自动跳转
|
||||
|
||||
### 2. 可配置的参数管理
|
||||
|
||||
所有关键参数都可以通过配置文件统一管理,方便调试和部署。
|
||||
|
||||
## 📖 使用方法
|
||||
|
||||
### 基础请求
|
||||
|
||||
```javascript
|
||||
import { request } from '@/utils/request.js';
|
||||
|
||||
// 普通GET请求
|
||||
const response = await request({
|
||||
url: '/api/user/list',
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
// 普通POST请求
|
||||
const response = await request({
|
||||
url: '/api/user/create',
|
||||
method: 'POST',
|
||||
data: {
|
||||
name: '张三',
|
||||
age: 25
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 加密请求
|
||||
|
||||
```javascript
|
||||
import { request } from '@/utils/request.js';
|
||||
|
||||
// 启用加密的POST请求
|
||||
const response = await request({
|
||||
url: '/api/sensitive/data',
|
||||
method: 'POST',
|
||||
data: {
|
||||
sensitiveInfo: '敏感数据'
|
||||
},
|
||||
header: {
|
||||
isEncrypt: 'true' // 关键:启用加密
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 免Token请求
|
||||
|
||||
```javascript
|
||||
import { request } from '@/utils/request.js';
|
||||
|
||||
// 不需要Token的请求(如获取租户列表)
|
||||
const response = await request({
|
||||
url: '/auth/tenant/list',
|
||||
method: 'GET',
|
||||
header: {
|
||||
isToken: false // 关键:不携带Token
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 允许重复提交
|
||||
|
||||
```javascript
|
||||
import { request } from '@/utils/request.js';
|
||||
|
||||
// 允许重复提交的请求
|
||||
const response = await request({
|
||||
url: '/api/data/sync',
|
||||
method: 'POST',
|
||||
data: { syncData: 'xxx' },
|
||||
header: {
|
||||
repeatSubmit: false // 关键:允许重复提交
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## ⚙️ 配置管理
|
||||
|
||||
### 基础配置更新
|
||||
|
||||
```javascript
|
||||
import { updateConfig } from '@/utils/config.js';
|
||||
|
||||
// 更新API基础URL
|
||||
updateConfig({
|
||||
request: {
|
||||
baseURL: {
|
||||
production: 'https://new-api-domain.com'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 更新客户端ID
|
||||
updateConfig({
|
||||
app: {
|
||||
clientId: 'new-client-id-here'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 加密配置更新
|
||||
|
||||
```javascript
|
||||
import { updateConfig } from '@/utils/config.js';
|
||||
|
||||
// 更新RSA公钥
|
||||
updateConfig({
|
||||
encryption: {
|
||||
rsaPublicKey: 'new-rsa-public-key-here'
|
||||
}
|
||||
});
|
||||
|
||||
// 临时禁用加密(调试用)
|
||||
updateConfig({
|
||||
encryption: {
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 预设配置方案
|
||||
|
||||
```javascript
|
||||
import { applyPreset } from '@/utils/config.js';
|
||||
|
||||
// 应用生产环境配置
|
||||
applyPreset('production');
|
||||
|
||||
// 应用测试环境配置
|
||||
applyPreset('test');
|
||||
|
||||
// 应用开发环境配置
|
||||
applyPreset('development');
|
||||
```
|
||||
|
||||
### 单个配置项更新
|
||||
|
||||
```javascript
|
||||
import { setConfigItem, getConfigItem } from '@/utils/config.js';
|
||||
|
||||
// 设置超时时间
|
||||
setConfigItem('request.timeout', 600000); // 10分钟
|
||||
|
||||
// 获取当前客户端ID
|
||||
const clientId = getConfigItem('app.clientId');
|
||||
|
||||
// 检查是否启用加密
|
||||
const isEncrypted = getConfigItem('encryption.enabled');
|
||||
```
|
||||
|
||||
## 🔐 加密工具直接使用
|
||||
|
||||
```javascript
|
||||
import {
|
||||
encryptWithAES,
|
||||
decryptWithAES,
|
||||
generateRandomAESKey,
|
||||
encrypt,
|
||||
decrypt
|
||||
} from '@/utils/crypto.js';
|
||||
|
||||
// AES加密解密
|
||||
const aesKey = generateRandomAESKey();
|
||||
const encryptedData = encryptWithAES('敏感数据', aesKey);
|
||||
const decryptedData = decryptWithAES(encryptedData, aesKey);
|
||||
|
||||
// RSA加密解密
|
||||
const encryptedKey = encrypt('AES密钥');
|
||||
const decryptedKey = decrypt(encryptedKey);
|
||||
```
|
||||
|
||||
## 📦 API方法封装
|
||||
|
||||
### 登录
|
||||
|
||||
```javascript
|
||||
import { api } from '@/utils/request.js';
|
||||
|
||||
// 用户登录(自动加密)
|
||||
const loginResult = await api.login({
|
||||
username: 'admin',
|
||||
password: 'password123',
|
||||
tenantId: '000000'
|
||||
});
|
||||
```
|
||||
|
||||
### 获取用户信息
|
||||
|
||||
```javascript
|
||||
import { api } from '@/utils/request.js';
|
||||
|
||||
// 获取当前用户信息
|
||||
const userInfo = await api.getUserInfo();
|
||||
```
|
||||
|
||||
### 注销登录
|
||||
|
||||
```javascript
|
||||
import { api } from '@/utils/request.js';
|
||||
|
||||
// 用户注销
|
||||
await api.logout();
|
||||
```
|
||||
|
||||
## 💾 缓存工具
|
||||
|
||||
### 用户信息缓存
|
||||
|
||||
```javascript
|
||||
import { userStorage } from '@/utils/request.js';
|
||||
|
||||
// 保存登录信息
|
||||
userStorage.saveLoginInfo(loginResult, userInfo);
|
||||
|
||||
// 获取用户信息
|
||||
const user = userStorage.getUserInfo();
|
||||
|
||||
// 检查登录状态
|
||||
const isLoggedIn = userStorage.isLoggedIn();
|
||||
|
||||
// 清除登录信息
|
||||
userStorage.clearLoginInfo();
|
||||
```
|
||||
|
||||
### 通用缓存
|
||||
|
||||
```javascript
|
||||
import { storage } from '@/utils/request.js';
|
||||
|
||||
// 设置缓存(30分钟过期)
|
||||
storage.set('cacheKey', { data: 'value' }, 1800);
|
||||
|
||||
// 获取缓存
|
||||
const data = storage.get('cacheKey');
|
||||
|
||||
// 删除缓存
|
||||
storage.remove('cacheKey');
|
||||
|
||||
// 清空所有缓存
|
||||
storage.clear();
|
||||
```
|
||||
|
||||
## 📥 文件下载
|
||||
|
||||
```javascript
|
||||
import { download } from '@/utils/request.js';
|
||||
|
||||
// 下载文件
|
||||
await download('/api/export/users', {
|
||||
filters: { status: 'active' }
|
||||
}, 'users.xlsx');
|
||||
```
|
||||
|
||||
## 🔧 调试和日志
|
||||
|
||||
### 开启详细日志
|
||||
|
||||
```javascript
|
||||
import { updateConfig } from '@/utils/config.js';
|
||||
|
||||
// 开启所有调试日志
|
||||
updateConfig({
|
||||
debug: {
|
||||
enableVerboseLog: true,
|
||||
showRequestLog: true,
|
||||
showResponseLog: true,
|
||||
showCryptoLog: true
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 错误处理配置
|
||||
|
||||
```javascript
|
||||
import { updateConfig } from '@/utils/config.js';
|
||||
|
||||
// 自定义错误处理
|
||||
updateConfig({
|
||||
error: {
|
||||
silentCodes: [404, 403], // 静默处理的状态码
|
||||
toastDuration: 3000, // 错误提示显示时间
|
||||
loginRedirectDelay: 1000 // 跳转登录延迟
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 🚨 注意事项
|
||||
|
||||
### 1. 加密兼容性
|
||||
- 移动端加密算法与 Web 端完全一致
|
||||
- RSA 公钥必须与后端保持同步
|
||||
- 加密只对 POST/PUT 请求生效
|
||||
|
||||
### 2. 配置优先级
|
||||
- 运行时配置 > 环境预设 > 默认配置
|
||||
- 配置更新会立即生效,无需重启应用
|
||||
|
||||
### 3. Token 管理
|
||||
- Token 自动从本地存储获取
|
||||
- 401 错误会自动清除 Token 并跳转登录
|
||||
- 支持 Token 过期时间管理
|
||||
|
||||
### 4. 错误处理
|
||||
- 404 错误默认静默处理
|
||||
- 403 错误会显示提示但不跳转
|
||||
- 网络错误会显示友好提示
|
||||
|
||||
## 📊 性能优化
|
||||
|
||||
### 1. 防重复提交
|
||||
- 默认 500ms 间隔防重复提交
|
||||
- 可通过配置调整间隔时间
|
||||
- 支持单个请求禁用防重复
|
||||
|
||||
### 2. 请求超时
|
||||
- 普通请求:5分钟超时
|
||||
- 下载请求:20分钟超时
|
||||
- 可通过配置调整超时时间
|
||||
|
||||
### 3. 缓存策略
|
||||
- 会话级缓存用于防重复提交
|
||||
- 持久化缓存支持过期时间
|
||||
- 登录信息缓存支持时间戳
|
||||
|
||||
## 🔗 与 Web 端对比
|
||||
|
||||
| 功能 | Web 端 | 移动端 | 说明 |
|
||||
|------|--------|--------|------|
|
||||
| 入参加密 | ✅ | ✅ | 完全一致 |
|
||||
| 出参解密 | ✅ | ✅ | 完全一致 |
|
||||
| 防重复提交 | ✅ | ✅ | 完全一致 |
|
||||
| 401处理 | ✅ | ✅ | 跳转方式略有不同 |
|
||||
| 404静默 | ✅ | ✅ | 完全一致 |
|
||||
| 下载支持 | ✅ | ✅ | 平台实现不同 |
|
||||
| 配置管理 | 环境变量 | 配置文件 | 更灵活的配置方式 |
|
||||
|
||||
## 🎯 最佳实践
|
||||
|
||||
1. **生产环境**:关闭调试日志,启用加密
|
||||
2. **开发环境**:开启详细日志,保持加密测试
|
||||
3. **配置管理**:使用预设配置,避免硬编码
|
||||
4. **错误处理**:合理设置静默错误码
|
||||
5. **性能优化**:适当设置超时和缓存策略
|
||||
|
||||
---
|
||||
|
||||
**📝 更新日志**
|
||||
- 2025-01-07:完成移动端请求工具升级,与 Web 端功能对齐
|
||||
- 支持入参加密、出参解密、防重复提交等完整功能
|
||||
- 新增配置管理系统,提升可维护性
|
||||
@@ -373,8 +373,6 @@ class AccessibilityUtils {
|
||||
uni.switchTab = (options) => {
|
||||
const pageTitles = {
|
||||
'/pages/index/index': '智慧校园 - 首页',
|
||||
'/pages/workbench/index': '智慧校园 - 办公中心',
|
||||
'/pages/profile/index': '智慧校园 - 我的',
|
||||
};
|
||||
|
||||
const title = pageTitles[options.url];
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
// 移动端配置文件 - 集中管理所有可配置参数
|
||||
// 使用方法:import { updateConfig } from '@/utils/config.js' 然后调用 updateConfig(newConfig)
|
||||
|
||||
// 默认配置
|
||||
const DEFAULT_CONFIG = {
|
||||
// =========================
|
||||
// 加密相关配置
|
||||
// =========================
|
||||
encryption: {
|
||||
// 是否启用加密
|
||||
enabled: true,
|
||||
|
||||
// RSA公钥 - 与后端保持一致
|
||||
rsaPublicKey:
|
||||
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
|
||||
|
||||
// RSA私钥 - 前端不建议存放,仅用于测试环境
|
||||
rsaPrivateKey: '',
|
||||
|
||||
// 加密请求头名称
|
||||
encryptHeader: 'encrypt-key',
|
||||
|
||||
// AES密钥长度
|
||||
aesKeyLength: 32,
|
||||
},
|
||||
|
||||
// =========================
|
||||
// 请求相关配置
|
||||
// =========================
|
||||
request: {
|
||||
// API基础URL
|
||||
baseURL: {
|
||||
development: '/api',
|
||||
production: 'https://your-api-domain.com',
|
||||
},
|
||||
|
||||
// 请求超时时间(毫秒)
|
||||
timeout: 300000, // 5分钟
|
||||
|
||||
// 下载超时时间(毫秒)
|
||||
downloadTimeout: 1200000, // 20分钟
|
||||
|
||||
// 防重复提交间隔时间(毫秒)
|
||||
repeatSubmitInterval: 500,
|
||||
|
||||
// 默认请求头
|
||||
defaultHeaders: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
|
||||
// =========================
|
||||
// 应用相关配置
|
||||
// =========================
|
||||
app: {
|
||||
// 客户端ID
|
||||
clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
|
||||
// 租户ID
|
||||
tenantId: '000000',
|
||||
|
||||
// 默认referer
|
||||
referer: 'http://localhost/login?redirect=/index',
|
||||
|
||||
// 登录页面路径
|
||||
loginPath: '/pages/login/index',
|
||||
},
|
||||
|
||||
// =========================
|
||||
// 缓存相关配置
|
||||
// =========================
|
||||
cache: {
|
||||
// 默认缓存过期时间(秒)
|
||||
defaultExpire: 3600, // 1小时
|
||||
|
||||
// Token存储键名
|
||||
tokenKey: 'access_token',
|
||||
|
||||
// 用户信息存储键名
|
||||
userInfoKey: 'userInfo',
|
||||
|
||||
// 登录状态存储键名
|
||||
loginStatusKey: 'isLoggedIn',
|
||||
|
||||
// 会话缓存前缀
|
||||
sessionPrefix: 'session_',
|
||||
},
|
||||
|
||||
// =========================
|
||||
// 错误处理配置
|
||||
// =========================
|
||||
error: {
|
||||
// 静默处理的HTTP状态码
|
||||
silentCodes: [404],
|
||||
|
||||
// 自动跳转登录的状态码
|
||||
loginRedirectCodes: [401],
|
||||
|
||||
// 错误提示显示时间(毫秒)
|
||||
toastDuration: 2000,
|
||||
|
||||
// 跳转登录延迟时间(毫秒)
|
||||
loginRedirectDelay: 500,
|
||||
},
|
||||
|
||||
// =========================
|
||||
// 调试相关配置
|
||||
// =========================
|
||||
debug: {
|
||||
// 是否启用详细日志
|
||||
enableVerboseLog: process.env.NODE_ENV === 'development',
|
||||
|
||||
// 是否显示请求日志
|
||||
showRequestLog: true,
|
||||
|
||||
// 是否显示响应日志
|
||||
showResponseLog: true,
|
||||
|
||||
// 是否显示加密解密日志
|
||||
showCryptoLog: true,
|
||||
},
|
||||
};
|
||||
|
||||
// 当前配置 - 可被动态修改
|
||||
let currentConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
||||
|
||||
// =========================
|
||||
// 配置管理方法
|
||||
// =========================
|
||||
|
||||
/**
|
||||
* 获取完整配置
|
||||
* @returns {Object} 当前配置对象
|
||||
*/
|
||||
export const getConfig = () => {
|
||||
return currentConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取特定配置项
|
||||
* @param {string} path 配置路径,支持嵌套 如: 'encryption.enabled' 或 'request.timeout'
|
||||
* @returns {any} 配置值
|
||||
*/
|
||||
export const getConfigItem = (path) => {
|
||||
const keys = path.split('.');
|
||||
let value = currentConfig;
|
||||
|
||||
for (const key of keys) {
|
||||
if (value && typeof value === 'object' && key in value) {
|
||||
value = value[key];
|
||||
} else {
|
||||
console.warn(`配置项 ${path} 不存在`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
* @param {Object} newConfig 新配置对象(部分更新)
|
||||
* @param {boolean} deep 是否深度合并,默认true
|
||||
*/
|
||||
export const updateConfig = (newConfig, deep = true) => {
|
||||
if (deep) {
|
||||
currentConfig = deepMerge(currentConfig, newConfig);
|
||||
} else {
|
||||
currentConfig = { ...currentConfig, ...newConfig };
|
||||
}
|
||||
|
||||
console.log('🔧 配置已更新:', currentConfig);
|
||||
|
||||
// 通知其他模块配置已更新
|
||||
notifyConfigUpdate();
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置特定配置项
|
||||
* @param {string} path 配置路径
|
||||
* @param {any} value 配置值
|
||||
*/
|
||||
export const setConfigItem = (path, value) => {
|
||||
const keys = path.split('.');
|
||||
let target = currentConfig;
|
||||
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
const key = keys[i];
|
||||
if (!target[key] || typeof target[key] !== 'object') {
|
||||
target[key] = {};
|
||||
}
|
||||
target = target[key];
|
||||
}
|
||||
|
||||
const lastKey = keys[keys.length - 1];
|
||||
target[lastKey] = value;
|
||||
|
||||
console.log(`🔧 配置项 ${path} 已更新为:`, value);
|
||||
|
||||
// 通知其他模块配置已更新
|
||||
notifyConfigUpdate();
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置配置为默认值
|
||||
*/
|
||||
export const resetConfig = () => {
|
||||
currentConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
||||
console.log('🔄 配置已重置为默认值');
|
||||
|
||||
// 通知其他模块配置已更新
|
||||
notifyConfigUpdate();
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前环境的API基础URL
|
||||
* @returns {string} API基础URL
|
||||
*/
|
||||
export const getBaseURL = () => {
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
return currentConfig.request.baseURL[env] || currentConfig.request.baseURL.development;
|
||||
};
|
||||
|
||||
// =========================
|
||||
// 配置预设方案
|
||||
// =========================
|
||||
|
||||
/**
|
||||
* 开发环境配置
|
||||
*/
|
||||
export const developmentConfig = {
|
||||
encryption: {
|
||||
enabled: true, // 开发环境也启用加密测试
|
||||
},
|
||||
debug: {
|
||||
enableVerboseLog: true,
|
||||
showRequestLog: true,
|
||||
showResponseLog: true,
|
||||
showCryptoLog: true,
|
||||
},
|
||||
error: {
|
||||
toastDuration: 3000, // 开发环境错误提示显示久一点
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 生产环境配置
|
||||
*/
|
||||
export const productionConfig = {
|
||||
encryption: {
|
||||
enabled: true,
|
||||
},
|
||||
debug: {
|
||||
enableVerboseLog: false,
|
||||
showRequestLog: false,
|
||||
showResponseLog: false,
|
||||
showCryptoLog: false,
|
||||
},
|
||||
request: {
|
||||
timeout: 300000, // 生产环境保持较长超时
|
||||
repeatSubmitInterval: 1000, // 生产环境防重复提交间隔更长
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 测试环境配置
|
||||
*/
|
||||
export const testConfig = {
|
||||
encryption: {
|
||||
enabled: true,
|
||||
},
|
||||
debug: {
|
||||
enableVerboseLog: true,
|
||||
showRequestLog: true,
|
||||
showResponseLog: false,
|
||||
showCryptoLog: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 应用预设配置
|
||||
* @param {string} preset 预设名称: 'development' | 'production' | 'test'
|
||||
*/
|
||||
export const applyPreset = (preset) => {
|
||||
const presets = {
|
||||
development: developmentConfig,
|
||||
production: productionConfig,
|
||||
test: testConfig,
|
||||
};
|
||||
|
||||
if (presets[preset]) {
|
||||
updateConfig(presets[preset]);
|
||||
console.log(`✅ 已应用 ${preset} 环境配置预设`);
|
||||
} else {
|
||||
console.error(`❌ 未知的配置预设: ${preset}`);
|
||||
}
|
||||
};
|
||||
|
||||
// =========================
|
||||
// 工具方法
|
||||
// =========================
|
||||
|
||||
/**
|
||||
* 深度合并对象
|
||||
* @param {Object} target 目标对象
|
||||
* @param {Object} source 源对象
|
||||
* @returns {Object} 合并后的对象
|
||||
*/
|
||||
const deepMerge = (target, source) => {
|
||||
const result = { ...target };
|
||||
|
||||
for (const key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
||||
result[key] = deepMerge(result[key] || {}, source[key]);
|
||||
} else {
|
||||
result[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 通知其他模块配置已更新
|
||||
*/
|
||||
const notifyConfigUpdate = () => {
|
||||
// 这里可以实现事件通知机制
|
||||
// 例如使用 uni.$emit('configUpdated', currentConfig)
|
||||
if (typeof uni !== 'undefined' && uni.$emit) {
|
||||
uni.$emit('configUpdated', currentConfig);
|
||||
}
|
||||
};
|
||||
|
||||
// =========================
|
||||
// 初始化配置
|
||||
// =========================
|
||||
|
||||
// 根据环境自动应用配置预设
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
if (env !== 'development') {
|
||||
applyPreset(env);
|
||||
}
|
||||
|
||||
// 导出常用配置项的快捷访问方法
|
||||
export const isEncryptionEnabled = () => getConfigItem('encryption.enabled');
|
||||
export const getClientId = () => getConfigItem('app.clientId');
|
||||
export const getTenantId = () => getConfigItem('app.tenantId');
|
||||
export const getTimeout = () => getConfigItem('request.timeout');
|
||||
export const isDebugMode = () => getConfigItem('debug.enableVerboseLog');
|
||||
|
||||
console.log('📋 移动端配置初始化完成,当前环境:', env);
|
||||
@@ -0,0 +1,291 @@
|
||||
// 移动端加密解密工具 - 与web端保持一致
|
||||
import CryptoJS from 'crypto-js';
|
||||
import JSEncrypt from 'jsencrypt';
|
||||
|
||||
// 加密配置 - 可配置
|
||||
export const ENCRYPT_CONFIG = {
|
||||
// RSA公钥 - 与后端保持一致
|
||||
RSA_PUBLIC_KEY:
|
||||
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
|
||||
// RSA私钥 - 与web端配置文档保持一致
|
||||
RSA_PRIVATE_KEY:
|
||||
'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=',
|
||||
// 客户端ID
|
||||
CLIENT_ID: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
// 租户ID
|
||||
TENANT_ID: '000000',
|
||||
// 加密请求头
|
||||
ENCRYPT_HEADER: 'encrypt-key',
|
||||
// 是否启用加密
|
||||
ENCRYPT_ENABLED: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* 随机生成32位的字符串
|
||||
* @returns {string}
|
||||
*/
|
||||
const generateRandomString = () => {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
const charactersLength = characters.length;
|
||||
for (let i = 0; i < 32; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 随机生成AES密钥
|
||||
* @returns {CryptoJS.lib.WordArray}
|
||||
*/
|
||||
export const generateAesKey = () => {
|
||||
return CryptoJS.enc.Utf8.parse(generateRandomString());
|
||||
};
|
||||
|
||||
/**
|
||||
* 加密base64
|
||||
* @param {CryptoJS.lib.WordArray} str
|
||||
* @returns {string}
|
||||
*/
|
||||
export const encryptBase64 = (str) => {
|
||||
return CryptoJS.enc.Base64.stringify(str);
|
||||
};
|
||||
|
||||
/**
|
||||
* 解密base64
|
||||
* @param {string} str
|
||||
* @returns {CryptoJS.lib.WordArray}
|
||||
*/
|
||||
export const decryptBase64 = (str) => {
|
||||
return CryptoJS.enc.Base64.parse(str);
|
||||
};
|
||||
|
||||
/**
|
||||
* 使用密钥对数据进行AES加密
|
||||
* @param {string} message 待加密的数据
|
||||
* @param {CryptoJS.lib.WordArray} aesKey AES密钥
|
||||
* @returns {string} 加密后的数据
|
||||
*/
|
||||
export const encryptWithAes = (message, aesKey) => {
|
||||
const encrypted = CryptoJS.AES.encrypt(message, aesKey, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
return encrypted.toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* 使用密钥对数据进行AES解密
|
||||
* @param {string} message 待解密的数据
|
||||
* @param {CryptoJS.lib.WordArray} aesKey AES密钥
|
||||
* @returns {string} 解密后的数据
|
||||
*/
|
||||
export const decryptWithAes = (message, aesKey) => {
|
||||
const decrypted = CryptoJS.AES.decrypt(message, aesKey, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
return decrypted.toString(CryptoJS.enc.Utf8);
|
||||
};
|
||||
|
||||
/**
|
||||
* RSA加密
|
||||
* @param {string} txt 待加密的文本
|
||||
* @returns {string|false} 加密后的数据
|
||||
*/
|
||||
export const encrypt = (txt) => {
|
||||
try {
|
||||
console.log('🔐 开始RSA加密,输入文本长度:', txt ? txt.length : 'null');
|
||||
console.log('🔐 使用的RSA公钥:', ENCRYPT_CONFIG.RSA_PUBLIC_KEY.substring(0, 20) + '...');
|
||||
|
||||
const encryptor = new JSEncrypt();
|
||||
|
||||
// 构造完整的PEM格式RSA公钥
|
||||
const publicKey = `-----BEGIN PUBLIC KEY-----
|
||||
${ENCRYPT_CONFIG.RSA_PUBLIC_KEY}
|
||||
-----END PUBLIC KEY-----`;
|
||||
|
||||
console.log('🔐 构造的完整公钥:', publicKey.substring(0, 50) + '...');
|
||||
|
||||
encryptor.setPublicKey(publicKey);
|
||||
console.log('🔐 公钥设置完成');
|
||||
|
||||
const result = encryptor.encrypt(txt);
|
||||
|
||||
if (result) {
|
||||
console.log('✅ RSA加密成功,结果长度:', result.length);
|
||||
console.log('✅ 加密结果前50位:', result.substring(0, 50) + '...');
|
||||
} else {
|
||||
console.error('❌ RSA加密返回空结果');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('❌ RSA加密失败:', error);
|
||||
console.error('❌ 错误堆栈:', error.stack);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* RSA解密
|
||||
* @param {string} txt 待解密的文本
|
||||
* @returns {string|false} 解密后的数据
|
||||
*/
|
||||
export const decrypt = (txt) => {
|
||||
try {
|
||||
console.log('🔓 开始RSA解密,输入文本长度:', txt ? txt.length : 'null');
|
||||
console.log('🔓 使用的RSA私钥:', ENCRYPT_CONFIG.RSA_PRIVATE_KEY.substring(0, 20) + '...');
|
||||
|
||||
if (!ENCRYPT_CONFIG.RSA_PRIVATE_KEY) {
|
||||
console.warn('❌ RSA私钥未配置,无法解密');
|
||||
return false;
|
||||
}
|
||||
|
||||
const encryptor = new JSEncrypt();
|
||||
|
||||
// 构造完整的PEM格式RSA私钥
|
||||
const privateKey = `-----BEGIN PRIVATE KEY-----
|
||||
${ENCRYPT_CONFIG.RSA_PRIVATE_KEY}
|
||||
-----END PRIVATE KEY-----`;
|
||||
|
||||
console.log('🔓 构造的完整私钥:', privateKey.substring(0, 50) + '...');
|
||||
|
||||
encryptor.setPrivateKey(privateKey);
|
||||
console.log('🔓 私钥设置完成');
|
||||
|
||||
const result = encryptor.decrypt(txt);
|
||||
|
||||
if (result) {
|
||||
console.log('✅ RSA解密成功,结果长度:', result.length);
|
||||
console.log('✅ 解密结果前50位:', result.substring(0, 50) + '...');
|
||||
} else {
|
||||
console.error('❌ RSA解密返回空结果');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('❌ RSA解密失败:', error);
|
||||
console.error('❌ 错误堆栈:', error.stack);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成随机字符串AES密钥(字符串格式)
|
||||
* @param {number} length 密钥长度,默认32位
|
||||
* @returns {string} AES密钥字符串
|
||||
*/
|
||||
export const generateRandomAESKey = (length = 32) => {
|
||||
return generateRandomString();
|
||||
};
|
||||
|
||||
/**
|
||||
* 使用字符串密钥进行AES加密(兼容原有函数)
|
||||
* @param {string} data 待加密的数据
|
||||
* @param {string} key AES密钥字符串
|
||||
* @returns {string} 加密后的数据
|
||||
*/
|
||||
export const encryptWithAES = (data, key) => {
|
||||
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
|
||||
const encrypted = CryptoJS.AES.encrypt(data, keyUtf8, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
return encrypted.toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* 使用字符串密钥进行AES解密(兼容原有函数)
|
||||
* @param {string} data 待解密的数据
|
||||
* @param {string} key AES密钥字符串
|
||||
* @returns {string} 解密后的数据
|
||||
*/
|
||||
export const decryptWithAES = (data, key) => {
|
||||
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
|
||||
const decrypted = CryptoJS.AES.decrypt(data, keyUtf8, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
return decrypted.toString(CryptoJS.enc.Utf8);
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新加密配置
|
||||
* @param {Object} config 新的配置对象
|
||||
*/
|
||||
export const updateEncryptConfig = (config) => {
|
||||
Object.assign(ENCRYPT_CONFIG, config);
|
||||
console.log('🔧 加密配置已更新:', ENCRYPT_CONFIG);
|
||||
};
|
||||
|
||||
/**
|
||||
* 验证RSA密钥对配置
|
||||
* @returns {boolean} 验证结果
|
||||
*/
|
||||
export const validateKeyPair = () => {
|
||||
try {
|
||||
console.log('🔍 开始验证RSA密钥对配置...');
|
||||
|
||||
// 检查公钥和私钥是否存在
|
||||
if (!ENCRYPT_CONFIG.RSA_PUBLIC_KEY) {
|
||||
console.error('❌ RSA公钥未配置');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ENCRYPT_CONFIG.RSA_PRIVATE_KEY) {
|
||||
console.error('❌ RSA私钥未配置');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 测试加密解密功能
|
||||
const testMessage = 'RSA密钥对验证测试消息';
|
||||
console.log('🧪 测试消息:', testMessage);
|
||||
|
||||
// 使用公钥加密
|
||||
const encrypted = encrypt(testMessage);
|
||||
if (!encrypted) {
|
||||
console.error('❌ RSA加密测试失败');
|
||||
return false;
|
||||
}
|
||||
console.log('✅ RSA加密测试成功');
|
||||
|
||||
// 使用私钥解密
|
||||
const decrypted = decrypt(encrypted);
|
||||
if (!decrypted || decrypted !== testMessage) {
|
||||
console.error('❌ RSA解密测试失败');
|
||||
console.error('原始消息:', testMessage);
|
||||
console.error('解密结果:', decrypted);
|
||||
return false;
|
||||
}
|
||||
console.log('✅ RSA解密测试成功');
|
||||
|
||||
console.log('🎉 RSA密钥对配置验证通过!');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ RSA密钥对验证过程中出现错误:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前加密配置摘要
|
||||
* @returns {Object} 配置摘要
|
||||
*/
|
||||
export const getConfigSummary = () => {
|
||||
return {
|
||||
hasPublicKey: !!ENCRYPT_CONFIG.RSA_PUBLIC_KEY,
|
||||
hasPrivateKey: !!ENCRYPT_CONFIG.RSA_PRIVATE_KEY,
|
||||
publicKeyLength: ENCRYPT_CONFIG.RSA_PUBLIC_KEY ? ENCRYPT_CONFIG.RSA_PUBLIC_KEY.length : 0,
|
||||
privateKeyLength: ENCRYPT_CONFIG.RSA_PRIVATE_KEY
|
||||
? ENCRYPT_CONFIG.RSA_PRIVATE_KEY.length
|
||||
: 0,
|
||||
clientId: ENCRYPT_CONFIG.CLIENT_ID,
|
||||
tenantId: ENCRYPT_CONFIG.TENANT_ID,
|
||||
encryptHeader: ENCRYPT_CONFIG.ENCRYPT_HEADER,
|
||||
encryptEnabled: ENCRYPT_CONFIG.ENCRYPT_ENABLED,
|
||||
};
|
||||
};
|
||||
|
||||
// 导出配置以便外部修改
|
||||
export { ENCRYPT_CONFIG as config };
|
||||
@@ -375,7 +375,7 @@ class GestureUtils {
|
||||
class PageSwipeGesture {
|
||||
constructor() {
|
||||
this.currentPageIndex = 0;
|
||||
this.pages = ['home', 'workbench', 'profile'];
|
||||
this.pages = ['home'];
|
||||
this.isAnimating = false;
|
||||
}
|
||||
|
||||
@@ -387,29 +387,18 @@ class PageSwipeGesture {
|
||||
enableDoubleTap: false,
|
||||
});
|
||||
|
||||
gesture.on('swipeleft', () => {
|
||||
this.nextPage();
|
||||
});
|
||||
|
||||
gesture.on('swiperight', () => {
|
||||
this.prevPage();
|
||||
});
|
||||
|
||||
// 只有一个页面,禁用滑动切换
|
||||
return gesture;
|
||||
}
|
||||
|
||||
nextPage() {
|
||||
if (this.isAnimating) return;
|
||||
|
||||
this.currentPageIndex = (this.currentPageIndex + 1) % this.pages.length;
|
||||
this.switchPage(this.pages[this.currentPageIndex]);
|
||||
// 只有一个页面,不执行切换
|
||||
return;
|
||||
}
|
||||
|
||||
prevPage() {
|
||||
if (this.isAnimating) return;
|
||||
|
||||
this.currentPageIndex = (this.currentPageIndex - 1 + this.pages.length) % this.pages.length;
|
||||
this.switchPage(this.pages[this.currentPageIndex]);
|
||||
// 只有一个页面,不执行切换
|
||||
return;
|
||||
}
|
||||
|
||||
switchPage(pageName) {
|
||||
@@ -417,12 +406,10 @@ class PageSwipeGesture {
|
||||
|
||||
const pageUrls = {
|
||||
home: '/pages/index/index',
|
||||
workbench: '/pages/workbench/index',
|
||||
profile: '/pages/profile/index',
|
||||
};
|
||||
|
||||
if (pageUrls[pageName]) {
|
||||
uni.switchTab({
|
||||
uni.navigateTo({
|
||||
url: pageUrls[pageName],
|
||||
success: () => {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* iconfont 图标管理工具
|
||||
* 帮助开发者快速添加和管理自定义图标
|
||||
*/
|
||||
|
||||
/**
|
||||
* SVG Path 转换为 iconfont CSS 代码
|
||||
* @param {string} iconName - 图标名称 (如: star, heart)
|
||||
* @param {string} svgPath - SVG path 数据
|
||||
* @param {string} viewBox - SVG viewBox, 默认 "0 0 24 24"
|
||||
* @returns {string} CSS 代码
|
||||
*/
|
||||
export function generateIconCSS(iconName, svgPath, viewBox = '0 0 24 24') {
|
||||
const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='${viewBox}' fill='currentColor'><path d='${svgPath}'/></svg>`;
|
||||
const encodedSvg = encodeURIComponent(svg).replace(/'/g, '%27').replace(/"/g, '%22');
|
||||
|
||||
return `/* ${iconName}图标 */
|
||||
.icon-${iconName} {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,${encodedSvg}");
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量生成图标 CSS
|
||||
* @param {Array} icons - 图标数组 [{name, path, viewBox}]
|
||||
* @returns {string} 完整的 CSS 代码
|
||||
*/
|
||||
export function generateBatchIconCSS(icons) {
|
||||
return icons.map((icon) => generateIconCSS(icon.name, icon.path, icon.viewBox)).join('\n\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 从完整SVG生成CSS
|
||||
* @param {string} iconName - 图标名称
|
||||
* @param {string} svgContent - 完整的SVG内容
|
||||
* @returns {string} CSS 代码
|
||||
*/
|
||||
export function generateIconCSSFromSVG(iconName, svgContent) {
|
||||
const encodedSvg = encodeURIComponent(svgContent).replace(/'/g, '%27').replace(/"/g, '%22');
|
||||
|
||||
return `/* ${iconName}图标 */
|
||||
.icon-${iconName} {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,${encodedSvg}");
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预定义的常用图标路径
|
||||
* 来源:Material Design Icons
|
||||
*/
|
||||
export const COMMON_ICONS = {
|
||||
// 基础图标
|
||||
user: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z',
|
||||
lock: 'M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6z',
|
||||
home: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z',
|
||||
setting:
|
||||
'M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z',
|
||||
|
||||
// 操作图标
|
||||
add: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z',
|
||||
close: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z',
|
||||
edit: 'M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z',
|
||||
delete: 'M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z',
|
||||
search: 'M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z',
|
||||
check: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z',
|
||||
|
||||
// 状态图标
|
||||
success:
|
||||
'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z',
|
||||
warning: 'M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z',
|
||||
info: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z',
|
||||
error: 'M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z',
|
||||
|
||||
// 文件图标
|
||||
download: 'M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z',
|
||||
upload: 'M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z',
|
||||
|
||||
// 导航图标
|
||||
arrowLeft: 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z',
|
||||
arrowRight: 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z',
|
||||
arrowUp: 'M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z',
|
||||
arrowDown: 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z',
|
||||
|
||||
// 社交图标
|
||||
star: 'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z',
|
||||
heart: 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z',
|
||||
message:
|
||||
'M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z',
|
||||
phone: 'M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z',
|
||||
mail: 'M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z',
|
||||
};
|
||||
|
||||
/**
|
||||
* 快速生成常用图标的CSS
|
||||
* @param {string[]} iconNames - 图标名称数组
|
||||
* @returns {string} CSS 代码
|
||||
*/
|
||||
export function generateCommonIconsCSS(iconNames) {
|
||||
const selectedIcons = iconNames
|
||||
.map((name) => ({
|
||||
name,
|
||||
path: COMMON_ICONS[name],
|
||||
}))
|
||||
.filter((icon) => icon.path);
|
||||
|
||||
return generateBatchIconCSS(selectedIcons);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查图标名称是否已存在
|
||||
* @param {string} iconName - 图标名称
|
||||
* @param {string} cssContent - 当前CSS文件内容
|
||||
* @returns {boolean} 是否已存在
|
||||
*/
|
||||
export function checkIconExists(iconName, cssContent) {
|
||||
return cssContent.includes(`.icon-${iconName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证SVG内容
|
||||
* @param {string} svgContent - SVG内容
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
export function validateSVG(svgContent) {
|
||||
try {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(svgContent, 'image/svg+xml');
|
||||
return !doc.querySelector('parsererror');
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例
|
||||
*/
|
||||
export const USAGE_EXAMPLES = {
|
||||
// 单个图标生成
|
||||
singleIcon: {
|
||||
code: `import { generateIconCSS } from '@/utils/iconHelper';
|
||||
|
||||
const css = generateIconCSS(
|
||||
'calendar',
|
||||
'M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z'
|
||||
);
|
||||
console.log(css);`,
|
||||
output: `/* calendar图标 */
|
||||
.icon-calendar {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,...");
|
||||
}`,
|
||||
},
|
||||
|
||||
// 批量图标生成
|
||||
batchIcons: {
|
||||
code: `import { generateCommonIconsCSS } from '@/utils/iconHelper';
|
||||
|
||||
const css = generateCommonIconsCSS(['star', 'heart', 'message']);
|
||||
console.log(css);`,
|
||||
output: `/* star图标 */
|
||||
.icon-star { ... }
|
||||
|
||||
/* heart图标 */
|
||||
.icon-heart { ... }
|
||||
|
||||
/* message图标 */
|
||||
.icon-message { ... }`,
|
||||
},
|
||||
|
||||
// 从SVG生成
|
||||
fromSVG: {
|
||||
code: `import { generateIconCSSFromSVG } from '@/utils/iconHelper';
|
||||
|
||||
const svgContent = \`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/>
|
||||
</svg>\`;
|
||||
|
||||
const css = generateIconCSSFromSVG('circle', svgContent);
|
||||
console.log(css);`,
|
||||
},
|
||||
};
|
||||
|
||||
// 导出所有工具函数
|
||||
export default {
|
||||
generateIconCSS,
|
||||
generateBatchIconCSS,
|
||||
generateIconCSSFromSVG,
|
||||
generateCommonIconsCSS,
|
||||
checkIconExists,
|
||||
validateSVG,
|
||||
COMMON_ICONS,
|
||||
USAGE_EXAMPLES,
|
||||
};
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 移动端权限校验工具
|
||||
* 参考web端的permission.ts实现
|
||||
*/
|
||||
|
||||
import { userManager } from '@/utils/user'
|
||||
|
||||
/**
|
||||
* 字符权限校验
|
||||
* @param {Array} value 校验值
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const checkPermi = (value) => {
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const userInfo = userManager.getCurrentUser()
|
||||
if (!userInfo || !userInfo.permissions) {
|
||||
return false
|
||||
}
|
||||
|
||||
const permissions = userInfo.permissions
|
||||
const permissionDatas = value
|
||||
const all_permission = '*:*:*'
|
||||
|
||||
const hasPermission = permissions.some((permission) => {
|
||||
return all_permission === permission || permissionDatas.includes(permission)
|
||||
})
|
||||
|
||||
if (!hasPermission) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
console.error('need roles! Like checkPermi="[\'system:user:add\',\'system:user:edit\']"')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色权限校验
|
||||
* @param {Array} value 校验值
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const checkRole = (value) => {
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const userInfo = userManager.getCurrentUser()
|
||||
if (!userInfo || !userInfo.roles) {
|
||||
return false
|
||||
}
|
||||
|
||||
const roles = userInfo.roles
|
||||
const permissionRoles = value
|
||||
const super_admin = 'admin'
|
||||
|
||||
const hasRole = roles.some((role) => {
|
||||
return super_admin === role || permissionRoles.includes(role)
|
||||
})
|
||||
|
||||
if (!hasRole) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
console.error('need roles! Like checkRole="[\'admin\',\'editor\']"')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限指令校验函数
|
||||
* 用于在模板中进行权限判断
|
||||
* @param {Array|String} permissions 权限标识数组或单个权限标识
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const hasPermission = (permissions) => {
|
||||
if (!permissions) return true
|
||||
|
||||
// 如果传入的是字符串,转换为数组
|
||||
const perms = Array.isArray(permissions) ? permissions : [permissions]
|
||||
return checkPermi(perms)
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue过滤器:权限校验
|
||||
* 在模板中使用:v-if="hasPermi(['system:user:add'])"
|
||||
*/
|
||||
export const hasPermi = hasPermission
|
||||
|
||||
/**
|
||||
* 用户角色校验
|
||||
* @param {Array|String} roles 角色标识数组或单个角色标识
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const hasRole = (roles) => {
|
||||
if (!roles) return true
|
||||
|
||||
// 如果传入的是字符串,转换为数组
|
||||
const roleList = Array.isArray(roles) ? roles : [roles]
|
||||
return checkRole(roleList)
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限控制混入
|
||||
* 在页面或组件中使用
|
||||
*/
|
||||
export const permissionMixin = {
|
||||
methods: {
|
||||
// 权限校验方法
|
||||
hasPermi(permissions) {
|
||||
return hasPermission(permissions)
|
||||
},
|
||||
|
||||
// 角色校验方法
|
||||
hasRole(roles) {
|
||||
return hasRole(roles)
|
||||
},
|
||||
|
||||
// 检查单个权限
|
||||
checkPermission(permission) {
|
||||
return hasPermission([permission])
|
||||
},
|
||||
},
|
||||
}
|
||||
+321
-98
@@ -1,10 +1,66 @@
|
||||
import { encryptLoginData } from './encrypt-pure.js';
|
||||
import {
|
||||
generateAesKey,
|
||||
encryptBase64,
|
||||
decryptBase64,
|
||||
encryptWithAes,
|
||||
decryptWithAes,
|
||||
encrypt,
|
||||
decrypt,
|
||||
ENCRYPT_CONFIG,
|
||||
updateEncryptConfig,
|
||||
validateKeyPair,
|
||||
getConfigSummary,
|
||||
} from './crypto.js';
|
||||
|
||||
// API请求配置
|
||||
const baseURL =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? '/api' // 开发环境使用代理
|
||||
: 'https://your-api-domain.com';
|
||||
// API请求配置 - 可配置
|
||||
const REQUEST_CONFIG = {
|
||||
// 基础URL配置
|
||||
baseURL: process.env.NODE_ENV === 'development' ? '/api' : 'https://your-api-domain.com',
|
||||
|
||||
// 请求超时时间
|
||||
timeout: 300000, // 5分钟,适合大数据量导出
|
||||
|
||||
// 客户端ID
|
||||
clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
|
||||
// 默认referer
|
||||
referer: 'http://localhost/login?redirect=/index',
|
||||
|
||||
// 是否启用加密
|
||||
encryptEnabled: true,
|
||||
|
||||
// 防重复提交间隔时间(ms)
|
||||
repeatSubmitInterval: 500,
|
||||
};
|
||||
|
||||
// 会话缓存工具
|
||||
const sessionCache = {
|
||||
set: (key, value) => {
|
||||
try {
|
||||
uni.setStorageSync(`session_${key}`, JSON.stringify(value));
|
||||
} catch (e) {
|
||||
console.error('设置会话缓存失败:', e);
|
||||
}
|
||||
},
|
||||
|
||||
get: (key) => {
|
||||
try {
|
||||
const data = uni.getStorageSync(`session_${key}`);
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch (e) {
|
||||
console.error('获取会话缓存失败:', e);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
remove: (key) => {
|
||||
try {
|
||||
uni.removeStorageSync(`session_${key}`);
|
||||
} catch (e) {
|
||||
console.error('删除会话缓存失败:', e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 统一请求方法
|
||||
export const request = (options) => {
|
||||
@@ -12,18 +68,157 @@ export const request = (options) => {
|
||||
// 获取token
|
||||
const token = getToken();
|
||||
|
||||
uni.request({
|
||||
url: baseURL + options.url,
|
||||
// 处理请求配置
|
||||
const config = {
|
||||
url: REQUEST_CONFIG.baseURL + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data || {},
|
||||
timeout: options.timeout || REQUEST_CONFIG.timeout,
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
authorization: token,
|
||||
clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
referer: 'http://localhost/login?redirect=/index',
|
||||
clientid: options.clientId || REQUEST_CONFIG.clientId,
|
||||
// 注意:在H5环境中,浏览器会自动设置referer,手动设置会报错
|
||||
// referer: options.referer || REQUEST_CONFIG.referer,
|
||||
...options.header,
|
||||
},
|
||||
};
|
||||
|
||||
// 检查特殊header配置
|
||||
const isToken = options.header?.isToken === false;
|
||||
const isRepeatSubmit = options.header?.repeatSubmit === false;
|
||||
const isEncrypt = options.header?.isEncrypt === 'true';
|
||||
|
||||
// 如果不需要token,移除authorization
|
||||
if (isToken) {
|
||||
delete config.header.authorization;
|
||||
}
|
||||
|
||||
// 防重复提交检查
|
||||
if (!isRepeatSubmit && (config.method === 'POST' || config.method === 'PUT')) {
|
||||
const requestObj = {
|
||||
url: config.url,
|
||||
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
|
||||
time: new Date().getTime(),
|
||||
};
|
||||
|
||||
const sessionObj = sessionCache.get('sessionObj');
|
||||
if (sessionObj) {
|
||||
const s_url = sessionObj.url;
|
||||
const s_data = sessionObj.data;
|
||||
const s_time = sessionObj.time;
|
||||
const interval = REQUEST_CONFIG.repeatSubmitInterval;
|
||||
|
||||
if (
|
||||
s_data === requestObj.data &&
|
||||
requestObj.time - s_time < interval &&
|
||||
s_url === requestObj.url
|
||||
) {
|
||||
const message = '数据正在处理,请勿重复提交';
|
||||
console.warn(`[${s_url}]: ` + message);
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'none',
|
||||
});
|
||||
return reject(new Error(message));
|
||||
}
|
||||
}
|
||||
sessionCache.set('sessionObj', requestObj);
|
||||
}
|
||||
|
||||
// 加密处理
|
||||
if (
|
||||
REQUEST_CONFIG.encryptEnabled &&
|
||||
isEncrypt &&
|
||||
(config.method === 'POST' || config.method === 'PUT')
|
||||
) {
|
||||
try {
|
||||
console.log('🔐 开始请求加密处理...');
|
||||
|
||||
// 生成AES密钥
|
||||
const aesKey = generateAesKey();
|
||||
|
||||
// 使用RSA加密AES密钥
|
||||
const encryptedKey = encrypt(encryptBase64(aesKey));
|
||||
if (!encryptedKey) {
|
||||
throw new Error('RSA加密AES密钥失败');
|
||||
}
|
||||
|
||||
// 设置加密头
|
||||
config.header[ENCRYPT_CONFIG.ENCRYPT_HEADER] = encryptedKey;
|
||||
|
||||
// 加密请求数据
|
||||
const dataToEncrypt =
|
||||
typeof config.data === 'object' ? JSON.stringify(config.data) : config.data;
|
||||
config.data = encryptWithAes(dataToEncrypt, aesKey);
|
||||
|
||||
console.log('✅ 请求数据加密完成');
|
||||
} catch (error) {
|
||||
console.error('❌ 请求加密失败:', error);
|
||||
uni.showToast({
|
||||
title: '请求加密失败',
|
||||
icon: 'none',
|
||||
});
|
||||
return reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
// FormData处理
|
||||
if (config.data instanceof globalThis.FormData) {
|
||||
delete config.header['Content-Type'];
|
||||
}
|
||||
|
||||
console.log('📤 发送请求:', {
|
||||
url: config.url,
|
||||
method: config.method,
|
||||
hasToken: !!token,
|
||||
isEncrypt: isEncrypt,
|
||||
});
|
||||
|
||||
// 发送请求
|
||||
uni.request({
|
||||
...config,
|
||||
success: (res) => {
|
||||
console.log('📥 收到响应:', {
|
||||
statusCode: res.statusCode,
|
||||
url: config.url,
|
||||
hasEncryptHeader: !!res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER],
|
||||
});
|
||||
|
||||
// 解密处理
|
||||
if (REQUEST_CONFIG.encryptEnabled && res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER]) {
|
||||
try {
|
||||
console.log('🔓 开始响应解密处理...');
|
||||
|
||||
// 获取加密的AES密钥
|
||||
const keyStr = res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER];
|
||||
|
||||
// RSA解密获取AES密钥
|
||||
const base64Str = decrypt(keyStr);
|
||||
if (!base64Str) {
|
||||
throw new Error('RSA解密AES密钥失败');
|
||||
}
|
||||
|
||||
// Base64解码获取AES密钥
|
||||
const aesKey = decryptBase64(base64Str);
|
||||
|
||||
// AES解密响应数据
|
||||
const decryptedData = decryptWithAes(res.data, aesKey);
|
||||
|
||||
// 解析JSON
|
||||
res.data = JSON.parse(decryptedData);
|
||||
|
||||
console.log('✅ 响应数据解密完成');
|
||||
} catch (error) {
|
||||
console.error('❌ 响应解密失败:', error);
|
||||
uni.showToast({
|
||||
title: '响应解密失败',
|
||||
icon: 'none',
|
||||
});
|
||||
return reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理响应
|
||||
if (res.statusCode === 200) {
|
||||
// 检查业务状态码
|
||||
@@ -37,10 +232,14 @@ export const request = (options) => {
|
||||
reject(res.data);
|
||||
} else {
|
||||
// 其他业务错误
|
||||
uni.showToast({
|
||||
title: res.data.msg || '请求失败',
|
||||
icon: 'none',
|
||||
});
|
||||
const errorMsg = res.data.msg || '请求失败';
|
||||
if (res.data.code !== 404) {
|
||||
// 404错误静默处理
|
||||
uni.showToast({
|
||||
title: errorMsg,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
reject(res.data);
|
||||
}
|
||||
} else {
|
||||
@@ -51,18 +250,34 @@ export const request = (options) => {
|
||||
console.log('🔒 HTTP 401未授权,自动跳转到登录页');
|
||||
handleUnauthorized('登录已失效,请重新登录');
|
||||
reject(res);
|
||||
} else if (res.statusCode === 404) {
|
||||
// 404错误静默处理
|
||||
console.log('404错误被静默处理,不显示用户提示');
|
||||
reject(res);
|
||||
} else {
|
||||
// 其他HTTP状态码错误
|
||||
const errorMsg = `请求失败: ${res.statusCode}`;
|
||||
uni.showToast({
|
||||
title: `请求失败: ${res.statusCode}`,
|
||||
title: errorMsg,
|
||||
icon: 'none',
|
||||
});
|
||||
reject(res);
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('❌ 网络请求失败:', error);
|
||||
let message = '网络请求失败';
|
||||
|
||||
if (error.errMsg) {
|
||||
if (error.errMsg.includes('timeout')) {
|
||||
message = '请求超时,请检查网络连接';
|
||||
} else if (error.errMsg.includes('fail')) {
|
||||
message = '网络连接失败,请检查网络';
|
||||
}
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: '网络请求失败',
|
||||
title: message,
|
||||
icon: 'none',
|
||||
});
|
||||
reject(error);
|
||||
@@ -133,87 +348,25 @@ function handleUnauthorized(message = '登录已失效,请重新登录') {
|
||||
|
||||
// API方法封装
|
||||
export const api = {
|
||||
// 用户登录
|
||||
login: async (data) => {
|
||||
try {
|
||||
console.log('🔐 开始加密登录流程...');
|
||||
// 用户登录 - 使用统一的request方法和加密拦截器
|
||||
login: (data) => {
|
||||
console.log('🔐 开始登录流程...');
|
||||
|
||||
// 加密登录数据
|
||||
const encryptResult = encryptLoginData({
|
||||
return request({
|
||||
url: '/auth/login',
|
||||
method: 'POST',
|
||||
data: {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
tenantId: data.tenantId || '000000',
|
||||
clientId: data.clientId || 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
clientId: data.clientId || REQUEST_CONFIG.clientId,
|
||||
grantType: data.grantType || 'password',
|
||||
});
|
||||
|
||||
// 如果encryptLoginData返回的是Promise,需要await
|
||||
const actualEncryptResult = encryptResult.then ? await encryptResult : encryptResult;
|
||||
|
||||
if (!actualEncryptResult.success) {
|
||||
console.error('❌ 登录数据加密失败:', actualEncryptResult.error);
|
||||
throw new Error(actualEncryptResult.error);
|
||||
}
|
||||
|
||||
console.log('✅ 登录数据加密成功,发送加密请求...');
|
||||
|
||||
// 使用加密数据和自定义header发送请求
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: baseURL + '/auth/login',
|
||||
method: 'POST',
|
||||
data: actualEncryptResult.encryptedData, // 发送加密字符串,不是JSON对象
|
||||
header: {
|
||||
...actualEncryptResult.headers,
|
||||
referer: 'http://localhost/login?redirect=/index', // 添加正确格式的referer
|
||||
},
|
||||
success: (res) => {
|
||||
console.log('🔐 加密登录响应:', res);
|
||||
if (res.statusCode === 200) {
|
||||
if (res.data && res.data.code !== undefined) {
|
||||
if (res.data.code === 200) {
|
||||
console.log('✅ 加密登录成功');
|
||||
resolve(res.data);
|
||||
} else {
|
||||
console.log('❌ 登录失败:', res.data.msg);
|
||||
uni.showToast({
|
||||
title: res.data.msg || '登录失败',
|
||||
icon: 'none',
|
||||
});
|
||||
reject(res.data);
|
||||
}
|
||||
} else {
|
||||
resolve(res.data);
|
||||
}
|
||||
} else {
|
||||
console.log('❌ HTTP状态错误:', res.statusCode);
|
||||
uni.showToast({
|
||||
title: `请求失败: ${res.statusCode}`,
|
||||
icon: 'none',
|
||||
});
|
||||
reject(res);
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
console.log('❌ 登录请求失败:', error);
|
||||
uni.showToast({
|
||||
title: '网络请求失败',
|
||||
icon: 'none',
|
||||
});
|
||||
reject(error);
|
||||
},
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ 登录加密处理失败:', error);
|
||||
console.error('❌ 错误详情:', error.message);
|
||||
console.error('❌ 错误堆栈:', error.stack);
|
||||
uni.showToast({
|
||||
title: '登录处理失败: ' + error.message,
|
||||
icon: 'none',
|
||||
});
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
header: {
|
||||
isToken: false, // 登录接口不需要token
|
||||
isEncrypt: 'true', // 启用加密
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
@@ -227,11 +380,6 @@ export const api = {
|
||||
return request({
|
||||
url: '/system/user/getInfo',
|
||||
method: 'GET',
|
||||
header: {
|
||||
authorization: token,
|
||||
clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
referer: 'http://localhost/login?redirect=/index',
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -265,7 +413,7 @@ export const userStorage = {
|
||||
if (userInfo) {
|
||||
const enhancedUserInfo = {
|
||||
...userInfo,
|
||||
loginTime: new Date().toISOString(), // 记录登录时间
|
||||
loginTime: new Date().toISOString(),
|
||||
tokenExpireTime: loginResult.expires_in
|
||||
? new Date(Date.now() + loginResult.expires_in * 1000).toISOString()
|
||||
: null,
|
||||
@@ -333,6 +481,26 @@ export const userStorage = {
|
||||
console.error('清除登录信息失败:', e);
|
||||
}
|
||||
},
|
||||
|
||||
// 清除所有用户相关数据(包括refresh_token等)
|
||||
clearAll: () => {
|
||||
try {
|
||||
// 清除所有登录相关的存储
|
||||
uni.removeStorageSync('access_token');
|
||||
uni.removeStorageSync('userInfo');
|
||||
uni.removeStorageSync('isLoggedIn');
|
||||
uni.removeStorageSync('refresh_token');
|
||||
|
||||
// 清除可能存在的其他用户相关数据
|
||||
uni.removeStorageSync('userPermissions');
|
||||
uni.removeStorageSync('userRoles');
|
||||
uni.removeStorageSync('tenantInfo');
|
||||
|
||||
console.log('所有用户相关数据已清除');
|
||||
} catch (e) {
|
||||
console.error('清除所有用户数据失败:', e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 数据缓存工具
|
||||
@@ -390,3 +558,58 @@ export const storage = {
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 通用下载方法
|
||||
export const download = (url, params, fileName) => {
|
||||
// 显示加载提示
|
||||
uni.showLoading({
|
||||
title: '正在下载数据,请稍候',
|
||||
mask: true,
|
||||
});
|
||||
|
||||
return request({
|
||||
url: url,
|
||||
method: 'POST',
|
||||
data: params,
|
||||
header: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
timeout: 1200000, // 20分钟超时
|
||||
responseType: 'arraybuffer',
|
||||
})
|
||||
.then((resp) => {
|
||||
// 处理下载响应
|
||||
// 注意:uni-app中文件下载需要特殊处理
|
||||
console.log('文件下载完成:', fileName);
|
||||
uni.hideLoading();
|
||||
|
||||
// 这里需要根据平台进行不同的文件保存处理
|
||||
// 具体实现可能需要平台特定的代码
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('下载失败:', error);
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '下载文件出现错误,请联系管理员!',
|
||||
icon: 'none',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 配置更新方法
|
||||
export const updateRequestConfig = (config) => {
|
||||
Object.assign(REQUEST_CONFIG, config);
|
||||
console.log('🔧 请求配置已更新:', REQUEST_CONFIG);
|
||||
};
|
||||
|
||||
// 加密配置更新方法
|
||||
export const updateCryptoConfig = updateEncryptConfig;
|
||||
|
||||
// 密钥对验证方法
|
||||
export const validateCryptoKeys = validateKeyPair;
|
||||
|
||||
// 获取加密配置摘要
|
||||
export const getCryptoConfigSummary = getConfigSummary;
|
||||
|
||||
// 导出配置以便外部修改
|
||||
export { REQUEST_CONFIG as config, ENCRYPT_CONFIG as cryptoConfig };
|
||||
@@ -176,7 +176,7 @@ export const permissionUtils = {
|
||||
*/
|
||||
checkPagePermission(pagePath) {
|
||||
// 公开页面,无需权限检查
|
||||
const publicPages = ['/pages/login/index', '/pages/index/index', '/pages/profile/index'];
|
||||
const publicPages = ['/pages/login/index', '/pages/index/index'];
|
||||
|
||||
if (publicPages.includes(pagePath)) {
|
||||
return true;
|
||||
|
||||
Reference in new issue
Block a user