crud grid

master
veypi 1 year ago
parent 2dda72bbf3
commit 8a0b44fb96

@ -197,8 +197,8 @@ pub fn init_log() {
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_line_number(true) .with_line_number(true)
.with_timer(FormatTime {}) .with_timer(FormatTime {})
.with_max_level(Level::TRACE) .with_max_level(Level::INFO)
.with_target(false) // .with_target(false)
.with_file(true) // .with_file(true)
.init(); .init();
} }

@ -69,6 +69,7 @@ module.exports = {
// add your custom rules here // add your custom rules here
rules: { rules: {
'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/no-empty-function': 0, '@typescript-eslint/no-empty-function': 0,
'vue/multi-word-component-names': 0, 'vue/multi-word-component-names': 0,
'@typescript-eslint/no-unused-vars': 0, '@typescript-eslint/no-unused-vars': 0,

@ -35,7 +35,11 @@ onBeforeMount(() => {
html, html,
body, body,
#q-app { #q-app {
@apply font-mono h-full w-full select-none; @apply font-mono h-full w-full;
}
:root {
--z-index: 1;
} }
.page-h1 { .page-h1 {

@ -27,9 +27,9 @@ conf.timeout = 5000
Cfg.host.value = 'http://' + window.location.host Cfg.host.value = 'http://' + window.location.host
Cfg.uuid.value = 'FR9P5t8debxc11aFF' Cfg.uuid.value = 'FR9P5t8debxc11aFF'
evt.on('token', (t) => { evt.on('token', (t: any) => {
oafs.setCfg({ token: util.getToken() }) oafs.setCfg({ token: t })
Cfg.token.value = util.getToken() Cfg.token.value = t
}) })
// "async" is optional; // "async" is optional;

@ -0,0 +1,90 @@
<!--
* crud.vue
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-10 19:29
* Distributed under terms of the MIT license.
-->
<template>
<div>
<q-page-sticky position="top-right" style="z-index: 20" :offset="[27, 27]">
<q-btn @click="modeV = !modeV" round icon="save_as" class="" />
</q-page-sticky>
<div class="grid" :class="[modeV ? '' : 'grid-cols-2']">
<div class="grid" :style="modeV ? grid_len : ''">
<div :class="styles.k" v-for="k of keys" :key="k.name">
{{ k.label || k.name }}
</div>
</div>
<div class="grid" :style="modeV ? '' : grid_len">
<div class="grid hover:bg-gray-200" :style="modeV ? grid_len : ''" v-for="( item, idx ) in data " :key="idx">
<div :class="styles.v" v-for="k of keys" :key="k.name">
{{ item[k.name] }}
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
interface itemProp {
name: '',
label?: '',
value: any,
type?: '',
}
interface keyProp {
name: string,
label?: string,
default?: any,
typ?: string,
}
const grid_len = computed(() => {
return {
'grid-template-columns': 'repeat(' +
(modeV.value ? props.keys?.length : props.data?.length)
+ ', minmax(0, 1fr))'
}
})
let props = withDefaults(defineProps<{
vertical?: boolean
keys?: keyProp[],
data?: any[]
kclass?: Array<string>,
vclass?: Array<string>,
cclass?: Array<string>,
}>(),
{
vertical: false,
data: [] as any,
kclass: [] as any,
vclass: [] as any,
cclass: ['w-40', 'h-40'] as any,
}
)
const styles = computed(() => {
let k = [];
let v = [];
k.push(...props.kclass, ...props.cclass)
v.push(...props.vclass, ...props.cclass)
return {
k, v
}
})
const modeV = ref(props.vertical)
watch(computed(() => props.vertical), (v) => modeV.value = v)
</script>
<style scoped></style>

@ -20,27 +20,22 @@ import oafs from 'src/libs/oafs';
let editor = {} as Cherry; let editor = {} as Cherry;
let emits = defineEmits<{ let emits = defineEmits<{
(e: 'updated', v: string): void (e: 'save', v: string): void
(e: 'update:modelValue', v: boolean): void
}>() }>()
let props = withDefaults(defineProps<{ let props = withDefaults(defineProps<{
modelValue: boolean,
eid?: string, eid?: string,
content?: string, content?: string,
preview?: boolean,
static_dir?: string, static_dir?: string,
}>(), }>(),
{ {
eid: 'v-editor', eid: 'v-editor',
content: '', content: '',
preview: false,
} }
) )
watch(computed(() => props.preview), (e) => { watch(computed(() => props.modelValue), (e) => {
if (e) {
let des = editor.getValue()
console.log(des)
emits('updated', des)
}
set_mode(e) set_mode(e)
}) })
watch(computed(() => props.content), (e) => { watch(computed(() => props.content), (e) => {
@ -70,19 +65,36 @@ const fileUpload = (f: File, cb: (url: string, params: any) => void) => {
}) })
}) })
} }
const saveMenu = Cherry.createMenuHook('保存', {
onClick: function () {
let des = editor.getValue()
emits('save', des)
return
}
});
const backMenu = Cherry.createMenuHook('返回', {
onClick: function () {
emits('update:modelValue', true)
return
}
})
const init = () => { const init = () => {
let config = { let config = {
value: props.content, value: props.content,
id: props.eid, id: props.eid,
// isPreviewOnly: props.preview,
callback: { callback: {
}, },
fileUpload: fileUpload, fileUpload: fileUpload,
} as CherryOptions; };
config.callback.afterInit = () => { // @ts-ignore
} options.toolbars.customMenu.saveMenu = saveMenu
// @ts-ignore
options.toolbars.customMenu.backMenu = backMenu
editor = new Cherry(Object.assign({}, options, config)); editor = new Cherry(Object.assign({}, options, config));
set_mode(props.preview) set_mode(props.modelValue)
} }

