mirror of https://github.com/halo-dev/halo
feat: user editing support using yaml
Signed-off-by: Ryan Wang <i@ryanc.cc>pull/3445/head
parent
e218818a59
commit
51c87c1519
|
@ -49,7 +49,8 @@
|
|||
"vue": "^3.2.37",
|
||||
"vue-filepond": "^7.0.3",
|
||||
"vue-grid-layout": "3.0.0-beta1",
|
||||
"vue-router": "^4.1.2"
|
||||
"vue-router": "^4.1.2",
|
||||
"yaml": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.23.2",
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
"@rollup/plugin-typescript": "^8.3.3",
|
||||
"histoire": "^0.7.9",
|
||||
"unplugin-icons": "^0.14.7",
|
||||
"vite-plugin-dts": "^1.3.1"
|
||||
"vite-plugin-dts": "^1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.2.37",
|
||||
|
@ -64,6 +64,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@codemirror/commands": "^6.0.1",
|
||||
"@codemirror/language": "^6.2.1",
|
||||
"@codemirror/legacy-modes": "^6.1.0",
|
||||
"@codemirror/state": "^6.1.0",
|
||||
"@codemirror/view": "^6.1.0",
|
||||
"codemirror": "^6.0.1"
|
||||
|
|
|
@ -15,3 +15,4 @@ export * from "./components/textarea";
|
|||
export * from "./components/switch";
|
||||
export * from "./components/dialog";
|
||||
export * from "./components/pagination";
|
||||
export * from "./components/codemirror";
|
||||
|
|
|
@ -10,7 +10,7 @@ function initState() {
|
|||
<template>
|
||||
<Story :initState="initState" title="Codemirror">
|
||||
<template #default="{ state }">
|
||||
<VCodemirror v-model="state.value" height="500px" language="javascript" />
|
||||
<VCodemirror v-model="state.value" height="500px" language="yaml" />
|
||||
</template>
|
||||
</Story>
|
||||
</template>
|
||||
|
|
|
@ -5,6 +5,12 @@ import type { EditorStateConfig } from "@codemirror/state";
|
|||
import { EditorState } from "@codemirror/state";
|
||||
import { EditorView } from "@codemirror/view";
|
||||
import { basicSetup } from "codemirror";
|
||||
import { StreamLanguage } from "@codemirror/language";
|
||||
import { yaml } from "@codemirror/legacy-modes/mode/yaml";
|
||||
|
||||
const languages = {
|
||||
yaml: StreamLanguage.define(yaml),
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
|
@ -15,6 +21,10 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: "auto",
|
||||
},
|
||||
language: {
|
||||
type: String as PropType<"yaml">,
|
||||
default: "yaml",
|
||||
},
|
||||
extensions: {
|
||||
type: Array as PropType<EditorStateConfig["extensions"]>,
|
||||
default: () => [],
|
||||
|
@ -41,6 +51,7 @@ const createCmEditor = () => {
|
|||
basicSetup,
|
||||
EditorView.lineWrapping,
|
||||
customTheme,
|
||||
languages[props.language],
|
||||
EditorView.updateListener.of((viewUpdate) => {
|
||||
if (viewUpdate.docChanged) {
|
||||
const doc = viewUpdate.state.doc.toString();
|
||||
|
|
|
@ -39,6 +39,7 @@ import IconShieldUser from "~icons/ri/shield-user-line";
|
|||
import IconGitBranch from "~icons/ri/git-branch-line";
|
||||
import IconStopCircle from "~icons/ri/stop-circle-line";
|
||||
import IconForbidLine from "~icons/ri/forbid-line";
|
||||
import IconCodeBoxLine from "~icons/ri/code-box-line";
|
||||
|
||||
export {
|
||||
IconDashboard,
|
||||
|
@ -82,4 +83,5 @@ export {
|
|||
IconGitBranch,
|
||||
IconStopCircle,
|
||||
IconForbidLine,
|
||||
IconCodeBoxLine,
|
||||
};
|
||||
|
|
|
@ -71,6 +71,7 @@ importers:
|
|||
vue-grid-layout: 3.0.0-beta1
|
||||
vue-router: ^4.1.2
|
||||
vue-tsc: ^0.38.8
|
||||
yaml: ^2.1.1
|
||||
dependencies:
|
||||
'@formkit/addons': 1.0.0-beta.9_vue@3.2.37
|
||||
'@formkit/core': 1.0.0-beta.9
|
||||
|
@ -97,6 +98,7 @@ importers:
|
|||
vue-filepond: 7.0.3_filepond@4.30.4+vue@3.2.37
|
||||
vue-grid-layout: 3.0.0-beta1
|
||||
vue-router: 4.1.2_vue@3.2.37
|
||||
yaml: 2.1.1
|
||||
devDependencies:
|
||||
'@changesets/cli': 2.23.2
|
||||
'@rushstack/eslint-patch': 1.1.4
|
||||
|
@ -144,6 +146,8 @@ importers:
|
|||
packages/components:
|
||||
specifiers:
|
||||
'@codemirror/commands': ^6.0.1
|
||||
'@codemirror/language': ^6.2.1
|
||||
'@codemirror/legacy-modes': ^6.1.0
|
||||
'@codemirror/state': ^6.1.0
|
||||
'@codemirror/view': ^6.1.0
|
||||
'@iconify-json/ri': ^1.1.3
|
||||
|
@ -151,9 +155,11 @@ importers:
|
|||
codemirror: ^6.0.1
|
||||
histoire: ^0.7.9
|
||||
unplugin-icons: ^0.14.7
|
||||
vite-plugin-dts: ^1.3.1
|
||||
vite-plugin-dts: ^1.4.0
|
||||
dependencies:
|
||||
'@codemirror/commands': 6.0.1
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/legacy-modes': 6.1.0
|
||||
'@codemirror/state': 6.1.0
|
||||
'@codemirror/view': 6.1.0
|
||||
codemirror: 6.0.1
|
||||
|
@ -162,7 +168,7 @@ importers:
|
|||
'@rollup/plugin-typescript': 8.3.3
|
||||
histoire: 0.7.9
|
||||
unplugin-icons: 0.14.7
|
||||
vite-plugin-dts: 1.3.1
|
||||
vite-plugin-dts: 1.4.0
|
||||
|
||||
packages/shared:
|
||||
specifiers:
|
||||
|
@ -1644,6 +1650,12 @@ packages:
|
|||
style-mod: 4.0.0
|
||||
dev: false
|
||||
|
||||
/@codemirror/legacy-modes/6.1.0:
|
||||
resolution: {integrity: sha512-V/PgGpndkZeTn3Hdlg/gd8MLFdyvTCIX+iwJzjUw5iNziWiNsAY8X0jvf7m3gSfxnKkNzmid6l0g4rYSpiDaCw==}
|
||||
dependencies:
|
||||
'@codemirror/language': 6.2.1
|
||||
dev: false
|
||||
|
||||
/@codemirror/lint/6.0.0:
|
||||
resolution: {integrity: sha512-nUUXcJW1Xp54kNs+a1ToPLK8MadO0rMTnJB8Zk4Z8gBdrN0kqV7uvUraU/T2yqg+grDNR38Vmy/MrhQN/RgwiA==}
|
||||
dependencies:
|
||||
|
@ -1652,8 +1664,8 @@ packages:
|
|||
crelt: 1.0.5
|
||||
dev: false
|
||||
|
||||
/@codemirror/search/6.0.0:
|
||||
resolution: {integrity: sha512-rL0rd3AhI0TAsaJPUaEwC63KHLO7KL0Z/dYozXj6E7L3wNHRyx7RfE0/j5HsIf912EE5n2PCb4Vg0rGYmDv4UQ==}
|
||||
/@codemirror/search/6.0.1:
|
||||
resolution: {integrity: sha512-uOinkOrM+daMduCgMPomDfKLr7drGHB4jHl3Vq6xY2WRlL7MkNsBE0b+XHYa/Mee2npsJOgwvkW4n1lMFeBW2Q==}
|
||||
dependencies:
|
||||
'@codemirror/state': 6.1.0
|
||||
'@codemirror/view': 6.1.0
|
||||
|
@ -3659,7 +3671,7 @@ packages:
|
|||
'@codemirror/commands': 6.0.1
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/lint': 6.0.0
|
||||
'@codemirror/search': 6.0.0
|
||||
'@codemirror/search': 6.0.1
|
||||
'@codemirror/state': 6.1.0
|
||||
'@codemirror/view': 6.1.0
|
||||
dev: false
|
||||
|
@ -7923,6 +7935,23 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/vite-plugin-dts/1.4.0:
|
||||
resolution: {integrity: sha512-RyDCjQzVxUeDqF+Rl1hQT+t/rKmvfvo04gaGV/l3597FpeIWGKtNF1S4x509Kx1AfHOjLa1JdmjVgnSEIv+lpw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
vite: '>=2.4.4'
|
||||
dependencies:
|
||||
'@microsoft/api-extractor': 7.23.2
|
||||
'@rushstack/node-core-library': 3.45.5
|
||||
chalk: 4.1.2
|
||||
debug: 4.3.4
|
||||
fast-glob: 3.2.11
|
||||
fs-extra: 10.1.0
|
||||
ts-morph: 14.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/vite-plugin-externals/0.5.1_vite@2.9.14:
|
||||
resolution: {integrity: sha512-HvRFG5y9wXoJUG9FSbSp9ikOiJRh7EzN6tJC5oIOcEj+19GUw9Z1NNCPFtAmX75Ajcr10FdELKNmuXS3lExkcg==}
|
||||
peerDependencies:
|
||||
|
@ -8512,6 +8541,11 @@ packages:
|
|||
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
/yaml/2.1.1:
|
||||
resolution: {integrity: sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==}
|
||||
engines: {node: '>= 14'}
|
||||
dev: false
|
||||
|
||||
/yargs-parser/18.1.3:
|
||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
|
@ -3,10 +3,18 @@ import type { PropType } from "vue";
|
|||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { apiClient } from "@halo-dev/admin-shared";
|
||||
import type { Role, User } from "@halo-dev/api-client";
|
||||
import { IconSave, VButton, VModal } from "@halo-dev/components";
|
||||
import {
|
||||
IconCodeBoxLine,
|
||||
IconEye,
|
||||
IconSave,
|
||||
VButton,
|
||||
VCodemirror,
|
||||
VModal,
|
||||
} from "@halo-dev/components";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { roleLabels } from "@/constants/labels";
|
||||
import { rbacAnnotations } from "@/constants/annotations";
|
||||
import YAML from "yaml";
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
|
@ -24,6 +32,8 @@ const emit = defineEmits(["update:visible", "close"]);
|
|||
interface EditingFormState {
|
||||
user: User;
|
||||
saving: boolean;
|
||||
rawMode: boolean;
|
||||
raw: string;
|
||||
}
|
||||
|
||||
const roles = ref<Role[]>([]);
|
||||
|
@ -46,6 +56,8 @@ const editingFormState = ref<EditingFormState>({
|
|||
},
|
||||
},
|
||||
saving: false,
|
||||
rawMode: false,
|
||||
raw: "",
|
||||
});
|
||||
const selectedRole = ref("");
|
||||
|
||||
|
@ -57,6 +69,10 @@ const creationModalTitle = computed(() => {
|
|||
return isUpdateMode.value ? "编辑用户" : "新增用户";
|
||||
});
|
||||
|
||||
const modalWidth = computed(() => {
|
||||
return editingFormState.value.rawMode ? 800 : 700;
|
||||
});
|
||||
|
||||
const basicRoles = computed(() => {
|
||||
return roles.value.filter(
|
||||
(role) => role.metadata?.labels?.[roleLabels.TEMPLATE] !== "true"
|
||||
|
@ -118,38 +134,62 @@ const handleCreateUser = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleRawModeChange = () => {
|
||||
editingFormState.value.rawMode = !editingFormState.value.rawMode;
|
||||
|
||||
if (editingFormState.value.rawMode) {
|
||||
editingFormState.value.raw = YAML.stringify(editingFormState.value.user);
|
||||
} else {
|
||||
editingFormState.value.user = YAML.parse(editingFormState.value.raw);
|
||||
}
|
||||
};
|
||||
onMounted(handleFetchRoles);
|
||||
</script>
|
||||
<template>
|
||||
<VModal
|
||||
:title="creationModalTitle"
|
||||
:visible="visible"
|
||||
:width="700"
|
||||
:width="modalWidth"
|
||||
@update:visible="handleVisibleChange"
|
||||
>
|
||||
<FormKit id="user-form" type="form" @submit="handleCreateUser">
|
||||
<FormKit
|
||||
v-model="editingFormState.user.metadata.name"
|
||||
:disabled="true"
|
||||
label="用户名"
|
||||
type="text"
|
||||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="editingFormState.user.spec.displayName"
|
||||
label="显示名称"
|
||||
type="text"
|
||||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="editingFormState.user.spec.email"
|
||||
label="电子邮箱"
|
||||
type="email"
|
||||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="selectedRole"
|
||||
:options="
|
||||
<template #actions>
|
||||
<div class="modal-header-action" @click="handleRawModeChange">
|
||||
<IconCodeBoxLine v-if="!editingFormState.rawMode" />
|
||||
<IconEye v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<VCodemirror
|
||||
v-show="editingFormState.rawMode"
|
||||
v-model="editingFormState.raw"
|
||||
height="50vh"
|
||||
language="yaml"
|
||||
/>
|
||||
|
||||
<div v-show="!editingFormState.rawMode">
|
||||
<FormKit id="user-form" type="form" @submit="handleCreateUser">
|
||||
<FormKit
|
||||
v-model="editingFormState.user.metadata.name"
|
||||
:disabled="true"
|
||||
label="用户名"
|
||||
type="text"
|
||||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="editingFormState.user.spec.displayName"
|
||||
label="显示名称"
|
||||
type="text"
|
||||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="editingFormState.user.spec.email"
|
||||
label="电子邮箱"
|
||||
type="email"
|
||||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="selectedRole"
|
||||
:options="
|
||||
basicRoles.map((role:Role) => {
|
||||
return {
|
||||
label: role.metadata?.annotations?.[rbacAnnotations.DISPLAY_NAME] || role.metadata.name,
|
||||
|
@ -157,26 +197,27 @@ onMounted(handleFetchRoles);
|
|||
};
|
||||
})
|
||||
"
|
||||
label="角色"
|
||||
type="select"
|
||||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="editingFormState.user.spec.phone"
|
||||
label="手机号"
|
||||
type="text"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="editingFormState.user.spec.avatar"
|
||||
label="头像"
|
||||
type="text"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="editingFormState.user.spec.bio"
|
||||
label="描述"
|
||||
type="textarea"
|
||||
></FormKit>
|
||||
</FormKit>
|
||||
label="角色"
|
||||
type="select"
|
||||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="editingFormState.user.spec.phone"
|
||||
label="手机号"
|
||||
type="text"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="editingFormState.user.spec.avatar"
|
||||
label="头像"
|
||||
type="text"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="editingFormState.user.spec.bio"
|
||||
label="描述"
|
||||
type="textarea"
|
||||
></FormKit>
|
||||
</FormKit>
|
||||
</div>
|
||||
<template #footer>
|
||||
<VButton
|
||||
:loading="editingFormState.saving"
|
||||
|
|
Loading…
Reference in New Issue