diff --git a/oaer/lib/api/webapi.ts b/oaer/lib/api/webapi.ts index 2a2c07b..ff86f7b 100644 --- a/oaer/lib/api/webapi.ts +++ b/oaer/lib/api/webapi.ts @@ -4,7 +4,7 @@ // Distributed under terms of the MIT license. // -import axios, { AxiosError, type AxiosResponse } from 'axios'; +import axios, { AxiosError, AxiosInstance, type AxiosResponse } from 'axios'; // Be careful when using SSR for cross-request state pollution // due to creating a Singleton instance here; @@ -13,15 +13,6 @@ import axios, { AxiosError, type AxiosResponse } from 'axios'; // "export default () => {}" function below (which runs individually // for each client) -axios.defaults.withCredentials = true -const proxy = axios.create({ - withCredentials: true, - baseURL: "/api/", - headers: { - 'content-type': 'application/json;charset=UTF-8', - }, -}); - export const token = { value: '', update: () => { @@ -64,79 +55,89 @@ const beforeRequest = (config: any) => { // config.headers['auth_token'] = '' return config } -proxy.interceptors.request.use(beforeRequest) // 响应拦截器 -const responseSuccess = (response: AxiosResponse) => { - // eslint-disable-next-line yoda - // 这里没有必要进行判断,axios 内部已经判断 - // const isOk = 200 <= response.status && response.status < 300 - let data = response.data - if (typeof data === 'object') { - if (data.code !== 0) { - return responseFailed({ response } as any) +const responseSuccess = (client: AxiosInstance) => { + return (response: AxiosResponse) => { + // eslint-disable-next-line yoda + // 这里没有必要进行判断,axios 内部已经判断 + // const isOk = 200 <= response.status && response.status < 300 + let data = response.data + if (typeof data === 'object') { + if (data.code !== 0) { + return responseFailed(client)({ response } as any) + } + data = data.data } - data = data.data + return Promise.resolve(data) } - return Promise.resolve(data) } -const responseFailed = (error: AxiosError) => { - const { response } = error - const config = response?.config - const data = response?.data || {} as any - if (!window.navigator.onLine) { - alert('没有网络') - return Promise.reject(new Error('请检查网络连接')) - } +const responseFailed = (client: AxiosInstance) => { + return (error: AxiosError) => { + const { response } = error + const config = response?.config + const data = response?.data || {} as any + if (!window.navigator.onLine) { + alert('没有网络') + return Promise.reject(new Error('请检查网络连接')) + } - let needRetry = true - if (response?.status == 404) { - needRetry = false - } else if (response?.status == 401) { - needRetry = false - // AuthNotFound = New(40100, "auth not found") - // AuthExpired = New(40102, "auth expired") - if (data.code === 40102 || data.code === 40100) { - token.value = '' - return token.update().then(() => { - return requestRetry(1000, response!) - }) + let needRetry = true + if (response?.status == 404) { + needRetry = false + } else if (response?.status == 401) { + needRetry = false + // AuthNotFound = New(40100, "auth not found") + // AuthExpired = New(40102, "auth expired") + if (data.code === 40102 || data.code === 40100) { + token.value = '' + return token.update().then(() => { + return requestRetry(client)(1000, response!) + }) + } + } else if (response?.status == 500) { + needRetry = false } + if (!needRetry) { + return Promise.reject(data || response) + }; + return requestRetry(client)(1000, response!) } - if (!needRetry) { - return Promise.reject(data || response) - }; - return requestRetry(1000, response!) } -const requestRetry = (delay = 0, response: AxiosResponse) => { - const config = response?.config - // @ts-ignore - const { __retryCount = 0, retryDelay = 1000, retryTimes } = config; - // 在请求对象上设置重试次数 - // @ts-ignore - config.__retryCount = __retryCount + 1; - // 判断是否超过了重试次数 - if (__retryCount >= retryTimes) { - return Promise.reject(response?.data || response?.headers.error) - } - if (delay <= 0) { - return proxy.request(config as any) +const requestRetry = (client: AxiosInstance) => { + return (delay = 0, response: AxiosResponse) => { + const config = response?.config + // @ts-ignore + const { __retryCount = 0, retryDelay = 1000, retryTimes } = config; + // 在请求对象上设置重试次数 + // @ts-ignore + config.__retryCount = __retryCount + 1; + // 判断是否超过了重试次数 + if (__retryCount >= retryTimes) { + return Promise.reject(response?.data || response?.headers.error) + } + if (delay <= 0) { + return client.request(config as any) + } + // 延时处理 + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, delay) + }).then(() => { + return client.request(config as any) + }) } - // 延时处理 - return new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, delay) - }).then(() => { - return proxy.request(config as any) - }) } -proxy.interceptors.response.use(responseSuccess, responseFailed) - +const create_client = (client: AxiosInstance) => { + client.interceptors.request.use(beforeRequest) + client.interceptors.response.use(responseSuccess(client), responseFailed(client)) + return client +} interface data { json?: any query?: any @@ -144,6 +145,7 @@ interface data { header?: any } + function transData(d: data) { let opts = { params: d.query, data: {}, headers: {} as any } if (d.form) { @@ -161,24 +163,31 @@ function transData(d: data) { } export const webapi = { + client: create_client(axios.create({ + withCredentials: true, + baseURL: "/api/", + headers: { + 'content-type': 'application/json;charset=UTF-8', + }, + })), Get(url: string, req: data): Promise { - return proxy.request(Object.assign({ method: 'get', url: url }, transData(req))) + return webapi.client.request(Object.assign({ method: 'get', url: url }, transData(req))) }, Head(url: string, req: data): Promise { - return proxy.request(Object.assign({ method: 'head', url: url }, transData(req))) + return webapi.client.request(Object.assign({ method: 'head', url: url }, transData(req))) }, Delete(url: string, req: data): Promise { - return proxy.request(Object.assign({ method: 'delete', url: url }, transData(req))) + return webapi.client.request(Object.assign({ method: 'delete', url: url }, transData(req))) }, Post(url: string, req: data): Promise { - return proxy.request(Object.assign({ method: 'post', url: url }, transData(req))) + return webapi.client.request(Object.assign({ method: 'post', url: url }, transData(req))) }, Put(url: string, req: data): Promise { - return proxy.request(Object.assign({ method: 'put', url: url }, transData(req))) + return webapi.client.request(Object.assign({ method: 'put', url: url }, transData(req))) }, Patch(url: string, req: data): Promise { - return proxy.request(Object.assign({ method: 'patch', url: url }, transData(req))) + return webapi.client.request(Object.assign({ method: 'patch', url: url }, transData(req))) }, } diff --git a/oaer/lib/components/index.ts b/oaer/lib/components/index.ts index d3837b8..aada22b 100644 --- a/oaer/lib/components/index.ts +++ b/oaer/lib/components/index.ts @@ -22,7 +22,7 @@ export default class { let frame_user = v({ class: 'voa-on', vclass: [() => logic.ready ? 'voa-scale-in' : 'voa-scale-off'], - children: [() => ``], + children: [() => ``], onclick: () => { logic.getDetailUser() if (!this.slide) { @@ -42,7 +42,6 @@ export default class { } }) return () => { - console.log('gen_avatar', logic.ready) if (logic.ready) { return frame_user } else { diff --git a/oaer/lib/fs.ts b/oaer/lib/fs.ts new file mode 100644 index 0000000..756d033 --- /dev/null +++ b/oaer/lib/fs.ts @@ -0,0 +1,177 @@ +/* + * fs.ts + * Copyright (C) 2023 veypi + * 2023-10-08 01:55 + * Distributed under terms of the MIT license. + */ + + +import * as webdav from 'webdav' +import { proxy } from "./v2dom"; +import logic from "./logic"; + +export interface fileProps { + filename: string, + basename: string, + lastmod: string, + size: number, + type: "directory" | "file", + etag: string +} + +function delay(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +class davWraper { + private client: webdav.WebDAVClient + private host: string + private prefix: string + private token: string + constructor(host: string, prefix: string, token: string) { + this.host = host + this.prefix = prefix + this.token = token + this.client = webdav.createClient(host + prefix) + } + set(k: 'host' | 'token', value: string) { + if (value !== this[k]) { + this[k] = value + if (k === 'token') { + this.client.setHeaders({ + authorization: "bearer " + value + }) + } else if (k === 'host') { + this.client = webdav.createClient(this.host + this.prefix) + } + } + } + putFileContents(filename: string, data: string, options?: webdav.PutFileContentsOptions) { + return this.retry(() => this.client.putFileContents(filename, data, options)) + } + getFileContents(filename: string, options?: webdav.GetFileContentsOptions) { + return this.retry(() => this.client.getFileContents(filename, options)) + } + getDirectoryContents(path: string, options?: webdav.GetDirectoryContentsOptions) { + return this.retry(() => this.client.getDirectoryContents(path, options)) + } + stat(path: string, options?: webdav.StatOptions) { + return this.retry(() => this.client.stat(path, options)) + } + private retry(fn: () => Promise): Promise { + let retries = 0; + function attempt(): Promise { + return fn().catch(error => { + if (retries < 3) { + retries++; + console.log(`Attempt ${retries} failed, retrying after 1 second...`); + return delay(1000).then(attempt); + } else { + // 超过最大重试次数后不再重试 + throw error; + } + }) + } + return attempt() + } +} + +let token = logic.token.oa.raw() +const user = new davWraper(logic.Host(), '/fs/u/', token) +const app = new davWraper(logic.Host(), '/fs/a/', token) + + +export const set_host = (h: string) => { + user.set('host', h) + app.set('host', h) +} +const sync = () => { + if (logic.token.oa.isVaild()) { + let t = logic.token.oa.raw() + // console.warn('sync oafs token: ' + t) + user.set('token', t) + app.set('token', t) + } +} +proxy.Listen(() => { + sync() +}) + +const rename = (o: string, n?: string) => { + let ext = '.' + o.split('.').pop()?.toLowerCase() + if (n) { + return n + ext + } + let d = new Date().getTime() + return d + o + ext +} + + +// const get = (url: string): Promise => { +// return fetch(cfg.Host() + url, { headers: { authorization: "bearer " + cfg.oa_token.value } }).then((response) => response.text()) +// } + +// rename 可以保持url不变 +// const upload = (f: FileList | File[], dir?: string, renames?: string[]) => { +// return new Promise((resolve, reject) => { +// var data = new FormData(); +// for (let i = 0; i < f.length; i++) { +// let nf = new File([f[i]], rename(f[i].name, renames && renames[i] ? renames[i] : undefined), { type: f[i].type }) +// data.append('files', nf, nf.name) +// } +// axios.post("/api/upload/" + (dir || ''), data, { +// headers: { +// "Content-Type": 'multipart/form-data', +// 'auth_token': cfg.oa_token.value, +// } +// }).then(e => { +// resolve(e.data) +// }).catch(reject) +// }) +// } + +const get_dav = (client: webdav.WebDAVClient, base_url: string) => { + return { + client: client, + stat: client.stat, + dir: client.getDirectoryContents, + uploadstr: (dir: string, name: string, data: string) => { + if (dir.startsWith('/')) { + dir = dir.slice(1) + } + return new Promise((resolve, reject) => { + let temp = () => { + let reader = new FileReader() + reader.onload = function (event) { + var res = event.target?.result + // let data = new Blob([res]) + client.putFileContents(dir + name, res).then(e => { + if (e) { + resolve(base_url + dir + name) + } + }).catch(reject) + } + reader.readAsArrayBuffer(new Blob([data], { type: 'plain/text' })) + } + client.stat(dir).then(() => { + temp() + }).catch((_) => { + client.createDirectory(dir, { recursive: true }).then(() => { + temp() + }).catch(e => { + console.warn(e) + }) + }) + }); + } + } +} + + + +export default { + user, + app, + rename, +} + diff --git a/oaer/lib/logic.ts b/oaer/lib/logic.ts index 81d8c3a..9a6fe85 100644 --- a/oaer/lib/logic.ts +++ b/oaer/lib/logic.ts @@ -149,9 +149,9 @@ const logic = proxy.Watch({ }) }) }, - host: '', + host: window.location.origin, Host() { - return this.host || (window.location.protocol + window.location.host) + return this.host }, goto(url: string) { if (url.startsWith('http')) { diff --git a/oaer/lib/main.ts b/oaer/lib/main.ts index 9ca1374..ff99e38 100644 --- a/oaer/lib/main.ts +++ b/oaer/lib/main.ts @@ -10,6 +10,7 @@ import bus from './bus' import logic from './logic' import ui from './components' import api from './api' +import fs from './fs' export default new class { @@ -22,6 +23,9 @@ export default new class { access() { return logic.token.app.access! } + fs() { + return fs + } init(host?: string, code?: string) { if (host) { logic.host = host diff --git a/oaweb/app.vue b/oaweb/app.vue index 33ea6cb..b7affe2 100644 --- a/oaweb/app.vue +++ b/oaweb/app.vue @@ -10,7 +10,6 @@ let app = useAppConfig() let menu = useMenuStore() onMounted(() => { menu.default() - console.log('init app') app.layout.size = [document.body.clientWidth, document.body.clientHeight] window.onresize = () => { app.layout.size = [document.body.clientWidth, document.body.clientHeight] @@ -19,7 +18,7 @@ onMounted(() => { - - diff --git a/oaweb/components/editor/options.ts b/oaweb/components/editor/options.ts index adfe005..2044ea1 100644 --- a/oaweb/components/editor/options.ts +++ b/oaweb/components/editor/options.ts @@ -6,11 +6,29 @@ */ -import { CherryOptions } from 'cherry-markdown/types/cherry'; +import { type CherryOptions } from 'cherry-markdown/types/cherry'; +import '@/assets/css/editor.scss' const basicConfig: CherryOptions = { id: '', value: '', + nameSpace: 'cherry', + themeSettings: { + // 主题列表,用于切换主题 + themeList: [ + { className: 'dark', label: '黑' }, + { className: 'light', label: '白' }, + { className: 'orange', label: '橘里橘气' }, + ], + // 目前应用的主题 + mainTheme: 'oa', + /** 代码块主题 */ + codeBlockTheme: 'dark', + /** 行内代码主题,只有 red 和 black 两个主题 */ + inlineCodeTheme: 'red', + /** 工具栏主题,只有 light 和 dark 两个主题,优先级低于 mainTheme */ + toolbarTheme: 'dark', + }, externals: { // echarts: window.echarts, // katex: window.katex, @@ -98,23 +116,8 @@ const basicConfig: CherryOptions = { theme: 'red', }, codeBlock: { - theme: 'twilight', // Default to dark theme wrap: true, // If it exceeds the length, whether to wrap the line. If false, the scroll bar will be displayed lineNumber: true, // Default display line number - customRenderer: { - // Custom syntax renderer - }, - /** - * indentedCodeBlock Is the switch whether indent code block is enabled - * - * this syntax is not supported by default in versions before 6.X. - * Because cherry's development team thinks the syntax is too ugly (easy to touch by mistake) - * The development team hopes to completely replace this syntax with ` ` code block syntax - * However, in the subsequent communication, the development team found that the syntax had better display effect in some scenarios - * Therefore, the development team in 6 This syntax was introduced in version X - * if you want to upgrade the following versions of services without users' awareness, you can remove this syntax: - * indentedCodeBlock:false - */ indentedCodeBlock: true, }, fontEmphasis: { @@ -134,8 +137,6 @@ const basicConfig: CherryOptions = { }, emoji: { useUnicode: false, - // customResourceURL: 'https://github.githubassets.com/images/icons/emoji/unicode/${code}.png?v8', - upperCase: true, }, toc: { /** By default, only one directory is rendered */ @@ -188,8 +189,8 @@ const basicConfig: CherryOptions = { 'graph', 'togglePreview', 'export', - 'saveMenu', - 'backMenu' + // @ts-ignore + 'saveMenu', 'backMenu' ], // toolbarRight: [], bubble: ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', 'ruby', '|', 'size', 'color'], // array or false diff --git a/oaweb/composables/api/app.ts b/oaweb/composables/api/app.ts index 8488392..0914567 100644 --- a/oaweb/composables/api/app.ts +++ b/oaweb/composables/api/app.ts @@ -14,8 +14,9 @@ export interface PatchOpts { name?: string icon?: string des?: string - participate?: string init_role_id?: string + typ?: string + status?: string } export function Patch(app_id: string, json: PatchOpts) { return webapi.Patch(`/app/${app_id}`, { json }) @@ -29,7 +30,8 @@ export interface PostOpts { name: string icon: string des: string - participate: string + typ: string + status: string } export function Post(json: PostOpts) { return webapi.Post(`/app`, { json }) diff --git a/oaweb/composables/api/index.ts b/oaweb/composables/api/index.ts index 267b0ce..51274bb 100644 --- a/oaweb/composables/api/index.ts +++ b/oaweb/composables/api/index.ts @@ -4,8 +4,10 @@ import * as token from "./token" import * as role from "./role" import * as app from "./app" import * as access from "./access" +import { token as apitoken } from './webapi' export default { + apitoken, user, token, role, diff --git a/oaweb/composables/api/models.ts b/oaweb/composables/api/models.ts index 81f3c55..e5800cc 100644 --- a/oaweb/composables/api/models.ts +++ b/oaweb/composables/api/models.ts @@ -15,11 +15,13 @@ export interface App { name: string icon: string des: string - participate: string init_role_id?: string init_role?: Role init_url: string user_count: number + typ: string + status: string + user_status: string } export interface AppUser { id: string diff --git a/oaweb/composables/api/webapi.ts b/oaweb/composables/api/webapi.ts index 3f0c1c1..8c062c1 100644 --- a/oaweb/composables/api/webapi.ts +++ b/oaweb/composables/api/webapi.ts @@ -1,6 +1,6 @@ // // Copyright (C) 2024 veypi -// 2024-10-28 17:43:57 +// 2024-10-28 19:22:21 // Distributed under terms of the MIT license. // @@ -34,7 +34,7 @@ export const token = { return new Promise((resolve) => { setTimeout(() => { resolve(); - }, 1000) + }, 100) }).then(() => { return token.update() }) @@ -100,9 +100,11 @@ const responseFailed = (error: AxiosError) => { if (data.code === 40102 || data.code === 40100) { token.value = '' return token.update().then(() => { - return requestRetry(1000, response!) + return requestRetry(100, response!) }) } + } else if (response?.status == 500) { + needRetry = false } if (!needRetry) { return Promise.reject(data || response) diff --git a/oaweb/layouts/default.vue b/oaweb/layouts/default.vue index 3d02f48..8daa6c4 100644 --- a/oaweb/layouts/default.vue +++ b/oaweb/layouts/default.vue @@ -33,17 +33,16 @@