Files
console-dev/app/components/RichTextEditor.vue
T
cneicy 42dfead9cd
前端验证 / verify (push) Failing after 15m3s
feat(ui): 优化控制台响应式布局与视觉体验
- 统一页面标题、圆角、字体和响应式断点,调整暗色主题及图表配色
- 新增表格列标签插件,在窄屏下将表格转换为卡片布局,优化实例、订单、工单等列表展示
- 支持移动端点击订单行进入详情,并完善订单凭据的打印布局
- 在财务中心展示本月及累计消费,补充充值关闭时的提示
- 优化工单详情布局,将附件上传入口整合至富文本编辑器工具栏
- 调整实例操作按钮、SSH 密钥操作及安全菜单图标
- 为多个页面显式导入 definePageMeta,完善页面元数据声明
2026-11-07 00:09:57 +08:00

255 lines
8.0 KiB
Vue

<script>
// 轻量富文本输入:用浏览器编辑命令维护选区,并在模型输入/输出边界清洗 HTML。
import "~/assets/styles/RichTextEditor.NkmgPT5m.css";
import { renderList } from "vue";
import { onMounted } from "vue";
import { normalizeClass } from "vue";
import { mergeModels } from "vue";
import { renderSlot } from "vue";
import { useModel } from "vue";
import { watch } from "vue";
import { ref } from "vue";
import { createElementBlock } from "vue";
import { Fragment } from "vue";
import { createElementVNode } from "vue";
import { defineComponent } from "vue";
import { openBlock } from "vue";
import { normalizeStyle } from "vue";
import { withModifiers } from "vue";
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
import { n as sanitize_n } from "~/lib/sanitize.js";
var g = [`aria-label`],
_ = [`title`, `aria-label`, `disabled`, `onClick`],
v = [
`id`,
`aria-label`,
`aria-disabled`,
`contenteditable`,
`data-placeholder`,
],
y = Object.assign(
_plugin_vue_export_helper_t(
defineComponent({
__name: `RichTextEditor`,
props: mergeModels(
{
id: {},
label: {
default: `正文`,
},
placeholder: {
default: `输入内容`,
},
disabled: {
type: Boolean,
},
minHeight: {
default: 240,
},
},
{
modelValue: {
default: ``,
},
modelModifiers: {},
},
),
emits: [`update:modelValue`],
setup(r) {
let u = r,
m = useModel(r, `modelValue`),
y = ref(null),
b = null,
x = [
{
name: `加粗`,
command: `bold`,
icon: `pi pi-bold`,
},
{
name: `斜体`,
command: `italic`,
icon: `pi pi-italic`,
},
{
name: `下划线`,
command: `underline`,
icon: `pi pi-underline`,
},
{
name: `有序列表`,
command: `insertOrderedList`,
icon: `pi pi-list`,
},
{
name: `无序列表`,
command: `insertUnorderedList`,
icon: `pi pi-list-check`,
},
{
name: `左对齐`,
command: `justifyLeft`,
icon: `pi pi-align-left`,
},
{
name: `居中对齐`,
command: `justifyCenter`,
icon: `pi pi-align-center`,
},
{
name: `右对齐`,
command: `justifyRight`,
icon: `pi pi-align-right`,
},
];
// 仅保存编辑区域内的选区,点击工具栏后可恢复到用户原来的光标位置。
function S() {
let e = window.getSelection();
e?.rangeCount &&
y.value?.contains(e.anchorNode) &&
(b = e.getRangeAt(0).cloneRange());
}
// 空白正文输出空串,其余内容先清洗再同步 v-model,并更新当前选区。
function C() {
y.value &&
((m.value = y.value.innerText.trim()
? sanitize_n(y.value.innerHTML)
: ``),
S());
}
function w(e) {
if (!u.disabled && y.value) {
if (
(y.value.focus(),
b && y.value.contains(b.commonAncestorContainer))
) {
let e = window.getSelection();
(e?.removeAllRanges(), e?.addRange(b));
}
(document.execCommand(e, !1), C());
}
}
// 粘贴只接收纯文本,阻止剪贴板 HTML 直接带入额外标签和属性。
function T(e) {
(e.preventDefault(),
!u.disabled &&
(document.execCommand(
`insertText`,
!1,
e.clipboardData?.getData(`text/plain`) || ``,
),
C()));
}
return (
onMounted(() => {
y.value && (y.value.innerHTML = sanitize_n(m.value));
}),
watch(m, (e) => {
y.value &&
sanitize_n(y.value.innerHTML) !== e &&
(y.value.innerHTML = sanitize_n(e));
}),
(t, i) => (
openBlock(),
createElementBlock(
`div`,
{
class: normalizeClass([
`rich-editor`,
{
disabled: r.disabled,
},
]),
},
[
createElementVNode(
`div`,
{
class: `editor-toolbar`,
role: `toolbar`,
"aria-label": `${r.label}格式`,
},
[
(openBlock(),
createElementBlock(
Fragment,
null,
renderList(x, (e) =>
createElementVNode(
`button`,
{
key: e.command,
type: `button`,
title: e.name,
"aria-label": e.name,
disabled: r.disabled,
onMousedown: (i[0] ||= withModifiers(() => {}, [
`prevent`,
])),
onClick: (t) => w(e.command),
},
[
createElementVNode(
`i`,
{
class: normalizeClass(e.icon),
"aria-hidden": `true`,
},
null,
2,
),
],
40,
_,
),
),
64,
)),
renderSlot(t.$slots, `toolbar-end`, {}, void 0, !0),
],
8,
g,
),
createElementVNode(
`div`,
{
id: r.id,
ref_key: `editor`,
ref: y,
class: `editor-content`,
role: `textbox`,
"aria-label": r.label,
"aria-multiline": `true`,
"aria-disabled": r.disabled,
contenteditable: !r.disabled,
"data-placeholder": r.placeholder,
style: normalizeStyle({
minHeight: `${r.minHeight}px`,
}),
onInput: C,
onKeyup: S,
onMouseup: S,
onBlur: S,
onPaste: T,
},
null,
44,
v,
),
],
2,
)
)
);
},
}),
[[`__scopeId`, `data-v-858a2ddd`]],
),
{
__name: `RichTextEditor`,
},
);
export {};
export default y;
</script>