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 等工具,确保代码规范与质量。 ## 项目截图 -![](https://www.youlai.tech/storage/blog/2025/04/30/app.jpg) +![截图](https://www.youlai.tech/storage/blog/2025/04/30/app.jpg) ## 项目文档 @@ -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 @@