@liujitcn/kratos-admin-core 0.0.64 → 0.0.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package/declarations/src/components/CodeBlock/index.vue.d.ts +15 -0
- package/dist/package/declarations/src/components/ProForm/components/ProFormItem.vue.d.ts +4 -4
- package/dist/package/declarations/src/components/ProForm/interface/index.d.ts +1 -1
- package/dist/package/declarations/src/components/YamlEditor/index.vue.d.ts +7 -1
- package/dist/package/declarations/src/locales/generated.d.ts +356 -0
- package/dist/package/declarations/src/utils/request-response.d.ts +1 -1
- package/dist/package/declarations/src/views/demo/components/DemoDetailDrawer.vue.d.ts +15 -0
- package/dist/package/declarations/src/views/demo/components/DemoFormDialog.vue.d.ts +69 -0
- package/dist/package/declarations/src/views/demo/demo-data.d.ts +145 -0
- package/dist/package/declarations/src/views/demo/table/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/demo/tree-grid/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/demo/tree-table/index.vue.d.ts +3 -0
- package/dist/package/src/components/CodeBlock/index.vue +156 -0
- package/dist/package/src/components/ProForm/components/ProFormItem.vue +5 -1
- package/dist/package/src/components/ProForm/interface/index.ts +1 -0
- package/dist/package/src/components/YamlEditor/index.vue +103 -7
- package/dist/package/src/locales/en-US.json +90 -1
- package/dist/package/src/locales/ja-JP.json +90 -1
- package/dist/package/src/locales/zh-CN.json +90 -1
- package/dist/package/src/locales/zh-TW.json +90 -1
- package/dist/package/src/modules/kratosAdmin.ts +11 -1
- package/dist/package/src/stores/modules/auth.ts +10 -4
- package/dist/package/src/stores/modules/config.ts +2 -0
- package/dist/package/src/utils/index.ts +2 -1
- package/dist/package/src/utils/request-response.ts +1 -1
- package/dist/package/src/utils/request.ts +2 -1
- package/dist/package/src/views/demo/components/DemoDetailDrawer.vue +108 -0
- package/dist/package/src/views/demo/components/DemoFormDialog.vue +470 -0
- package/dist/package/src/views/demo/demo-data.ts +279 -0
- package/dist/package/src/views/demo/table/index.vue +391 -0
- package/dist/package/src/views/demo/tree-grid/index.vue +272 -0
- package/dist/package/src/views/demo/tree-table/index.vue +346 -0
- package/dist/package/types/generated/components.d.ts +4 -1
- package/package.json +9 -1
- package/src/runtime-messages.ts +39 -0
- package/types/generated/components.d.ts +4 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo 演示模块共享的静态 mock 数据与类型。
|
|
3
|
+
*
|
|
4
|
+
* 约定:
|
|
5
|
+
* - 全部数据纯前端静态维护,不调用任何后端接口;真实项目中对应字段来自 RPC 返回结构。
|
|
6
|
+
* - 记录内容本身是"业务数据",与表格列头等 UI 文案不同,不进入语言包(与真实项目一致)。
|
|
7
|
+
* - avatar 使用内联 SVG data URL,避免依赖外网图片服务,离线环境也能渲染图片列。
|
|
8
|
+
*/
|
|
9
|
+
/** Demo 通用状态值:与 base.v1 Status 枚举保持一致(1 启用 / 2 禁用),core 不反向依赖业务 RPC。 */
|
|
10
|
+
export declare const STATUS_ENABLE = 1;
|
|
11
|
+
/** 禁用状态值。 */
|
|
12
|
+
export declare const STATUS_DISABLE = 2;
|
|
13
|
+
/** Demo 员工记录,左树右表页面的行数据结构。 */
|
|
14
|
+
export interface DemoEmployee {
|
|
15
|
+
/** 员工ID。 */
|
|
16
|
+
id: number;
|
|
17
|
+
/** 姓名列表格首列。 */
|
|
18
|
+
name: string;
|
|
19
|
+
/** 所属部门ID,关联左侧部门树的 id。 */
|
|
20
|
+
dept_id: number;
|
|
21
|
+
/** 邮箱,演示普通文本列。 */
|
|
22
|
+
email: string;
|
|
23
|
+
/** 手机号。 */
|
|
24
|
+
phone: string;
|
|
25
|
+
/** 头像,演示 cellType: "image" 图片列。 */
|
|
26
|
+
avatar: string;
|
|
27
|
+
/** 薪资(单位:分),演示 cellType: "money" 金额列,金额列按分换算展示。 */
|
|
28
|
+
salary: number;
|
|
29
|
+
/** 状态,1 启用 / 0 停用,演示 cellType: "status" 状态开关列。 */
|
|
30
|
+
status: number;
|
|
31
|
+
/** 入职日期,演示日期区间搜索。 */
|
|
32
|
+
entry_date: string;
|
|
33
|
+
}
|
|
34
|
+
/** Demo 项目记录,纯表格页面的行数据结构。 */
|
|
35
|
+
export interface DemoProject {
|
|
36
|
+
/** 项目ID。 */
|
|
37
|
+
id: number;
|
|
38
|
+
/** 项目名称,演示 input 搜索 + tag 标签列。 */
|
|
39
|
+
name: string;
|
|
40
|
+
/** 项目等级,演示 tag: true + enum tagType 标签列。 */
|
|
41
|
+
level: string;
|
|
42
|
+
/** 负责人。 */
|
|
43
|
+
owner: string;
|
|
44
|
+
/** 状态,1 启用 / 0 停用。 */
|
|
45
|
+
status: number;
|
|
46
|
+
/** 预算金额(单位:分),演示 cellType: "money" 金额列,金额列按分换算展示。 */
|
|
47
|
+
budget: number;
|
|
48
|
+
/** 任务总数,放在多级表头“任务情况”下。 */
|
|
49
|
+
task_total: number;
|
|
50
|
+
/** 完成进度(0-100),演示 render 自定义渲染进度条列。 */
|
|
51
|
+
progress: number;
|
|
52
|
+
/** 所属城市(演示级联选择搜索),格式:省份/城市。 */
|
|
53
|
+
region: string[];
|
|
54
|
+
/** 创建时间,演示日期区间搜索。 */
|
|
55
|
+
created_at: string;
|
|
56
|
+
}
|
|
57
|
+
/** Demo 目录节点,树形表格页面的行数据结构(children 内嵌子级行)。 */
|
|
58
|
+
export interface DemoCatalog {
|
|
59
|
+
/** 节点ID。 */
|
|
60
|
+
id: number;
|
|
61
|
+
/** 目录名称,作为树形展开列。 */
|
|
62
|
+
name: string;
|
|
63
|
+
/** 编码。 */
|
|
64
|
+
code: string;
|
|
65
|
+
/** 负责人。 */
|
|
66
|
+
owner: string;
|
|
67
|
+
/** 状态,1 启用 / 0 停用。 */
|
|
68
|
+
status: number;
|
|
69
|
+
/** 文档数量。 */
|
|
70
|
+
doc_count: number;
|
|
71
|
+
/** 子级目录,树形表格通过 tree-props.children 识别。 */
|
|
72
|
+
children?: DemoCatalog[];
|
|
73
|
+
}
|
|
74
|
+
/** 部门树节点,左侧 TreeFilter 静态数据结构。 */
|
|
75
|
+
export interface DemoDeptNode {
|
|
76
|
+
/** 部门ID,字符串形式以匹配 TreeFilter 默认选中逻辑。 */
|
|
77
|
+
id: string;
|
|
78
|
+
/** 部门名称。 */
|
|
79
|
+
name: string;
|
|
80
|
+
/** 部门人数,展示在树节点右侧。 */
|
|
81
|
+
count: number;
|
|
82
|
+
/** 子部门。 */
|
|
83
|
+
children?: DemoDeptNode[];
|
|
84
|
+
}
|
|
85
|
+
/** 详情抽屉单条字段:label 走 t() 文案,value 为记录内容。 */
|
|
86
|
+
export interface DemoDetailField {
|
|
87
|
+
/** 字段中文名。 */
|
|
88
|
+
label: string;
|
|
89
|
+
/** 字段值。 */
|
|
90
|
+
value: string;
|
|
91
|
+
/** 是否用 el-tag 展示。 */
|
|
92
|
+
tag?: boolean;
|
|
93
|
+
/** el-tag 展示类型(success/info/warning/danger)。 */
|
|
94
|
+
tagType?: "success" | "info" | "warning" | "danger";
|
|
95
|
+
}
|
|
96
|
+
/** 详情抽屉信息分组,一组对应一个 el-descriptions 区块。 */
|
|
97
|
+
export interface DemoDetailSection {
|
|
98
|
+
/** 分组标题。 */
|
|
99
|
+
title: string;
|
|
100
|
+
/** 分组内字段。 */
|
|
101
|
+
fields: DemoDetailField[];
|
|
102
|
+
}
|
|
103
|
+
/** 详情抽屉入参模型,由各页面从行数据组装。 */
|
|
104
|
+
export interface DemoDetailModel {
|
|
105
|
+
/** 抽屉标题。 */
|
|
106
|
+
title: string;
|
|
107
|
+
/** 基本信息分组。 */
|
|
108
|
+
sections: DemoDetailSection[];
|
|
109
|
+
/** 流程步骤,演示 el-steps。 */
|
|
110
|
+
steps: {
|
|
111
|
+
title: string;
|
|
112
|
+
description: string;
|
|
113
|
+
}[];
|
|
114
|
+
/** 操作时间线,演示 el-timeline。 */
|
|
115
|
+
timeline: {
|
|
116
|
+
title: string;
|
|
117
|
+
timestamp: string;
|
|
118
|
+
type: "primary" | "success" | "warning" | "danger" | "info";
|
|
119
|
+
}[];
|
|
120
|
+
/** 原始数据,演示 JSON 预览。 */
|
|
121
|
+
raw: Record<string, unknown>;
|
|
122
|
+
}
|
|
123
|
+
/** 左侧部门树静态数据:三层结构演示树的展开、搜索与联动。 */
|
|
124
|
+
export declare const demoDeptTree: DemoDeptNode[];
|
|
125
|
+
/** 员工静态数据:覆盖图片列、金额列、状态列与部门树联动过滤演示。 */
|
|
126
|
+
export declare const demoEmployees: DemoEmployee[];
|
|
127
|
+
/** 项目等级枚举选项:同时用于标签列展示与搜索下拉、表单单选。 */
|
|
128
|
+
export declare const demoLevelOptions: {
|
|
129
|
+
label: string;
|
|
130
|
+
value: string;
|
|
131
|
+
tagType: string;
|
|
132
|
+
}[];
|
|
133
|
+
/** 城市级联静态数据:演示搜索区 cascader 与 tree-select 控件。 */
|
|
134
|
+
export declare const demoRegionTree: {
|
|
135
|
+
value: string;
|
|
136
|
+
label: string;
|
|
137
|
+
children: {
|
|
138
|
+
value: string;
|
|
139
|
+
label: string;
|
|
140
|
+
}[];
|
|
141
|
+
}[];
|
|
142
|
+
/** 项目静态数据:25 条覆盖分页、多级表头、进度条、标签列等展示场景。 */
|
|
143
|
+
export declare const demoProjects: DemoProject[];
|
|
144
|
+
/** 目录树静态数据:三层嵌套演示树形表格的展开、折叠与子级操作。 */
|
|
145
|
+
export declare const demoCatalogTree: DemoCatalog[];
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="core-code-block" :style="{ '--core-code-block-max-height': props.maxHeight }">
|
|
3
|
+
<button
|
|
4
|
+
class="core-code-block__copy"
|
|
5
|
+
type="button"
|
|
6
|
+
:title="t('core.markdown.action.copy_code')"
|
|
7
|
+
:aria-label="t('core.markdown.action.copy_code')"
|
|
8
|
+
@click="copyCode"
|
|
9
|
+
>
|
|
10
|
+
<el-icon>
|
|
11
|
+
<Select v-if="copied" />
|
|
12
|
+
<CopyDocument v-else />
|
|
13
|
+
</el-icon>
|
|
14
|
+
</button>
|
|
15
|
+
<pre class="core-code-block__pre"><code class="hljs" v-html="highlightedHtml"></code></pre>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { computed, ref } from "vue";
|
|
21
|
+
import { CopyDocument, Select } from "@element-plus/icons-vue";
|
|
22
|
+
import hljs from "highlight.js";
|
|
23
|
+
import { t } from "../../locales";
|
|
24
|
+
import { copyText } from "../../utils/clipboard";
|
|
25
|
+
|
|
26
|
+
defineOptions({ name: "CoreCodeBlock" });
|
|
27
|
+
|
|
28
|
+
/** 代码块组件属性。 */
|
|
29
|
+
interface CodeBlockProps {
|
|
30
|
+
/** 代码原文。 */
|
|
31
|
+
code: string;
|
|
32
|
+
/** 高亮语言,缺省时自动检测。 */
|
|
33
|
+
language?: string;
|
|
34
|
+
/** 代码区最大高度。 */
|
|
35
|
+
maxHeight?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const props = withDefaults(defineProps<CodeBlockProps>(), {
|
|
39
|
+
language: "",
|
|
40
|
+
maxHeight: "420px"
|
|
41
|
+
});
|
|
42
|
+
const copied = ref(false);
|
|
43
|
+
let copiedTimer: ReturnType<typeof setTimeout> | undefined;
|
|
44
|
+
|
|
45
|
+
const highlightedHtml = computed(() => {
|
|
46
|
+
const code = props.code ?? "";
|
|
47
|
+
if (props.language && hljs.getLanguage(props.language)) {
|
|
48
|
+
return hljs.highlight(code, { language: props.language, ignoreIllegals: true }).value;
|
|
49
|
+
}
|
|
50
|
+
return hljs.highlightAuto(code).value;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** 复制代码原文并短暂反馈复制结果。 */
|
|
54
|
+
async function copyCode() {
|
|
55
|
+
try {
|
|
56
|
+
await copyText(props.code ?? "");
|
|
57
|
+
} catch {
|
|
58
|
+
ElMessage.error(t("core.markdown.message.copy_failed"));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
ElMessage.success(t("core.clipboard.success"));
|
|
62
|
+
copied.value = true;
|
|
63
|
+
clearTimeout(copiedTimer);
|
|
64
|
+
copiedTimer = setTimeout(() => {
|
|
65
|
+
copied.value = false;
|
|
66
|
+
}, 2000);
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<style scoped lang="scss">
|
|
71
|
+
.core-code-block {
|
|
72
|
+
position: relative;
|
|
73
|
+
overflow: hidden;
|
|
74
|
+
background: var(--admin-page-card-bg-muted);
|
|
75
|
+
border: 1px solid var(--admin-page-card-border-soft);
|
|
76
|
+
border-radius: var(--admin-page-radius);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.core-code-block__copy {
|
|
80
|
+
position: absolute;
|
|
81
|
+
top: 8px;
|
|
82
|
+
right: 8px;
|
|
83
|
+
z-index: 1;
|
|
84
|
+
display: inline-flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
justify-content: center;
|
|
87
|
+
width: 26px;
|
|
88
|
+
height: 26px;
|
|
89
|
+
padding: 0;
|
|
90
|
+
color: var(--admin-page-text-secondary);
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
background: var(--admin-page-card-bg);
|
|
93
|
+
border: 1px solid var(--admin-page-card-border-soft);
|
|
94
|
+
border-radius: var(--admin-page-radius);
|
|
95
|
+
box-shadow: var(--admin-page-shadow);
|
|
96
|
+
opacity: 0;
|
|
97
|
+
transition: opacity 0.15s ease;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.core-code-block:hover .core-code-block__copy,
|
|
101
|
+
.core-code-block__copy:focus-visible {
|
|
102
|
+
opacity: 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.core-code-block__pre {
|
|
106
|
+
max-height: var(--core-code-block-max-height);
|
|
107
|
+
margin: 0;
|
|
108
|
+
overflow: auto;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.core-code-block__pre code.hljs {
|
|
112
|
+
display: block;
|
|
113
|
+
padding: 10px 12px;
|
|
114
|
+
margin: 0;
|
|
115
|
+
font-size: 13px;
|
|
116
|
+
line-height: 1.6;
|
|
117
|
+
color: var(--admin-page-text-primary);
|
|
118
|
+
white-space: pre-wrap;
|
|
119
|
+
word-break: break-word;
|
|
120
|
+
background: transparent;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.core-code-block :deep(.hljs-comment),
|
|
124
|
+
.core-code-block :deep(.hljs-quote) {
|
|
125
|
+
font-style: italic;
|
|
126
|
+
color: var(--admin-page-text-secondary);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.core-code-block :deep(.hljs-keyword),
|
|
130
|
+
.core-code-block :deep(.hljs-selector-tag),
|
|
131
|
+
.core-code-block :deep(.hljs-built_in),
|
|
132
|
+
.core-code-block :deep(.hljs-name) {
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
color: var(--el-color-primary);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.core-code-block :deep(.hljs-string),
|
|
138
|
+
.core-code-block :deep(.hljs-attr),
|
|
139
|
+
.core-code-block :deep(.hljs-symbol),
|
|
140
|
+
.core-code-block :deep(.hljs-bullet) {
|
|
141
|
+
color: var(--el-color-success);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.core-code-block :deep(.hljs-number),
|
|
145
|
+
.core-code-block :deep(.hljs-literal),
|
|
146
|
+
.core-code-block :deep(.hljs-variable),
|
|
147
|
+
.core-code-block :deep(.hljs-template-variable) {
|
|
148
|
+
color: var(--el-color-warning);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.core-code-block :deep(.hljs-title),
|
|
152
|
+
.core-code-block :deep(.hljs-section),
|
|
153
|
+
.core-code-block :deep(.hljs-type) {
|
|
154
|
+
color: var(--el-color-danger);
|
|
155
|
+
}
|
|
156
|
+
</style>
|
|
@@ -10,11 +10,14 @@
|
|
|
10
10
|
v-model.trim="fieldValue"
|
|
11
11
|
type="password"
|
|
12
12
|
show-password
|
|
13
|
+
autocomplete="new-password"
|
|
13
14
|
v-bind="fieldProps"
|
|
14
15
|
/>
|
|
15
16
|
|
|
16
17
|
<el-input v-else-if="field.component === 'textarea'" v-model.trim="fieldValue" type="textarea" v-bind="fieldProps" />
|
|
17
18
|
|
|
19
|
+
<YamlEditor v-else-if="field.component === 'yaml-editor'" v-model="fieldValue" v-bind="fieldProps" />
|
|
20
|
+
|
|
18
21
|
<el-input-number v-else-if="field.component === 'input-number'" v-model="fieldValue" v-bind="fieldProps" />
|
|
19
22
|
|
|
20
23
|
<el-color-picker v-else-if="field.component === 'color-picker'" v-model="fieldValue" v-bind="fieldProps" />
|
|
@@ -51,7 +54,7 @@
|
|
|
51
54
|
</el-radio-group>
|
|
52
55
|
|
|
53
56
|
<el-checkbox-group v-else-if="field.component === 'checkbox-group'" v-model="fieldValue" v-bind="fieldProps">
|
|
54
|
-
<el-checkbox v-for="option in fieldOptions" :key="String(option.value)" :
|
|
57
|
+
<el-checkbox v-for="option in fieldOptions" :key="String(option.value)" :value="option.value" :disabled="option.disabled">
|
|
55
58
|
{{ option.label }}
|
|
56
59
|
</el-checkbox>
|
|
57
60
|
</el-checkbox-group>
|
|
@@ -103,6 +106,7 @@ import TenantSelect from "../../../components/TenantSelect/index.vue";
|
|
|
103
106
|
// 非基础表单控件按需加载,避免 ProForm 基础包携带上传、富文本、Cron 等重组件。
|
|
104
107
|
const CronExpression = defineAsyncComponent(() => import("../../../components/CronExpression/index.vue"));
|
|
105
108
|
const WangEditor = defineAsyncComponent(() => import("../../../components/WangEditor/index.vue"));
|
|
109
|
+
const YamlEditor = defineAsyncComponent(() => import("../../../components/YamlEditor/index.vue"));
|
|
106
110
|
const DynamicList = defineAsyncComponent(() => import("../../../components/ProForm/components/DynamicList.vue"));
|
|
107
111
|
const KvList = defineAsyncComponent(() => import("../../../components/ProForm/components/KvList.vue"));
|
|
108
112
|
const UploadFile = defineAsyncComponent(() => import("../../../components/Upload/File.vue"));
|
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="yaml-editor">
|
|
3
|
-
<div
|
|
2
|
+
<div class="yaml-editor" :class="{ 'is-invalid': invalid, 'is-bounded': !!maxHeight }">
|
|
3
|
+
<div class="yaml-editor__toolbar">
|
|
4
|
+
<span class="yaml-editor__lang">
|
|
5
|
+
<el-icon><Document /></el-icon>
|
|
6
|
+
<span>YAML</span>
|
|
7
|
+
</span>
|
|
8
|
+
<div class="yaml-editor__actions">
|
|
9
|
+
<button
|
|
10
|
+
type="button"
|
|
11
|
+
class="yaml-editor__action"
|
|
12
|
+
:class="{ 'is-active': wrapEnabled }"
|
|
13
|
+
:title="t('core.yaml_editor.action.wrap')"
|
|
14
|
+
:aria-label="t('core.yaml_editor.action.wrap')"
|
|
15
|
+
@click="toggleWrap"
|
|
16
|
+
>
|
|
17
|
+
<el-icon :size="15"><WrapText /></el-icon>
|
|
18
|
+
</button>
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
class="yaml-editor__action"
|
|
22
|
+
:title="t('core.markdown.action.copy_code')"
|
|
23
|
+
:aria-label="t('core.markdown.action.copy_code')"
|
|
24
|
+
@click="copyCode"
|
|
25
|
+
>
|
|
26
|
+
<el-icon :size="15">
|
|
27
|
+
<Select v-if="copied" />
|
|
28
|
+
<CopyDocument v-else />
|
|
29
|
+
</el-icon>
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div ref="editorHost" class="yaml-editor__host" />
|
|
4
34
|
<div class="yaml-editor__footer">
|
|
5
35
|
<span>{{ statusText }}</span>
|
|
6
36
|
<span>{{ lineCount }} {{ lineLabel }}</span>
|
|
@@ -11,14 +41,25 @@
|
|
|
11
41
|
<script setup lang="ts">
|
|
12
42
|
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
13
43
|
import { basicSetup, EditorView } from "codemirror";
|
|
44
|
+
import { Compartment } from "@codemirror/state";
|
|
45
|
+
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
|
|
46
|
+
import { tags } from "@lezer/highlight";
|
|
14
47
|
import { yaml } from "@codemirror/lang-yaml";
|
|
15
48
|
import { placeholder } from "@codemirror/view";
|
|
49
|
+
import { CopyDocument, Document, Select } from "@element-plus/icons-vue";
|
|
50
|
+
import { WrapText } from "@lucide/vue";
|
|
51
|
+
import { t } from "../../locales";
|
|
52
|
+
import { copyText } from "../../utils/clipboard";
|
|
16
53
|
|
|
17
54
|
/** YAML 编辑器组件属性。 */
|
|
18
55
|
interface YamlEditorProps {
|
|
19
56
|
modelValue: string;
|
|
20
57
|
placeholderText?: string;
|
|
21
58
|
minHeight?: string;
|
|
59
|
+
/** 编辑器最大高度;内容超出后编辑器不再撑高页面,改为内部滚动。 */
|
|
60
|
+
maxHeight?: string;
|
|
61
|
+
/** 是否默认开启自动换行,可通过工具栏切换。 */
|
|
62
|
+
wrap?: boolean;
|
|
22
63
|
invalid?: boolean;
|
|
23
64
|
lineLabel?: string;
|
|
24
65
|
/** 根据光标行列生成状态栏文案。 */
|
|
@@ -28,6 +69,8 @@ interface YamlEditorProps {
|
|
|
28
69
|
const props = withDefaults(defineProps<YamlEditorProps>(), {
|
|
29
70
|
placeholderText: "",
|
|
30
71
|
minHeight: "280px",
|
|
72
|
+
maxHeight: undefined,
|
|
73
|
+
wrap: true,
|
|
31
74
|
invalid: false,
|
|
32
75
|
lineLabel: "lines",
|
|
33
76
|
formatPosition: (line: number, column: number) => `Ln ${line}, Col ${column}`
|
|
@@ -37,8 +80,23 @@ const emit = defineEmits<{ "update:modelValue": [value: string] }>();
|
|
|
37
80
|
const editorHost = ref<HTMLElement>();
|
|
38
81
|
const lineCount = ref(1);
|
|
39
82
|
const statusText = ref("");
|
|
83
|
+
const wrapEnabled = ref(props.wrap);
|
|
84
|
+
const copied = ref(false);
|
|
85
|
+
let copiedTimer: ReturnType<typeof setTimeout> | undefined;
|
|
40
86
|
let editor: EditorView | undefined;
|
|
41
87
|
|
|
88
|
+
const wrapCompartment = new Compartment();
|
|
89
|
+
|
|
90
|
+
/** 与管理端配色一致的 YAML 高亮样式(键绿、串蓝、值橙、注释灰斜体)。 */
|
|
91
|
+
const yamlHighlightStyle = HighlightStyle.define([
|
|
92
|
+
{ tag: [tags.propertyName, tags.attributeName], color: "var(--el-color-success)" },
|
|
93
|
+
{ tag: [tags.string, tags.attributeValue], color: "var(--el-color-primary)" },
|
|
94
|
+
{ tag: [tags.number, tags.bool, tags.keyword], color: "var(--el-color-warning)" },
|
|
95
|
+
{ tag: [tags.lineComment, tags.blockComment], color: "var(--el-text-color-secondary)", fontStyle: "italic" },
|
|
96
|
+
{ tag: [tags.typeName, tags.labelName, tags.definition(tags.variableName)], color: "var(--el-color-danger)" },
|
|
97
|
+
{ tag: [tags.meta, tags.separator, tags.punctuation], color: "var(--el-text-color-placeholder)" }
|
|
98
|
+
]);
|
|
99
|
+
|
|
42
100
|
const editorTheme = EditorView.theme({
|
|
43
101
|
"&": { color: "var(--el-text-color-primary)", backgroundColor: "var(--el-fill-color-blank)", fontSize: "13px" },
|
|
44
102
|
".cm-content": { minHeight: props.minHeight, padding: "12px 0", caretColor: "var(--el-color-primary)" },
|
|
@@ -59,7 +117,8 @@ onMounted(() => {
|
|
|
59
117
|
basicSetup,
|
|
60
118
|
yaml(),
|
|
61
119
|
editorTheme,
|
|
62
|
-
|
|
120
|
+
syntaxHighlighting(yamlHighlightStyle),
|
|
121
|
+
wrapCompartment.of(wrapEnabled.value ? EditorView.lineWrapping : []),
|
|
63
122
|
EditorView.contentAttributes.of({ "aria-label": "YAML editor", spellcheck: "false" }),
|
|
64
123
|
EditorView.updateListener.of(update => {
|
|
65
124
|
if (!update.docChanged) return;
|
|
@@ -88,20 +147,57 @@ onMounted(() => {
|
|
|
88
147
|
lineCount.value = editor.state.doc.lines;
|
|
89
148
|
});
|
|
90
149
|
|
|
150
|
+
/** 切换自动换行,通过 Compartment 热更新编辑器扩展。 */
|
|
151
|
+
function toggleWrap() {
|
|
152
|
+
wrapEnabled.value = !wrapEnabled.value;
|
|
153
|
+
editor?.dispatch({
|
|
154
|
+
effects: wrapCompartment.reconfigure(wrapEnabled.value ? EditorView.lineWrapping : [])
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** 复制编辑器全文并短暂反馈复制结果。 */
|
|
159
|
+
async function copyCode() {
|
|
160
|
+
const code = editor?.state.doc.toString() ?? "";
|
|
161
|
+
try {
|
|
162
|
+
await copyText(code);
|
|
163
|
+
} catch {
|
|
164
|
+
ElMessage.error(t("core.markdown.message.copy_failed"));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
ElMessage.success(t("core.clipboard.success"));
|
|
168
|
+
copied.value = true;
|
|
169
|
+
clearTimeout(copiedTimer);
|
|
170
|
+
copiedTimer = setTimeout(() => {
|
|
171
|
+
copied.value = false;
|
|
172
|
+
}, 2000);
|
|
173
|
+
}
|
|
174
|
+
|
|
91
175
|
watch(() => props.modelValue, value => {
|
|
92
176
|
if (!editor || value === editor.state.doc.toString()) return;
|
|
93
177
|
editor.dispatch({ changes: { from: 0, to: editor.state.doc.length, insert: value } });
|
|
94
178
|
lineCount.value = editor.state.doc.lines;
|
|
95
179
|
});
|
|
96
180
|
|
|
97
|
-
onBeforeUnmount(() =>
|
|
181
|
+
onBeforeUnmount(() => {
|
|
182
|
+
clearTimeout(copiedTimer);
|
|
183
|
+
editor?.destroy();
|
|
184
|
+
});
|
|
98
185
|
</script>
|
|
99
186
|
|
|
100
187
|
<style scoped>
|
|
101
|
-
.yaml-editor { width: 100%; overflow: hidden; border: 1px solid var(--el-border-color); border-radius: 4px; }
|
|
188
|
+
.yaml-editor { display: flex; flex-direction: column; width: 100%; overflow: hidden; background: var(--el-fill-color-blank); border: 1px solid var(--el-border-color); border-radius: 4px; }
|
|
189
|
+
.yaml-editor.is-bounded { max-height: v-bind(maxHeight); }
|
|
102
190
|
.yaml-editor:focus-within { border-color: var(--el-color-primary); }
|
|
103
|
-
.yaml-
|
|
191
|
+
.yaml-editor.is-invalid { border-color: var(--el-color-danger); }
|
|
192
|
+
.yaml-editor__toolbar { display: flex; align-items: center; justify-content: space-between; min-height: 32px; padding: 2px 8px; background: var(--el-fill-color-light); border-bottom: 1px solid var(--el-border-color-lighter); }
|
|
193
|
+
.yaml-editor__lang { display: inline-flex; gap: 5px; align-items: center; color: var(--el-text-color-secondary); font-size: 12px; font-weight: 600; letter-spacing: 0.5px; }
|
|
194
|
+
.yaml-editor__actions { display: inline-flex; gap: 2px; }
|
|
195
|
+
.yaml-editor__action { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; color: var(--el-text-color-secondary); cursor: pointer; background: transparent; border: none; border-radius: 4px; transition: color 0.15s ease, background-color 0.15s ease; }
|
|
196
|
+
.yaml-editor__action:hover, .yaml-editor__action:focus-visible { color: var(--el-color-primary); background: var(--el-fill-color); outline: none; }
|
|
197
|
+
.yaml-editor__action.is-active { color: var(--el-color-primary); }
|
|
198
|
+
.yaml-editor__host { flex: 1 1 auto; min-height: 0; }
|
|
199
|
+
.yaml-editor__host :deep(.cm-editor) { display: flex; flex-direction: column; min-height: v-bind(minHeight); height: 100%; }
|
|
104
200
|
.yaml-editor__host :deep(.cm-editor.cm-focused) { outline: none; }
|
|
105
|
-
.yaml-editor__host.
|
|
201
|
+
.yaml-editor__host :deep(.cm-scroller) { flex: 1 1 auto; min-height: 0; }
|
|
106
202
|
.yaml-editor__footer { display: flex; justify-content: space-between; min-height: 24px; padding: 3px 10px; color: var(--el-text-color-secondary); background: var(--el-fill-color-lighter); border-top: 1px solid var(--el-border-color-lighter); font-size: 12px; }
|
|
107
203
|
</style>
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"core.markdown.action.copy_code": "Copy code",
|
|
94
94
|
"core.markdown.message.copy_success": "Copied!",
|
|
95
95
|
"core.markdown.message.copy_failed": "Copy failed!",
|
|
96
|
+
"core.yaml_editor.action.wrap": "Word wrap",
|
|
96
97
|
"core.loading.text": "Loading",
|
|
97
98
|
"core.layout.avatar": "User avatar",
|
|
98
99
|
"core.layout.logo_alt": "System logo",
|
|
@@ -386,5 +387,93 @@
|
|
|
386
387
|
"common.field.user_id": "User ID",
|
|
387
388
|
"core.home.demo_scope": "Overview",
|
|
388
389
|
"core.home.main_title": "Admin Application",
|
|
389
|
-
"core.router.pending_view_unregistered": "The admin static page is not registered: {view}"
|
|
390
|
+
"core.router.pending_view_unregistered": "The admin static page is not registered: {view}",
|
|
391
|
+
"core.demo.resource.employee": "Employee",
|
|
392
|
+
"core.demo.resource.project": "Project",
|
|
393
|
+
"core.demo.resource.doc_catalog": "Document Catalog",
|
|
394
|
+
"core.demo.tree.title.dept_tree": "Department List",
|
|
395
|
+
"core.demo.tree.field.name": "Name",
|
|
396
|
+
"core.demo.tree.field.email": "Email",
|
|
397
|
+
"core.demo.tree.field.phone": "Phone",
|
|
398
|
+
"core.demo.tree.field.salary": "Salary",
|
|
399
|
+
"core.demo.tree.field.entry_date": "Entry Date",
|
|
400
|
+
"core.demo.table.field.name": "Project Name",
|
|
401
|
+
"core.demo.table.field.level": "Level",
|
|
402
|
+
"core.demo.table.field.owner": "Owner",
|
|
403
|
+
"core.demo.table.field.budget": "Budget",
|
|
404
|
+
"core.demo.table.field.task_total": "Total Tasks",
|
|
405
|
+
"core.demo.table.field.progress": "Progress",
|
|
406
|
+
"core.demo.table.field.region": "Region",
|
|
407
|
+
"core.demo.table.field.tags": "Content Tags",
|
|
408
|
+
"core.demo.table.field.owner_group": "Owner Team",
|
|
409
|
+
"core.demo.table.field.enabled": "Enabled",
|
|
410
|
+
"core.demo.table.group.task": "Tasks",
|
|
411
|
+
"core.demo.table.tooltip.progress": "Custom render demo: el-progress bar",
|
|
412
|
+
"core.demo.grid.field.name": "Catalog Name",
|
|
413
|
+
"core.demo.grid.field.code": "Catalog Code",
|
|
414
|
+
"core.demo.grid.field.doc_count": "Document Count",
|
|
415
|
+
"core.demo.grid.field.path": "Hierarchy Path",
|
|
416
|
+
"core.demo.grid.action.create_child": "Add Child",
|
|
417
|
+
"core.demo.grid.action.expand_all": "Expand All",
|
|
418
|
+
"core.demo.grid.action.collapse_all": "Collapse All",
|
|
419
|
+
"core.demo.grid.message.has_children": "This catalog has children; delete them first",
|
|
420
|
+
"core.demo.form.title.demo_form": "All-Component Form Demo",
|
|
421
|
+
"core.demo.form.group.basic": "Basic Input",
|
|
422
|
+
"core.demo.form.group.choice": "Select & Date",
|
|
423
|
+
"core.demo.form.group.advanced": "Advanced",
|
|
424
|
+
"core.demo.form.field.name": "Name",
|
|
425
|
+
"core.demo.form.field.password": "Access Secret",
|
|
426
|
+
"core.demo.form.field.intro": "Description",
|
|
427
|
+
"core.demo.form.field.count": "Task Count",
|
|
428
|
+
"core.demo.form.field.color": "Theme Color",
|
|
429
|
+
"core.demo.form.field.enabled": "Enabled",
|
|
430
|
+
"core.demo.form.field.agreed": "I have read the instructions",
|
|
431
|
+
"core.demo.form.field.invite_code": "Invite Code",
|
|
432
|
+
"core.demo.form.field.mode": "Display Mode",
|
|
433
|
+
"core.demo.form.mode.card": "Card",
|
|
434
|
+
"core.demo.form.mode.list": "List",
|
|
435
|
+
"core.demo.form.mode.table": "Table",
|
|
436
|
+
"core.demo.form.field.level": "Level",
|
|
437
|
+
"core.demo.form.field.gender": "Gender",
|
|
438
|
+
"core.demo.form.field.visibility": "Visibility",
|
|
439
|
+
"core.demo.form.visibility.public": "Public",
|
|
440
|
+
"core.demo.form.visibility.private": "Private",
|
|
441
|
+
"core.demo.form.field.hobbies": "Collaboration Preferences",
|
|
442
|
+
"core.demo.form.hobby.review": "Code Review",
|
|
443
|
+
"core.demo.form.hobby.deploy": "Continuous Deployment",
|
|
444
|
+
"core.demo.form.hobby.metric": "Metrics",
|
|
445
|
+
"core.demo.form.field.birthday": "Founding Date",
|
|
446
|
+
"core.demo.form.field.range": "Active Range",
|
|
447
|
+
"core.demo.form.field.cron": "Cron Expression",
|
|
448
|
+
"core.demo.form.placeholder.cron": "e.g. 0 0 2 * * ?",
|
|
449
|
+
"core.demo.form.field.members": "Collaborators",
|
|
450
|
+
"core.demo.form.transfer.candidates": "Candidates",
|
|
451
|
+
"core.demo.form.transfer.selected": "Selected",
|
|
452
|
+
"core.demo.form.field.avatar": "Cover Image",
|
|
453
|
+
"core.demo.form.field.gallery": "Gallery",
|
|
454
|
+
"core.demo.form.field.attachment": "Attachment",
|
|
455
|
+
"core.demo.form.field.attachments": "Attachments",
|
|
456
|
+
"core.demo.form.field.content": "Details",
|
|
457
|
+
"core.demo.form.field.config": "Deploy Config",
|
|
458
|
+
"core.demo.form.field.aliases": "Aliases",
|
|
459
|
+
"core.demo.form.field.properties": "Env Variables",
|
|
460
|
+
"core.demo.form.action.random_code": "Random",
|
|
461
|
+
"core.demo.form.action.add_property": "Add Variable",
|
|
462
|
+
"core.demo.form.tooltip.count": "Steps by 5, range 0-999",
|
|
463
|
+
"core.demo.form.message.submit_success": "Form validated; submitted in mock mode",
|
|
464
|
+
"core.demo.detail.tab.basic": "Basic Info",
|
|
465
|
+
"core.demo.detail.tab.progress": "Progress",
|
|
466
|
+
"core.demo.detail.tab.timeline": "Timeline",
|
|
467
|
+
"core.demo.detail.tab.raw": "Raw Data",
|
|
468
|
+
"core.demo.detail.raw.title": "Raw JSON Data",
|
|
469
|
+
"core.demo.detail.section.basic": "Basic Info",
|
|
470
|
+
"core.demo.detail.section.work": "Assignment Info",
|
|
471
|
+
"core.demo.detail.step.create": "Created",
|
|
472
|
+
"core.demo.detail.step.review": "Approved",
|
|
473
|
+
"core.demo.detail.step.publish": "Published",
|
|
474
|
+
"core.demo.detail.step.done": "Archived",
|
|
475
|
+
"core.demo.detail.timeline.created": "Record created",
|
|
476
|
+
"core.demo.detail.timeline.updated": "Base info updated",
|
|
477
|
+
"core.demo.detail.timeline.enabled": "Record enabled",
|
|
478
|
+
"core.demo.detail.timeline.disabled": "Record disabled"
|
|
390
479
|
}
|