fix(login): 修复获取用户绑定租户列表时用户名为空导致的URL错误

确保username参数存在,避免因为空值造成接口调用失败

feat(editor): 重构富文本编辑器组件并优化图片上传逻辑
- 使用 Composition API 重构代码结构,提升可维护性
- 改进图片上传功能,增强对 quill 实例的安全访问
- 更新样式排版,提高组件可读性和一致性

refactor(file-upload): 移除旧代理引用,使用 modern Vue API
替换 `proxy` 调用为 `modal` 插件直接调用,提升代码清晰度与健壮性

refactor(image-upload): 替换旧实例调用方式,强化错误提示机制
统一使用 `modal` 进行消息提示和加载状态控制,改善用户体验

refactor(tree-select): 引入 Composition API 优化节点操作逻辑
移除 `getCurrentInstance` 的不必要使用,改为明确的模板引用管理

chore(main): 添加 util._extend 补丁以消除 Node.js 环境警告
解决开发环境下由于 Node.js 内建模块缺失造成的运行时警告问题

feat(template): 完善跌倒/坠床评估护理记录单模板
- 增加详细注释说明各部分作用,便于后续维护
- 明确组件名称为中文,利于业务识别
- 丰富表单交互细节及数据处理逻辑,支持动态打分、措施选择等功能

refactor(template-index): 加强模板组件自动注册逻辑
增加组件 name 属性校验,防止无效或匿名组件被注册到全局
This commit is contained in:
2025-12-14 14:22:55 +08:00
parent e1b9d36153
commit 5bfadb9174
16 changed files with 367 additions and 237 deletions

View File

