feat: fsupload

v3
veypi 4 weeks ago
parent 9900b6987c
commit fee81d6bee

@ -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<void>((resolve) => {
setTimeout(() => {
resolve();
}, delay)
}).then(() => {
return client.request(config as any)
})
}
// 延时处理
return new Promise<void>((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<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'get', url: url }, transData(req)))
return webapi.client.request<T, any>(Object.assign({ method: 'get', url: url }, transData(req)))
},
Head<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'head', url: url }, transData(req)))
return webapi.client.request<T, any>(Object.assign({ method: 'head', url: url }, transData(req)))
},
Delete<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'delete', url: url }, transData(req)))
return webapi.client.request<T, any>(Object.assign({ method: 'delete', url: url }, transData(req)))
},
Post<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'post', url: url }, transData(req)))
return webapi.client.request<T, any>(Object.assign({ method: 'post', url: url }, transData(req)))
},
Put<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'put', url: url }, transData(req)))
return webapi.client.request<T, any>(Object.assign({ method: 'put', url: url }, transData(req)))
},
Patch<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'patch', url: url }, transData(req)))
return webapi.client.request<T, any>(Object.assign({ method: 'patch', url: url }, transData(req)))
},
}

@ -22,7 +22,7 @@ export default class {
let frame_user = v({
class: 'voa-on',
vclass: [() => logic.ready ? 'voa-scale-in' : 'voa-scale-off'],
children: [() => `<img src="${logic.user.icon}" />`],
children: [() => `<img src="${logic.user.icon || logic.token.refresh.icon}" />`],
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 {

@ -0,0 +1,177 @@
/*
* fs.ts
* Copyright (C) 2023 veypi <i@veypi.com>
* 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<T>(fn: () => Promise<T>): Promise<T> {
let retries = 0;
function attempt(): Promise<T> {
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<string> => {
// 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<string[]>((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,
}

@ -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')) {

@ -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

@ -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(() => {
</script>
<style >
<style>
body,
html {
height: 100vh;

@ -95,7 +95,7 @@ $toolbarBtnHoverColor: $toolbarBtnHoverColorLight;
$toolbarColorItemHoverBorderColor: rgb(247, 133, 83);
$sidebarShadow: $shadow;
/** 编辑区域样式 */
$editorBg: rgb(255, 255, 255);
$editorBg: rgba(0, 0, 0, 0);
$editorColor: rgb(63, 74, 86);
$editorSelectedBg: $toolbarBtnBgHover;
$editorUrlBg: rgb(215, 230, 254);
@ -103,7 +103,7 @@ $editorCursorColor: rgb(0, 0, 0);
$editorImportantColor: rgb(34, 139, 230);
$editorCodeColor: rgb(77, 171, 247);
/** 预览区域样式 */
$previewBg: none;
$previewBg: rgba(0, 0, 0, 0);
$previewMobileBgColor: $previewBg;
/** markdown样式 */
$mdColor: $editorColor;
@ -117,6 +117,14 @@ $mdBlockquoteBg: rgb(231, 245, 255);
/** 编辑器样式 */
.cherry.theme__oa {
background-color: $editorBg;
box-shadow: $shadow;
.cherry-drag {
background-color: rgba(0, 0, 0, 0.3);
width: 5px;
z-index: 10;
}
/** 顶部按钮, 选中文字时弹出的按钮, 光标focus到空行时联想出的按钮, 侧边栏按钮 */
.cherry-toolbar,
@ -125,6 +133,7 @@ $mdBlockquoteBg: rgb(231, 245, 255);
.cherry-sidebar {
background: $toolbarBg;
border-color: $toolbarBg;
box-shadow: none;
.cherry-toolbar-button {
color: $toolbarBtnColor;
@ -243,6 +252,7 @@ $mdBlockquoteBg: rgb(231, 245, 255);
.cherry-previewer {
background-color: $previewBg;
border-left: none;
.cherry-mobile-previewer-content {
background-color: $previewMobileBgColor;

@ -1,4 +1,4 @@
<!--
<!--
* appcard.vue
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-06-07 16:48
@ -15,21 +15,22 @@
</div>
</template>
<script setup lang="ts">
import type { models } from "#imports";
import msg from "@veypi/msg";
import oaer from "@veypi/oaer";
const router = useRouter()
let props = withDefaults(defineProps<{
core: modelsApp,
core: models.App,
is_part: boolean
}>(),
{}
)
const u = useUserStore()
function Go() {
if (props.is_part) {
@ -41,7 +42,8 @@ function Go() {
// message: ' ' + props.core.name,
// cancel: true,
// }).onOk(() => {
api.app.user(props.core.id).add(u.id).then(e => {
api.app.AppUserPost(props.core.id, { user_id: oaer.local().id, status: 'ok' }).then(e => {
msg.Info('申请成功')
switch (e.status) {
case AUStatus.OK:
msg.Info('加入成功')

@ -1,4 +1,4 @@
<!--
<!--
* index.vue
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-07 18:57
@ -7,7 +7,7 @@
<template>
<div class="w-full h-full">
<!-- <div class="absolute bg-red-400 left-0 top-0 w-full h-full"></div> -->
<div class="w-full h-full" :id="eid"></div>
<div class="w-full h-full veditor" :id="eid"></div>
</div>
</template>
@ -15,7 +15,7 @@
import 'cherry-markdown/dist/cherry-markdown.css';
import Cherry from 'cherry-markdown';
import options from './options'
import { oafs } from '@veypi/oaer'
// import { oafs } from '@veypi/oaer'
let editor = {} as Cherry;
let emits = defineEmits<{
@ -40,6 +40,7 @@ watch(computed(() => props.modelValue), (e) => {
})
watch(computed(() => props.content), (e) => {
if (e) {
console.log(e)
editor.setValue(e)
}
})
@ -59,11 +60,11 @@ const fileUpload = (f: File, cb: (url: string, params: any) => void) => {
* @param params.width 设置宽度可以是像素也可以是百分比图片视频场景下生效
* @param params.height 设置高度可以是像素也可以是百分比图片视频场景下生效
*/
oafs.upload([f], props.static_dir).then((e: any) => {
cb(e[0], {
name: f.name, isBorder: false, isShadow: false, isRadius: false, width: '', height: '',
})
})
// oafs.upload([f], props.static_dir).then((e: any) => {
// cb(e[0], {
// name: f.name, isBorder: false, isShadow: false, isRadius: false, width: '', height: '',
// })
// })
}
const saveMenu = Cherry.createMenuHook('保存', {
@ -104,25 +105,29 @@ onMounted(() => {
})
</script>
<style>
iframe.cherry-dialog-iframe {
width: 100%;
height: 100%;
}
<style lang="scss">
.cherry-dialog {
.cherry-dialog--body {
bottom: 45px !important;
iframe {
width: 100%;
height: 100%;
}
}
.cherry {
background: none;
box-shadow: none;
height: 100%;
.cherry-dialog--foot {
height: 45px !important;
line-height: 34px !important;
}
}
.cherry-previewer {
background: none;
border: none;
.veditor .cherry .cherry-previewer {
// background-color: rgba(0, 0, 0, 0);
// box-shadow: none;
}
.cherry-toolbar {
box-shadow: none;
// box-shadow: none;
}
</style>

@ -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:
* indentedCodeBlockfalse
*/
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

@ -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<models.App>(`/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<models.App>(`/app`, { json })

@ -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,

@ -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

@ -1,6 +1,6 @@
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 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<void>((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)

@ -33,17 +33,16 @@
<script lang="ts" setup>
import { OneIcon } from '@veypi/one-icon'
import oaer from '../../oaer/lib/main'
import '@veypi/oaer/dist/index.css'
import oaer from '@veypi/oaer'
let app = useAppConfig()
let router = useRouter()
let user = useUserStore()
app.host = window.location.protocol + '//' + window.location.host
oaer.init(app.host).then(() => {
api.apitoken.value = oaer.Token()
oaer.init(app.host).then((e) => {
oaer.render_ui('oaer')
user.fetchUserData()
api.apitoken.set_updator(oaer.TokenRefresh)
}).catch(() => {
oaer.logout()
})
@ -80,7 +79,6 @@ const toggle_theme = () => {
}
onMounted(() => {
console.log('mount frame')
oaer.render_ui('oaer')
})

@ -6,6 +6,9 @@ export default defineNuxtConfig({
'~/assets/css/app.scss',
'~/assets/css/tailwind.css',
],
plugins: [
'~/plugins/third'
],
postcss: {
plugins: {
tailwindcss: {},

@ -12,10 +12,10 @@
"dependencies": {
"@pinia/nuxt": "^0.5.1",
"@veypi/msg": "^0.2.0",
"@veypi/oaer": "^0.2.4",
"@veypi/oaer": "^0.3.0",
"@veypi/one-icon": "^3.0.0",
"axios": "^1.7.2",
"cherry-markdown": "^0.8.42",
"cherry-markdown": "^0.8.49",
"crypto-js": "^4.2.0",
"echarts": "^5.5.0",
"js-base64": "^3.7.7",

@ -1,4 +1,4 @@
<!--
<!--
* app.[id].vue
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-06-07 17:46
@ -17,13 +17,14 @@
</template>
<script lang="ts" setup>
import type { models } from '#imports';
import msg from '@veypi/msg';
const route = useRoute()
const router = useRouter()
let menu = useMenuStore()
let core = ref({} as modelsApp)
let core = ref({} as models.App)
const set_menu = () => {
let p = '/app/' + core.value.id
menu.set([
@ -35,7 +36,7 @@ const set_menu = () => {
}
onMounted(() => {
api.app.get(route.params.id as string).then((e) => {
api.app.Get(route.params.id as string).then((e) => {
core.value = e
set_menu()
}).catch(e => {
@ -55,4 +56,3 @@ onBeforeRouteLeave(() => {
</script>
<style scoped></style>

@ -1,4 +1,4 @@
<!--
<!--
* index.vue
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-06-07 17:46
@ -7,7 +7,7 @@
<template>
<div>
<div class="vbtn" v-if="preview_mode" @click="preview_mode = false">
ss
<OneIcon name='edit-square'>plus</OneIcon>
</div>
<Editor style="" v-if="core.id" :eid="core.id + '.des'" v-model="preview_mode" :content="content" @save="save">
</Editor>
@ -15,17 +15,19 @@
</template>
<script lang="ts" setup>
import { oafs } from '@veypi/oaer'
import type { models } from '#imports';
import oaer from '@veypi/oaer'
let props = withDefaults(defineProps<{
core: modelsApp,
core: models.App,
}>(),
{}
)
let preview_mode = ref(true)
let content = ref()
let content = ref('编辑')
watch(computed(() => props.core.id), () => {
sync()
@ -33,22 +35,27 @@ watch(computed(() => props.core.id), () => {
const sync = () => {
if (props.core.des) {
oafs.get(props.core.des).then(e => content.value = e)
console.log(props.core.des)
oaer.fs().app.getFileContents("/net/go.mod", { format: 'text' }).then((e) => {
content.value = e as string
})
}
}
const save = (des: string) => {
let a = new File([des], props.core.name + '.md');
oafs.upload([a], props.core.id).then(url => {
api.app.update(props.core.id, { des: url[0] }).then(e => {
preview_mode.value = true
props.core.des = url[0]
}).catch(e => {
// msg.Warn(": " + e)
})
}).catch(e => {
// msg.Warn(": " + e)
oaer.fs().app.putFileContents("/info/des.md", des).then((e) => {
console.log(e)
})
// oafs.upload([a], props.core.id).then(url => {
// api.app.update(props.core.id, { des: url[0] }).then(e => {
// preview_mode.value = true
// props.core.des = url[0]
// }).catch(e => {
// // msg.Warn(": " + e)
// })
// }).catch(e => {
// // msg.Warn(": " + e)
// })
}
@ -59,4 +66,3 @@ onMounted(() => {
</script>
<style scoped></style>

@ -12,7 +12,7 @@
<div class="flex justify-between">
<h1 class="page-h1">我的应用</h1>
<div class="my-5 mr-10">
<div class='vbtn bg-gray-400' @click="new_flag = true" v-if="user.auth.Get(R.App, '').CanCreate()">
<div class='vbtn bg-gray-400' @click="new_flag = true" v-if="oaer.access().Get('app', '').CanCreate()">
</div>
</div>
</div>
@ -64,28 +64,18 @@
<script setup lang="ts">
import type { models } from '#imports';
import msg from '@veypi/msg';
import oaer from '@veypi/oaer';
let user = useUserStore()
let apps = ref<models.App[]>([]);
let ofApps = ref<models.App[]>([]);
let allApps = ref<models.App[]>([]);
let apps = computed(() => allApps.value.filter(e => e.user_status !== 'ok'))
let ofApps = computed(() => allApps.value.filter(e => e.user_status === 'ok'))
function getApps() {
api.app.List({}).then(
(e) => {
apps.value = e;
api.app.AppUserList("*", { user_id: user.id }).then(aus => {
for (let i in aus) {
let ai = apps.value.findIndex(a => a.id === aus[i].app_id)
if (ai >= 0) {
if (aus[i].status === "ok") {
ofApps.value.push(apps.value[ai])
apps.value.splice(ai, 1)
}
}
}
})
allApps.value = e;
}
);
}
@ -108,7 +98,7 @@ let rules = {
function create_new() {
api.app.Post({
name: temp_app.value.name, icon: temp_app.value.icon,
des: "", participate: "auto"
des: "", typ: "public", status: "ok"
}).then((e: models.App) => {
ofApps.value.push(e);
msg.Info("创建成功");

@ -94,8 +94,7 @@
<script lang="ts" setup>
import msg from '@veypi/msg';
import * as crypto from 'crypto-js'
import oaer from '../../oaer/lib/main'
import { isValid } from 'js-base64';
import oaer from '@veypi/oaer'
definePageMeta({
@ -160,10 +159,10 @@ const login = () => {
redirect("")
}).catch((e) => {
console.warn(e)
msg.Warn('登录失败:' + (e.err || e))
msg.Warn('登录失败:' + (e?.err || e))
})
}).catch(e => {
msg.Warn('登录失败:' + (e.err || e))
msg.Warn('登录失败:' + (e?.err || e))
})
}).catch(e => {
if (e.code === 40401) {
@ -241,7 +240,7 @@ function redirect(url: string) {
if (uuid.value && uuid.value !== app.id) {
api.app.Get(uuid.value as string).then((app) => {
api.token.Post({
token: localStorage.getItem('refresh') || '',
refresh: '',
user_id: uuid.value as string,
app_id: uuid.value as string,
}).then(e => {

@ -0,0 +1,15 @@
/*
* third.ts
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-10-29 19:52
* Distributed under terms of the GPL license.
*/
import oneicon from '@veypi/one-icon'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(oneicon)
})

File diff suppressed because it is too large Load Diff

@ -162,7 +162,7 @@ mxConstants.SHADOW_OPACITY = 0.25;
mxConstants.SHADOWCOLOR = '#000000';
mxConstants.VML_SHADOWCOLOR = '#d0d0d0';
mxCodec.allowlist = ['mxStylesheet', 'Array', 'mxGraphModel', 'html',
mxCodec.allowlist = ['mxStylesheet', 'Array', 'mxGraphModel',
'mxCell', 'mxGeometry', 'mxRectangle', 'mxPoint',
'mxChildChange', 'mxRootChange', 'mxTerminalChange',
'mxValueChange', 'mxStyleChange', 'mxGeometryChange',

@ -3,27 +3,25 @@ window.urlParams = window.urlParams || {};
// Public global variables
window.DOM_PURIFY_CONFIG = window.DOM_PURIFY_CONFIG ||
{
ADD_TAGS: ['use'], FORBID_TAGS: ['form'],
ALLOWED_URI_REGEXP: /^((?!javascript:).)*$/i,
ADD_ATTR: ['target', 'content']
};
{ADD_TAGS: ['use'], FORBID_TAGS: ['form'],
ALLOWED_URI_REGEXP: /^((?!javascript:).)*$/i,
ADD_ATTR: ['target', 'content']};
// Public global variables
window.MAX_REQUEST_SIZE = window.MAX_REQUEST_SIZE || 10485760;
window.MAX_REQUEST_SIZE = window.MAX_REQUEST_SIZE || 10485760;
window.MAX_AREA = window.MAX_AREA || 15000 * 15000;
// URLs for save and export
window.EXPORT_URL = window.EXPORT_URL || './drawio';
window.SAVE_URL = window.SAVE_URL || './drawio';
window.EXPORT_URL = window.EXPORT_URL || './drawio_demo';
window.SAVE_URL = window.SAVE_URL || './drawio_demo';
window.PROXY_URL = window.PROXY_URL || null;
window.OPEN_URL = window.OPEN_URL || './drawio';
window.RESOURCES_PATH = window.RESOURCES_PATH || './drawio/resources';
window.STENCIL_PATH = window.STENCIL_PATH || './drawio/image/stencils';
window.IMAGE_PATH = window.IMAGE_PATH || './drawio/image';
window.STYLE_PATH = window.STYLE_PATH || './drawio/src/css';
window.CSS_PATH = window.CSS_PATH || './drawio/src/css';
window.OPEN_FORM = window.OPEN_FORM || './drawio';
window.OPEN_URL = window.OPEN_URL || './drawio_demo';
window.RESOURCES_PATH = window.RESOURCES_PATH || './drawio_demo/resources';
window.STENCIL_PATH = window.STENCIL_PATH || './drawio_demo/image/stencils';
window.IMAGE_PATH = window.IMAGE_PATH || './drawio_demo/image';
window.STYLE_PATH = window.STYLE_PATH || './drawio_demo/src/css';
window.CSS_PATH = window.CSS_PATH || './drawio_demo/src/css';
window.OPEN_FORM = window.OPEN_FORM || './drawio_demo';
window.mxBasePath = window.mxBasePath || './drawio/src';
window.mxBasePath = window.mxBasePath || './drawio_demo/src';
window.mxLanguage = window.mxLanguage || window.RESOURCES_PATH + '/zh';
window.mxLanguages = window.mxLanguages || ['zh'];

@ -6,7 +6,7 @@
*/
import { Base64 } from 'js-base64'
import type { auth, models } from '~/composables';
import { models, type auth } from '~/composables';
export const useUserStore = defineStore('user', {
state: () => ({

Loading…
Cancel
Save