fix: resolved eslint errors

This commit is contained in:
0xJacky 2023-11-29 13:56:40 +08:00
parent 287ef7527d
commit d325dd7493
No known key found for this signature in database
GPG key ID: B6E4A6E4A561BAF0
33 changed files with 643 additions and 584 deletions

9
app/components.d.ts vendored
View file

@ -77,14 +77,6 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
SetLanguageSetLanguage: typeof import('./src/components/SetLanguage/SetLanguage.vue')['default'] SetLanguageSetLanguage: typeof import('./src/components/SetLanguage/SetLanguage.vue')['default']
StdDataDisplayStdBatchEdit: typeof import('./src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue')['default']
StdDataDisplayStdCurd: typeof import('./src/components/StdDesign/StdDataDisplay/StdCurd.vue')['default']
StdDataDisplayStdPagination: typeof import('./src/components/StdDesign/StdDataDisplay/StdPagination.vue')['default']
StdDataDisplayStdTable: typeof import('./src/components/StdDesign/StdDataDisplay/StdTable.vue')['default']
StdDataEntryComponentsStdPassword: typeof import('./src/components/StdDesign/StdDataEntry/components/StdPassword.vue')['default']
StdDataEntryComponentsStdSelect: typeof import('./src/components/StdDesign/StdDataEntry/components/StdSelect.vue')['default']
StdDataEntryComponentsStdSelector: typeof import('./src/components/StdDesign/StdDataEntry/components/StdSelector.vue')['default']
StdDataEntryStdFormItem: typeof import('./src/components/StdDesign/StdDataEntry/StdFormItem.vue')['default']
StdDesignStdDataDisplayStdBatchEdit: typeof import('./src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue')['default'] StdDesignStdDataDisplayStdBatchEdit: typeof import('./src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue')['default']
StdDesignStdDataDisplayStdCurd: typeof import('./src/components/StdDesign/StdDataDisplay/StdCurd.vue')['default'] StdDesignStdDataDisplayStdCurd: typeof import('./src/components/StdDesign/StdDataDisplay/StdCurd.vue')['default']
StdDesignStdDataDisplayStdPagination: typeof import('./src/components/StdDesign/StdDataDisplay/StdPagination.vue')['default'] StdDesignStdDataDisplayStdPagination: typeof import('./src/components/StdDesign/StdDataDisplay/StdPagination.vue')['default']
@ -92,6 +84,7 @@ declare module 'vue' {
StdDesignStdDataEntryComponentsStdPassword: typeof import('./src/components/StdDesign/StdDataEntry/components/StdPassword.vue')['default'] StdDesignStdDataEntryComponentsStdPassword: typeof import('./src/components/StdDesign/StdDataEntry/components/StdPassword.vue')['default']
StdDesignStdDataEntryComponentsStdSelect: typeof import('./src/components/StdDesign/StdDataEntry/components/StdSelect.vue')['default'] StdDesignStdDataEntryComponentsStdSelect: typeof import('./src/components/StdDesign/StdDataEntry/components/StdSelect.vue')['default']
StdDesignStdDataEntryComponentsStdSelector: typeof import('./src/components/StdDesign/StdDataEntry/components/StdSelector.vue')['default'] StdDesignStdDataEntryComponentsStdSelector: typeof import('./src/components/StdDesign/StdDataEntry/components/StdSelector.vue')['default']
StdDesignStdDataEntryStdDataEntry: typeof import('./src/components/StdDesign/StdDataEntry/StdDataEntry.vue')['default']
StdDesignStdDataEntryStdFormItem: typeof import('./src/components/StdDesign/StdDataEntry/StdFormItem.vue')['default'] StdDesignStdDataEntryStdFormItem: typeof import('./src/components/StdDesign/StdDataEntry/StdFormItem.vue')['default']
SwitchAppearanceIconsVPIconMoon: typeof import('./src/components/SwitchAppearance/icons/VPIconMoon.vue')['default'] SwitchAppearanceIconsVPIconMoon: typeof import('./src/components/SwitchAppearance/icons/VPIconMoon.vue')['default']
SwitchAppearanceIconsVPIconSun: typeof import('./src/components/SwitchAppearance/icons/VPIconSun.vue')['default'] SwitchAppearanceIconsVPIconSun: typeof import('./src/components/SwitchAppearance/icons/VPIconSun.vue')['default']

5
app/env.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
declare module '*.svg' {
import React from 'react'
const content: React.FC<React.SVGProps<SVGElement>>
export default content
}

View file

@ -1,11 +1,10 @@
{ {
"name": "nginx-ui-app-next", "name": "nginx-ui-app-next",
"private": true,
"version": "2.0.0-beta.4", "version": "2.0.0-beta.4",
"type": "commonjs",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"lint": "eslint . -c .eslintrc.js --fix --ext .ts,.vue,.tsx", "typecheck": "vue-tsc --noEmit",
"lint": "eslint . -c .eslintrc.js --fix --ext .ts,.vue,.tsx,.d.ts",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"gettext:extract": "vue-gettext-extract", "gettext:extract": "vue-gettext-extract",
@ -14,9 +13,6 @@
"dependencies": { "dependencies": {
"@ant-design/icons-vue": "^7.0.1", "@ant-design/icons-vue": "^7.0.1",
"@formkit/auto-animate": "^0.8.0", "@formkit/auto-animate": "^0.8.0",
"@types/lodash": "^4.14.202",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.15.0",
"@vue/reactivity": "^3.3.9", "@vue/reactivity": "^3.3.9",
"@vue/shared": "^3.3.9", "@vue/shared": "^3.3.9",
"@vueuse/core": "^10.6.1", "@vueuse/core": "^10.6.1",
@ -45,6 +41,10 @@
"xterm-addon-fit": "^0.8.0" "xterm-addon-fit": "^0.8.0"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.14.202",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.15.0",
"@vue/tsconfig": "^0.4.0",
"@antfu/eslint-config-vue": "^0.43.1", "@antfu/eslint-config-vue": "^0.43.1",
"@typescript-eslint/eslint-plugin": "^6.13.0", "@typescript-eslint/eslint-plugin": "^6.13.0",
"@typescript-eslint/parser": "^6.13.0", "@typescript-eslint/parser": "^6.13.0",
@ -67,7 +67,7 @@
"unplugin-auto-import": "^0.17.1", "unplugin-auto-import": "^0.17.1",
"unplugin-vue-components": "^0.25.2", "unplugin-vue-components": "^0.25.2",
"unplugin-vue-define-options": "^1.4.0", "unplugin-vue-define-options": "^1.4.0",
"vite": "^5.0.2", "vite": "^5.0.3",
"vite-plugin-html": "^3.2.0", "vite-plugin-html": "^3.2.0",
"vite-svg-loader": "^5.1.0", "vite-svg-loader": "^5.1.0",
"vue-tsc": "^1.8.22" "vue-tsc": "^1.8.22"

57
app/pnpm-lock.yaml generated
View file

@ -11,15 +11,6 @@ dependencies:
'@formkit/auto-animate': '@formkit/auto-animate':
specifier: ^0.8.0 specifier: ^0.8.0
version: 0.8.1 version: 0.8.1
'@types/lodash':
specifier: ^4.14.202
version: 4.14.202
'@types/nprogress':
specifier: ^0.2.0
version: 0.2.3
'@types/sortablejs':
specifier: ^1.15.0
version: 1.15.7
'@vue/reactivity': '@vue/reactivity':
specifier: ^3.3.9 specifier: ^3.3.9
version: 3.3.9 version: 3.3.9
@ -103,6 +94,15 @@ devDependencies:
'@antfu/eslint-config-vue': '@antfu/eslint-config-vue':
specifier: ^0.43.1 specifier: ^0.43.1
version: 0.43.1(@typescript-eslint/eslint-plugin@6.13.0)(@typescript-eslint/parser@6.13.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0)(typescript@5.3.2) version: 0.43.1(@typescript-eslint/eslint-plugin@6.13.0)(@typescript-eslint/parser@6.13.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0)(typescript@5.3.2)
'@types/lodash':
specifier: ^4.14.202
version: 4.14.202
'@types/nprogress':
specifier: ^0.2.0
version: 0.2.3
'@types/sortablejs':
specifier: ^1.15.0
version: 1.15.7
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^6.13.0 specifier: ^6.13.0
version: 6.13.0(@typescript-eslint/parser@6.13.0)(eslint@8.54.0)(typescript@5.3.2) version: 6.13.0(@typescript-eslint/parser@6.13.0)(eslint@8.54.0)(typescript@5.3.2)
@ -111,13 +111,16 @@ devDependencies:
version: 6.13.0(eslint@8.54.0)(typescript@5.3.2) version: 6.13.0(eslint@8.54.0)(typescript@5.3.2)
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: ^4.5.0 specifier: ^4.5.0
version: 4.5.0(vite@5.0.2)(vue@3.3.9) version: 4.5.0(vite@5.0.3)(vue@3.3.9)
'@vitejs/plugin-vue-jsx': '@vitejs/plugin-vue-jsx':
specifier: ^3.1.0 specifier: ^3.1.0
version: 3.1.0(vite@5.0.2)(vue@3.3.9) version: 3.1.0(vite@5.0.3)(vue@3.3.9)
'@vue/compiler-sfc': '@vue/compiler-sfc':
specifier: ^3.3.9 specifier: ^3.3.9
version: 3.3.9 version: 3.3.9
'@vue/tsconfig':
specifier: ^0.4.0
version: 0.4.0
ace-builds: ace-builds:
specifier: ^1.31.2 specifier: ^1.31.2
version: 1.31.2 version: 1.31.2
@ -167,11 +170,11 @@ devDependencies:
specifier: ^1.4.0 specifier: ^1.4.0
version: 1.4.0(vue@3.3.9) version: 1.4.0(vue@3.3.9)
vite: vite:
specifier: ^5.0.2 specifier: ^5.0.3
version: 5.0.2(less@4.2.0) version: 5.0.3(less@4.2.0)
vite-plugin-html: vite-plugin-html:
specifier: ^3.2.0 specifier: ^3.2.0
version: 3.2.0(vite@5.0.2) version: 3.2.0(vite@5.0.3)
vite-svg-loader: vite-svg-loader:
specifier: ^5.1.0 specifier: ^5.1.0
version: 5.1.0(vue@3.3.9) version: 5.1.0(vue@3.3.9)
@ -1297,7 +1300,7 @@ packages:
/@types/lodash@4.14.202: /@types/lodash@4.14.202:
resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==}
dev: false dev: true
/@types/mdast@3.0.15: /@types/mdast@3.0.15:
resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==}
@ -1327,7 +1330,7 @@ packages:
/@types/nprogress@0.2.3: /@types/nprogress@0.2.3:
resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==}
dev: false dev: true
/@types/parse-json@4.0.2: /@types/parse-json@4.0.2:
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@ -1343,7 +1346,7 @@ packages:
/@types/sortablejs@1.15.7: /@types/sortablejs@1.15.7:
resolution: {integrity: sha512-PvgWCx1Lbgm88FdQ6S7OGvLIjWS66mudKPlfdrWil0TjsO5zmoZmzoKiiwRShs1dwPgrlkr0N4ewuy0/+QUXYQ==} resolution: {integrity: sha512-PvgWCx1Lbgm88FdQ6S7OGvLIjWS66mudKPlfdrWil0TjsO5zmoZmzoKiiwRShs1dwPgrlkr0N4ewuy0/+QUXYQ==}
dev: false dev: true
/@types/unist@2.0.10: /@types/unist@2.0.10:
resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==}
@ -1549,7 +1552,7 @@ packages:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true dev: true
/@vitejs/plugin-vue-jsx@3.1.0(vite@5.0.2)(vue@3.3.9): /@vitejs/plugin-vue-jsx@3.1.0(vite@5.0.3)(vue@3.3.9):
resolution: {integrity: sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==} resolution: {integrity: sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -1559,20 +1562,20 @@ packages:
'@babel/core': 7.23.3 '@babel/core': 7.23.3
'@babel/plugin-transform-typescript': 7.23.4(@babel/core@7.23.3) '@babel/plugin-transform-typescript': 7.23.4(@babel/core@7.23.3)
'@vue/babel-plugin-jsx': 1.1.5(@babel/core@7.23.3) '@vue/babel-plugin-jsx': 1.1.5(@babel/core@7.23.3)
vite: 5.0.2(less@4.2.0) vite: 5.0.3(less@4.2.0)
vue: 3.3.9(typescript@5.3.2) vue: 3.3.9(typescript@5.3.2)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@vitejs/plugin-vue@4.5.0(vite@5.0.2)(vue@3.3.9): /@vitejs/plugin-vue@4.5.0(vite@5.0.3)(vue@3.3.9):
resolution: {integrity: sha512-a2WSpP8X8HTEww/U00bU4mX1QpLINNuz/2KMNpLsdu3BzOpak3AGI1CJYBTXcc4SPhaD0eNRUp7IyQK405L5dQ==} resolution: {integrity: sha512-a2WSpP8X8HTEww/U00bU4mX1QpLINNuz/2KMNpLsdu3BzOpak3AGI1CJYBTXcc4SPhaD0eNRUp7IyQK405L5dQ==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies: peerDependencies:
vite: ^4.0.0 || ^5.0.0 vite: ^4.0.0 || ^5.0.0
vue: ^3.2.25 vue: ^3.2.25
dependencies: dependencies:
vite: 5.0.2(less@4.2.0) vite: 5.0.3(less@4.2.0)
vue: 3.3.9(typescript@5.3.2) vue: 3.3.9(typescript@5.3.2)
dev: true dev: true
@ -1734,6 +1737,10 @@ packages:
/@vue/shared@3.3.9: /@vue/shared@3.3.9:
resolution: {integrity: sha512-ZE0VTIR0LmYgeyhurPTpy4KzKsuDyQbMSdM49eKkMnT5X4VfFBLysMzjIZhLEFQYjjOVVfbvUDHckwjDFiO2eA==} resolution: {integrity: sha512-ZE0VTIR0LmYgeyhurPTpy4KzKsuDyQbMSdM49eKkMnT5X4VfFBLysMzjIZhLEFQYjjOVVfbvUDHckwjDFiO2eA==}
/@vue/tsconfig@0.4.0:
resolution: {integrity: sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg==}
dev: true
/@vueuse/core@10.6.1(vue@3.3.9): /@vueuse/core@10.6.1(vue@3.3.9):
resolution: {integrity: sha512-Pc26IJbqgC9VG1u6VY/xrXXfxD33hnvxBnKrLlA2LJlyHII+BSrRoTPJgGYq7qZOu61itITFUnm6QbacwZ4H8Q==} resolution: {integrity: sha512-Pc26IJbqgC9VG1u6VY/xrXXfxD33hnvxBnKrLlA2LJlyHII+BSrRoTPJgGYq7qZOu61itITFUnm6QbacwZ4H8Q==}
dependencies: dependencies:
@ -5360,7 +5367,7 @@ packages:
- terser - terser
dev: false dev: false
/vite-plugin-html@3.2.0(vite@5.0.2): /vite-plugin-html@3.2.0(vite@5.0.3):
resolution: {integrity: sha512-2VLCeDiHmV/BqqNn5h2V+4280KRgQzCFN47cst3WiNK848klESPQnzuC3okH5XHtgwHH/6s1Ho/YV6yIO0pgoQ==} resolution: {integrity: sha512-2VLCeDiHmV/BqqNn5h2V+4280KRgQzCFN47cst3WiNK848klESPQnzuC3okH5XHtgwHH/6s1Ho/YV6yIO0pgoQ==}
peerDependencies: peerDependencies:
vite: '>=2.0.0' vite: '>=2.0.0'
@ -5377,7 +5384,7 @@ packages:
html-minifier-terser: 6.1.0 html-minifier-terser: 6.1.0
node-html-parser: 5.4.2 node-html-parser: 5.4.2
pathe: 0.2.0 pathe: 0.2.0
vite: 5.0.2(less@4.2.0) vite: 5.0.3(less@4.2.0)
dev: true dev: true
/vite-svg-loader@5.1.0(vue@3.3.9): /vite-svg-loader@5.1.0(vue@3.3.9):
@ -5426,8 +5433,8 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: false dev: false
/vite@5.0.2(less@4.2.0): /vite@5.0.3(less@4.2.0):
resolution: {integrity: sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g==} resolution: {integrity: sha512-WgEq8WEKpZ8c0DL4M1+E+kBZEJyjBmGVrul6z8Ljfhv+PPbNF4aGq014DwNYxGz2FGq6NKL0N8usdiESWd2l2w==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:

View file

@ -26,6 +26,7 @@ class Curd<T> {
get = this._get.bind(this) get = this._get.bind(this)
save = this._save.bind(this) save = this._save.bind(this)
destroy = this._destroy.bind(this) destroy = this._destroy.bind(this)
update_order = this._update_order.bind(this)
constructor(baseUrl: string, plural: string | null = null) { constructor(baseUrl: string, plural: string | null = null) {
this.baseUrl = baseUrl this.baseUrl = baseUrl
@ -51,6 +52,14 @@ class Curd<T> {
_destroy(id: any = null) { _destroy(id: any = null) {
return http.delete(`${this.baseUrl}/${id}`) return http.delete(`${this.baseUrl}/${id}`)
} }
_update_order(data: {
target_id: number
direction: number
affected_ids: number[]
}) {
return http.post(`${this.plural}/order`, data)
}
} }
export default Curd export default Curd

View file

@ -1,5 +1,12 @@
import type { ModelBase } from '@/api/curd'
import Curd from '@/api/curd' import Curd from '@/api/curd'
const environment = new Curd('/environment') export interface Environment extends ModelBase {
name: string
url: string
token: string
}
const environment: Curd<Environment> = new Curd('/environment')
export default environment export default environment

View file

@ -7,7 +7,7 @@ export interface ChatComplicationMessage {
} }
const openai = { const openai = {
store_record(data: { file_name: string; messages: ChatComplicationMessage[] }) { store_record(data: { file_name?: string; messages: ChatComplicationMessage[] }) {
return http.post('/chat_gpt_record', data) return http.post('/chat_gpt_record', data)
}, },
} }

View file

@ -1,5 +1,11 @@
import type { ModelBase } from '@/api/curd'
import Curd from '@/api/curd' import Curd from '@/api/curd'
const user: Curd = new Curd('user') export interface User extends ModelBase {
name: string
password: string
}
const user: Curd<User> = new Curd('user')
export default user export default user

View file

@ -17,7 +17,7 @@ const breadList = computed(() => {
route.matched.forEach(item => { route.matched.forEach(item => {
// item.name !== 'index' && this.breadList.push(item) // item.name !== 'index' && this.breadList.push(item)
_breadList.push({ _breadList.push({
name: item.name as () => string, name: item.name as never as () => string,
path: item.path, path: item.path,
}) })
}) })

View file

@ -5,7 +5,7 @@ import type { Ref } from 'vue'
import { useSettingsStore } from '@/pinia' import { useSettingsStore } from '@/pinia'
import type { Series } from '@/components/Chart/types' import type { Series } from '@/components/Chart/types'
const { series, max, y_formatter } = defineProps<{ const { series, max, yFormatter } = defineProps<{
series: Series[] series: Series[]
max?: number max?: number
yFormatter?: (value: number) => string yFormatter?: (value: number) => string
@ -69,7 +69,7 @@ let chartOptions = {
style: { style: {
colors: fontColor(), colors: fontColor(),
}, },
formatter: y_formatter, formatter: yFormatter,
}, },
}, },
legend: { legend: {
@ -106,7 +106,7 @@ const callback = () => {
style: { style: {
colors: fontColor(), colors: fontColor(),
}, },
formatter: y_formatter, formatter: yFormatter,
}, },
}, },
legend: { legend: {

View file

@ -295,7 +295,7 @@ const show = computed(() => !messages.value || messages.value?.length === 0)
v-model:value="ask_buffer" v-model:value="ask_buffer"
auto-size auto-size
/> />
<div class="sned-btn"> <div class="send-btn">
<AButton <AButton
size="small" size="small"
type="text" type="text"
@ -356,7 +356,7 @@ const show = computed(() => !messages.value || messages.value?.length === 0)
justify-content: center; justify-content: center;
} }
.sned-btn { .send-btn {
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 3px; bottom: 3px;

View file

@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import environment from '@/api/environment' import environment from '@/api/environment'

View file

@ -1,82 +1,45 @@
<script setup lang="ts"> <script setup lang="ts">
import { provide, reactive, ref } from 'vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import type { ComputedRef } from 'vue'
import type { StdTableProps } from './StdTable.vue'
import StdTable from './StdTable.vue' import StdTable from './StdTable.vue'
import gettext from '@/gettext' import gettext from '@/gettext'
import StdDataEntry from '@/components/StdDesign/StdDataEntry' import StdDataEntry from '@/components/StdDesign/StdDataEntry'
import type { Column } from '@/components/StdDesign/types'
const props = defineProps({ export interface StdCurdProps {
api: Object, cardTitleKey?: string
columns: Array, modalMaxWidth?: string | number
title: String, disableAdd?: boolean
data_key: { onClickAdd?: () => void
type: String, // eslint-disable-next-line @typescript-eslint/no-explicit-any
default: 'data', onClickEdit?: (id: number | string, record: any, index: number) => void
}, // eslint-disable-next-line @typescript-eslint/no-explicit-any
disable_search: { beforeSave?: (data: any) => void
type: Boolean, }
default: false,
}, const props = defineProps<StdTableProps & StdCurdProps>()
disable_add: {
type: Boolean,
default: false,
},
soft_delete: {
type: Boolean,
default: false,
},
edit_text: String,
deletable: {
type: Boolean,
default: true,
},
get_params: {
type: Object,
default() {
return {}
},
},
editable: {
type: Boolean,
default: true,
},
beforeSave: {
type: Function,
default: () => {
},
},
exportCsv: {
type: Boolean,
default: false,
},
modalWidth: {
type: Number,
default: 600,
},
useSortable: Boolean,
})
const { $gettext } = gettext const { $gettext } = gettext
const visible = ref(false) const visible = ref(false)
const update = ref(0) const update = ref(0)
const data: any = reactive({ id: null }) const data = reactive({ id: null })
provide('data', data) provide('data', data)
const error: any = reactive({}) const error = reactive({})
const selected = ref([]) const selected = ref([])
function onSelect(keys: any) { function onSelect(keys) {
selected.value = keys selected.value = keys
} }
function editableColumns() { const editableColumns = computed(() => {
return props.columns!.filter((c: any) => { return props.columns!.filter(c => {
return c.edit return c.edit
}) })
} }) as ComputedRef<Column[]>
function add() { function add() {
Object.keys(data).forEach(v => { Object.keys(data).forEach(v => {
@ -86,11 +49,9 @@ function add() {
clear_error() clear_error()
visible.value = true visible.value = true
} }
const table = ref()
function get_list() { function get_list() {
const t: Table = table.value! table.value?.get_list()
t!.get_list()
} }
defineExpose({ defineExpose({
@ -99,12 +60,6 @@ defineExpose({
data, data,
}) })
const table = ref(null)
interface Table {
get_list(): void
}
function clear_error() { function clear_error() {
Object.keys(error).forEach(v => { Object.keys(error).forEach(v => {
delete error[v] delete error[v]
@ -114,12 +69,12 @@ function clear_error() {
const ok = async () => { const ok = async () => {
clear_error() clear_error()
await props?.beforeSave?.(data) await props?.beforeSave?.(data)
props.api!.save(data.id, data).then((r: any) => { props.api!.save(data.id, data).then(r => {
message.success($gettext('Save Successfully')) message.success($gettext('Save Successfully'))
Object.assign(data, r) Object.assign(data, r)
get_list() get_list()
visible.value = false visible.value = false
}).catch((e: any) => { }).catch(e => {
message.error($gettext(e?.message ?? 'Server error'), 5) message.error($gettext(e?.message ?? 'Server error'), 5)
Object.assign(error, e.errors) Object.assign(error, e.errors)
}) })
@ -131,15 +86,15 @@ function cancel() {
clear_error() clear_error()
} }
function edit(id: any) { function edit(id) {
props.api!.get(id).then(async (r: any) => { props.api!.get(id).then(async r => {
Object.keys(data).forEach(k => { Object.keys(data).forEach(k => {
delete data[k] delete data[k]
}) })
data.id = null data.id = null
Object.assign(data, r) Object.assign(data, r)
visible.value = true visible.value = true
}).catch((e: any) => { }).catch(e => {
message.error($gettext(e?.message ?? 'Server error'), 5) message.error($gettext(e?.message ?? 'Server error'), 5)
}) })
} }
@ -151,7 +106,7 @@ const selectedRowKeys = ref([])
<div class="std-curd"> <div class="std-curd">
<ACard :title="title || $gettext('Table')"> <ACard :title="title || $gettext('Table')">
<template <template
v-if="!disable_add" v-if="!disableAdd"
#extra #extra
> >
<a @click="add">{{ $gettext('Add') }}</a> <a @click="add">{{ $gettext('Add') }}</a>
@ -162,7 +117,7 @@ const selectedRowKeys = ref([])
v-bind="props" v-bind="props"
:key="update" :key="update"
v-model:selected-row-keys="selectedRowKeys" v-model:selected-row-keys="selectedRowKeys"
@clickEdit="edit" @click-edit="edit"
@selected="onSelect" @selected="onSelect"
> >
<template #actions="slotProps"> <template #actions="slotProps">
@ -177,11 +132,11 @@ const selectedRowKeys = ref([])
<AModal <AModal
class="std-curd-edit-modal" class="std-curd-edit-modal"
:mask="false" :mask="false"
:title="edit_text ? edit_text : (data.id ? $gettext('Modify') : $gettext('Add'))" :title="data.id ? $gettext('Modify') : $gettext('Add')"
:open="visible" :open="visible"
:cancel-text="$gettext('Cancel')" :cancel-text="$gettext('Cancel')"
:ok-text="$gettext('OK')" :ok-text="$gettext('OK')"
:width="modalWidth" :width="modalMaxWidth"
destroy-on-close destroy-on-close
@cancel="cancel" @cancel="cancel"
@ok="ok" @ok="ok"
@ -197,8 +152,7 @@ const selectedRowKeys = ref([])
</div> </div>
<StdDataEntry <StdDataEntry
ref="std_data_entry" :data-list="editableColumns"
:data-list="editableColumns()"
:data-source="data" :data-source="data"
:error="error" :error="error"
/> />

View file

@ -4,7 +4,7 @@ import type { Pagination } from '@/api/curd'
const props = defineProps<{ const props = defineProps<{
pagination: Pagination pagination: Pagination
size?: 'small' | 'default' size?: string
}>() }>()
const emit = defineEmits(['change', 'changePageSize', 'update:pagination']) const emit = defineEmits(['change', 'changePageSize', 'update:pagination'])

View file

@ -1,103 +1,71 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, reactive, ref, toRaw, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import Sortable from 'sortablejs'
import { HolderOutlined } from '@ant-design/icons-vue' import { HolderOutlined } from '@ant-design/icons-vue'
import _ from 'lodash' import { useGettext } from 'vue3-gettext'
import type { ComputedRef } from 'vue'
import type { SorterResult } from 'ant-design-vue/lib/table/interface'
import StdPagination from './StdPagination.vue' import StdPagination from './StdPagination.vue'
import { downloadCsv } from '@/lib/helper'
import StdDataEntry from '@/components/StdDesign/StdDataEntry' import StdDataEntry from '@/components/StdDesign/StdDataEntry'
import gettext from '@/gettext' import type { Pagination } from '@/api/curd'
import type { Column } from '@/components/StdDesign/types'
import exportCsvHandler from '@/components/StdDesign/StdDataDisplay/methods/exportCsv'
import useSortable from '@/components/StdDesign/StdDataDisplay/methods/sortable'
import type Curd from '@/api/curd'
const props = defineProps({ export interface StdTableProps {
api: Object, title?: string
columns: Array, mode?: string
data_key: { rowKey?: string
type: String, // eslint-disable-next-line @typescript-eslint/no-explicit-any
default: 'data', api: Curd<any>
}, columns: Column[]
disable_search: { // eslint-disable-next-line @typescript-eslint/no-explicit-any
type: Boolean, getParams?: Record<string, any>
default: false, size?: string
}, disableQueryParams?: boolean
disable_query_params: { disableSearch?: boolean
type: Boolean, pithy?: boolean
default: false, exportCsv?: boolean
}, // eslint-disable-next-line @typescript-eslint/no-explicit-any
disable_add: { overwriteParams?: Record<string, any>
type: Boolean, disabledModify?: boolean
default: false, selectionType?: string
}, sortable?: boolean
edit_text: String, disableDelete?: boolean
deletable: { disablePagination?: boolean
type: Boolean, // eslint-disable-next-line @typescript-eslint/no-explicit-any
default: true, selectedRowKeys?: any | any[]
}, sortableMoveHook?: (oldRow: number[], newRow: number[]) => boolean
get_params: { scrollX?: string | number
type: Object, }
default() {
return {} const props = withDefaults(defineProps<StdTableProps>(), {
}, rowKey: 'id',
},
editable: {
type: Boolean,
default: true,
},
selectionType: {
type: String,
validator(value: string) {
return ['checkbox', 'radio'].includes(value)
},
},
pithy: {
type: Boolean,
default: false,
},
scrollX: {
type: [Number, Boolean],
default: true,
},
rowKey: {
type: String,
default: 'id',
},
exportCsv: {
type: Boolean,
default: false,
},
size: String,
selectedRowKeys: {
type: Array,
},
useSortable: Boolean,
}) })
const emit = defineEmits(['onSelected', 'onSelectedRecord', 'clickEdit', 'update:selectedRowKeys', 'clickBatchModify']) const emit = defineEmits(['onSelected', 'onSelectedRecord', 'clickEdit', 'update:selectedRowKeys', 'clickBatchModify'])
const { $gettext } = useGettext()
const { $gettext, interpolate } = gettext const route = useRoute()
const dataSource = ref([])
const data_source: any = ref([]) const expandKeysList = ref([])
const expand_keys_list: any = ref([]) const rowsKeyIndexMap = ref({})
const rows_key_index_map: any = ref({})
const loading = ref(true) const loading = ref(true)
const pagination = reactive({ // This can be useful if there are more than one StdTable in the same page.
const randomId = ref(Math.random().toString(36).substring(2, 8))
const pagination: Pagination = reactive({
total: 1, total: 1,
per_page: 10, per_page: 10,
current_page: 1, current_page: 1,
total_pages: 1, total_pages: 1,
}) })
const route = useRoute()
const params = reactive({ const params = reactive({
...props.get_params, ...props.getParams,
}) })
const selectedKeysLocalBuffer: any = ref([]) const selectedKeysLocalBuffer = ref([])
const selectedRowKeysBuffer = computed({ const selectedRowKeysBuffer = computed({
get() { get() {
@ -109,17 +77,47 @@ const selectedRowKeysBuffer = computed({
}, },
}) })
const searchColumns = getSearchColumns() const searchColumns = computed(() => {
const pithyColumns = getPithyColumns() const _searchColumns = []
const batchColumns = getBatchEditColumns()
props.columns?.forEach(column => {
if (column.search)
_searchColumns.push(column)
})
return _searchColumns
})
const pithyColumns = computed(() => {
if (props.pithy) {
return props.columns?.filter(c => {
return c.pithy === true && !c.hidden
})
}
return props.columns?.filter(c => {
return !c.hidden
})
}) as ComputedRef<Column[]>
const batchColumns = computed(() => {
const batch = []
props.columns?.forEach(column => {
if (column.batch)
batch.push(column)
})
return batch
})
onMounted(() => { onMounted(() => {
if (!props.disable_query_params) if (!props.disableQueryParams)
Object.assign(params, route.query) Object.assign(params, route.query)
get_list() get_list()
if (props.useSortable) if (props.sortable)
initSortable() initSortable()
}) })
@ -127,11 +125,11 @@ defineExpose({
get_list, get_list,
}) })
function destroy(id: any) { function destroy(id) {
props.api!.destroy(id).then(() => { props.api!.destroy(id).then(() => {
get_list() get_list()
message.success(interpolate($gettext('Delete ID: %{id}'), { id })) message.success($gettext('Deleted successfully'))
}).catch((e: any) => { }).catch(e => {
message.error($gettext(e?.message ?? 'Server error')) message.error($gettext(e?.message ?? 'Server error'))
}) })
} }
@ -142,37 +140,35 @@ function get_list(page_num = null, page_size = 20) {
params.page = page_num params.page = page_num
params.page_size = page_size params.page_size = page_size
} }
props.api!.get_list(params).then(async (r: any) => { props.api?.get_list(params).then(async r => {
data_source.value = r.data dataSource.value = r.data
rows_key_index_map.value = {} rowsKeyIndexMap.value = {}
if (props.useSortable) { if (props.sortable)
function buildIndexMap(data: any, level: number = 0, index: number = 0, total: number[] = []) {
if (data && data.length > 0) {
data.forEach((v: any) => {
v.level = level
const current_index = [...total, index++]
rows_key_index_map.value[v.id] = current_index
if (v.children)
buildIndexMap(v.children, level + 1, 0, current_index)
})
}
}
buildIndexMap(r.data) buildIndexMap(r.data)
}
if (r.pagination !== undefined) if (r.pagination)
Object.assign(pagination, r.pagination) Object.assign(pagination, r.pagination)
loading.value = false loading.value = false
}).catch((e: any) => { }).catch(e => {
message.error(e?.message ?? $gettext('Server error')) message.error(e?.message ?? $gettext('Server error'))
}) })
} }
function buildIndexMap(data, level: number = 0, index: number = 0, total: number[] = []) {
if (data && data.length > 0) {
data.forEach(v => {
v.level = level
function stdChange(pagination: any, filters: any, sorter: any) { const current_indexes = [...total, index++]
rowsKeyIndexMap.value[v.id] = current_indexes
if (v.children)
buildIndexMap(v.children, level + 1, 0, current_indexes)
})
}
}
function orderPaginationChange(_pagination: Pagination, filters, sorter: SorterResult) {
if (sorter) { if (sorter) {
selectedRowKeysBuffer.value = [] selectedRowKeysBuffer.value = []
params.order_by = sorter.field params.order_by = sorter.field
@ -189,67 +185,29 @@ function stdChange(pagination: any, filters: any, sorter: any) {
break break
} }
} }
if (pagination) if (_pagination)
selectedRowKeysBuffer.value = [] selectedRowKeysBuffer.value = []
} }
function expandedTable(keys: any) { function expandedTable(keys) {
expand_keys_list.value = keys expandKeysList.value = keys
} }
function getSearchColumns() { const crossPageSelect = {}
const searchColumns: any = []
props.columns!.forEach((column: any) => { async function onSelectChange(_selectedRowKeys) {
if (column.search)
searchColumns.push(column)
})
return searchColumns
}
function getBatchEditColumns() {
const batch: any = []
props.columns!.forEach((column: any) => {
if (column.batch)
batch.push(column)
})
return batch
}
function getPithyColumns() {
if (props.pithy) {
return props.columns!.filter((c: any, index: any, columns: any) => {
return c.pithy === true && c.display !== false
})
}
return props.columns!.filter((c: any, index: any, columns: any) => {
return c.display !== false
})
}
function checked(c: any) {
params[c.target.value] = c.target.checked
}
const crossPageSelect: any = {}
async function onSelectChange(_selectedRowKeys: any) {
const page = params.page || 1 const page = params.page || 1
crossPageSelect[page] = await _selectedRowKeys crossPageSelect[page] = await _selectedRowKeys
let t: any = [] let t = []
Object.keys(crossPageSelect).forEach(v => { Object.keys(crossPageSelect).forEach(v => {
t.push(...crossPageSelect[v]) t.push(...crossPageSelect[v])
}) })
const n: any = [..._selectedRowKeys] const n = [..._selectedRowKeys]
t = await t.concat(n) t = t.concat(n)
// console.log(crossPageSelect) // console.log(crossPageSelect)
const set = new Set(t) const set = new Set(t)
@ -258,7 +216,7 @@ async function onSelectChange(_selectedRowKeys: any) {
emit('onSelected', selectedRowKeysBuffer.value) emit('onSelected', selectedRowKeysBuffer.value)
} }
function onSelect(record: any) { function onSelect(record) {
emit('onSelectedRecord', record) emit('onSelectedRecord', record)
} }
@ -270,7 +228,7 @@ const reset_search = async () => {
}) })
Object.assign(params, { Object.assign(params, {
...props.get_params, ...props.getParams,
}) })
router.push({ query: {} }).catch(() => { router.push({ query: {} }).catch(() => {
@ -278,19 +236,19 @@ const reset_search = async () => {
} }
watch(params, () => { watch(params, () => {
if (!props.disable_query_params) if (!props.disableQueryParams)
router.push({ query: params }) router.push({ query: params })
get_list() get_list()
}) })
const rowSelection = computed(() => { const rowSelection = computed(() => {
if (batchColumns.length > 0 || props.selectionType) { if (batchColumns.value.length > 0 || props.selectionType) {
return { return {
selectedRowKeys: selectedRowKeysBuffer.value, selectedRowKeys: selectedRowKeysBuffer.value,
onChange: onSelectChange, onChange: onSelectChange,
onSelect, onSelect,
type: batchColumns.length > 0 ? 'checkbox' : props.selectionType, type: batchColumns.value.length > 0 ? 'checkbox' : props.selectionType,
} }
} }
else { else {
@ -298,188 +256,27 @@ const rowSelection = computed(() => {
} }
}) })
const fn = _.get
async function export_csv() {
const header = []
const headerKeys: any[] = []
const showColumnsMap: any = {}
for (const showColumnsKey in pithyColumns) {
if (pithyColumns[showColumnsKey].dataIndex === 'action')
continue
let t = pithyColumns[showColumnsKey].title
if (typeof t === 'function')
t = t()
header.push({
title: t,
key: pithyColumns[showColumnsKey].dataIndex,
})
headerKeys.push(pithyColumns[showColumnsKey].dataIndex)
showColumnsMap[pithyColumns[showColumnsKey].dataIndex] = pithyColumns[showColumnsKey]
}
let dataSource: any = []
let hasMore = true
let page = 1
while (hasMore) {
// DataSource
await props.api!.get_list({ page }).then((response: any) => {
if (response.data.length === 0) {
hasMore = false
return
}
if (response[props.data_key] === undefined)
dataSource = dataSource.concat(...response.data)
else
dataSource = dataSource.concat(...response[props.data_key])
}).catch((e: any) => {
message.error(e.message ?? $gettext('Server error'))
hasMore = false
})
page += 1
}
const data: any[] = []
dataSource.forEach((row: Object) => {
const obj: any = {}
headerKeys.forEach(key => {
let data = fn(row, key)
const c = showColumnsMap[key]
data = c?.customRender?.({ text: data }) ?? data
obj[c.dataIndex] = data
})
data.push(obj)
})
downloadCsv(header, data,
`${$gettext('Export')}-${dayjs().format('YYYYMMDDHHmmss')}.csv`)
}
const hasSelectedRow = computed(() => { const hasSelectedRow = computed(() => {
return batchColumns.length > 0 && selectedRowKeysBuffer.value.length > 0 return batchColumns.value.length > 0 && selectedRowKeysBuffer.value.length > 0
}) })
function click_batch_edit() { function clickBatchEdit() {
emit('clickBatchModify', batchColumns, selectedRowKeysBuffer.value) emit('clickBatchModify', batchColumns.value, selectedRowKeysBuffer.value)
}
function getLeastIndex(index: number) {
return index >= 1 ? index : 1
}
function getTargetData(data: any, indexList: number[]): any {
let target: any = { children: data }
indexList.forEach((index: number) => {
target.children[index].parent = target
target = target.children[index]
})
return target
} }
function initSortable() { function initSortable() {
const table: any = document.querySelector('#std-table tbody') useSortable(props, randomId, dataSource, rowsKeyIndexMap, expandKeysList)
new Sortable(table, {
handle: '.ant-table-drag-icon',
animation: 150,
sort: true,
forceFallback: true,
setData(dataTransfer) {
dataTransfer.setData('Text', '')
},
onStart({ item }) {
const targetRowKey = Number(item.dataset.rowKey)
if (targetRowKey)
expand_keys_list.value = expand_keys_list.value.filter((item: number) => item !== targetRowKey)
},
onMove({ dragged, related }) {
const oldRow: number[] = rows_key_index_map.value?.[Number(dragged.dataset.rowKey)]
const newRow: number[] = rows_key_index_map.value?.[Number(related.dataset.rowKey)]
if (oldRow.length !== newRow.length || oldRow[oldRow.length - 2] != newRow[newRow.length - 2])
return false
},
async onEnd({ item, newIndex, oldIndex }) {
if (newIndex === oldIndex)
return
const indexDelta: number = Number(oldIndex) - Number(newIndex)
const direction: number = indexDelta > 0 ? +1 : -1
const rowIndex: number[] = rows_key_index_map.value?.[Number(item.dataset.rowKey)]
const newRow = getTargetData(data_source.value, rowIndex)
const newRowParent = newRow.parent
const level: number = newRow.level
const currentRowIndex: number[] = [...rows_key_index_map.value?.
[Number(table.children[Number(newIndex) + direction].dataset.rowKey)]]
const currentRow: any = getTargetData(data_source.value, currentRowIndex)
// Reset parent
currentRow.parent = newRow.parent = null
newRowParent.children.splice(rowIndex[level], 1)
newRowParent.children.splice(currentRowIndex[level], 0, toRaw(newRow))
const changeIds: number[] = []
function processChanges(row: any, children: boolean = false, newIndex: number | undefined = undefined) {
// Build changes ID list expect new row
if (children || newIndex === undefined)
changeIds.push(row.id)
if (newIndex !== undefined)
rows_key_index_map.value[row.id][level] = newIndex
else if (children)
rows_key_index_map.value[row.id][level] += direction
row.parent = null
if (row.children)
row.children.forEach((v: any) => processChanges(v, true, newIndex))
}
// Replace row index for new row
processChanges(newRow, false, currentRowIndex[level])
// Rebuild row index maps for changes row
for (let i = Number(oldIndex); i != newIndex; i -= direction) {
const rowIndex: number[] = rows_key_index_map.value?.[table.children[i].dataset.rowKey]
rowIndex[level] += direction
processChanges(getTargetData(data_source.value, rowIndex))
}
console.log('Change row id', newRow.id, 'order', newRow.id, '=>', currentRow.id, ', direction: ', direction,
', changes IDs:', changeIds)
props.api!.update_order({
target_id: newRow.id,
direction,
affected_ids: changeIds,
}).then(() => {
message.success($gettext('Updated successfully'))
}).catch((e: any) => {
message.error(e?.message ?? $gettext('Server error'))
})
},
})
} }
function export_csv() {
exportCsvHandler(props, pithyColumns)
}
</script> </script>
<template> <template>
<div class="std-table"> <div class="std-table">
<StdDataEntry <StdDataEntry
v-if="!disable_search && searchColumns.length" v-if="!disableSearch && searchColumns.length"
:data-list="searchColumns" :data-list="searchColumns"
:data-source="params" :data-source="params"
layout="inline" layout="inline"
@ -487,7 +284,7 @@ function initSortable() {
<template #action> <template #action>
<ASpace class="action-btn"> <ASpace class="action-btn">
<AButton <AButton
v-if="exportCsv" v-if="props.exportCsv"
type="primary" type="primary"
ghost ghost
@click="export_csv" @click="export_csv"
@ -499,7 +296,7 @@ function initSortable() {
</AButton> </AButton>
<AButton <AButton
v-if="hasSelectedRow" v-if="hasSelectedRow"
@click="click_batch_edit" @click="clickBatchEdit"
> >
{{ $gettext('Batch Modify') }} {{ $gettext('Batch Modify') }}
</AButton> </AButton>
@ -509,36 +306,36 @@ function initSortable() {
<ATable <ATable
id="std-table" id="std-table"
:columns="pithyColumns" :columns="pithyColumns"
:data-source="data_source" :data-source="dataSource"
:loading="loading" :loading="loading"
:pagination="false" :pagination="false"
:row-key="rowKey" :row-key="rowKey"
:row-selection="rowSelection" :row-selection="rowSelection"
:scroll="{ x: scrollX }" :scroll="{ x: scrollX }"
:size="size" :size="size"
:expanded-row-keys="expand_keys_list" :expanded-row-keys="expandKeysList"
@change="stdChange" @change="orderPaginationChange"
@expandedRowsChange="expandedTable" @expanded-rows-change="expandedTable"
> >
<template #bodyCell="{ text, record, index, column }"> <template #bodyCell="{ text, record, column }">
<template v-if="column.handle === true"> <template v-if="column.handle === true">
<span class="ant-table-drag-icon"><HolderOutlined /></span> <span class="ant-table-drag-icon"><HolderOutlined /></span>
{{ text }} {{ text }}
</template> </template>
<template v-if="column.dataIndex === 'action'"> <template v-if="column.dataIndex === 'action'">
<AButton <AButton
v-if="props.editable" v-if="!props.disabledModify"
type="link" type="link"
size="small" size="small"
@click="$emit('clickEdit', record[props.rowKey], record)" @click="$emit('clickEdit', record[props.rowKey], record)"
> >
{{ props.edit_text || $gettext('Modify') }} {{ $gettext('Modify') }}
</AButton> </AButton>
<slot <slot
name="actions" name="actions"
:record="record" :record="record"
/> />
<template v-if="props.deletable"> <template v-if="!props.disableDelete">
<ADivider type="vertical" /> <ADivider type="vertical" />
<APopconfirm <APopconfirm
:cancel-text="$gettext('No')" :cancel-text="$gettext('No')"
@ -561,7 +358,7 @@ function initSortable() {
:size="size" :size="size"
:pagination="pagination" :pagination="pagination"
@change="get_list" @change="get_list"
@changePageSize="stdChange" @change-page-size="orderPaginationChange"
/> />
</div> </div>
</template> </template>

View file

@ -0,0 +1,9 @@
import StdTable from './StdTable.vue'
import StdCurd from './StdCurd.vue'
import StdBatchEdit from './StdBatchEdit.vue'
export {
StdTable,
StdCurd,
StdBatchEdit,
}

View file

@ -0,0 +1,71 @@
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import type { ComputedRef } from 'vue'
import _ from 'lodash'
import { downloadCsv } from '@/lib/helper'
import type { Column, StdTableResponse } from '@/components/StdDesign/types'
import gettext from '@/gettext'
import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
const { $gettext } = gettext
async function exportCsv(props: StdTableProps, pithyColumns: ComputedRef<Column[]>) {
const header: { title?: string; key: string }[] = []
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const headerKeys: any[] = []
const showColumnsMap: Record<string, Column> = {}
pithyColumns.value.forEach((column: Column) => {
if (column.dataIndex === 'action')
return
let t = column.title
if (typeof t === 'function')
t = t()
header.push({
title: t,
key: column.dataIndex,
})
headerKeys.push(column.dataIndex)
showColumnsMap[column.dataIndex] = column
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const dataSource: any[] = []
let hasMore = true
let page = 1
while (hasMore) {
// 准备 DataSource
await props.api!.get_list({ page }).then((r: StdTableResponse) => {
if (r.data.length === 0) {
hasMore = false
return
}
dataSource.push(...r.data)
}).catch((e: { message?: string }) => {
message.error(e.message ?? $gettext('Server error'))
hasMore = false
})
page += 1
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any[] = []
dataSource.forEach(row => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const obj: Record<string, any> = {}
headerKeys.forEach(key => {
let _data = _.get(row, key)
const c = showColumnsMap[key]
_data = c?.customRender?.({ text: _data }) ?? _data
obj[c.dataIndex] = _data
})
data.push(obj)
})
downloadCsv(header, data,
`${$gettext('Export')}-${props.title}-${dayjs().format('YYYYMMDDHHmmss')}.csv`)
}
export default exportCsv

View file

@ -0,0 +1,132 @@
import { message } from 'ant-design-vue'
import SortableJs from 'sortablejs'
import type { Ref } from 'vue'
import gettext from '@/gettext'
import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
const { $gettext } = gettext
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getRowKey(item: any) {
return item.children[0].children[0].dataset.rowKey
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getTargetData(data: any, indexList: number[]): any {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let target: any = { children: data }
indexList.forEach((index: number) => {
target.children[index].parent = target
target = target.children[index]
})
return target
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function useSortable(props: StdTableProps, randomId: Ref<string>, dataSource: Ref<any[]>,
rowsKeyIndexMap: Ref<Record<number, number[]>>, expandKeysList: Ref<number[]>) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const table: any = document.querySelector(`#std-table-${randomId.value} tbody`)
// eslint-disable-next-line no-new
new SortableJs(table, {
handle: '.table-drag-icon',
animation: 150,
sort: true,
forceFallback: true,
setData(dataTransfer) {
dataTransfer.setData('Text', '')
},
onStart({ item }) {
const targetRowKey = Number(getRowKey(item))
if (targetRowKey)
expandKeysList.value = expandKeysList.value.filter((_item: number) => _item !== targetRowKey)
},
onMove({
dragged,
related,
}) {
const oldRow: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(dragged))]
const newRow: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(related))]
if (oldRow.length !== newRow.length || oldRow[oldRow.length - 2] !== newRow[newRow.length - 2])
return false
if (props.sortableMoveHook)
return props.sortableMoveHook(oldRow, newRow)
},
async onEnd({
item,
newIndex,
oldIndex,
}) {
if (newIndex === oldIndex)
return
const indexDelta: number = Number(oldIndex) - Number(newIndex)
const direction: number = indexDelta > 0 ? +1 : -1
const rowIndex: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(item))]
const newRow = getTargetData(dataSource.value, rowIndex)
const newRowParent = newRow.parent
const level: number = newRow.level
const currentRowIndex: number[] = [...rowsKeyIndexMap.value?.
[Number(getRowKey(table.children[Number(newIndex) + direction]))]]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const currentRow: any = getTargetData(dataSource.value, currentRowIndex)
// Reset parent
currentRow.parent = newRow.parent = null
newRowParent.children.splice(rowIndex[level], 1)
newRowParent.children.splice(currentRowIndex[level], 0, toRaw(newRow))
const changeIds: number[] = []
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function processChanges(row: any, children = false, _newIndex: number | undefined = undefined) {
// Build changes ID list expect new row
if (children || _newIndex === undefined)
changeIds.push(row.id)
if (_newIndex !== undefined)
rowsKeyIndexMap.value[row.id][level] = _newIndex
else if (children)
rowsKeyIndexMap.value[row.id][level] += direction
row.parent = null
if (row.children) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
row.children.forEach((v: any) => processChanges(v, true, _newIndex))
}
}
// Replace row index for new row
processChanges(newRow, false, currentRowIndex[level])
// Rebuild row index maps for changes row
for (let i = Number(oldIndex); i !== newIndex; i -= direction) {
const _rowIndex: number[] = rowsKeyIndexMap.value?.[getRowKey(table.children[i])]
_rowIndex[level] += direction
processChanges(getTargetData(dataSource.value, _rowIndex))
}
console.log('Change row id', newRow.id, 'order', newRow.id, '=>', currentRow.id, ', direction: ', direction,
', changes IDs:', changeIds)
props.api.update_order({
target_id: newRow.id,
direction,
affected_ids: changeIds,
}).then(() => {
message.success($gettext('Updated successfully'))
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}).catch((e: any) => {
message.error(e?.message ?? $gettext('Server error'))
})
},
})
}
export default useSortable

View file

@ -1,54 +0,0 @@
import { defineComponent } from 'vue'
import { Form } from 'ant-design-vue'
import StdFormItem from '@/components/StdDesign/StdDataEntry/StdFormItem.vue'
import './style.less'
export default defineComponent({
props: {
dataList: {
type: Array,
required: true,
},
dataSource: {
type: Object,
required: true,
},
error: {
type: Object,
required: false,
},
layout: {
type: String,
required: false,
},
},
emits: ['update:dataSource'],
setup(props, { slots }) {
return () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const template: any = []
// eslint-disable-next-line @typescript-eslint/no-explicit-any
props.dataList.forEach((v: any) => {
let show = true
if (v.edit.show) {
if (typeof v.edit.show === 'boolean')
show = v.edit.show
else if (typeof v.edit.show === 'function')
show = v.edit.show(props.dataSource)
}
if (v.edit.type && show) {
template.push(<StdFormItem dataIndex={v.dataIndex} label={v.title()} extra={v.extra} error={props.error}>
{v.edit.type(v.edit, props.dataSource, v.dataIndex)}
</StdFormItem>,
)
}
})
if (slots.action)
template.push(<div class={'std-data-entry-action'}>{slots.action()}</div>)
return <Form layout={props.layout || 'vertical'}>{template}</Form>
}
},
})

View file

@ -0,0 +1,83 @@
<script setup lang="tsx">
import { Form } from 'ant-design-vue'
import type { Column } from '@/components/StdDesign/types'
import StdFormItem from '@/components/StdDesign/StdDataEntry/StdFormItem.vue'
const props = defineProps<{
dataList: Column[]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dataSource: Record<string, any>
errors?: Record<string, string>
layout?: 'horizontal' | 'vertical'
}>()
const emit = defineEmits<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
'update:dataSource': (v: any[]) => void
}>()
const dataSource = computed({
get() {
return props.dataSource
},
set(v) {
emit('update:dataSource', v)
},
})
const slots = useSlots()
function labelRender(title?: string | (() => string)) {
if (typeof title === 'function')
return title()
return title
}
function extraRender(extra?: string | (() => string)) {
if (typeof extra === 'function')
return extra()
return extra
}
function Render() {
const template = []
props.dataList.forEach((v: Column) => {
let show = true
if (v.edit?.show && typeof v.edit.show === 'function')
show = v.edit.show(props.dataSource)
if (v.edit?.type && show) {
template.push(<StdFormItem
dataIndex={v.dataIndex}
label={labelRender(v.title)}
extra={extraRender(v.extra)}
error={props.errors}>
{v.edit.type(v.edit, dataSource.value, v.dataIndex)}
</StdFormItem>,
)
}
})
if (slots.action)
template.push(<div class={'std-data-entry-action'}>{slots.action()}</div>)
return <Form layout={props.layout || 'vertical'}>{template}</Form>
}
</script>
<template>
<Render />
</template>
<style scoped lang="less">
.std-data-entry-action {
@media (max-width: 375px) {
display: block;
width: 100%;
margin: 10px 0;
}
}
</style>

View file

@ -2,6 +2,7 @@
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue' import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
import gettext from '@/gettext' import gettext from '@/gettext'
import type Curd from '@/api/curd' import type Curd from '@/api/curd'
import type { Column } from '@/components/StdDesign/types'
const props = defineProps<{ const props = defineProps<{
selectedKey: string | number selectedKey: string | number
@ -10,7 +11,7 @@ const props = defineProps<{
selectionType: 'radio' | 'checkbox' selectionType: 'radio' | 'checkbox'
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
api: Curd<any> api: Curd<any>
columns: any[] columns: Column[]
dataKey: string dataKey: string
disableSearch: boolean disableSearch: boolean
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View file

@ -1,7 +1,7 @@
import { h } from 'vue' import { h } from 'vue'
import { Input, InputNumber, Switch, Textarea } from 'ant-design-vue' import { Input, InputNumber, Switch, Textarea } from 'ant-design-vue'
import _ from 'lodash' import _ from 'lodash'
import StdDataEntry from './StdDataEntry' import StdDataEntry from './StdDataEntry.vue'
import StdSelector from './components/StdSelector.vue' import StdSelector from './components/StdSelector.vue'
import StdSelect from './components/StdSelect.vue' import StdSelect from './components/StdSelect.vue'
import StdPassword from './components/StdPassword.vue' import StdPassword from './components/StdPassword.vue'

View file

@ -1,10 +1,13 @@
import Curd from '@/api/curd' import Curd, {Pagination} from '@/api/curd'
import {IKeyEvt} from '@/components/StdDesign/StdDataDisplay/types' import { Ref } from 'vue'
import {Ref} from 'vue'
export interface StdDesignEdit { export interface StdDesignEdit {
type?: function // component type type?: function // component type
show?: function // show component
batch?: boolean // batch edit
mask?: { mask?: {
[key: string]: () => string [key: string]: () => string
} // use for select-option } // use for select-option
@ -39,7 +42,6 @@ export interface StdDesignEdit {
flex?: Flex flex?: Flex
} }
export interface Flex { export interface Flex {
sm?: string | number | boolean sm?: string | number | boolean
md?: string | number | boolean md?: string | number | boolean
@ -47,3 +49,43 @@ export interface Flex {
xl?: string | number | boolean xl?: string | number | boolean
xxl?: string | number | boolean xxl?: string | number | boolean
} }
export interface Column {
title?: string | (() => string);
dataIndex: string;
edit?: StdDesignEdit;
customRender?: function;
extra?: string | (() => string);
pithy?: boolean;
search?: boolean | StdDesignEdit;
sortable?: boolean;
hidden?: boolean;
width?: string | number;
handle?: boolean;
hiddenInTrash?: boolean;
hiddenInCreate?: boolean;
hiddenInModify?: boolean;
batch?: boolean;
}
export interface StdTableProvideData {
displayColumns: Column[];
pithyColumns: Column[];
columnsMap: { [key: string]: Column };
displayKeys: string[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
editItem: (id: number, data: any, index: string | number) => void;
deleteItem: (id: number, index: string | number) => void;
recoverItem: (id: number) => {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
params: any;
dataSource: any;
get_list: () => void;
loading: Ref<boolean>;
}
export interface StdTableResponse {
data: any[]
pagination: Pagination
}

View file

@ -1,7 +1,4 @@
<script> <script setup lang="ts">
export default {
name: 'BaseRouterView',
}
</script> </script>
<template> <template>

View file

@ -1,13 +1,7 @@
<script> <script setup lang="ts">
export default { defineProps<{
name: 'Loading', loading: boolean | string
props: { }>()
loading: {
type: [Boolean, String],
default: false,
},
},
}
</script> </script>
<template> <template>

View file

@ -1 +1 @@
{"version":"2.0.0-beta.4","build_id":53,"total_build":257} {"version":"2.0.0-beta.4","build_id":56,"total_build":260}

View file

@ -97,7 +97,7 @@ function handle_uptime(t: number) {
uptime.value = `${uptime_days}d ${uptime_hours}h ${Math.floor(_uptime / 60)}m` uptime.value = `${uptime_days}d ${uptime_hours}h ${Math.floor(_uptime / 60)}m`
} }
function wsOnMessage(m: { data: any }) { function wsOnMessage(m) {
const r = JSON.parse(m.data) const r = JSON.parse(m.data)
const cpu_usage = r.cpu.system + r.cpu.user const cpu_usage = r.cpu.system + r.cpu.user

View file

@ -5,7 +5,25 @@ import memory from '@/assets/svg/memory.svg'
import { bytesToSize } from '@/lib/helper' import { bytesToSize } from '@/lib/helper'
import UsageProgressLine from '@/components/Chart/UsageProgressLine.vue' import UsageProgressLine from '@/components/Chart/UsageProgressLine.vue'
defineProps(['item']) defineProps<{
item: {
avg_load: {
load1: number
load5: number
load15: number
}
network: {
bytesSent: number
bytesRecv: number
}
cpu_percent: number
cpu_num: number
memory_percent: number
memory_total: string
disk_percent: number
disk_total: string
}
}>()
</script> </script>
<template> <template>

View file

@ -52,10 +52,6 @@ const columns = [{
const table = ref() const table = ref()
interface Table {
get_list(): void
}
function enable(name) { function enable(name) {
domain.enable(name).then(() => { domain.enable(name).then(() => {
message.success($gettext('Enabled successfully')) message.success($gettext('Enabled successfully'))
@ -76,9 +72,7 @@ function disable(name) {
function destroy(site_name) { function destroy(site_name) {
domain.destroy(site_name).then(() => { domain.destroy(site_name).then(() => {
const t: Table | null = table.value table.value.get_list()
t!.get_list()
message.success($gettext('Delete site: %{site_name}', { site_name })) message.success($gettext('Delete site: %{site_name}', { site_name }))
}).catch(e => { }).catch(e => {
message.error(e?.message ?? $gettext('Server error')) message.error(e?.message ?? $gettext('Server error'))
@ -163,7 +157,7 @@ watch(route, () => {
<SiteDuplicate <SiteDuplicate
v-model:visible="show_duplicator" v-model:visible="show_duplicator"
:name="target" :name="target"
@duplicated="table.get_list()" @duplicated="() => table.get_list()"
/> />
</ACard> </ACard>
</template> </template>

View file

@ -11,7 +11,7 @@ const this_year = new Date().getFullYear()
<template> <template>
<ACard <ACard
style="text-align: center" class="text-center"
:bordered="false" :bordered="false"
> >
<div class="logo"> <div class="logo">
@ -34,26 +34,23 @@ const this_year = new Date().getFullYear()
Star Star
</GithubButton> </GithubButton>
</div> </div>
<h3 v-translate> <h3>
Project Team {{ $gettext('Project Team') }}
</h3> </h3>
<p><a href="https://jackyu.cn/">@0xJacky</a> <a href="https://blog.kugeek.com/">@Hintay</a></p> <p><a href="https://jackyu.cn/">@0xJacky</a> <a href="https://blog.kugeek.com/">@Hintay</a></p>
<h3 v-translate> <h3>
Build with {{ $gettext('Build with') }}
</h3> </h3>
<p></p> <p></p>
<p>Go</p> <p>Go</p>
<p>Gin</p> <p>Gin</p>
<p>Vue3 + Vite + TypeScript</p> <p>Vue3 + Vite + TypeScript</p>
<p>Websocket</p> <p>Websocket</p>
<h3 <h3>
v-translate {{ $gettext('License') }}
translate-context="Project"
>
License
</h3> </h3>
<p>GNU General Public License v3.0</p> <p>GNU General Public License v3.0</p>
<p>Copyright © 2020 - {{ this_year }} Nginx UI </p> <p>Copyright © 2020 - {{ this_year }} Nginx UI Team</p>
</ACard> </ACard>
</template> </template>
@ -64,14 +61,6 @@ const this_year = new Date().getFullYear()
} }
} }
.egg {
padding: 10px 0;
}
.ant-btn {
margin: 10px 10px 0 0;
}
.star-on-github { .star-on-github {
margin-bottom: 10px; margin-bottom: 10px;
} }

View file

@ -4,13 +4,14 @@ import gettext from '@/gettext'
import user from '@/api/user' import user from '@/api/user'
import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer' import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
import { input, password } from '@/components/StdDesign/StdDataEntry' import { input, password } from '@/components/StdDesign/StdDataEntry'
import type { Column } from '@/components/StdDesign/types'
const { $gettext } = gettext const { $gettext } = gettext
const columns = [{ const columns: Column[] = [{
title: () => $gettext('Username'), title: () => $gettext('Username'),
dataIndex: 'name', dataIndex: 'name',
sorter: true, sortable: true,
pithy: true, pithy: true,
edit: { edit: {
type: input, type: input,
@ -19,25 +20,27 @@ const columns = [{
}, { }, {
title: () => $gettext('Password'), title: () => $gettext('Password'),
dataIndex: 'password', dataIndex: 'password',
sorter: true, sortable: true,
pithy: true, pithy: true,
edit: { edit: {
type: password, type: password,
placeholder: () => $gettext('Leave blank for no change'), config: {
generate: true, placeholder: () => $gettext('Leave blank for no change'),
generate: true,
},
}, },
display: false, hidden: true,
}, { }, {
title: () => $gettext('Created at'), title: () => $gettext('Created at'),
dataIndex: 'created_at', dataIndex: 'created_at',
customRender: datetime, customRender: datetime,
sorter: true, sortable: true,
pithy: true, pithy: true,
}, { }, {
title: () => $gettext('Updated at'), title: () => $gettext('Updated at'),
dataIndex: 'updated_at', dataIndex: 'updated_at',
customRender: datetime, customRender: datetime,
sorter: true, sortable: true,
pithy: true, pithy: true,
}, { }, {
title: () => $gettext('Action'), title: () => $gettext('Action'),

View file

@ -1,4 +1,5 @@
{ {
"extends": "@vue/tsconfig/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
@ -20,9 +21,11 @@
"@/*": [ "@/*": [
"./src/*" "./src/*"
] ]
} },
"types": ["vite/client"]
}, },
"include": [ "include": [
"env.d.ts",
"src/**/*.ts", "src/**/*.ts",
"src/**/*.d.ts", "src/**/*.d.ts",
"src/**/*.tsx", "src/**/*.tsx",

View file

@ -1 +1 @@
{"version":"2.0.0-beta.4","build_id":53,"total_build":257} {"version":"2.0.0-beta.4","build_id":56,"total_build":260}