@@ -1,28 +1,16 @@
<template>
<div>
<el-upload
:action="uploadUrl"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
name="file"
:show-file-list="false"
:headers="headers"
class="editor-img-uploader"
v-if="type == 'url'"
>
<i ref="uploadRef" class="editor-img-uploader"></i>
</el-upload>
</div>
<div class="editor">
<quill-editor
ref="quillEditorRef"
v-model:content="content"
contentType="html"
@textChange="(e) => $emit('update:modelValue', content)"
:options="options"
:style="styles"
/>
<div class="editor-container">
<div>
<el-upload :action="uploadUrl" :before-upload="handleBeforeUpload" :on-success="handleUploadSuccess"
:on-error="handleUploadError" name="file" :show-file-list="false" :headers="headers" class="editor-img-uploader"
v-if="type == 'url'">
<i ref="uploadRef" class="editor-img-uploader"></i>
</el-upload>
</div>
<div class="editor">
<quill-editor ref="quillEditorRef" :content="content" @update:content="content = $event" contentType="html"
@textChange="(e) => $emit('update:modelValue', content)" :options="options" :style="styles" />
</div>
</div>
</template>
@@ -30,10 +18,11 @@
import { QuillEditor } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { getToken } from "@/utils/auth";
const { proxy } = getCurrentInstance();
import { ref, computed, watch, onMounted } from 'vue';
import modal from '@/plugins/modal';
const quillEditorRef = ref();
const uploadRef = ref();
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 上传的图片服务器地址
const headers = ref({
Authorization: "Bearer " + getToken()
@@ -115,32 +104,34 @@ watch(() => props.modelValue, (v) => {
// 如果设置了上传地址则自定义图片上传事件
onMounted(() => {
if (props.type == 'url') {
let quill = quillEditorRef.value.getQuill();
let toolbar = quill.getModule("toolbar");
toolbar.addHandler("image", (value) => {
if (value) {
proxy.$refs.uploadRef.click();
} else {
quill.format("image", false);
}
});
let quill = quillEditorRef.value?.getQuill();
if (quill) {
let toolbar = quill.getModule("toolbar");
toolbar.addHandler("image", (value) => {
if (value && uploadRef.value) {
uploadRef.value.click();
} else {
quill.format("image", false);
}
});
}
}
});
// 上传前校检格式和大小
function handleBeforeUpload(file) {
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg+xml"];
const isJPG = type.includes(file.type);
//检验文件格式
if (!isJPG) {
proxy.$modal.msgError(`图片格式错误!`);
modal.msgError(`图片格式错误!`);
return false;
}
// 校检文件大小
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
@@ -152,21 +143,24 @@ function handleUploadSuccess(res, file) {
// 如果上传成功
if (res.code == 200) {
// 获取富文本实例
let quill = toRaw(quillEditorRef.value).getQuill();
// 获取光标位置
let length = quill.selection.savedRange.index;
// 插入图片res.url为服务器返回的图片链接地址
quill.insertEmbed(length, "image", import.meta.env.VITE_APP_BASE_API + res.fileName);
// 调整光标到最后
quill.setSelection(length + 1);
let quill = quillEditorRef.value?.getQuill();
if (quill) {
// 获取光标位置
let range = quill.selection?.savedRange || quill.getSelection();
let length = range?.index || 0;
// 插入图片res.url为服务器返回的图片链接地址
quill.insertEmbed(length, "image", import.meta.env.VITE_APP_BASE_API + res.fileName);
// 调整光标到最后
quill.setSelection(length + 1);
}
} else {
proxy.$modal.msgError("图片插入失败");
modal.msgError("图片插入失败");
}
}
// 上传失败处理
function handleUploadError() {
proxy.$modal.msgError("图片插入失败");
modal.msgError("图片插入失败");
}
</script>
@@ -174,78 +168,98 @@ function handleUploadError() {
.editor-img-uploader {
display: none;
}
.editor, .ql-toolbar {
.editor,
.ql-toolbar {
white-space: pre-wrap !important;
line-height: normal !important;
}
.quill-img {
display: none;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: "保存";
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode="video"]::before {
content: "请输入视频地址:";
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: "14px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
content: "10px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
content: "32px";
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: "文本";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: "标题1";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: "标题2";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: "标题3";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: "标题4";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: "标题5";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: "标题6";
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: "标准字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
content: "衬线字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
content: "等宽字体";
}
</style>
</style>

View File

@@ -39,7 +39,9 @@
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { getToken } from "@/utils/auth";
import modal from '@/plugins/modal';
const props = defineProps({
modelValue: [String, Object, Array],
@@ -65,7 +67,7 @@ const props = defineProps({
}
});
const { proxy } = getCurrentInstance();
const fileUpload = ref(null);
const emit = defineEmits();
const number = ref(0);
const uploadList = ref([]);
@@ -104,7 +106,7 @@ function handleBeforeUpload(file) {
const fileExt = fileName[fileName.length - 1];
const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
if (!isTypeOk) {
proxy.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join("/")}格式文件!`);
modal.msgError(`文件格式不正确, 请上传${props.fileType.join("/")}格式文件!`);
return false;
}
}
@@ -112,23 +114,23 @@ function handleBeforeUpload(file) {
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
proxy.$modal.loading("正在上传文件,请稍候...");
modal.loading("正在上传文件,请稍候...");
number.value++;
return true;
}
// 文件个数超出
function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}
// 上传失败
function handleUploadError(err) {
proxy.$modal.msgError("上传文件失败");
modal.msgError("上传文件失败");
}
// 上传成功回调
@@ -138,9 +140,9 @@ function handleUploadSuccess(res, file) {
uploadedSuccessfully();
} else {
number.value--;
proxy.$modal.closeLoading();
proxy.$modal.msgError(res.msg);
proxy.$refs.fileUpload.handleRemove(file);
modal.closeLoading();
modal.msgError(res.msg);
fileUpload.value.handleRemove(file);
uploadedSuccessfully();
}
}
@@ -158,7 +160,7 @@ function uploadedSuccessfully() {
uploadList.value = [];
number.value = 0;
emit("update:modelValue", listToString(fileList.value));
proxy.$modal.closeLoading();
modal.closeLoading();
}
}
@@ -204,4 +206,4 @@ function listToString(list, separator) {
.ele-upload-list__item-content-action .el-link {
margin-right: 10px;
}
</style>
</style>

View File

@@ -47,6 +47,8 @@
<script setup>
import { getToken } from "@/utils/auth";
import { ref, computed, watch, getCurrentInstance } from 'vue';
import modal from '@/plugins/modal';
const props = defineProps({
modelValue: [String, Object, Array],
@@ -72,7 +74,6 @@ const props = defineProps({
},
});
const { proxy } = getCurrentInstance();
const emit = defineEmits();
const number = ref(0);
const uploadList = ref([]);
@@ -82,6 +83,7 @@ const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 上传的图片服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() });
const fileList = ref([]);
const imageUpload = ref(null);
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
@@ -124,7 +126,7 @@ function handleBeforeUpload(file) {
isImg = file.type.indexOf("image") > -1;
}
if (!isImg) {
proxy.$modal.msgError(
modal.msgError(
`文件格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`
);
return false;
@@ -132,17 +134,17 @@ function handleBeforeUpload(file) {
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
proxy.$modal.loading("正在上传图片,请稍候...");
modal.loading("正在上传图片,请稍候...");
number.value++;
}
// 文件个数超出
function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}
// 上传成功回调
@@ -152,9 +154,9 @@ function handleUploadSuccess(res, file) {
uploadedSuccessfully();
} else {
number.value--;
proxy.$modal.closeLoading();
proxy.$modal.msgError(res.msg);
proxy.$refs.imageUpload.handleRemove(file);
modal.closeLoading();
modal.msgError(res.msg);
imageUpload.value.handleRemove(file);
uploadedSuccessfully();
}
}
@@ -176,14 +178,14 @@ function uploadedSuccessfully() {
uploadList.value = [];
number.value = 0;
emit("update:modelValue", listToString(fileList.value));
proxy.$modal.closeLoading();
modal.closeLoading();
}
}
// 上传失败
function handleUploadError() {
proxy.$modal.msgError("上传图片失败");
proxy.$modal.closeLoading();
modal.msgError("上传图片失败");
modal.closeLoading();
}
// 预览

View File

@@ -29,8 +29,11 @@
</template>
<script setup>
import { ref, computed, watch, onMounted, nextTick } from 'vue';
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();
const selectTree = ref(null);
const treeSelect = ref(null);
const props = defineProps({
/* 配置项 */
@@ -83,10 +86,10 @@ function initHandle() {
nextTick(() => {
const selectedValue = valueId.value;
if(selectedValue !== null && typeof (selectedValue) !== 'undefined') {
const node = proxy.$refs.selectTree.getNode(selectedValue)
const node = selectTree.value.getNode(selectedValue)
if (node) {
valueTitle.value = node.data[props.objMap.label]
proxy.$refs.selectTree.setCurrentKey(selectedValue) // 设置默认选中
selectTree.value.setCurrentKey(selectedValue) // 设置默认选中
defaultExpandedKey.value = [selectedValue] // 设置默认展开
}
} else {
@@ -98,11 +101,11 @@ function handleNodeClick(node) {
valueTitle.value = node[props.objMap.label]
valueId.value = node[props.objMap.value];
defaultExpandedKey.value = [];
proxy.$refs.treeSelect.blur()
treeSelect.value.blur()
selectFilterData('')
}
function selectFilterData(val) {
proxy.$refs.selectTree.filter(val)
selectTree.value.filter(val)
}
function filterNode(value, data) {
if (!value) return true