diff --git a/env/.env b/env/.env
index 03c7ecf..1cfc505 100644
--- a/env/.env
+++ b/env/.env
@@ -1,15 +1,25 @@
-VITE_APP_TITLE = 'unibest'
+VITE_APP_TITLE = '芋道管理系统'
VITE_APP_PORT = 9000
VITE_UNI_APPID = 'H57F2ACE4'
-VITE_WX_APPID = 'wxa2abb91f64032a2b'
+VITE_WX_APPID = 'wx90bcb2127c1d8720'
# h5部署网站的base,配置到 manifest.config.ts 里的 h5.router.base
VITE_APP_PUBLIC_BASE=/unibest/
-VITE_SERVER_BASEURL = 'https://ukw0y1.laf.run'
-VITE_UPLOAD_BASEURL = 'https://ukw0y1.laf.run/upload'
+VITE_SERVER_BASEURL = 'http://localhost:48080/admin-api'
+VITE_UPLOAD_BASEURL = 'http://localhost:48080/upload'
# h5是否需要配置代理
VITE_APP_PROXY=false
-VITE_APP_PROXY_PREFIX = '/api'
+VITE_APP_PROXY_PREFIX = '/admin-api'
+
+
+# 租户开关
+VITE_APP_TENANT_ENABLE=true
+# 验证码的开关
+VITE_APP_CAPTCHA_ENABLE=true
+# 默认账户密码
+VITE_APP_DEFAULT_LOGIN_TENANT = 芋道源码
+VITE_APP_DEFAULT_LOGIN_USERNAME = admin
+VITE_APP_DEFAULT_LOGIN_PASSWORD = admin123
\ No newline at end of file
diff --git a/package.json b/package.json
index 7d8b8da..04106a1 100644
--- a/package.json
+++ b/package.json
@@ -107,6 +107,7 @@
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@iconify-json/carbon": "^1.1.35",
+ "@iconify-json/ic": "^1.2.1",
"@rollup/rollup-darwin-x64": "^4.18.0",
"@types/node": "^20.14.2",
"@types/wechat-miniprogram": "^3.4.7",
diff --git a/pages.config.ts b/pages.config.ts
index c84a271..bd75c40 100644
--- a/pages.config.ts
+++ b/pages.config.ts
@@ -30,28 +30,34 @@ export default defineUniPages({
list: [
// 注意tabbar路由需要使用 layout:tabbar 布局
{
- pagePath: 'pages/index/index',
- text: '首页',
- icon: 'home',
- iconType: 'wot',
- },
- {
- pagePath: 'pages/about/about',
- text: '关于',
- icon: 'i-carbon-code',
+ pagePath: 'pages/message/index',
+ text: '消息',
+ icon: 'i-ic-outline-message',
+ iconType: 'unocss',
+ },
+ {
+ pagePath: 'pages/colab/index',
+ text: '协作',
+ icon: 'i-ic-outline-handshake',
+ iconType: 'unocss',
+ },
+ {
+ pagePath: 'pages/work/index',
+ text: '工作台',
+ icon: 'i-ic-baseline-apps',
+ iconType: 'unocss',
+ },
+ {
+ pagePath: 'pages/contacts/index',
+ text: '通讯录',
+ icon: 'i-ic-baseline-contact-page',
iconType: 'unocss',
},
- // {
- // pagePath: 'pages/my/index',
- // text: '我的',
- // icon: '/static/logo.svg',
- // iconType: 'local',
- // },
{
pagePath: 'pages/my/index',
text: '我的',
- icon: 'iconfont icon-my',
- iconType: 'iconfont',
+ icon: 'i-ic-baseline-person',
+ iconType: 'unocss',
},
],
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8b40ecd..576f4d6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -78,6 +78,9 @@ importers:
'@iconify-json/carbon':
specifier: ^1.1.35
version: 1.1.35
+ '@iconify-json/ic':
+ specifier: ^1.2.1
+ version: 1.2.1
'@rollup/rollup-darwin-x64':
specifier: ^4.18.0
version: 4.18.0
@@ -1185,6 +1188,9 @@ packages:
'@iconify-json/carbon@1.1.35':
resolution: {integrity: sha512-zKqioWceqFRiLJvxpjcCpVP3j2YcokYshlbwSAHBhOih5XNUymUS3hm1kpV4KljMI1xWH96UcozHaaf6x4YzdA==}
+ '@iconify-json/ic@1.2.1':
+ resolution: {integrity: sha512-UjL/bjJP/T5EV881+hTzcfTKVo0KEUjhnMiJcLtPzNgPtU2KZZmRx8BSKKR61H4CN/5FTEbyawGyG0aEt3SwGQ==}
+
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -6887,6 +6893,10 @@ snapshots:
dependencies:
'@iconify/types': 2.0.0
+ '@iconify-json/ic@1.2.1':
+ dependencies:
+ '@iconify/types': 2.0.0
+
'@iconify/types@2.0.0': {}
'@iconify/utils@2.1.24':
diff --git a/src/App.vue b/src/App.vue
index d0dface..21c41bb 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -13,6 +13,10 @@ onHide(() => {
diff --git a/src/pages/about/components/request.vue b/src/pages/about/components/request.vue
deleted file mode 100644
index 89a656d..0000000
--- a/src/pages/about/components/request.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-
-{
- layout: 'demo',
- style: {
- navigationBarTitleText: '请求',
- },
-}
-
-
-
-
- 使用的是 laf 云后台
- 我的推荐码,可以获得佣金
-
-
-
- {{ recommendUrl }}
-
-
-
-
- {{ recommendUrl }}
-
-
-
- 发送请求
-
- loading...
-
- 请求数据如下
- {{ JSON.stringify(data) }}
-
-
- 重置数据
-
-
-
-
diff --git a/src/pages/about/components/upload.vue b/src/pages/about/components/upload.vue
deleted file mode 100644
index 07f81f5..0000000
--- a/src/pages/about/components/upload.vue
+++ /dev/null
@@ -1,30 +0,0 @@
-
-{
- layout: 'default',
- style: {
- navigationBarTitleText: '上传-状态一体化',
- },
-}
-
-
-
-
- 选择图片并上传
- 上传...
-
- 上传后返回的接口数据:
- {{ data }}
-
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/colab/index.vue b/src/pages/colab/index.vue
new file mode 100644
index 0000000..5645a48
--- /dev/null
+++ b/src/pages/colab/index.vue
@@ -0,0 +1,23 @@
+
+
+{
+ layout: 'tabbar',
+ style: {
+ navigationBarTitleText: '协作',
+ },
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/contacts/components/ContactsItem.vue b/src/pages/contacts/components/ContactsItem.vue
new file mode 100644
index 0000000..d5d539a
--- /dev/null
+++ b/src/pages/contacts/components/ContactsItem.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ props.item.name }}
+
+ {{ props.item.post }}
+
+
+
+
+ {{ props.item.name }}
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/contacts/index.vue b/src/pages/contacts/index.vue
new file mode 100644
index 0000000..1cc263c
--- /dev/null
+++ b/src/pages/contacts/index.vue
@@ -0,0 +1,114 @@
+
+
+{
+ layout: 'tabbar',
+ style: {
+ navigationStyle: 'custom',
+ navigationBarTitleText: '通讯录',
+ },
+}
+
+
+
+
+
+
+
+
+
+
+
+
+ 通讯录
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/contacts/mock.js b/src/pages/contacts/mock.js
new file mode 100644
index 0000000..1896f17
--- /dev/null
+++ b/src/pages/contacts/mock.js
@@ -0,0 +1,70 @@
+export default [
+ {
+ id: 1,
+ name: '芋道集团',
+ isLeaf: false,
+ children: [
+ {
+ id: 11,
+ parentId: 1,
+ name: 'IT中心',
+ isLeaf: false,
+ children: [
+ {
+ id: 111,
+ parentId: 11,
+ name: '马青楷',
+ post: 'CTO',
+ avatar: '/static/images/avatar3.jpg',
+ isLeaf: true,
+ },
+ {
+ id: 112,
+ parentId: 11,
+ name: '王伟',
+ post: '前端开发',
+ avatar: '/static/images/avatar2.jpg',
+ isLeaf: true,
+ },
+ {
+ id: 113,
+ parentId: 11,
+ name: '王博',
+ post: '技术主管',
+ avatar: '/static/images/avatar1.jpg',
+ isLeaf: true,
+ },
+ ],
+ },
+ {
+ id: 12,
+ parentId: 1,
+ name: '行政部门',
+ isLeaf: true,
+ },
+ ],
+ },
+ {
+ id: 2,
+ name: '中软集团',
+ isLeaf: false,
+ children: [
+ {
+ id: 21,
+ parentId: 2,
+ name: '中软IT',
+ isLeaf: false,
+ children: [
+ {
+ id: 211,
+ parentId: 21,
+ name: '马青楷',
+ post: 'CTO',
+ isLeaf: true,
+ avatar: '/static/images/avatar3.jpg',
+ },
+ ],
+ },
+ ],
+ },
+]
diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue
deleted file mode 100644
index 893ed8f..0000000
--- a/src/pages/index/index.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-{
- layout: 'tabbar',
- style: {
- navigationStyle: 'custom',
- navigationBarTitleText: '首页',
- },
-}
-
-
-
-
-
-
- unibest
- 最好用的 uniapp 开发模板
-
- {{ description }}
-
- 当前平台是:
- {{ PLATFORM.platform }}
-
-
- 模板分支是:
- tabbar
-
-
-
-
-
-
-
diff --git a/src/pages/login/components/LoginForm.vue b/src/pages/login/components/LoginForm.vue
new file mode 100644
index 0000000..b449cbb
--- /dev/null
+++ b/src/pages/login/components/LoginForm.vue
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+ handleInputFocus('tenantName')"
+ />
+
+
+
+ handleInputFocus('username')"
+ />
+
+
+
+ handleInputFocus('password')"
+ />
+
+
+
+
+
+
+ 登录
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/login/index.vue b/src/pages/login/index.vue
new file mode 100644
index 0000000..9479b3b
--- /dev/null
+++ b/src/pages/login/index.vue
@@ -0,0 +1,74 @@
+
+{
+ style: {
+ navigationBarTitleText: '登录页面',
+ },
+}
+
+
+
+
+
+
+
+ 你好
+ 欢迎登录芋道快速开发平台
+
+
+
+
+
+
+
+
+
+
+ 手机号登录
+
+
+
+
+
+
+
+
+
+
+
+ 已阅读并同意
+ 《用户协议》
+ 和
+ 《隐私政策》
+
+
+
+
+
+
+
+
diff --git a/src/pages/message/components/MessageItem.vue b/src/pages/message/components/MessageItem.vue
new file mode 100644
index 0000000..d3222f7
--- /dev/null
+++ b/src/pages/message/components/MessageItem.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+ {{ props.item.title }}
+
+
+ {{ props.item.content }}
+
+
+
+
+
+ {{ props.item.time }}
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/message/index.vue b/src/pages/message/index.vue
new file mode 100644
index 0000000..c784323
--- /dev/null
+++ b/src/pages/message/index.vue
@@ -0,0 +1,41 @@
+
+
+{
+ layout: 'tabbar',
+ style: {
+ navigationBarTitleText: '消息',
+ },
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/message/mock.js b/src/pages/message/mock.js
new file mode 100644
index 0000000..6589646
--- /dev/null
+++ b/src/pages/message/mock.js
@@ -0,0 +1,92 @@
+export default [
+ {
+ title: 'IT中心工作群',
+ content: '今天下午2点到5点,开部门会议,请各位提前准备好汇报材料。',
+ time: '昨天 15:30',
+ avatar: '/static/images/avatar1.jpg',
+ },
+ {
+ title: '产品设计讨论组',
+ content: '关于新功能的设计稿,大家有什么建议可以在群里提出来。',
+ time: '昨天 18:45',
+ avatar: '/static/images/avatar2.jpg',
+ },
+ {
+ title: '前端开发小组',
+ content: '明天开始进行项目代码review,请各位准备好自己的代码。',
+ time: '今天 09:00',
+ avatar: '/static/images/avatar3.jpg',
+ },
+ {
+ title: '公司年会筹备组',
+ content: '年会节目征集开始了,欢迎大家积极报名参加!',
+ time: '今天 10:20',
+ avatar: '/static/images/avatar4.jpg',
+ },
+ {
+ title: '技术分享会',
+ content: '本周五下午的技术分享会主题是“Vue 3的新特性”,不要错过哦。',
+ time: '今天 14:30',
+ avatar: '/static/images/avatar5.jpg',
+ },
+ {
+ title: 'IT中心工作群',
+ content: '今天下午2点到5点,开部门会议,请各位提前准备好汇报材料。',
+ time: '昨天 15:30',
+ avatar: '/static/images/avatar1.jpg',
+ },
+ {
+ title: '产品设计讨论组',
+ content: '关于新功能的设计稿,大家有什么建议可以在群里提出来。',
+ time: '昨天 18:45',
+ avatar: '/static/images/avatar2.jpg',
+ },
+ {
+ title: '前端开发小组',
+ content: '明天开始进行项目代码review,请各位准备好自己的代码。',
+ time: '今天 09:00',
+ avatar: '/static/images/avatar3.jpg',
+ },
+ {
+ title: '公司年会筹备组',
+ content: '年会节目征集开始了,欢迎大家积极报名参加!',
+ time: '今天 10:20',
+ avatar: '/static/images/avatar4.jpg',
+ },
+ {
+ title: '技术分享会',
+ content: '本周五下午的技术分享会主题是“Vue 3的新特性”,不要错过哦。',
+ time: '今天 14:30',
+ avatar: '/static/images/avatar5.jpg',
+ },
+ {
+ title: 'IT中心工作群',
+ content: '今天下午2点到5点,开部门会议,请各位提前准备好汇报材料。',
+ time: '昨天 15:30',
+ avatar: '/static/images/avatar2.jpg',
+ },
+ {
+ title: '产品设计讨论组',
+ content: '关于新功能的设计稿,大家有什么建议可以在群里提出来。',
+ time: '昨天 18:45',
+ avatar: '/static/images/avatar1.jpg',
+ },
+ {
+ title: '前端开发小组',
+ content: '明天开始进行项目代码review,请各位准备好自己的代码。',
+ time: '今天 09:00',
+ avatar: '/static/images/avatar3.jpg',
+ },
+ {
+ title: '公司年会筹备组',
+ content: '年会节目征集开始了,欢迎大家积极报名参加!',
+ time: '今天 10:20',
+ avatar: '/static/images/avatar2.jpg',
+ },
+ {
+ title: '技术分享会',
+ content: '本周五下午的技术分享会主题是“Vue 3的新特性”,不要错过哦。',
+ time: '今天 14:30',
+ avatar: '/static/images/avatar4.jpg',
+ },
+]
diff --git a/src/pages/my/index.vue b/src/pages/my/index.vue
index 169627b..19cb4d1 100644
--- a/src/pages/my/index.vue
+++ b/src/pages/my/index.vue
@@ -8,11 +8,121 @@
- 我的页面
+
+
+
+
+
+
+
+
+ {{ user?.nickname }}
+
+
+ 芋道集团
+
+
+
+
+
+
+
+
+
+
+ 点击登录
+
+
+
+
+
+
+ 应用设置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 退出登录
+
+
+
+
+
+
diff --git a/src/pages/work/index.vue b/src/pages/work/index.vue
new file mode 100644
index 0000000..9ec4f98
--- /dev/null
+++ b/src/pages/work/index.vue
@@ -0,0 +1,49 @@
+
+
+{
+ layout: 'tabbar',
+ style: {
+ navigationBarTitleText: '工作台',
+ },
+}
+
+
+
+
+
+
+
+
+ 系统管理
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/service/login/LoginAPI.ts b/src/service/login/LoginAPI.ts
new file mode 100644
index 0000000..4b844b6
--- /dev/null
+++ b/src/service/login/LoginAPI.ts
@@ -0,0 +1,21 @@
+import { http, httpGet, httpPost } from '@/utils/http'
+
+export const login = (data) => {
+ return http({
+ url: '/system/auth/login',
+ method: 'POST',
+ data,
+ })
+}
+
+export const loginOut = () => {
+ return httpPost('/system/auth/logout')
+}
+
+export const getInfo = (): Promise => {
+ return httpGet('/system/auth/get-permission-info')
+}
+
+export const getTenantIdByName = (name: string) => {
+ return httpGet('/system/tenant/get-id-by-name', { name })
+}
diff --git a/src/static/images/avatar1.jpg b/src/static/images/avatar1.jpg
new file mode 100644
index 0000000..9d0c8ad
Binary files /dev/null and b/src/static/images/avatar1.jpg differ
diff --git a/src/static/images/avatar2.jpg b/src/static/images/avatar2.jpg
new file mode 100644
index 0000000..62ccbbf
Binary files /dev/null and b/src/static/images/avatar2.jpg differ
diff --git a/src/static/images/avatar3.jpg b/src/static/images/avatar3.jpg
new file mode 100644
index 0000000..898248d
Binary files /dev/null and b/src/static/images/avatar3.jpg differ
diff --git a/src/static/images/avatar4.jpg b/src/static/images/avatar4.jpg
new file mode 100644
index 0000000..23d5047
Binary files /dev/null and b/src/static/images/avatar4.jpg differ
diff --git a/src/static/images/avatar5.jpg b/src/static/images/avatar5.jpg
new file mode 100644
index 0000000..673ffda
Binary files /dev/null and b/src/static/images/avatar5.jpg differ
diff --git a/src/static/images/contacts.png b/src/static/images/contacts.png
new file mode 100644
index 0000000..312a5a1
Binary files /dev/null and b/src/static/images/contacts.png differ
diff --git a/src/static/images/empty.png b/src/static/images/empty.png
new file mode 100644
index 0000000..9e16702
Binary files /dev/null and b/src/static/images/empty.png differ
diff --git a/src/static/images/login-bg.png b/src/static/images/login-bg.png
new file mode 100644
index 0000000..c749afd
Binary files /dev/null and b/src/static/images/login-bg.png differ
diff --git a/src/store/user.ts b/src/store/user.ts
index 82bd873..0c6e902 100644
--- a/src/store/user.ts
+++ b/src/store/user.ts
@@ -1,35 +1,74 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
+import { getAccessToken, removeToken } from '@/utils/auth'
+import { getInfo, loginOut } from '@/service/login/LoginAPI'
-const initState = { nickname: '', avatar: '' }
+const initState = {
+ permissions: [],
+ roles: [],
+ isSetUser: false,
+ user: {
+ id: 0,
+ avatar: '',
+ nickname: '',
+ deptId: 0,
+ },
+}
export const useUserStore = defineStore(
'user',
() => {
- const userInfo = ref({ ...initState })
+ // state
- const setUserInfo = (val: IUserInfo) => {
- userInfo.value = val
+ const userInfo = ref({ ...initState })
+
+ // actions methods
+ const setUserInfoAction = async () => {
+ if (!getAccessToken()) {
+ // 获取不到accessToken,直接返回
+ resetState()
+ return
+ }
+
+ const data = await getInfo()
+ userInfo.value = {
+ ...data,
+ isSetUser: true,
+ }
}
- const clearUserInfo = () => {
- userInfo.value = { ...initState }
+ const setUserAvatarAction = async (avatar: string) => {
+ userInfo.value.user.avatar = avatar
}
- // 一般没有reset需求,不需要的可以删除
- const reset = () => {
- userInfo.value = { ...initState }
- }
- const isLogined = computed(() => !!userInfo.value.token)
+ const setUserNicknameAction = async (nickname: string) => {
+ userInfo.value.user.nickname = nickname
+ }
+
+ const LogOut = async () => {
+ await loginOut()
+ removeToken()
+ resetState()
+ }
+
+ const resetState = () => {
+ console.log('initState', initState)
+ userInfo.value = initState
+ console.log('重置userInfo', userInfo.value)
+ }
+
+ // 暴露到外面的方法
return {
userInfo,
- setUserInfo,
- clearUserInfo,
- isLogined,
- reset,
+ setUserInfoAction,
+ setUserAvatarAction,
+ setUserNicknameAction,
+ LogOut,
+ resetState,
}
},
{
+ // 持久化
persist: true,
},
)
diff --git a/src/style/index.scss b/src/style/index.scss
index 86184d9..4a9f82c 100644
--- a/src/style/index.scss
+++ b/src/style/index.scss
@@ -15,4 +15,8 @@ page {
// 修改按钮背景色
// --wot-button-primary-bg-color: green;
+
+ //
+ --wot-search-light-bg: #f2f3f7;
+ --wot-search-input-radius: 5px;
}
diff --git a/src/types/system.d.ts b/src/types/system.d.ts
new file mode 100644
index 0000000..5a18f0f
--- /dev/null
+++ b/src/types/system.d.ts
@@ -0,0 +1,20 @@
+type UserVO = {
+ id: number
+ avatar: string
+ nickname: string
+ deptId: number
+}
+
+// USER 缓存
+type UserInfoVO = {
+ permissions: string[]
+ roles: string[]
+ isSetUser: boolean
+ user: UserVO
+}
+
+declare namespace JSX {
+ interface IntrinsicElements {
+ block: any // 或者更具体的类型定义
+ }
+}
diff --git a/src/types/uni-pages.d.ts b/src/types/uni-pages.d.ts
index f73e4a9..b2589ec 100644
--- a/src/types/uni-pages.d.ts
+++ b/src/types/uni-pages.d.ts
@@ -4,14 +4,17 @@
// Generated by vite-plugin-uni-pages
interface NavigateToOptions {
- url: "/pages/index/index" |
- "/pages/about/about" |
+ url: "/pages/work/index" |
+ "/pages/colab/index" |
+ "/pages/contacts/index" |
+ "/pages/login/index" |
+ "/pages/message/index" |
"/pages/my/index";
}
interface RedirectToOptions extends NavigateToOptions {}
interface SwitchTabOptions {
- url: "/pages/index/index" | "/pages/about/about" | "/pages/my/index"
+ url: "/pages/message/index" | "/pages/colab/index" | "/pages/work/index" | "/pages/contacts/index" | "/pages/my/index"
}
type ReLaunchOptions = NavigateToOptions | SwitchTabOptions;
diff --git a/src/utils/auth.ts b/src/utils/auth.ts
new file mode 100644
index 0000000..9799614
--- /dev/null
+++ b/src/utils/auth.ts
@@ -0,0 +1,37 @@
+const AccessTokenKey = 'ACCESS_TOKEN'
+const RefreshTokenKey = 'REFRESH_TOKEN'
+const TenantIdKey = 'tenantId'
+
+// ========== Token 相关 ==========
+
+// 获取 Token
+export function getAccessToken() {
+ return uni.getStorageSync(AccessTokenKey)
+}
+
+// 获取 RefreshToken
+export function getRefreshToken() {
+ return uni.getStorageSync(RefreshTokenKey)
+}
+
+// 设置 Token
+export function setToken(token) {
+ uni.setStorageSync(AccessTokenKey, token.accessToken)
+ uni.setStorageSync(RefreshTokenKey, token.refreshToken)
+}
+
+// 移除 Token
+export function removeToken() {
+ uni.removeStorageSync(AccessTokenKey)
+ uni.removeStorageSync(RefreshTokenKey)
+}
+
+// ========== 租户相关 ==========
+
+export const getTenantId = () => {
+ return uni.getStorageSync(TenantIdKey)
+}
+
+export const setTenantId = (username: string) => {
+ uni.setStorageSync(TenantIdKey, username)
+}
diff --git a/src/utils/common.ts b/src/utils/common.ts
new file mode 100644
index 0000000..a122276
--- /dev/null
+++ b/src/utils/common.ts
@@ -0,0 +1,30 @@
+/**
+ * 显示模态弹窗
+ *
+ * @param content 提示的标题
+ */
+export function showConfirm(content: string) {
+ return new Promise((resolve, reject) => {
+ uni.showModal({
+ title: '提示',
+ content,
+ cancelText: '取消',
+ confirmText: '确定',
+ success: function (res) {
+ resolve(res)
+ },
+ })
+ })
+}
+
+/**
+ * 显示消息提示框
+ *
+ * @param content 提示的标题
+ */
+export function toast(content: string) {
+ uni.showToast({
+ title: content,
+ icon: 'none',
+ })
+}
diff --git a/src/utils/errorCode.ts b/src/utils/errorCode.ts
new file mode 100644
index 0000000..abb3409
--- /dev/null
+++ b/src/utils/errorCode.ts
@@ -0,0 +1,7 @@
+export default {
+ '401': '认证失败,无法访问系统资源',
+ '403': '当前操作没有权限',
+ '404': '访问资源不存在',
+ '500': '服务器错误',
+ default: '系统未知错误,请反馈给管理员',
+}
diff --git a/src/utils/http.ts b/src/utils/http.ts
index 4e3f38c..e2e6fe3 100644
--- a/src/utils/http.ts
+++ b/src/utils/http.ts
@@ -1,8 +1,27 @@
import { CustomRequestOptions } from '@/interceptors/request'
+import { useUserStore } from '@/store'
+import { getAccessToken, getRefreshToken, setToken } from '@/utils/auth'
+import { showConfirm, toast } from '@/utils/common'
+import { getEvnBaseUrl } from '@/utils'
+import errorCode from './errorCode'
+
+/*
+ 双 token 刷新说明:
+ 1. token 和 tenantId 都是在拦截器中赋值
+ 2. 后端并不直接更改 http statusCode,而是返回一个重新包装后的对象,所以不能使用失败回调函数就行判断token是否过期需要重新获取
+ 3. 应该在 success 中,解析 res,如果 code 是 401,则说明 access token 过期,需要重新获取
+*/
+
+// 是否正在刷新中
+let isRefreshToken = false
+// 请求的方法栈
+let requestList = []
+// 请求基准地址
+const baseUrl = getEvnBaseUrl()
export const http = (options: CustomRequestOptions) => {
// 1. 返回 Promise 对象
- return new Promise>((resolve, reject) => {
+ return new Promise((resolve, reject) => {
uni.request({
...options,
dataType: 'json',
@@ -10,25 +29,97 @@ export const http = (options: CustomRequestOptions) => {
responseType: 'json',
// #endif
// 响应成功
- success(res) {
- // 状态码 2xx,参考 axios 的设计
- if (res.statusCode >= 200 && res.statusCode < 300) {
- // 2.1 提取核心数据 res.data
- resolve(res.data as IResData)
- } else if (res.statusCode === 401) {
- // 401错误 -> 清理用户信息,跳转到登录页
- // userStore.clearUserInfo()
- // uni.navigateTo({ url: '/pages/login/login' })
- reject(res)
- } else {
- // 其他错误 -> 根据后端错误信息轻提示
- !options.hideErrorToast &&
- uni.showToast({
- icon: 'none',
- title: (res.data as IResData).msg || '请求错误',
+ async success(res) {
+ const resp = res.data as IResData
+ const { data, code, msg } = resp
+
+ if (code === 401) {
+ // 未授权
+ const userStore = useUserStore()
+
+ if (!isRefreshToken) {
+ // 设置为正在刷新
+ isRefreshToken = true
+
+ // 1. 如果获取不到刷新令牌,则只能执行登出操作
+ if (!getRefreshToken()) {
+ showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then(
+ (res: any) => {
+ if (res.confirm) {
+ // 清除缓存起来的用户信息
+ userStore.LogOut().then((res) => {
+ uni.reLaunch({ url: '/pages/login/index' })
+ })
+ }
+ },
+ )
+ }
+
+ // 2. 刷新accesstoken
+ try {
+ const refreshTokenRes: any = await refreshToken()
+ if (refreshTokenRes.data.code !== 0) {
+ // 如果获取不到refresh token,就直接跳转到登录页面
+ showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then(
+ (res: any) => {
+ if (res.confirm) {
+ // 清除缓存起来的用户信息
+ userStore.LogOut().then((res) => {
+ uni.reLaunch({ url: '/pages/login/index' })
+ })
+ }
+ },
+ )
+ const rejMsg = '无效的会话,或者会话已过期,请重新登录。'
+ reject(rejMsg)
+ }
+
+ // 2.1 刷新成功,则回放队列的请求 + 当前请求
+ setToken(refreshTokenRes.data.data)
+ options.header.Authorization = 'Bearer ' + getAccessToken()
+ // 将所有的请求方法都进行调用
+ requestList.forEach((cb) => {
+ cb()
+ })
+ // 调用之后就清空
+ requestList = []
+
+ // 到达这里,属于是请求已经失败了,需要重新请求的
+ // 所以这里是一个递归调用
+ resolve(http(options))
+ } catch (e) {
+ // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
+ // 2.2 刷新失败,只回放队列的请求
+ requestList.forEach((cb) => {
+ cb()
+ })
+ const rejMsg = '无效的会话,或者会话已过期,请重新登录。'
+ reject(rejMsg)
+ } finally {
+ requestList = []
+ isRefreshToken = false
+ }
+ } else {
+ // 添加到队列,等待刷新获取到新的令牌
+ return new Promise((resolve) => {
+ requestList.push(() => {
+ options.header.Authorization = 'Bearer ' + getAccessToken()
+ resolve(http(options))
+ })
})
- reject(res)
+ }
+ } else if (code === 500) {
+ // 服务器错误
+ toast(msg)
+ reject(errorCode['500'])
+ } else if (code !== 0) {
+ // 其他的错误
+ // ps:yudao 的 success 的 code,默认是 0 而不是 200
+ toast(msg)
+ reject(code)
}
+
+ resolve(data)
},
// 响应失败
fail(err) {
@@ -78,3 +169,13 @@ export const httpPost = (
http.get = httpGet
http.post = httpPost
+
+const refreshToken = async () => {
+ return await uni.request({
+ method: 'POST',
+ url: baseUrl + '/system/auth/refresh-token?refreshToken=' + getRefreshToken(),
+ header: {
+ 'tenant-id': 1,
+ },
+ })
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 70131bf..8283d7d 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -131,16 +131,16 @@ export const getEvnBaseUrl = () => {
const {
miniProgram: { envVersion },
} = uni.getAccountInfoSync()
-
+ // 开发、体验、正式版 三种不同的小程序都可以配置不同的后端 url
switch (envVersion) {
case 'develop':
- baseUrl = 'https://ukw0y1.laf.run'
+ baseUrl = 'http://localhost:48080/admin-api'
break
case 'trial':
- baseUrl = 'https://ukw0y1.laf.run'
+ baseUrl = 'http://localhost:48080/admin-api'
break
case 'release':
- baseUrl = 'https://ukw0y1.laf.run'
+ baseUrl = 'http://localhost:48080/admin-api'
break
}
}