diff --git a/backend/app/api/v1/schemas/monitor/online_schema.py b/backend/app/api/v1/schemas/monitor/online_schema.py
index b1faacb5..ea84ef9b 100644
--- a/backend/app/api/v1/schemas/monitor/online_schema.py
+++ b/backend/app/api/v1/schemas/monitor/online_schema.py
@@ -22,4 +22,5 @@ class OnlineOutSchema(BaseModel):
os: Optional[str] = Field(default=None, description='操作系统')
browser: Optional[str] = Field(default=None, description='浏览器')
login_time: Optional[DateTimeStr] = Field(default=None, description='登录时间')
+ login_type: Optional[str] = Field(default=None, description='登录类型 PC端 | 移动端')
diff --git a/backend/app/api/v1/services/system/auth_service.py b/backend/app/api/v1/services/system/auth_service.py
index 25ff704a..dc6c1209 100644
--- a/backend/app/api/v1/services/system/auth_service.py
+++ b/backend/app/api/v1/services/system/auth_service.py
@@ -83,12 +83,12 @@ class LoginService:
user = await UserCRUD(auth).update_last_login_crud(id=user.id)
# 创建token
- token = await cls.create_token_service(request=request, redis=redis, user=user)
+ token = await cls.create_token_service(request=request, redis=redis, user=user, login_type=login_form.login_type)
return token
@classmethod
- async def create_token_service(cls, request: Request, redis: Redis, user: UserModel) -> JWTOutSchema:
+ async def create_token_service(cls, request: Request, redis: Redis, user: UserModel, login_type: str) -> JWTOutSchema:
"""
创建访问令牌和刷新令牌
@@ -129,6 +129,7 @@ class LoginService:
os=user_agent.os.family,
browser = user_agent.browser.family,
login_time=user.last_login.isoformat() if isinstance(user.last_login, datetime) else str(user.last_login),
+ login_type=login_type
).model_dump_json()
access_token = create_access_token(payload=JWTPayloadSchema(
diff --git a/backend/app/core/dependencies.py b/backend/app/core/dependencies.py
index 9395310f..beb7ef36 100644
--- a/backend/app/core/dependencies.py
+++ b/backend/app/core/dependencies.py
@@ -58,7 +58,7 @@ async def get_current_user(
# 解析token
payload = decode_access_token(token)
if not payload or not hasattr(payload, 'is_refresh') or payload.is_refresh:
- raise CustomException(msg="非法凭证", status_code=401)
+ raise CustomException(msg="非法凭证", code=10401, status_code=401)
online_user_info = payload.sub
# 从Redis中获取用户信息
@@ -66,23 +66,23 @@ async def get_current_user(
session_id = user_info.get("session_id")
if not session_id:
- raise CustomException(msg="认证已失效", status_code=401)
+ raise CustomException(msg="认证已失效", code=10401, status_code=401)
# 检查用户是否在线
online_ok = await RedisCURD(redis).exists(key=f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}')
if not online_ok:
- raise CustomException(msg="认证已失效", status_code=401)
+ raise CustomException(msg="认证已失效", code=10401, status_code=401)
auth = AuthSchema(db=db)
username = user_info.get("user_name")
if not username:
- raise CustomException(msg="认证已失效", status_code=401)
+ raise CustomException(msg="认证已失效", code=10401, status_code=401)
# 获取用户信息
user = await UserCRUD(auth).get_by_username_crud(username=username)
if not user:
- raise CustomException(msg="用户不存在", status_code=401)
+ raise CustomException(msg="用户不存在", code=10401, status_code=401)
if not user.status:
- raise CustomException(msg="用户已被停用", status_code=401)
+ raise CustomException(msg="用户已被停用", code=10401, status_code=401)
# 设置请求上下文
request.scope["user_id"] = user.id
@@ -156,11 +156,11 @@ class AuthPermission:
# 严格模式:要求所有权限都满足
if not all(perm in user_permissions for perm in self.permissions):
logger.error(f"用户缺少所需的权限: {self.permissions}")
- raise CustomException(msg="无权限操作", status_code=403)
+ raise CustomException(msg="无权限操作", code=10403, status_code=403)
else:
# 非严格模式:满足任一权限即可
if not any(perm in user_permissions for perm in self.permissions):
logger.error(f"用户缺少任何所需的权限: {self.permissions}")
- raise CustomException(msg="无权限操作", status_code=403)
+ raise CustomException(msg="无权限操作", code=10403, status_code=403)
return auth
diff --git a/backend/app/core/security.py b/backend/app/core/security.py
index c719c77b..ee520d2f 100644
--- a/backend/app/core/security.py
+++ b/backend/app/core/security.py
@@ -55,6 +55,7 @@ class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm):
password: str = Form(),
captcha_key: Optional[str] = Form(default=""),
captcha: Optional[str] = Form(default=""),
+ login_type: Optional[str] = Form(default="PC端", description="PC端 | 移动端")
):
super().__init__(
grant_type=grant_type,
@@ -62,10 +63,11 @@ class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm):
client_id=client_id,
client_secret=client_secret,
username=username,
- password=password
+ password=password,
)
self.captcha_key = captcha_key
self.captcha = captcha
+ self.login_type = login_type
# OAuth2认证配置
@@ -88,7 +90,7 @@ def create_access_token(payload: JWTPayloadSchema) -> str:
def decode_access_token(token: str) -> JWTPayloadSchema:
"""解析JWT访问令牌"""
if not token:
- raise CustomException(msg="认证不存在,请重新登录", status_code=401)
+ raise CustomException(msg="认证不存在,请重新登录", code=10401, status_code=401)
try:
payload = jwt.decode(
@@ -99,15 +101,15 @@ def decode_access_token(token: str) -> JWTPayloadSchema:
online_user_info = payload.get("sub")
if not online_user_info:
- raise CustomException(msg="无效认证,请重新登录", status_code=401)
+ raise CustomException(msg="无效认证,请重新登录", code=10401, status_code=401)
return JWTPayloadSchema(**payload)
except (jwt.InvalidSignatureError, jwt.DecodeError):
- raise CustomException(msg="无效认证,请重新登录", status_code=401)
+ raise CustomException(msg="无效认证,请重新登录", code=10401, status_code=401)
except jwt.ExpiredSignatureError:
- raise CustomException(msg="认证已过期,请重新登录", status_code=401)
+ raise CustomException(msg="认证已过期,请重新登录", code=10401, status_code=401)
except jwt.InvalidTokenError:
- raise CustomException(msg="token已失效,请重新登录", status_code=401)
+ raise CustomException(msg="token已失效,请重新登录", code=10401, status_code=401)
diff --git a/backend/app/scripts/data/system_config.json b/backend/app/scripts/data/system_config.json
index d84139ea..2b4d3747 100644
--- a/backend/app/scripts/data/system_config.json
+++ b/backend/app/scripts/data/system_config.json
@@ -66,7 +66,7 @@
"id": 8,
"config_name": "帮助文档",
"config_key": "sys_help_doc",
- "config_value": "https://service.fastapiadmin.com/site/index.html",
+ "config_value": "https://service.fastapiadmin.com",
"config_type": true,
"description": "帮助文档",
"creator_id": 1
diff --git a/backend/static/image/favicon.png b/backend/static/image/favicon.png
index 8c016668..43eeb762 100644
Binary files a/backend/static/image/favicon.png and b/backend/static/image/favicon.png differ
diff --git a/backend/static/image/group.jpg b/backend/static/image/group.jpg
deleted file mode 100644
index 8e14219d..00000000
Binary files a/backend/static/image/group.jpg and /dev/null differ
diff --git a/backend/static/image/logo.png b/backend/static/image/logo.png
index 4a924529..81834963 100644
Binary files a/backend/static/image/logo.png and b/backend/static/image/logo.png differ
diff --git a/fastapp/.env.development b/fastapp/.env.development
index 2a18a149..791c6403 100644
--- a/fastapp/.env.development
+++ b/fastapp/.env.development
@@ -1,3 +1,5 @@
+# 变量必须以 VITE_ 为前缀才能暴露给外部读取
+
# 环境
VITE_APP_ENV=development
@@ -17,6 +19,5 @@ VITE_APP_PORT=5180
# 超时时间
VITE_TIMEOUT=10000
-
# WebSocket 服务器的 URL,不配置默认关闭 WebSocket
-#VITE_APP_WS_ENDPOINT= ws://localhost:5180/ws
\ No newline at end of file
+VITE_APP_WS_ENDPOINT= ws://localhost:5180/ws
\ No newline at end of file
diff --git a/fastapp/.env.production b/fastapp/.env.production
index 5d9ed9bf..9a994be0 100644
--- a/fastapp/.env.production
+++ b/fastapp/.env.production
@@ -1,3 +1,5 @@
+# 变量必须以 VITE_ 为前缀才能暴露给外部读取
+
# 环境
VITE_APP_ENV=production
@@ -17,4 +19,4 @@ VITE_APP_PORT=5180
VITE_TIMEOUT=10000
# WebSocket 服务器的 URL,不配置默认关闭 WebSocket
-#VITE_APP_WS_ENDPOINT= ws://localhost:4096/ws
\ No newline at end of file
+VITE_APP_WS_ENDPOINT= ws://localhost:5180/ws
\ No newline at end of file
diff --git a/fastapp/.eslintrc-auto-import.json b/fastapp/.eslintrc-auto-import.json
index 662da57f..3e525d14 100644
--- a/fastapp/.eslintrc-auto-import.json
+++ b/fastapp/.eslintrc-auto-import.json
@@ -93,6 +93,68 @@
"watchSyncEffect": true,
"DirectiveBinding": true,
"MaybeRef": true,
- "MaybeRefOrGetter": true
+ "MaybeRefOrGetter": true,
+ "CommonUtil": true,
+ "Storage": true,
+ "acceptHMRUpdate": true,
+ "applyThemeOnPageShow": true,
+ "applyThemeToMiniProgram": true,
+ "auth": true,
+ "checkLogin": true,
+ "clearAll": true,
+ "clearToken": true,
+ "clearTokens": true,
+ "clearUserInfo": true,
+ "createPinia": true,
+ "createRouter": true,
+ "debounce": true,
+ "defineStore": true,
+ "dept": true,
+ "dict": true,
+ "file": true,
+ "getAccessToken": true,
+ "getActivePinia": true,
+ "getDarkerColor": true,
+ "getLighterColor": true,
+ "getRefreshToken": true,
+ "getToken": true,
+ "getUserInfo": true,
+ "isLoggedIn": true,
+ "log": true,
+ "mapActions": true,
+ "mapGetters": true,
+ "mapState": true,
+ "mapStores": true,
+ "mapWritableState": true,
+ "menu": true,
+ "notice": true,
+ "request": true,
+ "requireLogin": true,
+ "role": true,
+ "setAccessToken": true,
+ "setActivePinia": true,
+ "setMapStoreSuffix": true,
+ "setRefreshToken": true,
+ "setToken": true,
+ "setUserInfo": true,
+ "setupStore": true,
+ "store": true,
+ "storeToRefs": true,
+ "useDictStore": true,
+ "useMessage": true,
+ "useNotify": true,
+ "useRoute": true,
+ "useRouter": true,
+ "useThemeStore": true,
+ "useToast": true,
+ "useUserStore": true,
+ "user": true,
+ "config": true,
+ "extendedColorOptions": true,
+ "themeColorOptions": true,
+ "useStomp": true,
+ "useTabbar": true,
+ "useTheme": true,
+ "useWechat": true
}
}
diff --git a/fastapp/.stylelintrc.json b/fastapp/.stylelintrc.json
index 856b1abd..dc2cf332 100644
--- a/fastapp/.stylelintrc.json
+++ b/fastapp/.stylelintrc.json
@@ -1,11 +1,5 @@
{
- "extends": [
- "stylelint-config-recommended",
- "stylelint-config-recommended-scss",
- "stylelint-config-recommended-vue/scss",
- "stylelint-config-html/vue",
- "stylelint-config-recess-order"
- ],
+ "extends": ["stylelint-config-recommended", "stylelint-config-recommended-scss", "stylelint-config-recommended-vue/scss", "stylelint-config-html/vue", "stylelint-config-recess-order"],
"plugins": ["stylelint-prettier"],
"overrides": [
{
diff --git a/fastapp/README.md b/fastapp/README.md
index 5d3d8ada..c8febeed 100644
--- a/fastapp/README.md
+++ b/fastapp/README.md
@@ -1,10 +1,10 @@
-## 项目介绍
+# 项目介绍
基于 uni-app + Vue 3 + TypeScript 移动端跨平台开发模板,集成了 ESLint、Prettier、Stylelint、Husky 和 Commitlint 等工具,确保代码规范与质量。
## 项目截图
-
+
## 项目文档
@@ -14,51 +14,20 @@
安装依赖
-```
+```node
pnpm install
```
h5启动
-```
+```node
pnpm run dev:h5
```
构建
-```
+```node
pnpm run build:h5
```
-访问 [http://localhost:4096](http://localhost:4096)
-
-## 组件结构
-
-项目组件分为以下几类:
-
-- **基础组件**:`src/components` 下的通用组件
-- **业务组件**:`src/components/business` 下的业务相关组件
- - `WechatProfile.vue`: 微信小程序环境下的头像、昵称和性别选择组件
-
-使用业务组件:
-
-```vue
-
-
-
-
-
-```
+访问 [http://localhost:5180/app](http://localhost:5180/app)
diff --git a/fastapp/components.d.ts b/fastapp/components.d.ts
index 2701df0b..494ce6e8 100644
--- a/fastapp/components.d.ts
+++ b/fastapp/components.d.ts
@@ -8,7 +8,10 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
CuDateQuery: typeof import('./src/components/cu-date-query/index.vue')['default']
+ CuDict: typeof import('./src/components/cu-dict/index.vue')['default']
+ CuDictLabel: typeof import('./src/components/cu-dict-label/index.vue')['default']
CuPicker: typeof import('./src/components/cu-picker/index.vue')['default']
+ DataTree: typeof import('./src/components/data-tree/index.vue')['default']
Loading1: typeof import('./src/components/qiun-loading/loading1.vue')['default']
Loading2: typeof import('./src/components/qiun-loading/loading2.vue')['default']
Loading3: typeof import('./src/components/qiun-loading/loading3.vue')['default']
@@ -17,29 +20,35 @@ declare module 'vue' {
QiunDataCharts: typeof import('./src/components/qiun-data-charts/qiun-data-charts.vue')['default']
QiunError: typeof import('./src/components/qiun-error/qiun-error.vue')['default']
QiunLoading: typeof import('./src/components/qiun-loading/qiun-loading.vue')['default']
+ TodoItem: typeof import('./src/components/todo/TodoItem.vue')['default']
+ TodoList: typeof import('./src/components/todo/TodoList.vue')['default']
+ WdBadge: typeof import('wot-design-uni/components/wd-badge/wd-badge.vue')['default']
WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default']
WdCard: typeof import('wot-design-uni/components/wd-card/wd-card.vue')['default']
WdCell: typeof import('wot-design-uni/components/wd-cell/wd-cell.vue')['default']
WdCellGroup: typeof import('wot-design-uni/components/wd-cell-group/wd-cell-group.vue')['default']
+ WdCheckbox: typeof import('wot-design-uni/components/wd-checkbox/wd-checkbox.vue')['default']
WdCollapse: typeof import('wot-design-uni/components/wd-collapse/wd-collapse.vue')['default']
WdCollapseItem: typeof import('wot-design-uni/components/wd-collapse-item/wd-collapse-item.vue')['default']
WdConfigProvider: typeof import('wot-design-uni/components/wd-config-provider/wd-config-provider.vue')['default']
WdDivider: typeof import('wot-design-uni/components/wd-divider/wd-divider.vue')['default']
+ WdField: typeof import('wot-design-uni/components/wd-field/wd-field.vue')['default']
WdForm: typeof import('wot-design-uni/components/wd-form/wd-form.vue')['default']
- WdFormItem: typeof import('wot-design-uni/components/wd-form-item/wd-form-item.vue')['default']
+ WdFormItem: (typeof import("wot-design-uni/components/wd-form-item/wd-form-item.vue"))["default"]
WdGrid: typeof import('wot-design-uni/components/wd-grid/wd-grid.vue')['default']
WdGridItem: typeof import('wot-design-uni/components/wd-grid-item/wd-grid-item.vue')['default']
WdIcon: typeof import('wot-design-uni/components/wd-icon/wd-icon.vue')['default']
WdImage: typeof import('wot-design-uni/components/wd-image/wd-image.vue')['default']
WdImg: typeof import('wot-design-uni/components/wd-img/wd-img.vue')['default']
WdImgCropper: typeof import('wot-design-uni/components/wd-img-cropper/wd-img-cropper.vue')['default']
+ WdImge: typeof import('wot-design-uni/components/wd-imge/wd-imge.vue')['default']
WdInput: typeof import('wot-design-uni/components/wd-input/wd-input.vue')['default']
WdLoading: typeof import('wot-design-uni/components/wd-loading/wd-loading.vue')['default']
WdMessageBox: typeof import('wot-design-uni/components/wd-message-box/wd-message-box.vue')['default']
WdNavbar: typeof import('wot-design-uni/components/wd-navbar/wd-navbar.vue')['default']
WdNoticeBar: typeof import('wot-design-uni/components/wd-notice-bar/wd-notice-bar.vue')['default']
WdNotify: typeof import('wot-design-uni/components/wd-notify/wd-notify.vue')['default']
- WdPasswordInput: typeof import('wot-design-uni/components/wd-password-input/wd-password-input.vue')['default']
+ WdPasswordInput: (typeof import("wot-design-uni/components/wd-password-input/wd-password-input.vue"))["default"]
WdPopup: typeof import('wot-design-uni/components/wd-popup/wd-popup.vue')['default']
WdProgress: typeof import('wot-design-uni/components/wd-progress/wd-progress.vue')['default']
WdRadio: typeof import('wot-design-uni/components/wd-radio/wd-radio.vue')['default']
@@ -47,13 +56,16 @@ declare module 'vue' {
WdStatusTip: typeof import('wot-design-uni/components/wd-status-tip/wd-status-tip.vue')['default']
WdSwiper: typeof import('wot-design-uni/components/wd-swiper/wd-swiper.vue')['default']
WdSwitch: typeof import('wot-design-uni/components/wd-switch/wd-switch.vue')['default']
+ WdTab: typeof import('wot-design-uni/components/wd-tab/wd-tab.vue')['default']
WdTabbar: typeof import('wot-design-uni/components/wd-tabbar/wd-tabbar.vue')['default']
WdTabbarItem: typeof import('wot-design-uni/components/wd-tabbar-item/wd-tabbar-item.vue')['default']
+ WdTabs: typeof import('wot-design-uni/components/wd-tabs/wd-tabs.vue')['default']
WdTag: typeof import('wot-design-uni/components/wd-tag/wd-tag.vue')['default']
WdText: typeof import('wot-design-uni/components/wd-text/wd-text.vue')['default']
WdTextarea: typeof import('wot-design-uni/components/wd-textarea/wd-textarea.vue')['default']
- WdTitle: typeof import('wot-design-uni/components/wd-title/wd-title.vue')['default']
+ WdTitle: (typeof import("wot-design-uni/components/wd-title/wd-title.vue"))["default"]
WdToast: typeof import('wot-design-uni/components/wd-toast/wd-toast.vue')['default']
WdUpload: typeof import('wot-design-uni/components/wd-upload/wd-upload.vue')['default']
+ WdView: typeof import('wot-design-uni/components/wd-view/wd-view.vue')['default']
}
}
diff --git a/fastapp/docs/theme-system-guide.md b/fastapp/docs/theme-system-guide.md
index f67e715e..4ab07cdf 100644
--- a/fastapp/docs/theme-system-guide.md
+++ b/fastapp/docs/theme-system-guide.md
@@ -34,12 +34,7 @@ const {
```vue
-
+
@@ -49,12 +44,7 @@ const {
```vue
-
+
@@ -170,6 +160,7 @@ const THEME_COLOR_STORAGE_KEY = "app_theme_color";
```
主题设置会在以下情况自动保存:
+
- 切换主题模式时
- 设置主题色时
- 重置主题时
diff --git a/fastapp/docs/uniapp整合mini-router.md b/fastapp/docs/uniapp整合mini-router.md
index b91498bc..51492094 100644
--- a/fastapp/docs/uniapp整合mini-router.md
+++ b/fastapp/docs/uniapp整合mini-router.md
@@ -29,55 +29,55 @@ pnpm add - uni-mini-router
```typescript
// src/router/index.ts
-import { createRouter } from 'uni-mini-router'
-import { pages, subPackages } from 'virtual:uni-pages'
+import { createRouter } from "uni-mini-router";
+import { pages, subPackages } from "virtual:uni-pages";
// 生成路由配置
function generateRoutes() {
const routes = pages.map((page) => {
- const newPath = `/${page.path}`
- return { ...page, path: newPath }
- })
+ const newPath = `/${page.path}`;
+ return { ...page, path: newPath };
+ });
// 处理分包路由
if (subPackages && subPackages.length > 0) {
subPackages.forEach((subPackage) => {
const subRoutes = subPackage.pages.map((page: any) => {
- const newPath = `/${subPackage.root}/${page.path}`
- return { ...page, path: newPath }
- })
- routes.push(...subRoutes)
- })
+ const newPath = `/${subPackage.root}/${page.path}`;
+ return { ...page, path: newPath };
+ });
+ routes.push(...subRoutes);
+ });
}
- return routes
+ return routes;
}
// 创建路由实例
const router = createRouter({
routes: generateRoutes(),
-})
+});
-export default router
+export default router;
```
### 3. 在main.ts中挂载路由
```typescript
// src/main.ts
-import { createSSRApp } from 'vue'
-import App from './App.vue'
-import router from './router'
+import { createSSRApp } from "vue";
+import App from "./App.vue";
+import router from "./router";
export function createApp() {
- const app = createSSRApp(App)
+ const app = createSSRApp(App);
// 使用路由
- app.use(router)
+ app.use(router);
return {
- app
- }
+ app,
+ };
}
```
@@ -87,22 +87,22 @@ export function createApp() {
```typescript
// vite.config.ts
-import AutoImport from 'unplugin-auto-import/vite'
+import AutoImport from "unplugin-auto-import/vite";
export default defineConfig({
plugins: [
AutoImport({
imports: [
- 'vue',
+ "vue",
{
- from: 'uni-mini-router',
- imports: ['createRouter', 'useRouter', 'useRoute']
- }
+ from: "uni-mini-router",
+ imports: ["createRouter", "useRouter", "useRoute"],
+ },
],
- dts: 'src/auto-imports.d.ts'
- })
- ]
-})
+ dts: "src/auto-imports.d.ts",
+ }),
+ ],
+});
```
## 三、路由基本用法
@@ -112,55 +112,55 @@ export default defineConfig({
uni-mini-router提供了多种导航方法:
```typescript
-const router = useRouter()
+const router = useRouter();
// 字符串路径导航
-router.push('/pages/index/index')
+router.push("/pages/index/index");
// 对象导航(通过路径)
-router.push({ path: '/pages/index/index' })
+router.push({ path: "/pages/index/index" });
// 对象导航(通过名称)
-router.push({ name: 'index' })
+router.push({ name: "index" });
// 携带参数
router.push({
- path: '/pages/detail/index',
- query: { id: 10 }
-})
+ path: "/pages/detail/index",
+ query: { id: 10 },
+});
// 通过名称 + 参数
router.push({
- name: 'detail',
- params: { id: 10 }
-})
+ name: "detail",
+ params: { id: 10 },
+});
// Tab页面导航
-router.pushTab('/pages/home/index')
+router.pushTab("/pages/home/index");
// 关闭当前页面并跳转
-router.replace('/pages/index/index')
+router.replace("/pages/index/index");
// 关闭所有页面并跳转
-router.replaceAll('/pages/index/index')
+router.replaceAll("/pages/index/index");
// 返回上一级
-router.back()
+router.back();
// 返回多级
-router.back(2)
+router.back(2);
```
### 2. 获取和使用路由信息
```typescript
-const route = useRoute()
+const route = useRoute();
// 访问当前路由信息
-console.log(route.path) // 当前路由路径
-console.log(route.name) // 当前路由名称
-console.log(route.query) // 查询参数
-console.log(route.params) // 路由参数
+console.log(route.path); // 当前路由路径
+console.log(route.name); // 当前路由名称
+console.log(route.query); // 查询参数
+console.log(route.params); // 路由参数
```
### 3. 接收页面参数
@@ -194,24 +194,24 @@ uni-mini-router提供了全局导航守卫功能,可以在路由跳转前后
```typescript
// src/router/index.ts
router.beforeEach((to, from, next) => {
- console.log('路由跳转:', from.path, '->', to.path)
+ console.log("路由跳转:", from.path, "->", to.path);
// 检查是否需要登录
if (to.meta && to.meta.requireAuth) {
// 检查登录状态
- const isLoggedIn = uni.getStorageSync('token')
+ const isLoggedIn = uni.getStorageSync("token");
if (!isLoggedIn) {
// 未登录,跳转到登录页
- uni.showToast({ title: '请先登录', icon: 'none' })
- next('/pages/login/index')
- return
+ uni.showToast({ title: "请先登录", icon: "none" });
+ next("/pages/login/index");
+ return;
}
}
// 继续导航
- next()
-})
+ next();
+});
```
### 2. 全局后置守卫
@@ -219,10 +219,10 @@ router.beforeEach((to, from, next) => {
```typescript
// src/router/index.ts
router.afterEach((to, from) => {
- console.log('路由跳转完成:', to.path)
+ console.log("路由跳转完成:", to.path);
// 可以在这里做一些统计或记录
-})
+});
```
### 3. 路由元数据配置
@@ -255,45 +255,45 @@ router.afterEach((to, from) => {
```typescript
// src/router/index.ts
-import { createRouter } from 'uni-mini-router'
+import { createRouter } from "uni-mini-router";
const router = createRouter({
- routes: generateRoutes()
-})
+ routes: generateRoutes(),
+});
// 全局前置守卫
router.beforeEach((to, from, next) => {
// 检查页面是否需要登录
if (to.meta && to.meta.requireAuth) {
- const token = uni.getStorageSync('token')
+ const token = uni.getStorageSync("token");
if (!token) {
// 显示登录提示
uni.showModal({
- title: '提示',
- content: '该功能需要登录后使用',
- confirmText: '去登录',
- cancelText: '返回',
+ title: "提示",
+ content: "该功能需要登录后使用",
+ confirmText: "去登录",
+ cancelText: "返回",
success: (res) => {
if (res.confirm) {
// 记住原来要去的页面
- uni.setStorageSync('redirect', to.fullPath)
- next('/pages/login/index')
+ uni.setStorageSync("redirect", to.fullPath);
+ next("/pages/login/index");
} else {
// 取消则返回首页
- next('/pages/index/index')
+ next("/pages/index/index");
}
- }
- })
- return
+ },
+ });
+ return;
}
}
// 继续导航
- next()
-})
+ next();
+});
-export default router
+export default router;
```
### 2. 登录成功后跳转回原页面
@@ -301,21 +301,21 @@ export default router
```vue
```
@@ -336,25 +336,25 @@ function handleLogin() {
```typescript
// 传递复杂对象
-const complexData = { name: 'product', details: { id: 1, features: ['a', 'b'] } }
+const complexData = { name: "product", details: { id: 1, features: ["a", "b"] } };
// 方法1: JSON序列化 + URL编码
router.push({
- path: '/pages/detail/index',
- query: { data: encodeURIComponent(JSON.stringify(complexData)) }
-})
+ path: "/pages/detail/index",
+ query: { data: encodeURIComponent(JSON.stringify(complexData)) },
+});
// 接收页面
onLoad((option) => {
if (option.data) {
try {
- const data = JSON.parse(decodeURIComponent(option.data))
- console.log(data)
+ const data = JSON.parse(decodeURIComponent(option.data));
+ console.log(data);
} catch (e) {
- console.error('参数解析错误', e)
+ console.error("参数解析错误", e);
}
}
-})
+});
// 方法2: 对于非常大的数据,考虑使用全局状态管理或本地存储
```
@@ -391,12 +391,12 @@ uni-mini-router自动支持小程序的分包加载特性,可以在pages.json
```typescript
// src/router/index.ts
router.beforeEach((to, from, next) => {
- console.log(`[Router] ${from.path || '初始页面'} -> ${to.path}`, {
+ console.log(`[Router] ${from.path || "初始页面"} -> ${to.path}`, {
params: to.params,
query: to.query,
- })
- next()
-})
+ });
+ next();
+});
```
### 2. 常见问题解决
diff --git a/fastapp/eslint.config.mjs b/fastapp/eslint.config.mjs
index ac8b60c9..0a1400bf 100644
--- a/fastapp/eslint.config.mjs
+++ b/fastapp/eslint.config.mjs
@@ -54,6 +54,9 @@ export default [
ApiResponse: "readonly", // 统一响应数据类型
creatorType: "readonly", // 创建人类型
UploadFileResult: "readonly", // 上传文件返回类型
+ Todo: "readonly", // 待办事项
+ TodoState: "readonly", // 待办事项状态
+ plus: true, // HTML5+ 运行时环境
},
},
},
diff --git a/fastapp/package.json b/fastapp/package.json
index e524fafa..3c80b88b 100644
--- a/fastapp/package.json
+++ b/fastapp/package.json
@@ -120,7 +120,7 @@
"@uni-helper/uni-use": "^0.19.14",
"@vueuse/core": "9.13.0",
"pinia": "^2.2.2",
- "vue": "^3.4.21",
+ "vue": "^3.5.13",
"vue-i18n": "^9.1.9",
"wot-design-uni": "^1.9.1"
},
diff --git a/fastapp/pages.config.ts b/fastapp/pages.config.ts
index b5e19653..ea20c85c 100644
--- a/fastapp/pages.config.ts
+++ b/fastapp/pages.config.ts
@@ -41,6 +41,9 @@ export default defineUniPages({
{
pagePath: "pages/index/index",
},
+ {
+ pagePath: "pages/work/index",
+ },
{
pagePath: "pages/mine/index",
},
diff --git a/fastapp/public/favicon.png b/fastapp/public/favicon.png
index 8c016668..43eeb762 100644
Binary files a/fastapp/public/favicon.png and b/fastapp/public/favicon.png differ
diff --git a/fastapp/public/logo.png b/fastapp/public/logo.png
index 4a924529..81834963 100644
Binary files a/fastapp/public/logo.png and b/fastapp/public/logo.png differ
diff --git a/fastapp/src/App.vue b/fastapp/src/App.vue
index 553ca4dc..27a3c9b5 100644
--- a/fastapp/src/App.vue
+++ b/fastapp/src/App.vue
@@ -1,12 +1,13 @@
-
+
diff --git a/fastapp/src/api/auth.ts b/fastapp/src/api/auth.ts
index 0f61d819..756b4920 100644
--- a/fastapp/src/api/auth.ts
+++ b/fastapp/src/api/auth.ts
@@ -13,9 +13,8 @@ const AuthAPI = {
return request({
url: `${AUTH_BASE_URL}/login`,
method: "POST",
- headers: { [ApiHeader.KEY]: ApiHeader.FORM },
+ header: { [ApiHeader.KEY]: ApiHeader.FORM },
data: body,
- skipAuth: true,
});
},
@@ -40,7 +39,6 @@ const AuthAPI = {
return request({
url: `${AUTH_BASE_URL}/captcha/get`,
method: "GET",
- skipAuth: true,
});
},
@@ -56,45 +54,10 @@ const AuthAPI = {
data: body,
});
},
-
- /**
- * 微信小程序手机号授权登录
- * @param data 包含code、encryptedData、iv等手机号相关数据
- * @returns 登录结果
- */
- loginByWxMiniAppPhone(data: WxLoginData): Promise {
- return request({
- url: `${AUTH_BASE_URL}/wx/miniapp/phone-login`,
- method: "POST",
- data,
- skipAuth: true,
- });
- },
-
- /**
- * 微信小程序授权登录 (仅使用code获取OpenID)
- * @param code 微信登录凭证
- * @returns 登录结果
- */
- loginByWxMiniAppCode(code: string): Promise {
- return request({
- url: `${AUTH_BASE_URL}/wx/miniapp/code-login`,
- method: "POST",
- data: { code },
- skipAuth: true,
- });
- },
};
export default AuthAPI;
-export interface WxLoginData {
- code: string;
- encryptedData?: string;
- iv?: string;
- phoneCode?: string;
-}
-
/** 登录表单数据 */
export interface LoginFormData {
username: string;
@@ -102,6 +65,7 @@ export interface LoginFormData {
captcha_key: string;
captcha: string;
remember: boolean;
+ login_type: string;
}
// 刷新令牌
diff --git a/fastapp/src/api/file.ts b/fastapp/src/api/file.ts
index ccdd1d85..ba42730e 100644
--- a/fastapp/src/api/file.ts
+++ b/fastapp/src/api/file.ts
@@ -1,4 +1,4 @@
-import { getToken } from "@/utils/storage";
+import { getAccessToken } from "@/utils/auth";
import { ApiCode } from "@/enums/api-code.enum";
// H5 使用 VITE_APP_BASE_API 作为代理路径,其他平台使用 VITE_API_BASE_URL 作为请求路径
@@ -25,12 +25,12 @@ const FileAPI = {
filePath: filePath,
name: "file",
header: {
- Authorization: getToken() ? `Bearer ${getToken()}` : "",
+ Authorization: getAccessToken() ? `Bearer ${getAccessToken()}` : "",
},
formData: {},
success: (response) => {
const resData = JSON.parse(response.data) as ApiResponse;
- // 业务状态码 00000 表示成功
+ // 业务状态码 0 表示成功
if (resData.code === ApiCode.SUCCESS) {
resolve(resData.data);
} else {
diff --git a/fastapp/src/api/user.ts b/fastapp/src/api/user.ts
index a105da96..10a31269 100644
--- a/fastapp/src/api/user.ts
+++ b/fastapp/src/api/user.ts
@@ -1,4 +1,5 @@
import request from "@/utils/request";
+import { ApiHeader } from "@/enums/api-header.enum";
const USER_BASE_URL = "/system/user";
@@ -26,7 +27,7 @@ const UserAPI = {
url: `${USER_BASE_URL}/current/avatar/upload`,
method: "POST",
data: body,
- headers: { "Content-Type": "multipart/form-data" },
+ header: { [ApiHeader.KEY]: ApiHeader.MULTIPART },
});
},
@@ -213,7 +214,7 @@ export interface UserInfo {
avatar?: string;
email?: string;
mobile?: string;
- gender?: number;
+ gender?: string;
password?: string;
menus?: MenuTable[];
dept?: deptTreeType;
@@ -290,7 +291,7 @@ export interface positionSelectorType {
export interface UserProfileForm {
id?: number;
name?: string;
- gender?: number;
+ gender?: string;
mobile?: string;
email?: string;
username?: string;
diff --git a/fastapp/src/components/qiun-data-charts/license.md b/fastapp/src/components/qiun-data-charts/license.md
deleted file mode 100644
index c61b6639..00000000
--- a/fastapp/src/components/qiun-data-charts/license.md
+++ /dev/null
@@ -1,201 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
-2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
-3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
-4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
-5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
-6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
-8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
-Copyright [yyyy] [name of copyright owner]
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
diff --git a/fastapp/src/components/u-charts/license.md b/fastapp/src/components/u-charts/license.md
deleted file mode 100644
index c61b6639..00000000
--- a/fastapp/src/components/u-charts/license.md
+++ /dev/null
@@ -1,201 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
-2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
-3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
-4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
-5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
-6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
-8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
-Copyright [yyyy] [name of copyright owner]
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
diff --git a/fastapp/src/components/u-charts/readme.md b/fastapp/src/components/u-charts/readme.md
deleted file mode 100644
index b3906903..00000000
--- a/fastapp/src/components/u-charts/readme.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# uCharts JSSDK说明
-
-1、如不使用uCharts组件,可直接引用u-charts.js,打包编译后会`自动压缩`,压缩后体积约为`120kb`。
-2、如果120kb的体积仍需压缩,请手到uCharts官网通过在线定制选择您需要的图表。
-3、config-ucharts.js为uCharts组件的用户配置文件,升级前请`自行备份config-ucharts.js`文件,以免被强制覆盖。
-4、config-echarts.js为ECharts组件的用户配置文件,升级前请`自行备份config-echarts.js`文件,以免被强制覆盖。
diff --git a/fastapp/src/composables/useTheme.ts b/fastapp/src/composables/useTheme.ts
index 296c3898..f341676b 100644
--- a/fastapp/src/composables/useTheme.ts
+++ b/fastapp/src/composables/useTheme.ts
@@ -1,345 +1,48 @@
-import type { ConfigProviderThemeVars } from "wot-design-uni";
-import { useThemeStore } from "@/store/modules/theme.store";
-
-// 定义主题色选项
-export interface ThemeColorOption {
- name: string;
- value: string;
- primary: string;
-}
-
-// 预定义的主题色选项
-export const themeColorOptions: ThemeColorOption[] = [
- { name: "默认蓝", value: "blue", primary: "#4D7FFF" },
- { name: "活力橙", value: "orange", primary: "#FF7D00" },
- { name: "薄荷绿", value: "green", primary: "#07C160" },
- { name: "樱花粉", value: "pink", primary: "#FF69B4" },
- { name: "紫罗兰", value: "purple", primary: "#8A2BE2" },
- { name: "朱砂红", value: "red", primary: "#FF4757" },
- { name: "天空蓝", value: "sky", primary: "#00BFFF" },
- { name: "柠檬黄", value: "yellow", primary: "#FFD700" },
-];
-
-// 扩展的颜色选项,用于自定义颜色选择器
-export const extendedColorOptions: ThemeColorOption[] = [
- ...themeColorOptions,
- { name: "珊瑚红", value: "coral", primary: "#FF6B6B" },
- { name: "薄荷绿", value: "mint", primary: "#4ECDC4" },
- { name: "黄金黄", value: "gold", primary: "#FFD166" },
- { name: "经典红", value: "classic-red", primary: "#CD5C5C" },
- { name: "自然绿", value: "nature-green", primary: "#228B22" },
- { name: "天蓝色", value: "sky-blue", primary: "#1890FF" },
- { name: "青绿色", value: "teal", primary: "#0FC6C2" },
- { name: "深紫色", value: "deep-purple", primary: "#722ED1" },
-];
-
-export interface ThemeState {
- theme: "light" | "dark";
- isDark: boolean;
- followSystem: boolean;
- hasUserSet: boolean;
- currentThemeColor: ThemeColorOption;
- showThemeColorSheet: boolean;
-}
+import { useThemeStore, ThemeMode, ThemeColorOption } from "@/store";
export function useTheme() {
- // 状态定义
- const theme = ref<"light" | "dark">("light");
- const followSystem = ref(true); // 是否跟随系统主题
- const hasUserSet = ref(false); // 用户是否手动设置过主题
- const currentThemeColor = ref(themeColorOptions[0]);
- const showThemeColorSheet = ref(false);
+ const store = useThemeStore();
- // 主题变量
- const themeVars = reactive({
- darkBackground: "#0f0f0f",
- darkBackground2: "#1a1a1a",
- darkBackground3: "#242424",
- darkBackground4: "#2f2f2f",
- darkBackground5: "#3d3d3d",
- darkBackground6: "#4a4a4a",
- darkBackground7: "#606060",
- darkColor: "#ffffff",
- darkColor2: "#e0e0e0",
- darkColor3: "#a0a0a0",
- colorTheme: themeColorOptions[0].primary,
- });
-
- // 计算属性
- const isDark = computed(() => theme.value === "dark");
-
- // 主题状态
- const themeState = computed(() => ({
- theme: theme.value,
- isDark: isDark.value,
- followSystem: followSystem.value,
- hasUserSet: hasUserSet.value,
- currentThemeColor: currentThemeColor.value,
- showThemeColorSheet: showThemeColorSheet.value,
- }));
-
- /* 手动切换主题 */
- function toggleTheme(mode?: "light" | "dark") {
- theme.value = mode || (theme.value === "light" ? "dark" : "light");
- hasUserSet.value = true;
- followSystem.value = false;
- setNavigationBarColor();
- saveThemeSettings();
-
- // 实时应用主题变化
- applyThemeModeToApp();
+ /**
+ * 切换暗黑模式
+ * @param mode 指定主题模式,不传则自动切换
+ */
+ function toggleTheme(mode?: ThemeMode) {
+ store.toggleTheme(mode);
}
- /* 实时应用主题模式到应用 */
- function applyThemeModeToApp() {
- // 更新CSS变量
- if (typeof document !== "undefined") {
- document.documentElement.setAttribute("data-theme", theme.value);
- }
-
- // 更新导航栏颜色
- setNavigationBarColor();
+ /**
+ * 设置主题色
+ * @param option 主题色选项
+ */
+ function setThemeColor(option: ThemeColorOption) {
+ store.setCurrentThemeColor(option);
}
- /* 设置是否跟随系统主题 */
- function setFollowSystem(follow: boolean) {
- followSystem.value = follow;
- if (follow) {
- hasUserSet.value = false;
- initTheme();
- }
- saveThemeSettings();
-
- // 实时应用主题变化
- applyThemeModeToApp();
- }
-
- /* 设置主题色 */
- function setCurrentThemeColor(color: ThemeColorOption) {
- currentThemeColor.value = color;
- themeVars.colorTheme = color.primary;
- hasUserSet.value = true;
- saveThemeSettings();
-
- // 实时应用主题色到全局
- applyThemeColorToApp(color.primary);
- }
-
- /* 设置自定义主题色 */
- function setCustomThemeColor(color: string) {
- const customTheme: ThemeColorOption = {
- name: "自定义",
- value: "custom",
- primary: color,
- };
- setCurrentThemeColor(customTheme);
- }
-
- /* 重置主题 */
- function resetTheme() {
- setCurrentThemeColor(themeColorOptions[0]);
- theme.value = "light";
- followSystem.value = true;
- hasUserSet.value = false;
- initTheme();
- }
-
- /* 保存主题设置到本地存储 */
- function saveThemeSettings() {
- try {
- uni.setStorageSync("theme_settings", {
- theme: theme.value,
- currentThemeColor: currentThemeColor.value,
- followSystem: followSystem.value,
- hasUserSet: hasUserSet.value,
- });
- } catch (error) {
- console.warn("保存主题设置失败:", error);
- }
- }
-
- /* 从本地存储加载主题设置 */
- function loadThemeSettings() {
- try {
- const settings = uni.getStorageSync("theme_settings");
- if (settings) {
- if (settings.theme && (settings.theme === "light" || settings.theme === "dark")) {
- theme.value = settings.theme;
- }
- if (settings.currentThemeColor) {
- const savedColor =
- themeColorOptions.find((c) => c.value === settings.currentThemeColor.value) ||
- extendedColorOptions.find((c) => c.value === settings.currentThemeColor.value) ||
- settings.currentThemeColor;
- if (savedColor) {
- currentThemeColor.value = savedColor;
- themeVars.colorTheme = savedColor.primary;
- }
- }
- followSystem.value = settings.followSystem !== false;
- hasUserSet.value = settings.hasUserSet === true;
- }
- } catch (error) {
- console.warn("加载主题设置失败:", error);
- }
- }
-
- /* 获取系统主题 */
- function getSystemTheme(): "light" | "dark" {
- try {
- // #ifdef MP-WEIXIN
- const appBaseInfo = uni.getAppBaseInfo();
- if (appBaseInfo?.theme) {
- return appBaseInfo.theme as "light" | "dark";
- }
- // #endif
-
- // #ifndef MP-WEIXIN
- const systemInfo = uni.getSystemInfoSync();
- if (systemInfo?.theme) {
- return systemInfo.theme as "light" | "dark";
- }
- // #endif
- } catch (error) {
- console.warn("获取系统主题失败:", error);
- }
- return "light";
- }
-
- /* 设置导航栏颜色 */
- function setNavigationBarColor() {
- // #ifndef H5
- uni.setNavigationBarColor({
- frontColor: theme.value === "light" ? "#000000" : "#ffffff",
- backgroundColor: theme.value === "light" ? "#ffffff" : "#000000",
- });
- // #endif
- }
-
- /* 实时应用主题色到应用 */
- function applyThemeColorToApp(color: string) {
- // 更新Wot Design组件库主题色
- themeVars.colorTheme = color;
-
- // 更新CSS变量
- if (typeof document !== "undefined") {
- document.documentElement.style.setProperty("--wot-color-theme", color);
- document.documentElement.style.setProperty("--primary-color", color);
- document.documentElement.style.setProperty("--primary-color-light", color + "20");
- document.documentElement.style.setProperty("--primary-color-dark", color);
- }
-
- // 同步到主题存储
- const themeStore = useThemeStore();
- themeStore.setPrimaryColor(color);
-
- // 通过事件总线通知全局主题变化
- uni.$emit("theme-color-changed", color);
-
- // 强制刷新页面样式
- setTimeout(() => {
- uni.$emit("force-theme-refresh");
- }, 50);
- }
-
- /* 初始化主题 */
+ /**
+ * 初始化主题
+ */
function initTheme() {
- if (hasUserSet.value && !followSystem.value) {
- setNavigationBarColor();
- applyThemeColorToApp(currentThemeColor.value.primary);
- return;
- }
-
- const systemTheme = getSystemTheme();
- if (!hasUserSet.value || followSystem.value) {
- theme.value = systemTheme;
- }
-
- setNavigationBarColor();
- applyThemeColorToApp(currentThemeColor.value.primary);
+ store.initTheme();
}
- /* 主题色选择器相关 */
- function openThemeColorPicker() {
- showThemeColorSheet.value = true;
- }
-
- function closeThemeColorPicker() {
- showThemeColorSheet.value = false;
- }
-
- function selectThemeColor(option: ThemeColorOption) {
- setCurrentThemeColor(option);
- closeThemeColorPicker();
- }
-
- // 生命周期
- onBeforeMount(() => {
- loadThemeSettings();
- initTheme();
-
- // #ifdef MP-WEIXIN
- if (uni.onThemeChange) {
- uni.onThemeChange((res: any) => {
- if (followSystem.value) {
- theme.value = res.theme;
- setNavigationBarColor();
- }
- });
- }
- // #endif
-
- // #ifndef MP-WEIXIN
- if (uni.onThemeChange) {
- uni.onThemeChange((res: any) => {
- if (followSystem.value) {
- theme.value = res.theme;
- setNavigationBarColor();
- }
- });
- }
- // #endif
- });
-
- onUnmounted(() => {
- // 清理监听器
- if (uni.offThemeChange) {
- uni.offThemeChange(() => {});
- }
- });
+ // 注意:全局主题管理已在App.vue中处理
+ // 包括:系统主题监听、导航栏颜色同步等
+ // 组件中一般不需要再调用initTheme(),除非有特殊需求
return {
// 状态
- theme: computed(() => theme.value),
- isDark,
- followSystem: computed(() => followSystem.value),
- hasUserSet: computed(() => hasUserSet.value),
- currentThemeColor: computed(() => currentThemeColor.value),
- showThemeColorSheet: computed(() => showThemeColorSheet.value),
- themeState,
+ theme: computed(() => store.theme),
+ isDark: computed(() => store.isDark),
+ currentThemeColor: computed(() => store.currentThemeColor),
+ themeVars: computed(() => store.themeVars),
// 主题选项
- themeColorOptions,
- extendedColorOptions,
-
- // 主题变量
- themeVars,
+ themeColorOptions: computed(() => store.themeColorOptions),
// 方法
- toggleTheme,
- setFollowSystem,
- setCurrentThemeColor,
- setCustomThemeColor,
- resetTheme,
initTheme,
-
- // 主题色选择器
- openThemeColorPicker,
- closeThemeColorPicker,
- selectThemeColor,
-
- // 工具方法
- saveThemeSettings,
- loadThemeSettings,
+ toggleTheme,
+ setThemeColor,
};
}
diff --git a/fastapp/src/composables/useWechat.ts b/fastapp/src/composables/useWechat.ts
deleted file mode 100644
index 871d9385..00000000
--- a/fastapp/src/composables/useWechat.ts
+++ /dev/null
@@ -1,159 +0,0 @@
-/**
- * 微信授权服务
- * 处理微信登录授权、获取用户信息等功能
- */
-
-import { ref } from "vue";
-import { getAccessToken } from "@/utils/auth";
-
-export function useWechat() {
- // 定义微信授权状态
- const authState = ref({
- isLogining: false,
- authDenied: false,
- });
-
- /**
- * 获取微信登录凭证code
- * @returns Promise 返回登录凭证code
- */
- const getLoginCode = (): Promise => {
- return new Promise((resolve, reject) => {
- // #ifdef MP-WEIXIN
- uni.login({
- provider: "weixin",
- success: (res) => {
- if (res.code) {
- resolve(res.code);
- } else {
- reject(new Error("获取微信登录凭证失败"));
- }
- },
- fail: (err) => {
- reject(err);
- },
- });
- // #endif
-
- // #ifndef MP-WEIXIN
- reject(new Error("当前环境不支持微信登录"));
- // #endif
- });
- };
-
- /**
- * 获取微信用户手机号
- * @param e 微信授权返回的事件对象
- * @returns Promise 返回包含手机号加密数据的对象
- */
- const getPhoneNumber = (
- e: any
- ): Promise<{ code: string; encryptedData?: string; iv?: string }> => {
- return new Promise((resolve, reject) => {
- authState.value.isLogining = true;
-
- // 判断授权是否成功
- if (e.detail.errMsg !== "getPhoneNumber:ok") {
- authState.value.isLogining = false;
- authState.value.authDenied = true;
- reject(new Error("用户拒绝授权"));
- return;
- }
-
- // 获取登录凭证code
- getLoginCode()
- .then((code) => {
- // 在微信小程序环境下,可以获取encryptedData和iv
- // #ifdef MP-WEIXIN
- resolve({
- code,
- encryptedData: e.detail.encryptedData,
- iv: e.detail.iv,
- });
- // #endif
-
- // 其他环境或新版本接口
- // #ifndef MP-WEIXIN
- resolve({
- code,
- // 新版本接口在e.detail.code中包含手机号获取凭证
- ...(e.detail.code ? { phoneCode: e.detail.code } : {}),
- });
- // #endif
- })
- .catch((err) => {
- reject(err);
- })
- .finally(() => {
- authState.value.isLogining = false;
- });
- });
- };
-
- /**
- * 检查会话有效性
- * @returns Promise 返回会话是否有效
- */
- const checkSession = (): Promise => {
- return new Promise((resolve) => {
- const token = getAccessToken();
-
- if (!token) {
- resolve(false);
- return;
- }
-
- // 调用后端接口验证token有效性
- uni.request({
- url: "/api/v1/auth/check-session",
- method: "GET",
- header: {
- Authorization: `Bearer ${token}`,
- },
- success: (res: any) => {
- if (res.statusCode === 200 && res.data.valid) {
- resolve(true);
- } else {
- resolve(false);
- }
- },
- fail: () => {
- resolve(false);
- },
- });
- });
- };
-
- /**
- * 获取用户头像昵称
- * 注意:此接口已于2021年弃用,仅作为兼容保留
- * 推荐使用button组件的open-type="chooseAvatar"让用户选择头像
- */
- const getUserProfile = (): Promise => {
- return new Promise((resolve, reject) => {
- // #ifdef MP-WEIXIN
- uni.getUserProfile({
- desc: "用于完善用户资料",
- success: (res) => {
- resolve(res.userInfo);
- },
- fail: (err) => {
- reject(err);
- },
- });
- // #endif
-
- // #ifndef MP-WEIXIN
- reject(new Error("当前环境不支持获取用户信息"));
- // #endif
- });
- };
-
- return {
- authState,
- getLoginCode,
- getPhoneNumber,
- checkSession,
- getUserProfile,
- };
-}
diff --git a/fastapp/src/constants/storage.constant.ts b/fastapp/src/constants/storage.constant.ts
index 6567efa5..3c3d22f3 100644
--- a/fastapp/src/constants/storage.constant.ts
+++ b/fastapp/src/constants/storage.constant.ts
@@ -3,9 +3,14 @@
* 包括 localStorage、sessionStorage 等各种存储的键名
*/
-// 🔐 用户认证相关
-export const ACCESS_TOKEN_KEY = "access_token";
-export const REFRESH_TOKEN_KEY = "refresh_token";
+// 访问token
+export const APP_ACCESS_TOKEN_KEY = "appAccessToken";
-// 📊 用户缓存相关
-export const USER_INFO_KEY = "user_info";
+// 刷新token
+export const APP_REFRESH_TOKEN_KEY = "appRefreshToken";
+
+// 用户缓存相关
+export const APP_USER_INFO = "appUserInfo";
+
+// 全局配置相关
+export const APP_THEME_KEY = "appTheme";
diff --git a/fastapp/src/enums/api-code.enum.ts b/fastapp/src/enums/api-code.enum.ts
index 6b3f784c..94ca0dd9 100644
--- a/fastapp/src/enums/api-code.enum.ts
+++ b/fastapp/src/enums/api-code.enum.ts
@@ -20,12 +20,12 @@ export const enum ApiCode {
/**
* 未授权访问
*/
- UNAUTHORIZED = 401,
+ UNAUTHORIZED = 10403,
/**
* 令牌已过期
*/
- TOKEN_EXPIRED = 403,
+ TOKEN_EXPIRED = 10401,
/**
* 参数校验失败
diff --git a/fastapp/src/manifest.json b/fastapp/src/manifest.json
index 687e9793..b377e4fa 100644
--- a/fastapp/src/manifest.json
+++ b/fastapp/src/manifest.json
@@ -15,8 +15,11 @@
"autoclose": true,
"delay": 0
},
+ /* 模块配置 */
"modules": {},
+ /* 应用发布信息 */
"distribute": {
+ /* android打包配置 */
"android": {
"permissions": [
"",
@@ -36,11 +39,15 @@
""
]
},
+ /* ios打包配置 */
"ios": {},
+ /* SDK配置 */
"sdkConfigs": {}
}
},
+ /* 快应用特有相关 */
"quickapp": {},
+ /* 小程序特有相关:"appid": "wx99a151dc43d2637b" */
"mp-weixin": {
"appid": "微信开发者ID",
"setting": {
@@ -50,12 +57,15 @@
"darkmode": true,
"themeLocation": "theme.json"
},
+ /* 抖音小程序特有相关 */
"mp-alipay": {
"usingComponents": true
},
+ /* 百度小程序特有相关 */
"mp-baidu": {
"usingComponents": true
},
+ /* 字节跳动小程序特有相关 */
"mp-toutiao": {
"usingComponents": true
},
diff --git a/fastapp/src/pages.json b/fastapp/src/pages.json
index 7e7649b7..da1d2c49 100644
--- a/fastapp/src/pages.json
+++ b/fastapp/src/pages.json
@@ -25,25 +25,38 @@
{
"path": "pages/work/index",
"type": "page",
- "name": "index",
+ "name": "work",
"style": {
- "navigationBarTitleText": "工作台"
+ "navigationStyle": "custom"
},
+ "layout": "tabbar",
"meta": {
"requireAuth": true
}
},
{
"path": "pages/mine/about/index",
- "type": "page"
+ "type": "page",
+ "name": "about",
+ "style": {
+ "navigationBarTitleText": "关于我们"
+ }
},
{
"path": "pages/mine/faq/index",
- "type": "page"
+ "type": "page",
+ "name": "faq",
+ "style": {
+ "navigationBarTitleText": "常见问题"
+ }
},
{
"path": "pages/mine/feedback/index",
- "type": "page"
+ "type": "page",
+ "name": "feedback",
+ "style": {
+ "navigationBarTitleText": "问题反馈"
+ }
},
{
"path": "pages/mine/profile/complete-profile",
@@ -55,23 +68,54 @@
},
{
"path": "pages/mine/settings/index",
- "type": "page"
+ "type": "page",
+ "name": "settings",
+ "style": {
+ "navigationBarTitleText": "设置"
+ }
},
{
"path": "pages/mine/settings/account/index",
- "type": "page"
+ "type": "page",
+ "name": "account",
+ "style": {
+ "navigationBarTitleText": "账号和安全"
+ },
+ "layout": "tabbar"
},
{
"path": "pages/mine/settings/agreement/index",
- "type": "page"
+ "type": "page",
+ "name": "agreement",
+ "style": {
+ "navigationBarTitleText": "用户协议"
+ },
+ "layout": "tabbar"
},
{
"path": "pages/mine/settings/network/index",
- "type": "page"
+ "type": "page",
+ "name": "network",
+ "style": {
+ "navigationBarTitleText": "网络测试"
+ }
+ },
+ {
+ "path": "pages/mine/settings/privacy/index",
+ "type": "page",
+ "name": "privacy",
+ "style": {
+ "navigationBarTitleText": "隐私政策"
+ },
+ "layout": "tabbar"
},
{
"path": "pages/mine/settings/theme/index",
- "type": "page"
+ "type": "page",
+ "name": "theme",
+ "style": {
+ "navigationBarTitleText": "主题设置"
+ }
}
],
"globalStyle": {
@@ -100,6 +144,9 @@
{
"pagePath": "pages/index/index"
},
+ {
+ "pagePath": "pages/work/index"
+ },
{
"pagePath": "pages/mine/index"
}
diff --git a/fastapp/src/pages/index/index.vue b/fastapp/src/pages/index/index.vue
index ad12355f..55cc91a6 100644
--- a/fastapp/src/pages/index/index.vue
+++ b/fastapp/src/pages/index/index.vue
@@ -1,4 +1,5 @@
+
- {{ item.title }}
+ {{ item.title }}
@@ -38,9 +40,9 @@
-
+
-
+
访客数
@@ -49,9 +51,9 @@
-
+
-
+
浏览量
@@ -64,11 +66,11 @@
-
+
访问趋势
-
+
@@ -89,7 +91,6 @@
@@ -359,6 +322,7 @@ onLoad(() => {
flex-direction: column;
align-items: center;
height: 100%;
+ min-height: 100%;
overflow: hidden;
background-color: var(--wot-color-bg-container);
}
@@ -367,35 +331,10 @@ onLoad(() => {
position: absolute;
top: 0;
left: 0;
- z-index: -1;
width: 100%;
height: 100%;
}
-.loading-overlay {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 9999;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(0, 0, 0, 0.5);
-}
-
-.loading-content {
- display: flex;
- flex-direction: column;
- gap: 20rpx;
- align-items: center;
-}
-
-.loading-text {
- font-size: 28rpx;
- color: #fff;
-}
.header {
z-index: 2;
display: flex;
@@ -429,7 +368,7 @@ onLoad(() => {
z-index: 2;
display: flex;
flex-direction: column;
- width: 95%;
+ width: 90%;
margin-top: 80rpx;
overflow: hidden;
background-color: rgba(255, 255, 255, 0.9);
@@ -504,8 +443,6 @@ input:-webkit-autofill:active {
.form-item input,
input.form-input,
input.input-transparent {
- -webkit-appearance: none;
- appearance: none;
background: none !important;
background-color: transparent !important;
border: none !important;
@@ -712,14 +649,4 @@ input.input-transparent {
background-image: none !important;
}
}
-
-/* 修复Android Chrome输入框背景色问题 */
-@supports (-webkit-appearance: none) {
- input {
- -webkit-appearance: none;
- appearance: none;
- background: transparent !important;
- background-color: transparent !important;
- }
-}
diff --git a/fastapp/src/pages/mine/about/index.vue b/fastapp/src/pages/mine/about/index.vue
index 2ea954cd..8d3c91e4 100644
--- a/fastapp/src/pages/mine/about/index.vue
+++ b/fastapp/src/pages/mine/about/index.vue
@@ -1,393 +1,179 @@
-
+
-
-
-
+
+
+
-
-
-
-
-
-
-
- 前端技术
-
-
-
-
-
-
-
-
-
- 后端技术
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 版权信息
-
-
-
-
- {{ copyright }}
- {{ keepRecord }}
-
- 用户协议
- |
- 隐私政策
-
+
+
+
+
+
+ {{ webTitle }}
+ {{ webDescription }}
+
+
+
+
+
+
+
+ 基于 FastAPI + Uvicorn + SQLAlchemy 构建的RBAC管理系统
+
+
+
+
+
+ 基于 Vue3 + Vite6 + TypeScript + Element-Plus + Pinia 构建的中后台管理模板
+
+
+
+
+
+ 基于 uni-app + Vite6 + Vue 3 + TypeScript + Wot Design Uni + Pinia 构建的移动端应用模板
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{
+ "name": "about",
+ "style": {
+ "navigationBarTitleText": "关于我们"
+ }
+}
+
+
diff --git a/fastapp/src/pages/mine/faq/index.vue b/fastapp/src/pages/mine/faq/index.vue
index 7a76a6f8..124aea1e 100644
--- a/fastapp/src/pages/mine/faq/index.vue
+++ b/fastapp/src/pages/mine/faq/index.vue
@@ -1,12 +1,6 @@
-
+
@@ -21,7 +15,7 @@
-
+
邮箱支持
@@ -55,24 +49,16 @@
-
+
{{ item.answer }}
@@ -83,11 +69,7 @@
-
+
@@ -106,18 +88,14 @@
快速开始
-
+
{{ index + 1 }}
-
+
{{ step.title }}
- {{ step.desc }}
+ {{ step.desc }}
@@ -146,21 +124,25 @@ const scrollTop = ref(0);
const faqList = ref([
{
question: "如何重置密码?",
- answer: '在登录页面点击"忘记密码",按照提示操作即可重置密码。如果无法收到验证码,请检查邮箱或手机号是否正确,或联系客服协助处理。'
+ answer:
+ "在登录页面点击 '忘记密码',按照提示操作即可重置密码。如果无法收到验证码,请检查邮箱或手机号是否正确,或联系客服协助处理。",
},
{
question: "数据如何备份?",
- answer: '系统会自动定期备份数据,您也可以在"系统设置-数据管理"中手动导出数据。建议每月进行一次完整数据备份,确保数据安全。'
+ answer:
+ "系统会自动定期备份数据,您也可以在'系统设置-数据管理'中手动导出数据。建议每月进行一次完整数据备份,确保数据安全。",
},
{
question: "支持哪些浏览器?",
- answer: '推荐使用Chrome 90+、Firefox 88+、Safari 14+、Edge 90+等现代浏览器。IE浏览器仅支持IE11及以上版本。'
+ answer:
+ "推荐使用Chrome 90+、Firefox 88+、Safari 14+、Edge 90+等现代浏览器。IE浏览器仅支持IE11及以上版本。",
},
{
question: "如何联系客服?",
- answer: "您可以通过以下方式联系客服:1. 客服热线 400-123-4567(工作日9:00-18:00);2. 邮箱 support@example.com(7×24小时);3. 在线客服(工作日9:00-18:00)。"
- }
-])
+ answer:
+ "您可以通过以下方式联系客服:1. 客服热线 400-123-4567(工作日9:00-18:00);2. 邮箱 support@example.com(7×24小时);3. 在线客服(工作日9:00-18:00)。",
+ },
+]);
// 系统功能数据
const featureList = ref([
@@ -168,65 +150,65 @@ const featureList = ref([
name: "用户管理",
desc: "支持用户注册、登录、权限管理等功能",
icon: "user",
- color: "#0083f0"
+ color: "#0083f0",
},
{
name: "数据统计",
desc: "提供实时数据分析和可视化报表",
icon: "chart",
- color: "#00c250"
+ color: "#00c250",
},
{
name: "文件管理",
desc: "支持文件上传、下载、分类管理",
icon: "folder",
- color: "#ff9500"
+ color: "#ff9500",
},
{
name: "消息通知",
desc: "实时消息推送和系统通知",
icon: "notification",
- color: "#ff3b30"
- }
-])
+ color: "#ff3b30",
+ },
+]);
// 使用指南数据
const guideSteps = ref([
{
title: "注册账号并登录系统",
- desc: "使用手机号或邮箱注册账号,完成实名认证"
+ desc: "使用手机号或邮箱注册账号,完成实名认证",
},
{
title: "完善个人或企业信息",
- desc: "填写基本信息,设置安全问题和密保邮箱"
+ desc: "填写基本信息,设置安全问题和密保邮箱",
},
{
title: "根据需求配置功能模块",
- desc: "选择需要的功能模块,设置相关参数"
+ desc: "选择需要的功能模块,设置相关参数",
},
{
title: "开始使用各项功能",
- desc: "完成初始化设置,开始使用系统各项功能"
- }
-])
+ desc: "完成初始化设置,开始使用系统各项功能",
+ },
+]);
-// 方法定义
+// 返回
const handleBack = () => {
- uni.navigateBack()
-}
+ uni.navigateBack();
+};
const handleScroll = (e: any) => {
- scrollTop.value = e.detail.scrollTop
-}
+ scrollTop.value = e.detail.scrollTop;
+};
const toggleFaq = (index: number) => {
- currentFaq.value = currentFaq.value === index ? null : index
-}
+ currentFaq.value = currentFaq.value === index ? null : index;
+};
const handleContact = (type: "email" | "phone") => {
if (type === "email") {
// #ifdef H5
- window.location.href = "mailto:support@example.com"
+ window.location.href = "mailto:support@example.com";
// #endif
// #ifndef H5
uni.setClipboardData({
@@ -234,34 +216,41 @@ const handleContact = (type: "email" | "phone") => {
success: () => {
uni.showToast({
title: "邮箱已复制",
- icon: "success"
- })
- }
- })
+ icon: "success",
+ });
+ },
+ });
// #endif
} else if (type === "phone") {
// #ifdef H5
- window.location.href = "tel:400-123-4567"
+ window.location.href = "tel:400-123-4567";
// #endif
// #ifndef H5
uni.makePhoneCall({
- phoneNumber: "400-123-4567"
- })
+ phoneNumber: "400-123-4567",
+ });
// #endif
}
-}
+};
// 生命周期
onMounted(() => {
// 可以在这里添加页面埋点或初始化逻辑
-})
+});
-
+
+{
+ "name": "faq",
+ "style": {
+ "navigationBarTitleText": "常见问题"
+ }
+}
+
diff --git a/fastapp/src/pages/mine/profile/complete-profile.vue b/fastapp/src/pages/mine/profile/complete-profile.vue
index 0a10f622..94bd6910 100644
--- a/fastapp/src/pages/mine/profile/complete-profile.vue
+++ b/fastapp/src/pages/mine/profile/complete-profile.vue
@@ -159,7 +159,7 @@ const redirect = ref("/pages/index/index");
// 表单数据
const profileForm = reactive({
name: "",
- gender: 1,
+ gender: "0",
mobile: "",
email: "",
username: "",
@@ -197,7 +197,7 @@ onLoad((options: any) => {
if (userInfo) {
profileForm.name = userInfo.name || "";
profileForm.avatar = userInfo.avatar || "";
- profileForm.gender = userInfo.gender || 1;
+ profileForm.gender = userInfo.gender || "0";
profileForm.email = userInfo.email || "";
profileForm.username = userInfo.username || "";
profileForm.dept_name = userInfo.dept_name || "";
diff --git a/fastapp/src/pages/mine/profile/index.vue b/fastapp/src/pages/mine/profile/index.vue
index bc1ec905..4038cb02 100644
--- a/fastapp/src/pages/mine/profile/index.vue
+++ b/fastapp/src/pages/mine/profile/index.vue
@@ -1,6 +1,12 @@
-
+
@@ -24,13 +30,27 @@
+
+
+ {{ userProfile.status ? "启用" : "停用" }}
+
+
+
+
+ {{ userProfile.is_superuser ? "是" : "否" }}
+
+
+
+
-
+
+
+
@@ -52,12 +72,13 @@
/>
- 男
- 女
+ 男
+ 女
+ 未知
-
+
@@ -95,7 +116,7 @@ function handleAvatarConfirm(event: any) {
FileAPI.upload(tempFilePath).then((fileInfo: UploadFileResult) => {
const avatarForm = {
name: userProfile.value?.name || "",
- gender: userProfile.value?.gender || 1,
+ gender: userProfile.value?.gender || "0",
mobile: userProfile.value?.mobile || "",
email: userProfile.value?.email || "",
username: userProfile.value?.username || "",
@@ -125,7 +146,7 @@ const dialog = reactive({
const userProfileForm = reactive<{
name?: string;
- gender?: number;
+ gender?: string;
}>({});
const userProfileFormRef = ref();
@@ -137,7 +158,7 @@ const handleOpenDialog = () => {
dialog.visible = true;
// 初始化表单数据
userProfileForm.name = userProfile.value?.name || "";
- userProfileForm.gender = userProfile.value?.gender || 1;
+ userProfileForm.gender = userProfile.value?.gender || "0";
};
// 提交表单
@@ -207,16 +228,19 @@ function handleBack() {
:deep(.wd-cell__body) {
align-items: center;
}
+
.avatar {
display: flex;
align-items: center;
justify-content: right;
+
.img {
position: relative;
width: 80px;
height: 80px;
background-color: rgba(0, 0, 0, 0.04);
border-radius: 50%;
+
.img-icon {
position: absolute;
top: 50%;
@@ -229,6 +253,7 @@ function handleBack() {
.edit-form {
padding-top: 40rpx;
+
.ef-radio-group {
line-height: 1;
text-align: left;
diff --git a/fastapp/src/pages/mine/settings/account/index.vue b/fastapp/src/pages/mine/settings/account/index.vue
index f381d4de..6a92d407 100644
--- a/fastapp/src/pages/mine/settings/account/index.vue
+++ b/fastapp/src/pages/mine/settings/account/index.vue
@@ -1,6 +1,12 @@
-
+
@@ -93,6 +99,8 @@ const rules = reactive({
enum DialogType {
PASSWORD = "password",
+ MOBILE = "mobile",
+ EMAIL = "email",
}
const dialog = reactive({
@@ -152,4 +160,12 @@ onMounted(() => {
loadUserProfile();
});
+
+
+{
+ "name": "account",
+ "style": { "navigationBarTitleText": "账号和安全" },
+ "layout": "tabbar"
+}
+
diff --git a/fastapp/src/pages/mine/settings/agreement/index.vue b/fastapp/src/pages/mine/settings/agreement/index.vue
index 87ca4d61..a59bac08 100644
--- a/fastapp/src/pages/mine/settings/agreement/index.vue
+++ b/fastapp/src/pages/mine/settings/agreement/index.vue
@@ -1,6 +1,12 @@
-
+
@@ -80,5 +86,12 @@ const handleAgree = () => {
}, 1500);
};
+
+{
+ "name": "agreement",
+ "style": { "navigationBarTitleText": "用户协议" },
+ "layout": "tabbar"
+}
+
diff --git a/fastapp/src/pages/mine/settings/index.vue b/fastapp/src/pages/mine/settings/index.vue
index 148d57b2..07a05402 100644
--- a/fastapp/src/pages/mine/settings/index.vue
+++ b/fastapp/src/pages/mine/settings/index.vue
@@ -1,31 +1,18 @@
-
-
-
+
+
-
+
@@ -83,6 +70,13 @@ const navigateToUserAgreement = () => {
});
};
+// 隐私政策
+const navigateToPrivacy = () => {
+ uni.navigateTo({
+ url: "/pages/mine/settings/privacy/index",
+ });
+};
+
// 关于我们
const navigateToAbout = () => {
uni.navigateTo({
@@ -92,15 +86,13 @@ const navigateToAbout = () => {
// 进入官网
const navigateToOfficialWebsite = () => {
- uni.navigateTo({
- url: "https://service.fastapiadmin.com",
- });
+ // plus.runtime.openURL("https://service.fastapiadmin.com");
+ window.open("https://service.fastapiadmin.com", "_blank");
};
// 网络测试
const navigateToNetworkTest = () => {
- // #ifdef H5
- window.open("https://service.fastapiadmin.com", "_blank");
+ uni.navigateTo({ url: "/pages/mine/settings/network/index" });
};
// 是否正在清理
@@ -201,16 +193,21 @@ const handleLogout = () => {
});
};
-// 返回
-const handleBack = () => {
- uni.navigateBack();
-};
-
// 检查登录状态
onLoad(() => {
getCacheSize();
});
+
+
+{
+ "name": "settings",
+ "style": {
+ "navigationBarTitleText": "设置"
+ }
+}
+
+
diff --git a/fastapp/src/pages/mine/settings/theme/index.vue b/fastapp/src/pages/mine/settings/theme/index.vue
index 207f7ba8..7ac95f69 100644
--- a/fastapp/src/pages/mine/settings/theme/index.vue
+++ b/fastapp/src/pages/mine/settings/theme/index.vue
@@ -1,118 +1,142 @@
-
-
-
-
+
+
-
+
-
-
-
-
-
- 暗黑模式
-
-
-
-
-
-
-
-
-
- 跟随系统
-
-
-
-
-
-
-
-
-
-
-
-
- {{ item.name }}
-
-
-
-
-
-
-
-
-
-
- 自定义颜色
-
-
-
-
- {{ currentThemeColor.primary }}
-
-
-
-
-
-
-
-
-
- 主要按钮
- 次要按钮
- 标签
-
-
- 当前主题色预览
-
-
-
-
-
-
-
-
- 恢复默认
-
+ 个性化您的应用外观
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 预设主题色
+
+
+
+ ✓
+
+ {{ color.name }}
+
+
+
+
+
+
+
+ 当前主题色
+
+
+ {{ currentThemeColor }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 按钮
+
+
+ 边框
+
+
+
+
+
+
+ 标签
+
+
+
+
+
+
+ 重置为默认主题
+
+
+
+
@@ -120,155 +144,147 @@
+
+{
+ "name": "theme",
+ "style": {
+ "navigationBarTitleText": "主题设置"
+ }
+}
+
+
diff --git a/fastapp/src/pages/work/index.vue b/fastapp/src/pages/work/index.vue
index 4c851820..3f18927b 100644
--- a/fastapp/src/pages/work/index.vue
+++ b/fastapp/src/pages/work/index.vue
@@ -1,21 +1,309 @@
-
-
+
+
+
+
+
+
+
+
+
+ {{ stat.number }}
+ {{ stat.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ action.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ todo.title }}
+ {{ todo.time }}
+
+
+
+
+
+
+ 暂无待办事项
+
+
-
+
{
- "name": "index",
+ "name": "work",
"style": {
- "navigationBarTitleText": "工作台"
+ "navigationStyle": "custom"
},
+ "layout": "tabbar",
"meta": {
"requireAuth": true
}
}
-
+
diff --git a/fastapp/src/router/index.ts b/fastapp/src/router/index.ts
index fda51fa7..f20d671e 100644
--- a/fastapp/src/router/index.ts
+++ b/fastapp/src/router/index.ts
@@ -6,7 +6,9 @@ import { createRouter } from "uni-mini-router";
function generateRoutes() {
const routes = pages.map((page: { path: string; [key: string]: any }) => {
const newPath = `/${page.path}`;
- return { ...page, path: newPath };
+ // 透传 meta 字段(如果 pages.json 中定义了)
+ const meta = page.meta ?? undefined;
+ return { ...page, path: newPath, meta };
});
// 处理分包路由
@@ -14,7 +16,8 @@ function generateRoutes() {
subPackages.forEach((subPackage: { root: string; pages: any[] }) => {
const subRoutes = subPackage.pages.map((page: any) => {
const newPath = `/${subPackage.root}/${page.path}`;
- return { ...page, path: newPath };
+ const meta = page.meta ?? undefined;
+ return { ...page, path: newPath, meta };
});
routes.push(...subRoutes);
});
@@ -62,8 +65,8 @@ router.beforeEach((to, from, next) => {
}
});
-router.afterEach((to) => {
- console.log("路由跳转完成:", to.path);
+router.afterEach((to, from) => {
+ console.log("🎯 afterEach 钩子触发:", { to, from });
});
export default router;
diff --git a/fastapp/src/static/images/login-bg-bak.svg b/fastapp/src/static/images/login-bg-bak.svg
new file mode 100644
index 00000000..765ad334
--- /dev/null
+++ b/fastapp/src/static/images/login-bg-bak.svg
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/fastapp/src/store/modules/theme.store.ts b/fastapp/src/store/modules/theme.store.ts
index 959d008a..7d9231e5 100644
--- a/fastapp/src/store/modules/theme.store.ts
+++ b/fastapp/src/store/modules/theme.store.ts
@@ -1,50 +1,103 @@
import { defineStore } from "pinia";
-import { ref } from "vue";
-import { applyThemeToMiniProgram } from "@/utils/theme";
+import type { ConfigProviderThemeVars } from "wot-design-uni";
+import { useStorage } from "@uni-helper/uni-use";
-// 从缓存获取主题色
-const getThemeColor = (): string => {
- const savedColor = uni.getStorageSync("themeColor");
- return savedColor || "#165DFF"; // 默认Arco蓝色
-};
+// 主题色选项接口
+export interface ThemeColorOption {
+ name: string;
+ value: string;
+ primary: string;
+}
-// 保存主题色到缓存
-const setThemeColorCache = (color: string) => {
- uni.setStorageSync("themeColor", color);
-};
+// 主题类型
+export type ThemeMode = "light" | "dark";
+
+export const useThemeStore = defineStore("appTheme", () => {
+ const theme = useStorage("app-theme", "light");
+
+ // 预定义的主题色选项
+ const themeColorOptions: ThemeColorOption[] = [
+ { name: "默认蓝", value: "blue", primary: "#4D7FFF" },
+ { name: "活力橙", value: "orange", primary: "#FF7D00" },
+ { name: "薄荷绿", value: "green", primary: "#07C160" },
+ { name: "樱花粉", value: "pink", primary: "#FF69B4" },
+ { name: "紫罗兰", value: "purple", primary: "#8A2BE2" },
+ { name: "朱砂红", value: "red", primary: "#FF4757" },
+ ];
-export const useThemeStore = defineStore("theme", () => {
// 主题色
- const primaryColor = ref(getThemeColor());
+ const currentThemeColor = useStorage("app-theme-color", themeColorOptions[0]);
- // 设置主题色
- const setPrimaryColor = (color: string) => {
- primaryColor.value = color;
- setThemeColorCache(color);
+ // 主题变量(响应式对象)
+ const themeVars: ConfigProviderThemeVars = reactive({
+ darkBackground: "#0f0f0f",
+ darkBackground2: "#1a1a1a",
+ darkBackground3: "#242424",
+ darkBackground4: "#2f2f2f",
+ darkBackground5: "#3d3d3d",
+ darkBackground6: "#4a4a4a",
+ darkBackground7: "#606060",
+ darkColor: "#ffffff",
+ darkColor2: "#e0e0e0",
+ darkColor3: "#a0a0a0",
+ colorTheme: currentThemeColor.value.primary,
+ });
- // 检测运行环境,区分处理
- if (typeof document !== "undefined") {
- // H5环境
- document.documentElement.style.setProperty("--primary-color", color);
- document.documentElement.style.setProperty("--wot-color-theme", color);
+ // 计算属性
+ const isDark = computed(() => theme.value === "dark");
- // 设置简单的衍生色(不依赖外部工具函数)
- document.documentElement.style.setProperty("--primary-color-light", color + "80"); // 添加透明度
- document.documentElement.style.setProperty("--primary-color-dark", color);
+ // 切换主题, 指定主题模式,不传则自动切换
+ const toggleTheme = (mode?: ThemeMode) => {
+ theme.value = mode || (theme.value === "light" ? "dark" : "light");
+ setNavigationBarColor();
+ };
+
+ // 设置导航栏颜色
+ const setNavigationBarColor = () => {
+ // 只在非H5环境下调用setNavigationBarColor
+ if (process.env.UNI_PLATFORM !== "h5") {
+ console.log("设置导航栏颜色", theme.value);
+ uni.setNavigationBarColor({
+ frontColor: theme.value === "light" ? "#000000" : "#ffffff",
+ backgroundColor: theme.value === "light" ? "#ffffff" : "#000000",
+ });
} else {
- // 小程序环境
- applyThemeToMiniProgram(color);
+ console.log("H5环境下跳过设置导航栏颜色");
}
};
- // 初始化,应用主题色
+ // 设置主题色
+ const setCurrentThemeColor = (color: ThemeColorOption) => {
+ currentThemeColor.value = color;
+ themeVars.colorTheme = color.primary;
+ console.log("主题色已设置:", color.name);
+ };
+
+ // 初始化主题
const initTheme = () => {
- setPrimaryColor(primaryColor.value);
+ // 更新主题变量中的颜色
+ themeVars.colorTheme = currentThemeColor.value.primary;
+
+ // 设置导航栏颜色
+ nextTick(() => {
+ setNavigationBarColor();
+ });
};
return {
- primaryColor,
- setPrimaryColor,
+ // 状态
+ theme,
+ currentThemeColor,
+ themeVars,
+ themeColorOptions,
+
+ // 计算属性
+ isDark,
+
+ // 方法
+ toggleTheme,
+ setCurrentThemeColor,
+ setNavigationBarColor,
initTheme,
};
});
diff --git a/fastapp/src/store/modules/user.store.ts b/fastapp/src/store/modules/user.store.ts
index b039bef4..ad9b754b 100644
--- a/fastapp/src/store/modules/user.store.ts
+++ b/fastapp/src/store/modules/user.store.ts
@@ -1,17 +1,16 @@
import { defineStore } from "pinia";
-import AuthAPI, {
- type LoginFormData,
- type WxLoginData,
- type LoginResult,
- type LogoutBody,
-} from "@/api/auth";
import UserAPI, { type UserInfo } from "@/api/user";
-import { getAccessToken, setAccessToken, clearTokens } from "@/utils/auth";
-import { getUserInfo, setUserInfo } from "@/utils/storage";
-import { USER_INFO_KEY } from "@/constants";
-import { Storage } from "@/utils/storage";
+import AuthAPI, { type LoginFormData, type LoginResult, type LogoutBody } from "@/api/auth";
+import {
+ getAccessToken,
+ setAccessToken,
+ setRefreshToken,
+ clearAll,
+ getUserInfo,
+ setUserInfo,
+} from "@/utils/auth";
-export const useUserStore = defineStore("user_info", () => {
+export const useUserStore = defineStore("appUserInfo", () => {
const userInfo = ref(getUserInfo());
const isLoggingIn = ref(false);
@@ -23,6 +22,7 @@ export const useUserStore = defineStore("user_info", () => {
try {
const result = await loginFn();
setAccessToken(result.access_token);
+ setRefreshToken(result.refresh_token);
// 登录成功后获取用户信息
await getInfo();
@@ -41,16 +41,6 @@ export const useUserStore = defineStore("user_info", () => {
return handleLogin(() => AuthAPI.login(data), "账号密码");
};
- // 微信基础授权登录
- const loginWithWxCode = async (code: string) => {
- return handleLogin(() => AuthAPI.loginByWxMiniAppCode(code), "微信授权");
- };
-
- // 微信手机号授权登录
- const loginWithWxPhone = async (data: WxLoginData) => {
- return handleLogin(() => AuthAPI.loginByWxMiniAppPhone(data), "微信手机号");
- };
-
// 获取用户信息
const getInfo = async () => {
try {
@@ -69,14 +59,13 @@ export const useUserStore = defineStore("user_info", () => {
const logout = async () => {
try {
const logoutBody: LogoutBody = {
- token: getAccessToken(),
+ token: getAccessToken() || "",
};
await AuthAPI.logout(logoutBody); // 调用后台注销接口
} catch (error) {
console.error("登出失败", error);
} finally {
- clearTokens(); // 清除本地的 token
- Storage.remove(USER_INFO_KEY); // 清除用户信息缓存
+ clearAll(); // 清除本地的 token
userInfo.value = undefined; // 清空用户信息
// 跳转到登录页面
uni.reLaunch({
@@ -95,8 +84,6 @@ export const useUserStore = defineStore("user_info", () => {
userInfo,
isLoggingIn,
login,
- loginWithWxCode,
- loginWithWxPhone,
logout,
getInfo,
isUserInfoComplete,
diff --git a/fastapp/src/styles/index.scss b/fastapp/src/styles/index.scss
index 3084daae..ec62e8ae 100644
--- a/fastapp/src/styles/index.scss
+++ b/fastapp/src/styles/index.scss
@@ -8,4 +8,255 @@ body,
.app-container {
padding: 10rpx 10rpx;
+ :deep(.custom-item) {
+ height: 80px !important;
+ }
+ :deep(.wd-card) {
+ margin: 10rpx 0 !important;
+ }
+}
+
+/**
+ * 全局样式变量
+ */
+
+/*
+ * 主题颜色 - 会被theme.ts中的动态设置覆盖
+ * 这里作为默认值和IDE提示
+ */
+:root {
+ /* 主色 */
+ --primary-color: #165dff;
+ --primary-color-light: #94bfff;
+ --primary-color-dark: #0e3c9b;
+
+ /* 功能色 */
+ --success-color: #0fc6c2;
+ --warning-color: #ff7d00;
+ --danger-color: #f5222d;
+ --info-color: #86909c;
+
+ /* 文字颜色 */
+ --text-primary: #1d2129;
+ --text-regular: #4e5969;
+ --text-secondary: #86909c;
+ --text-placeholder: #c9cdd4;
+ --text-inverse: #ffffff;
+
+ /* 边框颜色 */
+ --border-color: #e5e6eb;
+ --border-light: #f2f3f5;
+
+ /* 背景颜色 */
+ --bg-white: #ffffff;
+ --bg-light: #f2f3f5;
+ --bg-gray: #f7f8fa;
+}
+
+/**
+ * 主题相关的通用类
+ */
+
+/* 主色文本 */
+.text-primary {
+ color: var(--primary-color) !important;
+}
+
+/* 主色背景 */
+.bg-primary {
+ color: #fff;
+ background-color: var(--primary-color) !important;
+}
+
+/* 主色边框 */
+.border-primary {
+ border-color: var(--primary-color) !important;
+}
+
+/* 主色按钮样式 */
+.btn-primary {
+ color: #fff;
+ background-color: var(--primary-color);
+ border: none;
+ border-radius: 8rpx;
+ transition: opacity 0.3s;
+
+ &:active {
+ opacity: 0.8;
+ }
+}
+
+/* 圆角按钮 */
+.btn-rounded {
+ border-radius: 45rpx !important;
+}
+
+/* 次级按钮 */
+.btn-secondary {
+ color: var(--primary-color);
+ background-color: #fff;
+ border: 1px solid var(--primary-color);
+ border-radius: 8rpx;
+ transition: background-color 0.3s;
+
+ &:active {
+ background-color: rgba(22, 93, 255, 0.05);
+ }
+}
+
+/**
+ * 字体大小
+ */
+.font-xs {
+ font-size: 24rpx;
+}
+
+.font-sm {
+ font-size: 28rpx;
+}
+
+.font-md {
+ font-size: 32rpx;
+}
+
+.font-lg {
+ font-size: 36rpx;
+}
+
+.font-xl {
+ font-size: 40rpx;
+}
+
+/**
+ * 边距辅助类
+ */
+.mt-10 {
+ margin-top: 10rpx;
+}
+.mt-20 {
+ margin-top: 20rpx;
+}
+.mt-30 {
+ margin-top: 30rpx;
+}
+.mt-40 {
+ margin-top: 40rpx;
+}
+
+.mb-10 {
+ margin-bottom: 10rpx;
+}
+.mb-20 {
+ margin-bottom: 20rpx;
+}
+.mb-30 {
+ margin-bottom: 30rpx;
+}
+.mb-40 {
+ margin-bottom: 40rpx;
+}
+
+.ml-10 {
+ margin-left: 10rpx;
+}
+.ml-20 {
+ margin-left: 20rpx;
+}
+.ml-30 {
+ margin-left: 30rpx;
+}
+.ml-40 {
+ margin-left: 40rpx;
+}
+
+.mr-10 {
+ margin-right: 10rpx;
+}
+.mr-20 {
+ margin-right: 20rpx;
+}
+.mr-30 {
+ margin-right: 30rpx;
+}
+.mr-40 {
+ margin-right: 40rpx;
+}
+
+.p-10 {
+ padding: 10rpx;
+}
+.p-20 {
+ padding: 20rpx;
+}
+.p-30 {
+ padding: 30rpx;
+}
+.p-40 {
+ padding: 40rpx;
+}
+
+/**
+ * 布局辅助类
+ */
+.flex {
+ display: flex;
+}
+.flex-wrap {
+ flex-wrap: wrap;
+}
+.flex-column {
+ flex-direction: column;
+}
+.items-center {
+ align-items: center;
+}
+.items-start {
+ align-items: flex-start;
+}
+.items-end {
+ align-items: flex-end;
+}
+.justify-center {
+ justify-content: center;
+}
+.justify-between {
+ justify-content: space-between;
+}
+.justify-around {
+ justify-content: space-around;
+}
+.justify-start {
+ justify-content: flex-start;
+}
+.justify-end {
+ justify-content: flex-end;
+}
+
+/**
+ * 其他辅助类
+ */
+.text-center {
+ text-align: center;
+}
+.text-left {
+ text-align: left;
+}
+.text-right {
+ text-align: right;
+}
+
+.rounded-sm {
+ border-radius: 4rpx;
+}
+.rounded {
+ border-radius: 8rpx;
+}
+.rounded-lg {
+ border-radius: 16rpx;
+}
+.rounded-xl {
+ border-radius: 24rpx;
+}
+.rounded-full {
+ border-radius: 9999rpx;
}
diff --git a/fastapp/src/types/auto-imports.d.ts b/fastapp/src/types/auto-imports.d.ts
index a622a697..b6efc359 100644
--- a/fastapp/src/types/auto-imports.d.ts
+++ b/fastapp/src/types/auto-imports.d.ts
@@ -14,10 +14,13 @@ declare global {
const applyThemeToMiniProgram: typeof import('../utils/theme')['applyThemeToMiniProgram']
const auth: typeof import('../api/auth')['default']
const checkLogin: typeof import('../utils/auth')['checkLogin']
- const clearAll: typeof import('../utils/storage')['clearAll']
+ const clearAll: typeof import('../utils/auth')['clearAll']
+ const clearToken: typeof import('../utils/storage')['clearToken']
const clearTokens: typeof import('../utils/auth')['clearTokens']
+ const clearUserInfo: typeof import('../utils/auth')['clearUserInfo']
const colorColumns: typeof import('../composables/useTheme')['colorColumns']
const computed: typeof import('vue')['computed']
+ const config: typeof import('../api/config')['default']
const createApp: typeof import('vue')['createApp']
const createPinia: typeof import('pinia')['createPinia']
const createRouter: typeof import('uni-mini-router')['createRouter']
@@ -27,6 +30,8 @@ declare global {
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
+ const dept: typeof import('../api/dept')['default']
+ const dict: typeof import('../api/dict')['default']
const effectScope: typeof import('vue')['effectScope']
const extendedColorOptions: typeof import('../composables/useTheme')['extendedColorOptions']
const file: typeof import('../api/file')['default']
@@ -34,9 +39,11 @@ declare global {
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
+ const getDarkerColor: typeof import('../utils/colorUtils')['getDarkerColor']
+ const getLighterColor: typeof import('../utils/colorUtils')['getLighterColor']
const getRefreshToken: typeof import('../utils/auth')['getRefreshToken']
const getToken: typeof import('../utils/storage')['getToken']
- const getUserInfo: typeof import('../utils/storage')['getUserInfo']
+ const getUserInfo: typeof import('../utils/auth')['getUserInfo']
const guessSerializerType: typeof import('@uni-helper/uni-use')['guessSerializerType']
const h: typeof import('vue')['h']
const initTheme: typeof import('../composables/useTheme')['initTheme']
@@ -46,13 +53,16 @@ declare global {
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
+ const log: typeof import('../api/log')['default']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
+ const menu: typeof import('../api/menu')['default']
const nextTick: typeof import('vue')['nextTick']
+ const notice: typeof import('../api/notice')['default']
const onActivated: typeof import('vue')['onActivated']
const onAddToFavorites: typeof import('@dcloudio/uni-app')['onAddToFavorites']
const onBackPress: typeof import('@dcloudio/uni-app')['onBackPress']
@@ -102,13 +112,14 @@ declare global {
const requireLogin: typeof import('../utils/auth')['requireLogin']
const resetTheme: typeof import('../composables/useTheme')['resetTheme']
const resolveComponent: typeof import('vue')['resolveComponent']
+ const role: typeof import('../api/role')['default']
const setAccessToken: typeof import('../utils/auth')['setAccessToken']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const setRefreshToken: typeof import('../utils/auth')['setRefreshToken']
const setThemeColor: typeof import('../composables/useTheme')['setThemeColor']
const setToken: typeof import('../utils/storage')['setToken']
- const setUserInfo: typeof import('../utils/storage')['setUserInfo']
+ const setUserInfo: typeof import('../utils/auth')['setUserInfo']
const setupStore: typeof import('../store/index')['setupStore']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
@@ -138,6 +149,7 @@ declare global {
const useClipboardData: typeof import('@uni-helper/uni-use')['useClipboardData']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
+ const useDictStore: typeof import('../store/modules/dict.store')['useDictStore']
const useDownloadFile: typeof import('@uni-helper/uni-use')['useDownloadFile']
const useGlobalData: typeof import('@uni-helper/uni-use')['useGlobalData']
const useId: typeof import('vue')['useId']
@@ -201,12 +213,11 @@ declare module 'vue' {
readonly EffectScope: UnwrapRef
readonly Storage: UnwrapRef
readonly acceptHMRUpdate: UnwrapRef
- readonly applyThemeOnPageShow: UnwrapRef
- readonly applyThemeToMiniProgram: UnwrapRef
readonly auth: UnwrapRef
readonly checkLogin: UnwrapRef
- readonly clearAll: UnwrapRef
+ readonly clearAll: UnwrapRef
readonly clearTokens: UnwrapRef
+ readonly clearUserInfo: UnwrapRef
readonly computed: UnwrapRef
readonly createApp: UnwrapRef
readonly createPinia: UnwrapRef
@@ -217,15 +228,13 @@ declare module 'vue' {
readonly defineComponent: UnwrapRef
readonly defineStore: UnwrapRef
readonly effectScope: UnwrapRef
- readonly extendedColorOptions: UnwrapRef
readonly file: UnwrapRef
readonly getAccessToken: UnwrapRef
readonly getActivePinia: UnwrapRef
readonly getCurrentInstance: UnwrapRef
readonly getCurrentScope: UnwrapRef
readonly getRefreshToken: UnwrapRef
- readonly getToken: UnwrapRef
- readonly getUserInfo: UnwrapRef
+ readonly getUserInfo: UnwrapRef
readonly h: UnwrapRef
readonly inject: UnwrapRef
readonly isLoggedIn: UnwrapRef
@@ -289,15 +298,13 @@ declare module 'vue' {
readonly setActivePinia: UnwrapRef
readonly setMapStoreSuffix: UnwrapRef
readonly setRefreshToken: UnwrapRef
- readonly setToken: UnwrapRef
- readonly setUserInfo: UnwrapRef
+ readonly setUserInfo: UnwrapRef
readonly setupStore: UnwrapRef
readonly shallowReactive: UnwrapRef
readonly shallowReadonly: UnwrapRef
readonly shallowRef: UnwrapRef
readonly store: UnwrapRef
readonly storeToRefs: UnwrapRef
- readonly themeColorOptions: UnwrapRef
readonly toRaw: UnwrapRef
readonly toRef: UnwrapRef
readonly toRefs: UnwrapRef
@@ -321,7 +328,6 @@ declare module 'vue' {
readonly useThemeStore: UnwrapRef
readonly useToast: UnwrapRef
readonly useUserStore: UnwrapRef
- readonly useWechat: UnwrapRef
readonly user: UnwrapRef
readonly watch: UnwrapRef
readonly watchEffect: UnwrapRef
diff --git a/fastapp/src/types/uni-pages.d.ts b/fastapp/src/types/uni-pages.d.ts
new file mode 100644
index 00000000..c92faa9b
--- /dev/null
+++ b/fastapp/src/types/uni-pages.d.ts
@@ -0,0 +1,36 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by vite-plugin-uni-pages
+
+interface NavigateToOptions {
+ url: "/pages/index/index" |
+ "/pages/login/index" |
+ "/pages/mine/index" |
+ "/pages/work/index" |
+ "/pages/mine/about/index" |
+ "/pages/mine/faq/index" |
+ "/pages/mine/feedback/index" |
+ "/pages/mine/profile/complete-profile" |
+ "/pages/mine/profile/index" |
+ "/pages/mine/settings/index" |
+ "/pages/mine/settings/account/index" |
+ "/pages/mine/settings/agreement/index" |
+ "/pages/mine/settings/network/index" |
+ "/pages/mine/settings/privacy/index" |
+ "/pages/mine/settings/theme/index";
+}
+interface RedirectToOptions extends NavigateToOptions {}
+
+interface SwitchTabOptions {
+ url: "/pages/index/index" | "/pages/work/index" | "/pages/mine/index"
+}
+
+type ReLaunchOptions = NavigateToOptions | SwitchTabOptions;
+
+declare interface Uni {
+ navigateTo(options: UniNamespace.NavigateToOptions & NavigateToOptions): void;
+ redirectTo(options: UniNamespace.RedirectToOptions & RedirectToOptions): void;
+ switchTab(options: UniNamespace.SwitchTabOptions & SwitchTabOptions): void;
+ reLaunch(options: UniNamespace.ReLaunchOptions & ReLaunchOptions): void;
+}
diff --git a/fastapp/src/types/vite-plugin-uni-pages.d.ts b/fastapp/src/types/vite-plugin-uni-pages.d.ts
index cf1b91a3..2d632fd4 100644
--- a/fastapp/src/types/vite-plugin-uni-pages.d.ts
+++ b/fastapp/src/types/vite-plugin-uni-pages.d.ts
@@ -1,59 +1,59 @@
// Type definitions for vite-plugin-uni-pages
// This file provides type definitions for the uni-pages plugin
-declare module '@uni-helper/vite-plugin-uni-pages' {
- import { Plugin } from 'vite'
-
+declare module "@uni-helper/vite-plugin-uni-pages" {
+ import { Plugin } from "vite";
+
export interface UniPagesOptions {
/**
* Subpackage directory
* @default 'src/pages'
*/
- subPackages?: string[]
-
+ subPackages?: string[];
+
/**
* Global style file
*/
- globalStyle?: Record
-
+ globalStyle?: Record;
+
/**
* Tabbar configuration
*/
- tabBar?: Record
-
+ tabBar?: Record;
+
/**
* Whether to merge pages.json
* @default true
*/
- mergePages?: boolean
-
+ mergePages?: boolean;
+
/**
* Whether to use uni-cloud-router
* @default false
*/
- uniCloudRouter?: boolean
+ uniCloudRouter?: boolean;
}
-
- export default function UniPages(options?: UniPagesOptions): Plugin
+
+ export default function UniPages(options?: UniPagesOptions): Plugin;
}
// Augment Uni namespace for type-safe routing
declare global {
namespace UniNamespace {
interface NavigateToOptions {
- url: string
+ url: string;
}
-
+
interface RedirectToOptions {
- url: string
+ url: string;
}
-
+
interface SwitchTabOptions {
- url: string
+ url: string;
}
-
+
interface ReLaunchOptions {
- url: string
+ url: string;
}
}
-}
\ No newline at end of file
+}
diff --git a/fastapp/src/utils/auth.ts b/fastapp/src/utils/auth.ts
index 36e015cd..c4d64e51 100644
--- a/fastapp/src/utils/auth.ts
+++ b/fastapp/src/utils/auth.ts
@@ -1,52 +1,56 @@
-import { useUserStore } from "@/store/modules/user.store";
+import { useUserStore } from "@/store";
import { Storage } from "./storage";
-import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from "@/constants";
+import { APP_ACCESS_TOKEN_KEY, APP_REFRESH_TOKEN_KEY, APP_USER_INFO } from "@/constants";
-/**
- * 获取访问令牌
- * @returns 返回访问令牌,如果不存在则返回null
- */
-export function getAccessToken(): string {
- return Storage.get(ACCESS_TOKEN_KEY);
+// 获取访问 token
+export function getAccessToken(): string | null {
+ return Storage.get(APP_ACCESS_TOKEN_KEY) || null;
}
-/**
- * 设置访问令牌
- * @param token 访问令牌
- */
+// 设置 token
export function setAccessToken(token: string): void {
- Storage.set(ACCESS_TOKEN_KEY, token);
+ return Storage.set(APP_ACCESS_TOKEN_KEY, token);
}
-/**
- * 获取刷新令牌
- * @returns 返回刷新令牌,如果不存在则返回null
- */
+// 获取刷新 token
export function getRefreshToken(): string | null {
- return Storage.get(REFRESH_TOKEN_KEY) || null;
+ return Storage.get(APP_REFRESH_TOKEN_KEY) || null;
}
-/**
- * 设置刷新令牌
- * @param token 刷新令牌
- */
+// 设置刷新 token
export function setRefreshToken(token: string): void {
- Storage.set(REFRESH_TOKEN_KEY, token);
+ return Storage.set(APP_REFRESH_TOKEN_KEY, token);
}
-/**
- * 清除所有令牌
- */
-export function clearTokens(): void {
- Storage.remove(ACCESS_TOKEN_KEY);
- Storage.remove(REFRESH_TOKEN_KEY);
+// 清除 token
+export function clearTokens() {
+ Storage.remove(APP_ACCESS_TOKEN_KEY);
+ Storage.remove(APP_REFRESH_TOKEN_KEY);
}
-/**
- * 检查用户登录状态,未登录则跳转到登录页面
- * @param silent 是否静默检查,不跳转登录页面
- * @returns 返回用户是否已登录
- */
+// 获取用户信息
+export function getUserInfo(): T | undefined {
+ return Storage.get(APP_USER_INFO);
+}
+
+// 设置用户信息
+export function setUserInfo(userInfo: any): void {
+ return Storage.set(APP_USER_INFO, userInfo);
+}
+
+// 清除用户信息
+export function clearUserInfo() {
+ return Storage.remove(APP_USER_INFO);
+}
+
+// 清除所有缓存信息
+export function clearAll() {
+ Storage.remove(APP_ACCESS_TOKEN_KEY);
+ Storage.remove(APP_REFRESH_TOKEN_KEY);
+ Storage.remove(APP_USER_INFO);
+}
+
+// 检查用户登录状态,未登录则跳转到登录页面
export function checkLogin(silent: boolean = false): boolean {
const userStore = useUserStore();
const accessToken = getAccessToken();
@@ -97,17 +101,12 @@ export function checkLogin(silent: boolean = false): boolean {
return isLoggedIn;
}
-/**
- * 检查用户是否已登录(静默检查,不跳转)
- * @returns 返回用户是否已登录
- */
+// 检查用户是否已登录(静默检查,不跳转)
export function isLoggedIn(): boolean {
return checkLogin(true);
}
-/**
- * 强制用户登录,清除无效的登录状态
- */
+// 强制用户登录,清除无效的登录状态
export function requireLogin(): void {
const userStore = useUserStore();
const accessToken = getAccessToken();
diff --git a/fastapp/src/utils/request.ts b/fastapp/src/utils/request.ts
index 529ca54e..cfe5c490 100644
--- a/fastapp/src/utils/request.ts
+++ b/fastapp/src/utils/request.ts
@@ -1,53 +1,18 @@
-import { getAccessToken } from "./auth";
-
-// 请求配置
-interface RequestOptions {
- url: string;
- method: "GET" | "POST" | "PUT" | "DELETE";
- data?: T;
- headers?: Record;
- timeout?: number;
- responseType?: "text" | "arraybuffer" | "blob" | "json";
- skipAuth?: boolean; // 标记是否跳过认证
-}
+import { ApiCode } from "@/enums/api-code.enum";
+import { getAccessToken, clearAll } from "@/utils/auth";
/**
* 请求拦截器 - 添加 Authorization 头
*/
-function request(options: RequestOptions): Promise {
+function request(options: UniApp.RequestOptions): Promise {
+ uni.showLoading({
+ title: "加载中...",
+ });
return new Promise((resolve, reject) => {
- // 构建请求头
- const header = Object.assign({}, options.headers || {});
-
- // 检查是否需要添加认证令牌
- if (!options.skipAuth) {
- const accessToken = getAccessToken();
- if (accessToken) {
- header["Authorization"] = `Bearer ${accessToken}`;
- } else {
- // 需要认证但没有令牌,跳转到登录页
- // 防止循环跳转:检查当前页面是否已经是登录页
- const currentPages = getCurrentPages();
- const currentPage = currentPages[currentPages.length - 1];
- if (!currentPage || !currentPage.route || !currentPage.route.includes("login")) {
- uni.navigateTo({
- url: "/pages/login/index",
- });
- }
- return reject(new Error("请先登录"));
- }
- }
-
- // 根据平台决定URL前缀
- let requestUrl = options.url;
- // #ifdef MP-WEIXIN
- // 微信小程序环境,使用完整URL
- requestUrl = `${import.meta.env.VITE_API_BASE_URL}${options.url}`;
- // #endif
-
- // #ifndef MP-WEIXIN
- // 非微信小程序环境,使用代理前缀
- requestUrl = `${import.meta.env.VITE_APP_BASE_API}${options.url}`;
+ // H5 使用 VITE_APP_BASE_API 作为代理路径,其他平台使用 VITE_API_BASE_URL 作为请求路径
+ let baseApi = import.meta.env.VITE_API_BASE_URL;
+ // #ifdef H5
+ baseApi = import.meta.env.VITE_APP_BASE_API;
// #endif
let timeout = Number(import.meta.env.VITE_TIMEOUT);
@@ -57,38 +22,64 @@ function request(options: RequestOptions): Promise {
// 统一处理请求
uni.request({
- url: requestUrl,
+ url: `${baseApi}${options.url}`,
method: options.method,
data: options.data,
- header: header,
+ header: {
+ ...options.header,
+ Authorization: getAccessToken() ? `Bearer ${getAccessToken()}` : "",
+ },
timeout: timeout,
responseType: options.responseType,
- success: (res: any) => {
+ success: (res: UniApp.RequestSuccessCallbackResult) => {
+ const result = res.data as ApiResponse;
// 请求成功
- if (res.statusCode >= 200 && res.statusCode < 300) {
- resolve(res.data.data);
+ if (result.code === ApiCode.SUCCESS) {
+ resolve(result.data);
}
- // 未授权错误
- else if (res.statusCode === 401) {
- // 防止循环跳转:检查当前页面是否已经是登录页
- const currentPages = getCurrentPages();
- const currentPage = currentPages[currentPages.length - 1];
- if (!currentPage || !currentPage.route || !currentPage.route.includes("login")) {
- uni.navigateTo({
- url: "/pages/login/index",
- });
- }
- reject(new Error(res.data.message || "未授权,请重新登录"));
+ // 令牌过期
+ else if (result.code === ApiCode.TOKEN_EXPIRED) {
+ // 清除所有缓存
+ clearAll();
+ // 跳转登录
+ uni.reLaunch({ url: "/pages/login/index" });
+ // 提示
+ uni.showToast({
+ title: result.msg || "登录已过期,请重新登录",
+ icon: "error",
+ });
+ reject(new Error(result.msg || "登录已过期,请重新登录"));
+ }
+ // 未授权访问
+ else if (result.code === ApiCode.UNAUTHORIZED) {
+ uni.showToast({
+ title: result.msg || "未授权访问",
+ icon: "error",
+ });
+ reject(new Error(result.msg || "未授权访问"));
}
// 其他错误
else {
- const errorMsg = res.data.message || `请求失败: ${res.statusCode}`;
- reject(new Error(errorMsg));
+ uni.showToast({
+ title: result.msg || "请求失败",
+ icon: "error",
+ });
+ reject(new Error(result.msg || "请求失败"));
}
},
- fail: (err) => {
+ fail: (err: UniApp.GeneralCallbackResult) => {
+ console.log("请求失败", err);
+ uni.showToast({
+ title: "网络请求失败",
+ icon: "none",
+ duration: 2000,
+ });
reject(new Error(err.errMsg || "网络请求失败"));
},
+ complete: () => {
+ // 请求完成,隐藏loading
+ uni.hideLoading();
+ },
});
});
}
diff --git a/fastapp/src/utils/storage.ts b/fastapp/src/utils/storage.ts
index a39f65ef..758d8642 100644
--- a/fastapp/src/utils/storage.ts
+++ b/fastapp/src/utils/storage.ts
@@ -31,42 +31,3 @@ export const Storage = {
get,
remove,
};
-
-// 为了向后兼容,导出具体的函数
-import { ACCESS_TOKEN_KEY, USER_INFO_KEY } from "@/constants";
-
-/**
- * 获取令牌
- */
-export function getToken(): string | null {
- return Storage.get(ACCESS_TOKEN_KEY) || null;
-}
-
-/**
- * 设置令牌
- */
-export function setToken(token: string): void {
- Storage.set(ACCESS_TOKEN_KEY, token);
-}
-
-/**
- * 获取用户信息
- */
-export function getUserInfo(): T | undefined {
- return Storage.get(USER_INFO_KEY);
-}
-
-/**
- * 设置用户信息
- */
-export function setUserInfo(userInfo: any): void {
- Storage.set(USER_INFO_KEY, userInfo);
-}
-
-/**
- * 清除所有数据
- */
-export function clearAll(): void {
- Storage.remove(ACCESS_TOKEN_KEY);
- Storage.remove(USER_INFO_KEY);
-}
diff --git a/fastapp/src/utils/theme.ts b/fastapp/src/utils/theme.ts
deleted file mode 100644
index d72a3d62..00000000
--- a/fastapp/src/utils/theme.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * 小程序主题工具类
- * 用于解决小程序环境中CSS变量不能动态设置的问题
- */
-
-// 注入小程序环境的全局样式
-export function applyThemeToMiniProgram(primaryColor: string) {
- // 确保在小程序环境中执行
- if (typeof document !== "undefined") return;
-
- try {
- // 设置TabBar样式
- uni.setTabBarStyle({
- color: "#000000",
- selectedColor: primaryColor,
- backgroundColor: "#ffffff",
- borderStyle: "black",
- });
-
- console.log("小程序主题色已应用:", primaryColor);
- } catch (error) {
- console.error("应用小程序主题色失败:", error);
- }
-}
-
-// 在页面展示时应用主题
-export function applyThemeOnPageShow(primaryColor: string) {
- // 各平台小程序可能需要不同处理
- const platform = uni.getSystemInfoSync().platform;
- console.log(`当前平台: ${platform}, 应用主题色: ${primaryColor}`);
-
- // 某些平台可能需要特定处理
-}
diff --git a/fastapp/tsconfig.json b/fastapp/tsconfig.json
index 070a0966..c6223b4a 100644
--- a/fastapp/tsconfig.json
+++ b/fastapp/tsconfig.json
@@ -3,7 +3,7 @@
"module": "esnext",
"moduleResolution": "node",
"target": "esnext",
- "allowJs": false,
+ "allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -13,12 +13,12 @@
"@/*": ["./src/*"]
},
"lib": ["esnext", "dom"],
- "types": ["@dcloudio/types", "@uni-helper/uni-types", "wot-design-uni/global", "./src/types/vite-plugin-uni-pages"]
+ "types": ["@dcloudio/types", "@uni-helper/uni-types", "wot-design-uni/global"]
},
"vueCompilerOptions": {
// 调整 Volar(Vue 语言服务工具)解析行为,用于为 uni-app 组件提供 TypeScript 类型
"plugins": ["@uni-helper/uni-types/volar-plugin"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
- "exclude": ["node_modules", "dist", "src/components/u-charts/*.js"]
+ "exclude": ["node_modules", "dist"]
}
diff --git a/fastapp/uni-pages.d.ts b/fastapp/uni-pages.d.ts
index 2065725a..c92faa9b 100644
--- a/fastapp/uni-pages.d.ts
+++ b/fastapp/uni-pages.d.ts
@@ -17,12 +17,13 @@ interface NavigateToOptions {
"/pages/mine/settings/account/index" |
"/pages/mine/settings/agreement/index" |
"/pages/mine/settings/network/index" |
+ "/pages/mine/settings/privacy/index" |
"/pages/mine/settings/theme/index";
}
interface RedirectToOptions extends NavigateToOptions {}
interface SwitchTabOptions {
- url: "/pages/index/index" | "/pages/mine/index"
+ url: "/pages/index/index" | "/pages/work/index" | "/pages/mine/index"
}
type ReLaunchOptions = NavigateToOptions | SwitchTabOptions;
diff --git a/fastapp/vite.config.ts b/fastapp/vite.config.ts
index 3a0334cb..b1ecd22c 100644
--- a/fastapp/vite.config.ts
+++ b/fastapp/vite.config.ts
@@ -16,6 +16,7 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise =>
host: true,
port: Number(env.VITE_APP_PORT),
open: true,
+ // 代理配置只在 H5(浏览器)开发时生效。 其他平台(如小程序、App)在开发时不使用 Vite 的开发服务器,它们直接运行在各自的环境中。
proxy: {
[env.VITE_APP_BASE_API]: {
changeOrigin: true,
@@ -36,7 +37,15 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise =>
// make sure put it before `Uni()`
UnoCss(),
UniLayouts(),
- UniPages(),
+ UniPages({
+ dts: "src/types/uni-pages.d.ts",
+ subPackages: ["src/subPages"],
+ /**
+ * 排除的页面,相对于 dir 和 subPackages
+ * @default []
+ */
+ exclude: ["**/components/**/*.*"],
+ }),
Components({
resolvers: [WotResolver()],
diff --git a/fastdocs/src/public/app_home.png b/fastdocs/src/public/app_home.png
new file mode 100644
index 00000000..e8418795
Binary files /dev/null and b/fastdocs/src/public/app_home.png differ
diff --git a/fastdocs/src/public/app_login.png b/fastdocs/src/public/app_login.png
new file mode 100644
index 00000000..58d0936e
Binary files /dev/null and b/fastdocs/src/public/app_login.png differ
diff --git a/fastdocs/src/public/app_mine.png b/fastdocs/src/public/app_mine.png
new file mode 100644
index 00000000..896ade40
Binary files /dev/null and b/fastdocs/src/public/app_mine.png differ
diff --git a/fastdocs/src/public/app_profile.png b/fastdocs/src/public/app_profile.png
new file mode 100644
index 00000000..2b92e02d
Binary files /dev/null and b/fastdocs/src/public/app_profile.png differ
diff --git a/fastdocs/src/public/app_setting.png b/fastdocs/src/public/app_setting.png
new file mode 100644
index 00000000..e75e6796
Binary files /dev/null and b/fastdocs/src/public/app_setting.png differ
diff --git a/fastdocs/src/public/app_work.png b/fastdocs/src/public/app_work.png
new file mode 100644
index 00000000..679e1047
Binary files /dev/null and b/fastdocs/src/public/app_work.png differ
diff --git a/frontend/src/api/monitor/online.ts b/frontend/src/api/monitor/online.ts
index 87949bd6..07b486e8 100644
--- a/frontend/src/api/monitor/online.ts
+++ b/frontend/src/api/monitor/online.ts
@@ -46,4 +46,5 @@ export interface OnlineUserTable {
os: string;
browser: string;
login_time: string;
+ login_type: string;
}
diff --git a/frontend/src/api/system/auth.ts b/frontend/src/api/system/auth.ts
index 03749c1b..e237746c 100644
--- a/frontend/src/api/system/auth.ts
+++ b/frontend/src/api/system/auth.ts
@@ -45,6 +45,7 @@ export interface LoginFormData {
captcha_key: string;
captcha: string;
remember: boolean;
+ login_type: string;
}
// 刷新令牌
diff --git a/frontend/src/enums/api/result.enum.ts b/frontend/src/enums/api/result.enum.ts
index cded8df8..c95d2e27 100644
--- a/frontend/src/enums/api/result.enum.ts
+++ b/frontend/src/enums/api/result.enum.ts
@@ -16,8 +16,13 @@ export const enum ResultEnum {
EXCEPTION = -1,
/**
- * 访问令牌无效或过期
+ * 未授权访问
*/
- ACCESS_TOKEN_INVALID = 401,
+ UNAUTHORIZED = 10403,
+
+ /**
+ * 令牌已过期
+ */
+ TOKEN_EXPIRED = 10401,
}
diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts
index 239040e0..10abad39 100644
--- a/frontend/src/utils/request.ts
+++ b/frontend/src/utils/request.ts
@@ -85,12 +85,15 @@ httpRequest.interceptors.response.use((response: AxiosResponse) =>
}
}
- if (data?.status_code === ResultEnum.ACCESS_TOKEN_INVALID) {
+ if (data?.status_code === ResultEnum.TOKEN_EXPIRED) {
await redirectToLogin("登录已过期,请重新登录");
return Promise.reject(new Error(data.msg));
} else if (data?.code === ResultEnum.ERROR) {
ElMessage.error(data.msg || "请求错误");
return Promise.reject(new Error(data.msg || "请求错误"));
+ } else if (data?.code === ResultEnum.UNAUTHORIZED) {
+ ElMessage.error(data.msg || "暂无权限");
+ return Promise.reject(new Error(data.msg || "请求错误"));
} else if (data?.code === ResultEnum.EXCEPTION) {
ElMessage.error(data.msg || "服务异常");
return Promise.reject(new Error(data.msg || "服务异常"));
diff --git a/frontend/src/views/monitor/online/index.vue b/frontend/src/views/monitor/online/index.vue
index e72797d2..1b366312 100644
--- a/frontend/src/views/monitor/online/index.vue
+++ b/frontend/src/views/monitor/online/index.vue
@@ -67,6 +67,7 @@
+
{{ scope.row.ipaddr }}
@@ -103,7 +104,7 @@ defineOptions({
});
import OnlineAPI, { type OnlineUserPageQuery, type OnlineUserTable } from "@/api/monitor/online";
-import { ElMessage, ElMessageBox } from "element-plus";
+import { ElMessageBox } from "element-plus";
const queryFormRef = ref();
const total = ref(0);
@@ -119,6 +120,7 @@ const tableColumns = ref([
{ label: '选择框', prop: 'selection', show: true },
{ label: '序号', prop: 'index', show: true },
{ label: '会话编号', prop: 'session_id', show: true },
+ { label: '登录类型', prop: 'login_type', show: true },
{ label: '登录名称', prop: 'name', show: true },
{ label: '用户账号', prop: 'user_name', show: true },
{ label: '主机', prop: 'ipaddr', show: true },
diff --git a/frontend/src/views/system/auth/components/Login.vue b/frontend/src/views/system/auth/components/Login.vue
index 5272716e..482d916c 100644
--- a/frontend/src/views/system/auth/components/Login.vue
+++ b/frontend/src/views/system/auth/components/Login.vue
@@ -145,7 +145,8 @@ const loginForm = reactive({
password: "",
captcha: "",
captcha_key: "",
- remember: true
+ remember: true,
+ login_type: "PC端",
});
const captchaState = reactive({