+ type: 'info',
+ message: `
${msgTxt}
${confirmTxt}
- ${cancelTxt}
`,
- dangerouslyUseHTMLString: true
+ dangerouslyUseHTMLString: true,
+ showClose: true
})
document
- .querySelector('.toggle-language-message .content-box .confirm')
+ .querySelector('.custom-message-container .language .confirm')
?.addEventListener('click', () => {
setLanguage(language)
store.dispatch('SET_USER_SETTINGS', {
@@ -98,16 +95,18 @@ const initSetLanguage = () => {
})
msgInstance.close()
})
-
- document
- .querySelector('.toggle-language-message .content-box .cancel')
- ?.addEventListener('click', () => {
- msgInstance.close()
- })
}
})
}
+const initSetLanguage = () => {
+ // 初始化设置
+ setLanguage(userSettings.language)
+
+ // 根据 IP 自动设置
+ setLanguageByIP()
+}
+
const init = () => {
elementPlusSizeHandle(window.innerWidth)
window.addEventListener(
@@ -119,6 +118,7 @@ const init = () => {
setThemeMode()
initSetLanguage()
+ initGithubAuthorize()
}
watch(
diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts
index 001d94e0..cee8c010 100644
--- a/src/auto-imports.d.ts
+++ b/src/auto-imports.d.ts
@@ -18,5 +18,6 @@ declare global {
const IEpPicture: typeof import('~icons/ep/picture')['default']
const IEpPostcard: typeof import('~icons/ep/postcard')['default']
const IEpSetting: typeof import('~icons/ep/setting')['default']
+ const IEpSwitch: typeof import('~icons/ep/switch')['default']
const IEpUpload: typeof import('~icons/ep/upload')['default']
}
diff --git a/src/common/api/repo.ts b/src/common/api/repo.ts
index becafbc4..394a3141 100644
--- a/src/common/api/repo.ts
+++ b/src/common/api/repo.ts
@@ -116,7 +116,8 @@ export const createRepo = (token: string) => {
description: INIT_REPO_DESC,
private: false
},
- headers: { Authorization: `token ${token}` },
- success422: true
+ headers: { Authorization: `Bearer ${token}` },
+ success422: true,
+ noShowErrorMsg: true
})
}
diff --git a/src/common/api/user.ts b/src/common/api/user.ts
index dfcbf380..ee278f3f 100644
--- a/src/common/api/user.ts
+++ b/src/common/api/user.ts
@@ -9,7 +9,7 @@ export const getGitHubUserInfo = (token: string) => {
return request({
url: '/user',
method: 'GET',
- headers: { Authorization: `token ${token}` }
+ headers: { Authorization: `Bearer ${token}` }
})
}
diff --git a/src/common/constant/settings.ts b/src/common/constant/settings.ts
index 464e425a..fcf39501 100644
--- a/src/common/constant/settings.ts
+++ b/src/common/constant/settings.ts
@@ -12,3 +12,8 @@ export const IMG_UPLOAD_MAX_SIZE: number = 30 // MB
* 图片重命名最大长度
*/
export const RENAME_MAX_LENGTH: number = 18
+
+/**
+ * GitHub APP 授权 Token 过期时间(8 小时)
+ */
+export const GITHUB_AUTHORIZE_EXPIRE: number = 8 * 60 * 60 * 1000
diff --git a/src/common/constant/storage.ts b/src/common/constant/storage.ts
index 118cfb37..b4f443af 100644
--- a/src/common/constant/storage.ts
+++ b/src/common/constant/storage.ts
@@ -5,3 +5,5 @@ export const LS_PICX_MANAGEMENT = `${PICX_PREFIX}MANAGEMENT`
export const LS_PICX_SETTINGS = `${PICX_PREFIX}SETTINGS`
export const SS_PICX_UPLOADED = `${PICX_PREFIX}UPLOADED`
export const SS_TOOLBOX_IMG_LIST = `${PICX_PREFIX}TOOLBOX_IMG_LIST`
+export const SS_PICX_AUTHORIZATION = `${PICX_PREFIX}AUTHORIZATION`
+export const LS_PICX_AUTHORIZATION = `${PICX_PREFIX}AUTHORIZATION`
diff --git a/src/common/model/user-config.ts b/src/common/model/user-config.ts
index 9fed0061..3d6314a4 100644
--- a/src/common/model/user-config.ts
+++ b/src/common/model/user-config.ts
@@ -36,6 +36,7 @@ export enum DirModeEnum {
export interface UserConfigInfoModel {
token: string
+ id: string
owner: string
email: string
name: string
diff --git a/src/common/model/vite-config.ts b/src/common/model/vite-config.ts
index c53a2203..f29cb6ff 100644
--- a/src/common/model/vite-config.ts
+++ b/src/common/model/vite-config.ts
@@ -1,5 +1,10 @@
export declare type Recordable
= Record
export declare interface ViteEnv {
- VITE_USE_PWA?: boolean
+ VITE_USE_PWA?: boolean // 是否启用 PWA
+ VITE_CLIENT_ID?: string // PicX GitHub APP Client ID
+ VITE_REDIRECT_URI?: string // PicX GitHub APP Callback URL
+ VITE_AUTHORIZE_URI?: string // GitHub Authorize URI
+ VITE_INSTALL_URL?: string
+ VITE_INSTALL_URL_USER?: string
}
diff --git a/src/components.d.ts b/src/components.d.ts
index 027e92c4..8fe16647 100644
--- a/src/components.d.ts
+++ b/src/components.d.ts
@@ -9,12 +9,12 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
+ AuthorizationStatusBar: typeof import('./components/authorization-status-bar/authorization-status-bar.vue')['default']
Base64Tool: typeof import('./components/tools/base64-tool/base64-tool.vue')['default']
CloudSettingsBar: typeof import('./components/cloud-settings-bar/cloud-settings-bar.vue')['default']
CompressConfigBox: typeof import('./components/compress-config-box/compress-config-box.vue')['default']
CompressTool: typeof import('./components/tools/compress-tool/compress-tool.vue')['default']
CopyImageLink: typeof import('./components/copy-image-link/copy-image-link.vue')['default']
- Deploy: typeof import('./components/deploy/deploy.vue')['default']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton']
@@ -43,7 +43,6 @@ declare module '@vue/runtime-core' {
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
- ElSpace: typeof import('element-plus/es')['ElSpace']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
@@ -53,17 +52,21 @@ declare module '@vue/runtime-core' {
FolderCard: typeof import('./components/folder-card/folder-card.vue')['default']
GettingImages: typeof import('./components/getting-images/getting-images.vue')['default']
HeaderContent: typeof import('./components/header-content/header-content.vue')['default']
+ IEpAim: typeof import('~icons/ep/aim')['default']
IEpCaretBottom: typeof import('~icons/ep/caret-bottom')['default']
IEpCaretLeft: typeof import('~icons/ep/caret-left')['default']
IEpCheck: typeof import('~icons/ep/check')['default']
+ IEpCircleCheckFilled: typeof import('~icons/ep/circle-check-filled')['default']
IEpClose: typeof import('~icons/ep/close')['default']
IEpCopyDocument: typeof import('~icons/ep/copy-document')['default']
IEpDelete: typeof import('~icons/ep/delete')['default']
- IEpInfoFilled: typeof import('~icons/ep/info-filled')['default']
+ IEpDocument: typeof import('~icons/ep/document')['default']
+ IEpLink: typeof import('~icons/ep/link')['default']
IEpMoreFilled: typeof import('~icons/ep/more-filled')['default']
IEpOperation: typeof import('~icons/ep/operation')['default']
IEpRefresh: typeof import('~icons/ep/refresh')['default']
IEpRemove: typeof import('~icons/ep/remove')['default']
+ IEpSwitch: typeof import('~icons/ep/switch')['default']
IEpUpload: typeof import('~icons/ep/upload')['default']
IEpUploadFilled: typeof import('~icons/ep/upload-filled')['default']
IEpUserFilled: typeof import('~icons/ep/user-filled')['default']
diff --git a/src/components/authorization-status-bar/authorization-status-bar.styl b/src/components/authorization-status-bar/authorization-status-bar.styl
new file mode 100644
index 00000000..33306e4b
--- /dev/null
+++ b/src/components/authorization-status-bar/authorization-status-bar.styl
@@ -0,0 +1,31 @@
+.authorization-status-box {
+ display flex
+ align-items center
+ justify-content space-between
+ padding 2rem 0 2rem 12rem
+ color var(--text-color-2)
+ font-size 14rem
+ border-color var(--text-color-4)
+ border-style solid
+ border-width 1rem
+ border-radius 6rem
+
+
+ &.success {
+ color var(--el-color-success)
+ background var(--el-color-success-light-9)
+ border-color var(--el-color-success)
+ }
+
+ &.warning {
+ color var(--el-color-warning)
+ background var(--el-color-warning-light-9)
+ border-color var(--el-color-warning)
+ }
+
+ &.error {
+ color var(--el-color-danger)
+ background var(--el-color-danger-light-9)
+ border-color var(--el-color-danger)
+ }
+}
diff --git a/src/components/authorization-status-bar/authorization-status-bar.vue b/src/components/authorization-status-bar/authorization-status-bar.vue
new file mode 100644
index 00000000..5ecce975
--- /dev/null
+++ b/src/components/authorization-status-bar/authorization-status-bar.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
+ {{ $t('authorization.text_4') }}
+
+ {{ $t('authorization.text_3') }}
+
+ {{ $t('authorization.text_5') }}
+
+
+
+ {{
+ $t('authorization.text_7')
+ }}
+
+
+
+
+
+
+
diff --git a/src/components/header-content/header-content.vue b/src/components/header-content/header-content.vue
index 8f47862e..1c8cc43e 100644
--- a/src/components/header-content/header-content.vue
+++ b/src/components/header-content/header-content.vue
@@ -85,7 +85,7 @@
>
{{ $t('header.logout') }}
-
+
{{ $t('header.login') }}
@@ -111,10 +111,14 @@ const persistUserSettings = () => {
store.dispatch('USER_SETTINGS_PERSIST')
}
+// 退出登录
const logout = () => {
store.dispatch('LOGOUT')
- router.push('/config')
+ router.push('/login')
document.body.click()
+ setTimeout(() => {
+ window.location.reload()
+ })
}
const jumpOwnerRepo = () => {
diff --git a/src/components/nav-content/nav-content.vue b/src/components/nav-content/nav-content.vue
index 9acf2258..960a81c8 100644
--- a/src/components/nav-content/nav-content.vue
+++ b/src/components/nav-content/nav-content.vue
@@ -66,6 +66,7 @@ import { useRouter } from 'vue-router'
import { useStore } from '@/stores'
import { ElementPlusSizeEnum } from '@/common/model'
import { navInfoList } from './nav-content.data'
+import i18n from '@/plugins/vue/i18n'
const router = useRouter()
const store = useStore()
@@ -89,13 +90,13 @@ const onNavClick = (e: any) => {
if (path === '/management') {
if (userConfigInfo.selectedRepo === '') {
- ElMessage.warning('请选择一个仓库')
+ ElMessage.warning(i18n.global.t('upload.message2'))
router.push('/config')
return
}
if (userConfigInfo.selectedDir === '') {
- ElMessage.warning('目录不能为空')
+ ElMessage.warning(i18n.global.t('upload.message3'))
router.push('/config')
return
}
diff --git a/src/locales/en.json b/src/locales/en.json
index 3471ebb7..5f2de2f5 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -23,6 +23,7 @@
"usage_count": "Usage count"
},
"nav": {
+ "login": "Log in",
"config": "Image Hosting Config",
"upload": "Upload Image",
"management": "Image Hosting Management",
@@ -255,5 +256,25 @@
"zh-TW": "Traditional Chinese",
"en": "English"
},
- "toggle_language_msg": "We detected that your IP is located in {region}. Do you want to switch to {language}?"
+ "toggle_language_msg": "We detected that your IP is located in {region}. Do you want to switch to {language}?",
+ "authorization": {
+ "msg_1": "GitHub OAuth authorization login has expired, please re-authorize",
+ "msg_2": "PicX APP is not installed on GitHub and has no operation permission yet",
+ "msg_3": "PicX GitHub APP is installed successfully. Do you want to authorize the login?",
+ "msg_4": "Authorization failed, try again later",
+ "btn_1": "Install now",
+ "loading_1": "Authorizing login...",
+ "text_1": "GitHub OAuth authorization login",
+ "text_2": "Fill in GitHub Token to log in",
+ "text_3": "Login using GitHub OAuth authorization",
+ "text_4": "GitHub Token has expired, please authorize your login again",
+ "text_5": "Logging in using GitHub Token",
+ "text_6": "Return to login page",
+ "text_7": "Switch",
+ "text_8": "PicX GitHub APP must be installed to log in using GitHub OAuth authorization",
+ "text_9": "Filling in the GitHub Token to log in must generate a Token with operation permissions",
+ "text_10": "View tutorial",
+ "text_11": "Install PicX GitHub APP",
+ "text_12": "Create GitHub Token"
+ }
}
diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json
index 9c4bcdcf..efc1e381 100644
--- a/src/locales/zh-CN.json
+++ b/src/locales/zh-CN.json
@@ -23,6 +23,7 @@
"usage_count": "使用次数"
},
"nav": {
+ "login": "登录",
"config": "图床配置",
"upload": "上传图片",
"management": "图床管理",
@@ -256,5 +257,25 @@
"zh-TW": "中文繁体",
"en": "英文"
},
- "toggle_language_msg": "检测到你的 IP 所属地为{region},是否切换{language}?"
+ "toggle_language_msg": "检测到你的 IP 所属地为{region},是否切换{language}?",
+ "authorization": {
+ "msg_1": "GitHub OAuth 授权登录已过期,请重新授权",
+ "msg_2": "未在 GitHub 安装 PicX APP,暂无操作权限",
+ "msg_3": "PicX GitHub APP 安装成功,是否进行授权登录?",
+ "msg_4": "授权失败,稍后重试",
+ "btn_1": "立即安装",
+ "loading_1": "正在授权登录...",
+ "text_1": "GitHub OAuth 授权登录",
+ "text_2": "填写 GitHub Token 登录",
+ "text_3": "正在使用 GitHub OAuth 授权登录",
+ "text_4": "GitHub Token 已过期,请重新授权登录",
+ "text_5": "正在使用填写 GitHub Token 登录",
+ "text_6": "返回登录页",
+ "text_7": "切换",
+ "text_8": "使用 GitHub OAuth 授权登录必须安装 PicX GitHub APP",
+ "text_9": "填写 GitHub Token 登录必须生成具有操作权限的 Token",
+ "text_10": "查看教程",
+ "text_11": "安装 PicX GitHub APP",
+ "text_12": "创建 GitHub Token"
+ }
}
diff --git a/src/locales/zh-TW.json b/src/locales/zh-TW.json
index 25ea1c60..217b2098 100644
--- a/src/locales/zh-TW.json
+++ b/src/locales/zh-TW.json
@@ -23,6 +23,7 @@
"usage_count": "使用次數"
},
"nav": {
+ "login": "登錄",
"config": "圖床配置",
"upload": "上傳圖片",
"management": "圖床管理",
@@ -256,5 +257,25 @@
"zh-TW": "中文繁體",
"en": "英文"
},
- "toggle_language_msg": "檢測到你的 IP 所屬地為{region},是否切換{language}?"
+ "toggle_language_msg": "檢測到你的 IP 所屬地為{region},是否切換{language}?",
+ "authorization": {
+ "msg_1": "GitHub OAuth 授權登入已過期,請重新授權",
+ "msg_2": "未在 GitHub 安裝 PicX APP,暫無操作權限",
+ "msg_3": "PicX GitHub APP 安裝成功,是否進行授權登入?",
+ "msg_4": "授權失敗,稍後重試",
+ "btn_1": "立即安裝",
+ "loading_1": "正在授權登入...",
+ "text_1": "GitHub OAuth 授權登入",
+ "text_2": "填入 GitHub Token 登入",
+ "text_3": "正在使用 GitHub OAuth 授權登入",
+ "text_4": "GitHub Token 已過期,請重新授權登入",
+ "text_5": "正在使用填入 GitHub Token 登入",
+ "text_6": "返回登入頁面",
+ "text_7": "切換",
+ "text_8": "使用 GitHub OAuth 授權登入必須安裝 PicX GitHub APP",
+ "text_9": "填寫 GitHub Token 登入必須產生具有操作權限的 Token",
+ "text_10": "檢視教學",
+ "text_11": "安裝 PicX GitHub APP",
+ "text_12": "建立 GitHub Token"
+ }
}
diff --git a/src/router/index.ts b/src/router/index.ts
index fce7839e..01ba7b02 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -1,5 +1,5 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
-import config from '@/views/my-config/my-config.vue'
+import login from '@/views/picx-login/picx-login.vue'
import upload from '@/views/upload-image/upload-image.vue'
import management from '@/views/imgs-management/imgs-management.vue'
import settings from '@/views/my-settings/my-settings.vue'
@@ -13,15 +13,16 @@ import { setWindowTitle } from '@/utils'
const routes: Array = [
{
path: '/',
- name: 'index',
- redirect: {
- name: 'upload'
+ name: 'login',
+ component: login,
+ meta: {
+ title: `nav.login`
}
},
{
path: '/config',
name: 'config',
- component: config,
+ component: () => import('@/views/my-config/my-config.vue'),
meta: {
title: `nav.config`
}
diff --git a/src/stores/index.ts b/src/stores/index.ts
index b380baae..8e294905 100644
--- a/src/stores/index.ts
+++ b/src/stores/index.ts
@@ -8,6 +8,7 @@ import uploadAreaActiveModule from './modules/upload-area-active'
import userSettingsModule from './modules/user-settings'
import toolboxImageListModule from './modules/toolbox-image-list'
import uploadImageListModule from './modules/upload-image-list'
+import githubAuthorizeModule from './modules/github-authorize'
// Create a new store instance.
export const store = createStore({
@@ -18,7 +19,8 @@ export const store = createStore({
uploadAreaActiveModule,
userSettingsModule,
toolboxImageListModule,
- uploadImageListModule
+ uploadImageListModule,
+ githubAuthorizeModule
},
state: {
diff --git a/src/stores/modules/github-authorize/index.ts b/src/stores/modules/github-authorize/index.ts
new file mode 100644
index 00000000..575826de
--- /dev/null
+++ b/src/stores/modules/github-authorize/index.ts
@@ -0,0 +1,59 @@
+import { Module } from 'vuex'
+import RootStateTypes from '@/stores/types'
+import GitHubAuthorizeStateTypes, { GitHubAuthorizationInfo } from './types'
+import { deepAssignObject, getLocal, setLocal } from '@/utils'
+import { LS_PICX_AUTHORIZATION } from '@/common/constant'
+
+const initAuthorizationInfo = (): GitHubAuthorizationInfo => {
+ const initInfo: GitHubAuthorizationInfo = {
+ authorized: false,
+ installed: false,
+ token: '',
+ tokenCreateTime: 0,
+ code: '',
+ codeCreateTime: 0,
+ installationId: '',
+ manualToken: '',
+ isAutoAuthorize: false
+ }
+
+ const LSInfo = getLocal(LS_PICX_AUTHORIZATION)
+
+ if (LSInfo) {
+ deepAssignObject(initInfo, LSInfo)
+ return initInfo
+ }
+
+ return initInfo
+}
+
+const githubAuthorizeModule: Module = {
+ state: {
+ authorizationInfo: initAuthorizationInfo()
+ },
+
+ actions: {
+ // 设置 GitHub APP 授权状态信息
+ SET_GITHUB_AUTHORIZATION_INFO({ state, dispatch }, authorizationInfo: GitHubAuthorizationInfo) {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const key in authorizationInfo) {
+ if (Object.hasOwn(state.authorizationInfo, key)) {
+ // @ts-ignore
+ state.authorizationInfo[key] = authorizationInfo[key]
+ }
+ }
+ dispatch('GITHUB_AUTHORIZATION_INFO_PERSIST')
+ },
+
+ // 持久化存储 GitHub APP 授权状态信息
+ GITHUB_AUTHORIZATION_INFO_PERSIST({ state }) {
+ setLocal(LS_PICX_AUTHORIZATION, state.authorizationInfo)
+ }
+ },
+
+ getters: {
+ getGitHubAuthorizationInfo: (state): GitHubAuthorizationInfo => state.authorizationInfo
+ }
+}
+
+export default githubAuthorizeModule
diff --git a/src/stores/modules/github-authorize/types.ts b/src/stores/modules/github-authorize/types.ts
new file mode 100644
index 00000000..ffbc8904
--- /dev/null
+++ b/src/stores/modules/github-authorize/types.ts
@@ -0,0 +1,15 @@
+export interface GitHubAuthorizationInfo {
+ authorized: boolean
+ token: string
+ tokenCreateTime: number
+ code: string
+ codeCreateTime: number
+ installed: boolean
+ installationId: string
+ manualToken: string
+ isAutoAuthorize: boolean
+}
+
+export default interface GitHubAuthorizeStateTypes {
+ authorizationInfo: GitHubAuthorizationInfo
+}
diff --git a/src/stores/modules/user-config-info/index.ts b/src/stores/modules/user-config-info/index.ts
index f008e25b..a0198029 100644
--- a/src/stores/modules/user-config-info/index.ts
+++ b/src/stores/modules/user-config-info/index.ts
@@ -8,6 +8,7 @@ import { LS_PICX_CONFIG, NEW_DIR_COUNT_MAX } from '@/common/constant'
const initUserConfigInfo = (): UserConfigInfoModel => {
const initConfig: UserConfigInfoModel = {
token: '',
+ id: '',
owner: '',
email: '',
name: '',
@@ -87,7 +88,7 @@ const userConfigInfoModule: Module = {
},
// 设置用户配置信息
- SET_USER_CONFIG_INFO({ state, dispatch }, configInfo: UserConfigInfoStateTypes) {
+ SET_USER_CONFIG_INFO({ state, dispatch }, configInfo: UserConfigInfoModel) {
// eslint-disable-next-line no-restricted-syntax
for (const key in configInfo) {
// eslint-disable-next-line no-prototype-builtins
diff --git a/src/stores/types.ts b/src/stores/types.ts
index 273cd43d..fb202b9a 100644
--- a/src/stores/types.ts
+++ b/src/stores/types.ts
@@ -3,6 +3,7 @@ import UserConfigInfoStateTypes from './modules/user-config-info/types'
import UploadAreaActiveStateTypes from './modules/upload-area-active/types'
import ToolboxImageListStateTypes from './modules/toolbox-image-list/types'
import UploadImageListStateTypes from './modules/upload-image-list/types'
+import GitHubAuthorizeStateTypes from './modules/github-authorize/types'
export default interface RootStateTypes {
rootName: string
@@ -14,4 +15,5 @@ export interface AllStateTypes extends RootStateTypes {
uploadAreaActiveModule: UploadAreaActiveStateTypes
toolboxImageListModule: ToolboxImageListStateTypes
uploadImageListModule: UploadImageListStateTypes
+ githubAuthorizeModule: GitHubAuthorizeStateTypes
}
diff --git a/src/styles/base.styl b/src/styles/base.styl
index 011913e1..e4f30f16 100644
--- a/src/styles/base.styl
+++ b/src/styles/base.styl
@@ -197,15 +197,8 @@ li {
}
-.toggle-language-message {
+.custom-message-container {
box-sizing border-box
- background var(--background-color)
- border-radius 3rem
- box-shadow var(--el-box-shadow-light)
-
- .el-icon {
- display none
- }
.content-box {
line-height 2
@@ -215,8 +208,9 @@ li {
}
.btn {
+ display inline-block
margin-left 2rem
- padding 2rem 4rem
+ padding 0 6rem
font-size 12rem
border-style solid
border-width 1rem
@@ -224,6 +218,7 @@ li {
cursor pointer
}
+
.confirm {
color var(--el-color-primary)
background var(--el-color-primary-light-9)
@@ -236,6 +231,7 @@ li {
}
}
+
.cancel {
color var(--el-color-info)
background var(--el-color-info-light-9)
diff --git a/src/styles/element-plus.styl b/src/styles/element-plus.styl
index 3d70cb60..eaaa4240 100644
--- a/src/styles/element-plus.styl
+++ b/src/styles/element-plus.styl
@@ -6,3 +6,13 @@
--el-color-primary-light-8 #d9e2ff
--el-color-primary-light-9 #ecefff
}
+
+.el-message__icon {
+ width 15rem
+ height 15rem
+
+ svg {
+ width 15rem
+ height 15rem
+ }
+}
diff --git a/src/utils/request/axios.ts b/src/utils/request/axios.ts
index f006cbbd..a2393cb8 100644
--- a/src/utils/request/axios.ts
+++ b/src/utils/request/axios.ts
@@ -18,7 +18,7 @@ axios.interceptors.request.use(
if (userConfig) {
const { token } = userConfig
if (config.baseURL?.includes(baseURL) && token) {
- config.headers.Authorization = `token ${token}`
+ config.headers.Authorization = `Bearer ${token}`
}
}
return config
diff --git a/src/utils/storage.ts b/src/utils/storage.ts
index f04485e3..cfc598a3 100644
--- a/src/utils/storage.ts
+++ b/src/utils/storage.ts
@@ -1,26 +1,35 @@
/**
- * 获取 sessionStorage 的值
+ * 获取 localStorage 值
* @param key
*/
-export const getSession = (key: string) => {
- const temp = window.sessionStorage.getItem(key)
+export const getLocal = (key: string) => {
+ const temp = window.localStorage.getItem(key)
return temp ? JSON.parse(temp) : null
}
/**
- * 设置 sessionStorage
+ * 设置 localStorage
* @param key
* @param value
*/
-export const setSession = (key: string, value: any) => {
- sessionStorage.setItem(key, JSON.stringify(value))
+export const setLocal = (key: string, value: any) => {
+ localStorage.setItem(key, JSON.stringify(value))
}
/**
- * 获取 localStorage 值
+ * 获取 sessionStorage 的值
* @param key
*/
-export const getLocal = (key: string) => {
- const temp = window.localStorage.getItem(key)
+export const getSession = (key: string) => {
+ const temp = window.sessionStorage.getItem(key)
return temp ? JSON.parse(temp) : null
}
+
+/**
+ * 设置 sessionStorage
+ * @param key
+ * @param value
+ */
+export const setSession = (key: string, value: any) => {
+ sessionStorage.setItem(key, JSON.stringify(value))
+}
diff --git a/src/views/github-authorize/github-authorize.styl b/src/views/github-authorize/github-authorize.styl
new file mode 100644
index 00000000..e69de29b
diff --git a/src/views/github-authorize/github-authorize.vue b/src/views/github-authorize/github-authorize.vue
new file mode 100644
index 00000000..8a7216cf
--- /dev/null
+++ b/src/views/github-authorize/github-authorize.vue
@@ -0,0 +1,22 @@
+
+
+ Query
+
+
+
+
+
+
diff --git a/src/views/my-config/my-config.util.ts b/src/views/my-config/my-config.util.ts
index f9c36dab..f4f35f77 100644
--- a/src/views/my-config/my-config.util.ts
+++ b/src/views/my-config/my-config.util.ts
@@ -11,34 +11,36 @@ import { createRepo, getGitHubUserInfo, initEmptyRepo } from '@/common/api'
import { INIT_REPO_BARNCH, INIT_REPO_NAME } from '@/common/constant'
import { formatDatetime } from '@/utils'
import router from '@/router'
+import i18n from '@/plugins/vue/i18n'
const userConfigInfo = computed(() => store.getters.getUserConfigInfo).value
/**
* 重置图床配置
*/
-export const resetConfig = () => {
- store.dispatch('LOGOUT')
+export const resetConfig = async () => {
+ await store.dispatch('LOGOUT')
}
/**
* 持久化用户图床配置信息
*/
-export const persistUserConfigInfo = () => {
- store.dispatch('USER_CONFIG_INFO_PERSIST')
+export const persistUserConfigInfo = async () => {
+ await store.dispatch('USER_CONFIG_INFO_PERSIST')
}
/**
* 保存用户信息
* @param userInfo
*/
-export function saveUserInfo(userInfo: any) {
+export async function saveUserInfo(userInfo: any) {
userConfigInfo.logined = true
+ userConfigInfo.id = userInfo.id
userConfigInfo.owner = userInfo.login
userConfigInfo.name = userInfo.name
userConfigInfo.email = userInfo.email
userConfigInfo.avatarUrl = userInfo.avatar_url
- persistUserConfigInfo()
+ await persistUserConfigInfo()
}
/**
@@ -58,18 +60,18 @@ export const initReHandConfig = () => {
/**
* 前往 上传图片 页面
*/
-export const goUploadPage = async ($t: any) => {
+export const goUploadPage = async () => {
const { selectedDir, dirMode } = userConfigInfo
- let warningMessage: string = $t('config.message6')
+ let warningMessage: string = i18n.global.t('config.message6')
if (selectedDir === '') {
// eslint-disable-next-line default-case
switch (dirMode) {
case DirModeEnum.newDir:
- warningMessage = $t('config.message7')
+ warningMessage = i18n.global.t('config.message7')
break
case DirModeEnum.repoDir:
- warningMessage = $t('config.message8', { repo: userConfigInfo.selectedRepo })
+ warningMessage = i18n.global.t('config.message8', { repo: userConfigInfo.selectedRepo })
break
}
ElMessage.warning({ message: warningMessage })
@@ -78,20 +80,61 @@ export const goUploadPage = async ($t: any) => {
}
}
+/**
+ * GitHub APP 安装状态处理
+ * @param repoInfo
+ * @param authorized
+ * @param token
+ */
+export const installedStatusHandle = async (repoInfo: any, authorized: boolean, token: string) => {
+ if (authorized && token) {
+ if (repoInfo) {
+ await store.dispatch('SET_GITHUB_AUTHORIZATION_INFO', {
+ installed: true
+ })
+ } else {
+ const msgInstance = ElMessage({
+ customClass: 'custom-message-container',
+ duration: 0,
+ offset: 20,
+ type: 'warning',
+ message: `
+ ${i18n.global.t('authorization.msg_2')}
+
+ ${i18n.global.t('authorization.btn_1')}
+
+
`,
+ dangerouslyUseHTMLString: true
+ })
+
+ document
+ .querySelector('.custom-message-container .authorization .confirm')
+ ?.addEventListener('click', () => {
+ msgInstance.close()
+ let url = import.meta.env.VITE_INSTALL_URL as string
+ if (userConfigInfo.id) {
+ url = import.meta.env.VITE_INSTALL_URL_USER + userConfigInfo.id
+ }
+ window.location.href = url
+ })
+ }
+ }
+}
+
/**
* 一键自动配置图床
*/
-export const oneClickAutoConfig = async ($t: any) => {
+export const oneClickAutoConfig = async () => {
const { token } = userConfigInfo
if (!token) {
- ElMessage.error({ message: $t('config.message1') })
+ ElMessage.error({ message: i18n.global.t('config.message1') })
return
}
const loading = ElLoading.service({
lock: true,
- text: $t('config.loading6')
+ text: i18n.global.t('config.loading6')
})
try {
@@ -100,18 +143,31 @@ export const oneClickAutoConfig = async ($t: any) => {
if (!userInfo) {
loading.close()
- ElMessage.error({ message: $t('config.message2') })
+ ElMessage.error({ message: i18n.global.t('config.message2') })
return
}
- saveUserInfo(userInfo)
+ if (!store.getters.getGitHubAuthorizationInfo.isAutoAuthorize) {
+ await store.dispatch('SET_GITHUB_AUTHORIZATION_INFO', {
+ manualToken: userConfigInfo.token
+ })
+ }
+
+ await saveUserInfo(userInfo)
const repoInfo = await createRepo(userConfigInfo.token)
console.log('createRepo >> ', repoInfo)
+ const authorizationInfo = computed(() => store.getters.getGitHubAuthorizationInfo).value
+ const { token, authorized } = authorizationInfo
+
+ await installedStatusHandle(repoInfo, authorized, token)
+
if (!repoInfo) {
loading.close()
- ElMessage.error({ message: $t('config.message3') })
+ if (!(authorized && token)) {
+ ElMessage.error({ message: i18n.global.t('config.message3') })
+ }
return
}
@@ -123,13 +179,13 @@ export const oneClickAutoConfig = async ($t: any) => {
userConfigInfo.selectedDir = formatDatetime('yyyyMMdd')
userConfigInfo.dirMode = DirModeEnum.autoDir
userConfigInfo.dirList = []
- persistUserConfigInfo()
+ await persistUserConfigInfo()
await initEmptyRepo(userConfigInfo, false)
loading.close()
- ElMessage.success({ message: $t('config.message4') })
+ ElMessage.success({ message: i18n.global.t('config.message4') })
await router.push('/upload')
} catch (err) {
- ElMessage.error({ message: $t('config.message5') })
+ ElMessage.error({ message: i18n.global.t('config.message5') })
console.error('oneClickAutoConfig >> ', err)
}
}
diff --git a/src/views/my-config/my-config.vue b/src/views/my-config/my-config.vue
index 89c7c41e..48158ea6 100644
--- a/src/views/my-config/my-config.vue
+++ b/src/views/my-config/my-config.vue
@@ -1,10 +1,13 @@
+
+
+
+
{{ reConfig ? $t('config.autoConfiguration1') : $t('config.autoConfiguration2') }}
@@ -205,7 +209,7 @@
plain
:disabled="btnDisabled"
type="primary"
- @click="goUploadPage($t)"
+ @click="goUploadPage"
v-if="userConfigInfo.selectedRepo"
>
{{ $t('confirm') }}
@@ -221,23 +225,25 @@ import { useStore } from '@/stores'
import { BranchModeEnum, DirModeEnum } from '@/common/model'
import { formatDatetime } from '@/utils'
import {
+ getAllRepoList,
getBranchInfoList,
- getGitHubUserInfo,
- initEmptyRepo,
getDirInfoList,
- getAllRepoList
+ getGitHubUserInfo,
+ initEmptyRepo
} from '@/common/api'
import { GH_PAGES, INIT_REPO_BARNCH } from '@/common/constant'
import {
goUploadPage,
- persistUserConfigInfo,
initReHandConfig,
+ oneClickAutoConfig,
+ persistUserConfigInfo,
resetConfig,
saveUserInfo,
- oneClickAutoConfig,
- setLabelWidth,
- setLabelPosition
+ setLabelPosition,
+ setLabelWidth
} from '@/views/my-config/my-config.util'
+import router from '@/router'
+import { isAuthorizeExpire } from '@/views/picx-login/picx-login.util'
const store = useStore()
const instance = getCurrentInstance()
@@ -296,7 +302,7 @@ async function getRepoList(owner: string) {
userInfoLoading.value = false
if (repoList) {
userConfigInfo.repoList = repoList
- persistUserConfigInfo()
+ await persistUserConfigInfo()
} else {
ElMessage.error({ message: instance?.proxy?.$t('config.message9') })
}
@@ -310,7 +316,7 @@ async function getDirList() {
if (dirList) {
userConfigInfo.dirList = dirList
}
- persistUserConfigInfo()
+ await persistUserConfigInfo()
}
async function getBranchList(repo: string) {
@@ -332,7 +338,7 @@ async function getBranchList(repo: string) {
await initEmptyRepo(userConfigInfo)
}
dirModeChange(dirMode)
- persistUserConfigInfo()
+ await persistUserConfigInfo()
}
async function getUserInfo() {
@@ -354,7 +360,13 @@ async function getUserInfo() {
return
}
- saveUserInfo(userInfo)
+ if (!store.getters.getGitHubAuthorizationInfo.isAutoAuthorize) {
+ await store.dispatch('SET_GITHUB_AUTHORIZATION_INFO', {
+ manualToken: userConfigInfo.token
+ })
+ }
+
+ await saveUserInfo(userInfo)
await getRepoList(userInfo.login)
}
@@ -372,13 +384,21 @@ async function selectBranch(branch: string) {
repoDirCascaderKey.value = userConfigInfo.selectedBranch
userConfigInfo.selectedDir = userConfigInfo.dirList[0].value
userConfigInfo.selectedDirList = [userConfigInfo.selectedDir]
- persistUserConfigInfo()
+ await persistUserConfigInfo()
+}
+
+const authorizeAutoConfig = () => {
+ const { token, isAutoAuthorize } = computed(() => store.getters.getGitHubAuthorizationInfo).value
+
+ if (isAutoAuthorize && token && !isAuthorizeExpire() && router.currentRoute.value.query.auto) {
+ oneClickAutoConfig()
+ }
}
watch(
() => logined,
- (_n) => {
- if (!_n) {
+ (nv) => {
+ if (!nv) {
userInfoLoading.value = false
dirLoading.value = false
branchLoading.value = false
@@ -387,11 +407,12 @@ watch(
)
onMounted(() => {
- if (!userConfigInfo.token) {
- setTimeout(() => {
- tokenInputRef.value!.focus()
- }, 100)
- }
+ setTimeout(() => {
+ if (!userConfigInfo.token || router.currentRoute.value.query.focus === '1') {
+ tokenInputRef.value?.focus()
+ }
+ authorizeAutoConfig()
+ }, 100)
})
diff --git a/src/views/picx-login/picx-login.model.ts b/src/views/picx-login/picx-login.model.ts
new file mode 100644
index 00000000..535c2e7b
--- /dev/null
+++ b/src/views/picx-login/picx-login.model.ts
@@ -0,0 +1,10 @@
+export enum UrlTypeEnum {
+ // eslint-disable-next-line no-unused-vars
+ installGitHubAppURL,
+ // eslint-disable-next-line no-unused-vars
+ oauthLoginDocs,
+ // eslint-disable-next-line no-unused-vars
+ generateTokenURL,
+ // eslint-disable-next-line no-unused-vars
+ tokenLoginDocs
+}
diff --git a/src/views/picx-login/picx-login.styl b/src/views/picx-login/picx-login.styl
new file mode 100644
index 00000000..cc215e72
--- /dev/null
+++ b/src/views/picx-login/picx-login.styl
@@ -0,0 +1,51 @@
+.login-container {
+ display grid
+ grid-gap 20rem
+ grid-template-rows repeat(1, 1fr)
+ grid-template-columns repeat(2, 1fr)
+
+ .box-item {
+ display flex
+ flex-direction column
+ width 100%
+ height 100%
+ color var(--text-color-4)
+ background var(--background-color-2)
+ border-radius 12rem
+
+ .tips-box {
+ display flex
+ flex-direction column
+ align-items center
+ justify-content center
+ padding 10rem
+ font-size 13rem
+
+ .tip-item {
+ display flex
+ align-items center
+ margin-bottom 8rem
+
+ .el-icon {
+ margin-right 4rem
+ }
+
+ .install-status {
+ margin-right 0
+ margin-left 4rem
+ color var(--el-color-success)
+ }
+
+ &.link {
+ color var(--el-color-primary)
+ font-weight bold
+ cursor pointer
+
+ &:hover {
+ border-bottom 1rem solid var(--el-color-primary)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/views/picx-login/picx-login.util.ts b/src/views/picx-login/picx-login.util.ts
new file mode 100644
index 00000000..7d478c18
--- /dev/null
+++ b/src/views/picx-login/picx-login.util.ts
@@ -0,0 +1,142 @@
+import { computed } from 'vue'
+import axios from 'axios'
+import { store } from '@/stores'
+import { GITHUB_AUTHORIZE_EXPIRE } from '@/common/constant'
+import router from '@/router'
+import i18n from '@/plugins/vue/i18n'
+
+const redirect_uri = import.meta!.env.VITE_REDIRECT_URI
+const authorize_api = 'https://apis.xpoet.cn/api/github-authorize'
+
+/**
+ * 判断授权获取的 Token 是否已过期
+ */
+export const isAuthorizeExpire = () => {
+ const { tokenCreateTime } = computed(() => store.getters.getGitHubAuthorizationInfo).value
+ return Date.now() - tokenCreateTime > GITHUB_AUTHORIZE_EXPIRE
+}
+
+/**
+ * GitHub APP 授权获取 Token
+ */
+export const githubAppAuthorize = () => {
+ const authorize_uri = import.meta!.env.VITE_AUTHORIZE_URI
+ const client_id = import.meta!.env.VITE_CLIENT_ID
+ window.location.href = `${authorize_uri}?client_id=${client_id}&redirect_uri=${redirect_uri}`
+}
+
+/**
+ * GitHub APP 授权回调处理
+ */
+export const githubAppAuthorizeCallback = async () => {
+ const params = new URLSearchParams(window.location.search)
+ const code = params.get('code')
+ const setup_action = params.get('setup_action')
+ const installation_id = params.get('installation_id')
+
+ // GitHub APP 安装之后的回调处理
+ if (setup_action === 'install' && installation_id && !code) {
+ await store.dispatch('SET_GITHUB_AUTHORIZATION_INFO', {
+ installed: true,
+ installationId: installation_id
+ })
+
+ ElMessageBox.confirm(i18n.global.t('authorization.msg_3'), i18n.global.t('tips'), {
+ confirmButtonText: i18n.global.t('confirm'),
+ cancelButtonText: i18n.global.t('cancel'),
+ type: 'success',
+ draggable: true
+ })
+ .then(() => {
+ githubAppAuthorize()
+ })
+ .catch(() => {
+ window.location.href = '/'
+ })
+ }
+
+ // GitHub APP 授权之后的回调处理
+ if (code) {
+ const loading = ElLoading.service({
+ lock: true,
+ text: i18n.global.t('authorization.loading_1')
+ })
+
+ try {
+ // 存储 code 状态信息
+ await store.dispatch('SET_GITHUB_AUTHORIZATION_INFO', {
+ authorized: true,
+ code,
+ codeCreateTime: Date.now(),
+ isAutoAuthorize: true
+ })
+
+ const { token } = computed(() => store.getters.getGitHubAuthorizationInfo).value
+
+ let newToken: string = ''
+
+ if (!token || (token && isAuthorizeExpire())) {
+ // 在服务端获取 Token
+ const res = await axios.get(`${authorize_api}?code=${code}&redirect_uri=${redirect_uri}`)
+
+ if (res.data?.data) {
+ newToken = res.data.data
+ // 存储授权 Token 信息
+ await store.dispatch('SET_GITHUB_AUTHORIZATION_INFO', {
+ token: newToken,
+ tokenCreateTime: Date.now()
+ })
+ } else {
+ ElMessage.error({ message: res.data.msg, duration: 6000 })
+ }
+ } else {
+ newToken = token
+ }
+
+ if (newToken) {
+ // 存储 Token
+ await store.dispatch('SET_USER_CONFIG_INFO', {
+ token: newToken
+ })
+
+ loading.close()
+
+ // 跳转到图床配置页面,进行一键自动配置
+ const oriUrl = window.location.href.replace(window.location.search, '')
+ window.location.href = `${oriUrl}config?auto=1`
+ }
+
+ loading.close()
+ } catch (e: any) {
+ loading.close()
+ ElMessage.error({ message: i18n.global.t('authorization.msg_4'), duration: 6000 })
+ }
+ }
+}
+
+/**
+ * GitHub APP 授权的初始处理流程
+ */
+export const initGithubAuthorize = async () => {
+ const { authorized, installed, token, isAutoAuthorize } = computed(
+ () => store.getters.getGitHubAuthorizationInfo
+ ).value
+
+ const tmpGoLogin = async () => {
+ await router.push({ path: '/login', query: { jump: '0' } })
+ }
+
+ if (isAutoAuthorize && authorized && installed) {
+ if (token && isAuthorizeExpire()) {
+ ElMessage.error({
+ message: i18n.global.t('authorization.msg_1'),
+ duration: 0,
+ showClose: true,
+ onClose: () => {
+ tmpGoLogin()
+ }
+ })
+ await tmpGoLogin()
+ }
+ }
+}
diff --git a/src/views/picx-login/picx-login.vue b/src/views/picx-login/picx-login.vue
new file mode 100644
index 00000000..f84dae46
--- /dev/null
+++ b/src/views/picx-login/picx-login.vue
@@ -0,0 +1,162 @@
+
+
+
+
+ {{ $t('authorization.text_1') }}
+
+
+
{{ $t('authorization.text_8') }}
+
+
+ {{ $t('authorization.text_10') }}
+
+
+
+ {{ $t('authorization.text_11') }}
+
+
+
+
+
+
+ {{ $t('authorization.text_2') }}
+
+
+
{{ $t('authorization.text_9') }}
+
+
+ {{ $t('authorization.text_10') }}
+
+
+
+ {{ $t('authorization.text_12') }}
+
+
+
+
+
+
+
+
+