@ -7,6 +7,7 @@
import { CherryOptions } from 'cherry-markdown/types/cherry'; import { CherryOptions } from 'cherry-markdown/types/cherry';
const basicConfig: CherryOptions = { const basicConfig: CherryOptions = {
id: '', id: '',
value: '', value: '',
@ -57,10 +58,10 @@ const basicConfig: CherryOptions = {
}, },
onClickPreview: () => { }, onClickPreview: () => { },
onCopyCode: (e: ClipboardEvent, code: string) => code, onCopyCode: (e: ClipboardEvent, code: string) => code,
changeString2Pinyin: (s) => s, changeString2Pinyin: (s: any) => s,
}, },
isPreviewOnly: false, isPreviewOnly: false,
fileUpload: (f) => { console.log('upload file: ' + f) }, fileUpload: (f: any) => { console.log('upload file: ' + f) },
fileTypeLimitMap: { fileTypeLimitMap: {
video: "", video: "",
audio: "", audio: "",
@ -72,18 +73,49 @@ const basicConfig: CherryOptions = {
openai: false, openai: false,
engine: { engine: {
global: { global: {
urlProcessor(url, srcType) { urlProcessor(url: any, srcType: any) {
// console.log(`url-processor`, url, srcType); // console.log(`url-processor`, url, srcType);
return url; return url;
}, },
}, },
syntax: { syntax: {
codeBlock: { autoLink: {
theme: 'twilight', /** default open short link display */
enableShortLink: true,
/** default display 20 characters */
shortLinkLength: 20,
},
list: {
listNested: false, // The sibling list type becomes a child after conversion
indentSpace: 2, // Default 2 space indents
}, },
table: { table: {
enableChart: false, enableChart: false,
// chartEngine: Engine Class // chartRenderEngine: EChartsTableEngine,
// externals: ['echarts'],
},
inlineCode: {
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: { fontEmphasis: {
allowWhitespace: false, // 是否允许首尾空格 allowWhitespace: false, // 是否允许首尾空格
@ -95,6 +127,7 @@ const basicConfig: CherryOptions = {
engine: 'MathJax', // katex或MathJax engine: 'MathJax', // katex或MathJax
// src: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js', // 如果使用MathJax plugins则需要使用该url通过script标签引入 // src: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js', // 如果使用MathJax plugins则需要使用该url通过script标签引入
src: '/deps/mathjax/tex-svg.js', src: '/deps/mathjax/tex-svg.js',
plugins: true,
}, },
inlineMath: { inlineMath: {
engine: 'MathJax', // katex或MathJax engine: 'MathJax', // katex或MathJax
@ -104,6 +137,19 @@ const basicConfig: CherryOptions = {
// customResourceURL: 'https://github.githubassets.com/images/icons/emoji/unicode/${code}.png?v8', // customResourceURL: 'https://github.githubassets.com/images/icons/emoji/unicode/${code}.png?v8',
upperCase: true, upperCase: true,
}, },
toc: {
/** By default, only one directory is rendered */
allowMultiToc: false,
},
header: {
/**
* Style of title
* - default Default style with anchor in front of title
* - autonumber There is a self incrementing sequence number anchor in front of the title
* - none Title has no anchor
*/
anchorStyle: 'autonumber',
},
// toc: { // toc: {
// tocStyle: 'nested' // tocStyle: 'nested'
// } // }
@ -142,11 +188,15 @@ const basicConfig: CherryOptions = {
'graph', 'graph',
'togglePreview', 'togglePreview',
'export', 'export',
'saveMenu',
'backMenu'
], ],
// toolbarRight: [], // toolbarRight: [],
bubble: ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', 'ruby', '|', 'size', 'color'], // array or false bubble: ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', 'ruby', '|', 'size', 'color'], // array or false
// sidebar: false, // sidebar: false,
// float: false // float: false
customMenu: {
} as any,
}, },
drawioIframeUrl: '/cherry/drawio.html', drawioIframeUrl: '/cherry/drawio.html',
editor: { editor: {

@ -12,14 +12,17 @@
// to match your app's branding. // to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website. // Tip: Use the "Theme Builder" on Quasar's documentation website.
$primary : #1976D2;
$secondary : #26A69A; // src/css/quasar.variables.sass
$primary : #f74d22;
$secondary : #fa9243;
$accent : #9C27B0; $accent : #9C27B0;
$dark : #1D1D1D; $dark : #1d1d1d;
$dark-page : #121212; $dark-page : #121212;
$positive : #21BA45; $positive : #21BA45;
$negative : #C10015; $negative : #ff0000;
$info : #31CCEC; $info : #28d4ce;
$warning : #F2C037; $warning : #f2e638;

@ -4,7 +4,7 @@
<q-toolbar class="pl-0"> <q-toolbar class="pl-0">
<q-toolbar-title class="flex items-center cursor-pointer" @click="router.push({ name: 'home' })"> <q-toolbar-title class="flex items-center cursor-pointer" @click="router.push({ name: 'home' })">
<q-icon size="3rem" class="mx-1" color="aqua" name='v-glassdoor' style="color: aqua;"></q-icon> <q-icon size="3rem" class="mx-1" color="#0ff" name='v-glassdoor' style="color: aqua;"></q-icon>
<q-separator dark vertical inset /> <q-separator dark vertical inset />
<span class="ml-3"> <span class="ml-3">
统一认证系统 统一认证系统
@ -42,9 +42,9 @@
</router-view> </router-view>
</q-page> </q-page>
</q-page-container> </q-page-container>
<q-footer bordered class="bg-grey-8 text-white flex justify-around"> <q-footer style="z-index: 1;" bordered class="bg-grey-8 text-white flex justify-around">
<span class="hover:text-black cursor-pointer" @click="$router.push({ name: 'about' })">关于OA</span> <span class="hover:text-black cursor-pointer" @click="$router.push({ name: 'doc' })">关于OA</span>
<span class="hover:text-black cursor-pointer">使用须知</span> <span class="hover:text-black cursor-pointer" @click="$router.push({ name: 'doc' })">使用须知</span>
<span class="hover:text-black cursor-pointer" @click="util.goto('https://veypi.com')"> <span class="hover:text-black cursor-pointer" @click="util.goto('https://veypi.com')">
©2021 veypi ©2021 veypi
</span> </span>

@ -42,8 +42,8 @@ const util = {
return localStorage.getItem('auth_token') || '' return localStorage.getItem('auth_token') || ''
}, },
setToken(t: string) { setToken(t: string) {
evt.emit('token', t)
localStorage.setItem('auth_token', t) localStorage.setItem('auth_token', t)
evt.emit('token', t)
}, },
addTokenOf(url: string) { addTokenOf(url: string) {
return url + '?auth_token=' + encodeURIComponent(this.getToken()) return url + '?auth_token=' + encodeURIComponent(this.getToken())

@ -0,0 +1,37 @@
<!--
* AppCfg.vue
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-10 16:08
* Distributed under terms of the MIT license.
-->
<template>
<div>
<div>
<CRUD :keys="keys" :data="data">
<template #a="">_____</template>
</CRUD>
</div>
</div>
</template>
<script lang="ts" setup>
import CRUD from 'src/components/crud.vue'
import { ref } from 'vue';
const keys = ref<any>([
{
name: 'name',
label: '阿萨德',
}, { name: 'key' }, { name: 'status' }
])
const data = ref([
{ name: 'asd', key: 'akk' },
{ name: '1sd', key: '1kk' },
{ name: '2sd', key: '2kk' },
{ name: '3sd', key: '3kk' },
{ name: '4sd', key: '4kk' },
])
</script>
<style scoped></style>

@ -6,13 +6,10 @@
--> -->
<template> <template>
<div> <div>
<q-page-sticky position="top-right" :offset="[27, 27]"> <q-page-sticky position="top-right" style="z-index: 20" :offset="[27, 27]">
<q-btn @click="sync_editor" :style="{ <q-btn v-if="preview_mode" @click="preview_mode = false" round icon="save_as" class="" />
color: edit_mode ? 'red' :
''
}" round icon="save_as" class="" />
</q-page-sticky> </q-page-sticky>
<Editor v-if="app.id" :eid="app.id + '.des'" :preview="!edit_mode" :content="content" @updated="save"></Editor> <Editor v-if="app.id" :eid="app.id + '.des'" v-model="preview_mode" :content="content" @save="save"></Editor>
</div> </div>
</template> </template>
@ -27,7 +24,7 @@ import oafs from 'src/libs/oafs';
let edit_mode = ref(false) let preview_mode = ref(true)
let app = inject('app') as Ref<modelsApp> let app = inject('app') as Ref<modelsApp>
let content = ref() let content = ref()
@ -46,7 +43,7 @@ const save = (des: string) => {
let a = new File([des], app.value.name + '.md'); let a = new File([des], app.value.name + '.md');
oafs.upload([a], app.value.id).then(url => { oafs.upload([a], app.value.id).then(url => {
api.app.update(app.value.id, { des: url[0] }).then(e => { api.app.update(app.value.id, { des: url[0] }).then(e => {
edit_mode.value = false preview_mode.value = true
app.value.des = url[0] app.value.des = url[0]
}).catch(e => { }).catch(e => {
msg.Warn("更新失败: " + e) msg.Warn("更新失败: " + e)
@ -57,12 +54,6 @@ const save = (des: string) => {
} }
const sync_editor = () => {
edit_mode.value = !edit_mode.value
}
onMounted(() => { onMounted(() => {
sync() sync()
}) })

@ -2,8 +2,9 @@
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<div class="px-10 pb-9 pt-28 rounded-xl w-96"> <div class="px-10 pb-9 pt-28 rounded-xl w-96">
<q-form autofocus @submit="onSubmit" @reset="onReset"> <q-form autofocus @submit="onSubmit" @reset="onReset">
<q-input v-model="data.username" label="用户名" hint="username" lazy-rules :rules="data_rules.username" /> <q-input v-model="data.username" autocomplete="username" label="用户名" hint="username" lazy-rules
<q-input v-model="data.password" :type="isPwd ? 'password' : :rules="data_rules.username" />
<q-input autocomplete="current-password" v-model="data.password" :type="isPwd ? 'password' :
'text'" hint="password" :rules="data_rules.password"> 'text'" hint="password" :rules="data_rules.password">
<template v-slot:append> <template v-slot:append>
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer" @click="isPwd = !isPwd" /> <q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer" @click="isPwd = !isPwd" />
@ -27,7 +28,7 @@ import msg from '@veypi/msg'
import util from 'src/libs/util' import util from 'src/libs/util'
import { useUserStore } from 'src/stores/user' import { useUserStore } from 'src/stores/user'
import { useAppStore } from 'src/stores/app' import { useAppStore } from 'src/stores/app'
import { modelsApp } from 'src/models' import { AUStatus, modelsApp } from 'src/models'
const app = useAppStore() const app = useAppStore()
@ -60,6 +61,12 @@ const onSubmit = () => {
let url = route.query.redirect || data.redirect || '/' let url = route.query.redirect || data.redirect || '/'
redirect(url) redirect(url)
console.log(data) console.log(data)
}).catch(e => {
console.log([e])
let m = e === '1' ? '被禁止登录' : e === '2' ? '正在申请中' : e
=== '3' ?
'申请被拒绝' : '登录失败:' + e
msg.Warn(m)
}) })
} }
const onReset = () => { const onReset = () => {

@ -2,9 +2,12 @@
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<div class="px-10 pb-9 pt-28 rounded-xl w-96"> <div class="px-10 pb-9 pt-28 rounded-xl w-96">
<q-form @submit="register" autofocus> <q-form @submit="register" autofocus>
<q-input v-model="data.username" label="用户名" hint="username" lazy-rules :rules="rules.username"></q-input> <q-input autocomplete="username" v-model="data.username" label="用户名" hint="username" lazy-rules
<q-input label="密码" v-model="data.password" type="password" lazy-rules :rules="rules.password"></q-input> :rules="rules.username"></q-input>
<q-input label="密码" v-model="data.pass" type="password" lazy-rules :rules="rules.pass"></q-input> <q-input autocomplete="new-password" label="密码" v-model="data.password" type="password" lazy-rules
:rules="rules.password"></q-input>
<q-input autocomplete="new-password" label="密码" v-model="data.pass" type="password" lazy-rules
:rules="rules.pass"></q-input>
<div class="flex justify-around mt-4"> <div class="flex justify-around mt-4">
<q-btn label="注册" type="submit" color="primary" /> <q-btn label="注册" type="submit" color="primary" />
</div> </div>

@ -46,7 +46,7 @@ const routes: RouteRecordRaw[] = [
loadcomponents('home', 'app.home', 'AppHome'), loadcomponents('home', 'app.home', 'AppHome'),
loadcomponents('user', 'app.user', 'AppUser'), loadcomponents('user', 'app.user', 'AppUser'),
loadcomponents('auth', 'app.auth', 'AppAuth'), loadcomponents('auth', 'app.auth', 'AppAuth'),
loadcomponents('settings', 'app.settings', 'IndexPage'), loadcomponents('settings', 'app.settings', 'AppCfg'),
] ]
} }
], ],

Loading…
Cancel
Save