This commit is contained in:
2025-05-22 10:27:07 +08:00
parent 963b239333
commit 95d43746f6
606 changed files with 19207 additions and 102770 deletions
+367 -365
View File
@@ -1,84 +1,84 @@
<!-- 用户编辑弹窗 -->
<template>
<ele-modal
width="980px"
:visible="visible"
:confirm-loading="loading"
:maxable="maxAble"
:title="isUpdate ? '编辑文章' : '添加文章'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
:maskClosable="false"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="{ md: { span: 8 }, sm: { span: 24 } }"
:wrapper-col="{ md: { span: 24 }, sm: { span: 24 } }"
layout="vertical"
<ele-modal
width="980px"
:visible="visible"
:confirm-loading="loading"
:maxable="maxAble"
:title="isUpdate ? '编辑文章' : '添加文章'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
:maskClosable="false"
@ok="save"
>
<a-form-item label="文章分类" name="categoryId">
<category-select
:data="categoryList"
placeholder="请选择文章分类"
v-model:value="form.categoryId"
/>
</a-form-item>
<a-form-item label="文章标题" name="title">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入文章标题"
v-model:value="form.title"
/>
</a-form-item>
<a-form-item label="封面图" name="images">
<ele-image-upload
v-model:value="images"
:limit="1"
:drag="true"
:upload-handler="uploadHandler"
@upload="onUpload"
/>
</a-form-item>
<a-form-item label="文章内容" name="content">
<!-- 编辑器 -->
<div class="content">
<tinymce-editor
v-model:value="content"
:disabled="disabled"
:init="config"
placeholder="图片直接粘贴自动上传"
@change="onChange"
@paste="onPaste"
/>
</div>
</a-form-item>
<a-form-item label="文章来源" name="source">
<source-select
v-model:value="form.source"
/>
</a-form-item>
<a-form-item label="展现方式" name="showType">
<a-radio-group v-model:value="form.showType">
<a-radio :value="10">小图模式(300 * 188)</a-radio>
<a-radio :value="20">大图模式(750 * 455)</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" name="sortNumber">
<a-input-number
:min="0"
:max="99999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
/>
</a-form-item>
</a-form>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="{ md: { span: 8 }, sm: { span: 24 } }"
:wrapper-col="{ md: { span: 24 }, sm: { span: 24 } }"
layout="vertical"
>
<a-form-item label="文章分类" name="categoryId">
<category-select
:data="categoryList"
placeholder="请选择文章分类"
v-model:value="form.categoryId"
/>
</a-form-item>
<a-form-item label="文章标题" name="title">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入文章标题"
v-model:value="form.title"
/>
</a-form-item>
<a-form-item label="封面图" name="images">
<ele-image-upload
v-model:value="images"
:limit="1"
:drag="true"
:upload-handler="uploadHandler"
@upload="onUpload"
/>
</a-form-item>
<a-form-item label="文章内容" name="content">
<!-- 编辑器 -->
<div class="content">
<tinymce-editor
v-model:value="content"
:disabled="disabled"
:init="config"
placeholder="图片直接粘贴自动上传"
@change="onChange"
@paste="onPaste"
/>
</div>
</a-form-item>
<a-form-item label="文章来源" name="source">
<source-select
v-model:value="form.source"
/>
</a-form-item>
<a-form-item label="展现方式" name="showType">
<a-radio-group v-model:value="form.showType">
<a-radio :value="10">小图模式(300 * 188)</a-radio>
<a-radio :value="20">大图模式(750 * 455)</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" name="sortNumber">
<a-input-number
:min="0"
:max="99999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
/>
</a-form-item>
</a-form>
</ele-modal>
</ele-modal>
</template>
<script lang="ts" setup>
@@ -100,17 +100,17 @@ import useFormData from "@/utils/use-form-data";
const useForm = Form.useForm;
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
categoryId?: number;
// 修改回显的数据
data?: Article | null;
categoryList?: ArticleCategory[];
// 弹窗是否打开
visible: boolean;
categoryId?: number;
// 修改回显的数据
data?: Article | null;
categoryList?: ArticleCategory[];
}>();
const emit = defineEmits<{
(e: "done"): void;
(e: "update:visible", visible: boolean): void;
(e: "done"): void;
(e: "update:visible", visible: boolean): void;
}>();
// 是否是修改
@@ -127,8 +127,8 @@ const images = ref<ItemType[]>([]);
const userStore = useUserStore();
const loginUser = computed(() => userStore.info ?? {});
// 编辑器内容,双向绑定
const content = ref<any>('');
const uploadImgContent = ref<string>('');
const content = ref<any>("");
const uploadImgContent = ref<string>("");
const formRef = ref<FormInstance | null>(null);
// 已上传数据, 可赋初始值用于回显
@@ -137,331 +137,333 @@ const avatar = ref(<any>[]);
const loading = ref(false);
// 表单数据
const { form, resetFields, assignFields } = useFormData<Article>({
articleId: undefined,
// 封面图
image: "",
// 文章标题
title: "",
// 展现方式
showType: 10,
// 文章来源
source: undefined,
// 文章类型
categoryId: undefined,
// 文章内容
content: "",
// 虚拟阅读量
virtualViews: "",
// 实际阅读量
actualViews: "",
// 用户ID
userId: "",
// 所属门店ID
shopId: "",
// 排序
sortNumber: 100,
// 备注
comments: "",
// 状态
status: 0,
// 创建时间
createTime: "",
// 更新时间
updateTime: ""
articleId: undefined,
// 封面图
image: "",
// 文章标题
title: "",
// 展现方式
showType: 10,
// 文章来源
source: undefined,
// 文章类型
categoryId: undefined,
// 文章内容
content: "",
// 虚拟阅读量
virtualViews: "",
// 实际阅读量
actualViews: "",
// 用户ID
userId: "",
// 所属门店ID
shopId: "",
// 排序
sortNumber: 100,
// 备注
comments: "",
// 状态
status: 0,
// 创建时间
createTime: "",
// 更新时间
updateTime: ""
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit("update:visible", value);
emit("update:visible", value);
};
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
title: [
{
required: true,
type: "string",
message: "请输入文章标题",
trigger: "blur"
}
],
content: [
{
required: true,
type: "string",
message: "请输入文章内容",
trigger: "blur",
validator: async (_rule: RuleObject, value: string) => {
if (content.value == "") {
return Promise.reject("请输入文字内容");
title: [
{
required: true,
type: "string",
message: "请输入文章标题",
trigger: "blur"
}
return Promise.resolve();
}
}
],
categoryId: [
{
required: true,
type: "number",
message: "请选择文章分类",
trigger: "blur"
}
],
// images: [
// {
// required: true,
// type: "string",
// message: "请上传文章封面图",
// trigger: "blur",
// validator: async (_rule: RuleObject, value: string) => {
// if (images.value.length == 0) {
// return Promise.reject('请上传封面图');
// }
// return Promise.resolve();
// }
// }
// ],
sortNumber: [
{
required: true,
type: "number",
message: "请输入排序",
trigger: "blur"
}
],
source: [
{
required: false,
type: "string",
message: "请输入文章来源",
trigger: "blur"
}
]
],
content: [
{
required: true,
type: "string",
message: "请输入文章内容",
trigger: "blur",
validator: async (_rule: RuleObject, value: string) => {
if (content.value == "") {
return Promise.reject("请输入文字内容");
}
return Promise.resolve();
}
}
],
categoryId: [
{
required: true,
type: "number",
message: "请选择文章分类",
trigger: "blur"
}
],
// images: [
// {
// required: true,
// type: "string",
// message: "请上传文章封面图",
// trigger: "blur",
// validator: async (_rule: RuleObject, value: string) => {
// if (images.value.length == 0) {
// return Promise.reject('请上传封面图');
// }
// return Promise.resolve();
// }
// }
// ],
sortNumber: [
{
required: true,
type: "number",
message: "请输入排序",
trigger: "blur"
}
],
source: [
{
required: false,
type: "string",
message: "请输入文章来源",
trigger: "blur"
}
]
});
/* 上传事件 */
const uploadHandler = (file: File) => {
const item: ItemType = {
file,
uid: (file as any).uid,
name: file.name
};
if (!file.type.startsWith('image')) {
message.error('只能选择图片');
return;
}
if (file.size / 1024 / 1024 > 2) {
message.error('大小不能超过 2MB');
return;
}
onUpload(item);
const item: ItemType = {
file,
uid: (file as any).uid,
name: file.name
};
if (!file.type.startsWith("image")) {
message.error("只能选择图片");
return;
}
if (file.size / 1024 / 1024 > 2) {
message.error("大小不能超过 2MB");
return;
}
onUpload(item);
};
// 上传文件
const onUpload = (item) => {
const { file } = item;
uploadFile(file)
.then((data) => {
images.value.push({
uid: data.id,
url: FILE_THUMBNAIL + data.path,
status: 'done'
});
form.image = data.path;
message.success('上传成功');
})
.catch((e) => {
message.error(e.message);
});
const { file } = item;
uploadFile(file)
.then((data) => {
images.value.push({
uid: data.id,
url: data.url,
status: "done"
});
form.image = data.path;
message.success("上传成功");
})
.catch((e) => {
message.error(e.message);
});
};
const editorRef = ref<InstanceType<typeof TinymceEditor> | null>(null);
const config = ref({
height: 500,
images_upload_handler: (blobInfo, success, error) => {
const file = blobInfo.blob();
// 使用 axios 上传,实际开发这段建议写在 api 中再调用 api
const formData = new FormData();
formData.append('file', file, file.name);
uploadFile(<File>file)
.then((result) => {
if (result.length) {
console.log(file.size / 1024);
if (file.size / 1024 / 1024 > 2) {
error('图片大小不能超过 2MB');
}
success(FILE_SERVER + result.path);
} else {
error('上传失败');
}
})
.catch((e) => {
message.error(e.message);
});
},
// 自定义文件上传(这里使用把选择的文件转成 blob 演示)
// file_picker_callback: (callback: any, _value: any, meta: any) => {
// const input = document.createElement("input");
// input.setAttribute("type", "file");
// // 设定文件可选类型
// if (meta.filetype === "image") {
// input.setAttribute("accept", "image/*");
// } else if (meta.filetype === "media") {
// input.setAttribute("accept", "video/*");
// }
// input.onchange = () => {
// const file = input.files?.[0];
// console.log(file);
// if (!file) {
// return;
// }
// if (meta.filetype === "media") {
// if (!file.type.startsWith("video/")) {
// editorRef.value?.alert({ content: "只能选择视频文件" });
// return;
// }
// }
// if (file.size / 1024 / 1024 > 20) {
// editorRef.value?.alert({ content: "大小不能超过 20MB" });
// return;
// }
// const reader = new FileReader();
// reader.onload = (e) => {
// if (e.target?.result != null) {
// const blob = new Blob([e.target.result], { type: file.type });
// callback(URL.createObjectURL(blob));
// }
// };
// reader.readAsArrayBuffer(file);
// };
// input.click();
// }
height: 500,
images_upload_handler: (blobInfo, success, error) => {
const file = blobInfo.blob();
// 使用 axios 上传,实际开发这段建议写在 api 中再调用 api
const formData = new FormData();
formData.append("file", file, file.name);
uploadFile(<File>file)
.then((result) => {
console.log(result);
if (result.length) {
console.log(file.size / 1024);
if (file.size / 1024 / 1024 > 2) {
error("图片大小不能超过 2MB");
}
success(result.url);
} else {
error("上传失败");
}
})
.catch((e) => {
message.error(e.message);
});
}
// 自定义文件上传(这里使用把选择的文件转成 blob 演示)
// file_picker_callback: (callback: any, _value: any, meta: any) => {
// const input = document.createElement("input");
// input.setAttribute("type", "file");
// // 设定文件可选类型
// if (meta.filetype === "image") {
// input.setAttribute("accept", "image/*");
// } else if (meta.filetype === "media") {
// input.setAttribute("accept", "video/*");
// }
// input.onchange = () => {
// const file = input.files?.[0];
// console.log(file);
// if (!file) {
// return;
// }
// if (meta.filetype === "media") {
// if (!file.type.startsWith("video/")) {
// editorRef.value?.alert({ content: "只能选择视频文件" });
// return;
// }
// }
// if (file.size / 1024 / 1024 > 20) {
// editorRef.value?.alert({ content: "大小不能超过 20MB" });
// return;
// }
// const reader = new FileReader();
// reader.onload = (e) => {
// if (e.target?.result != null) {
// const blob = new Blob([e.target.result], { type: file.type });
// callback(URL.createObjectURL(blob));
// }
// };
// reader.readAsArrayBuffer(file);
// };
// input.click();
// }
});
const onChange = (e) => {
uploadImgContent.value = e.originalEvent.value.content;
}
console.log(e)
// uploadImgContent.value = e.originalEvent.value.content;
};
/* 粘贴图片上传服务器并插入编辑器 */
const onPaste = (e) => {
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
console.log(items.length);
let hasFile = false;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
let file = items[i].getAsFile();
const item: ItemType = {
file,
uid: (file as any).lastModified,
name: file.name
};
console.log(item.file);
console.log(uploadImgContent.value);
uploadFile(<File>item.file)
.then((result) => {
console.log(content.value);
console.log(file);
const addPath = `<img class="content-img" src="${FILE_SERVER + result.path}">\n\r`;
content.value = content.value.replace(uploadImgContent.value,addPath)
})
.catch((e) => {
message.error(e.message);
});
hasFile = true;
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
console.log(items.length);
let hasFile = false;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") !== -1) {
let file = items[i].getAsFile();
const item: ItemType = {
file,
uid: (file as any).lastModified,
name: file.name
};
console.log(item.file);
console.log(uploadImgContent.value);
uploadFile(<File>item.file)
.then((result) => {
console.log(content.value);
console.log(file);
const addPath = `<img class="content-img" src="${result.url}">\n\r`;
content.value = content.value.replace(uploadImgContent.value, addPath);
})
.catch((e) => {
message.error(e.message);
});
hasFile = true;
}
}
}
if (hasFile) {
e.preventDefault();
}
}
if (hasFile) {
e.preventDefault();
}
};
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form,
content: content.value
};
const saveOrUpdate = isUpdate.value ? updateArticle : addArticle;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit("done");
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form,
content: content.value
};
const saveOrUpdate = isUpdate.value ? updateArticle : addArticle;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit("done");
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch((e) => {
loading.value = false;
message.error(e.message);
.catch(() => {
});
})
.catch(() => {
});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
form.categoryId = props.categoryId;
if (props.data) {
loading.value = false;
assignFields({
...props.data
});
images.value = [];
if(props.data.image){
images.value.push({
uid: 1,
url: FILE_SERVER + props.data.image,
status: 'done'
});
() => props.visible,
(visible) => {
if (visible) {
form.categoryId = props.categoryId;
if (props.data) {
loading.value = false;
assignFields({
...props.data
});
images.value = [];
if (props.data.image) {
images.value.push({
uid: 1,
url: FILE_THUMBNAIL + props.data.image,
status: "done"
});
}
content.value = props.data.content;
isUpdate.value = true;
} else {
isUpdate.value = false;
content.value = "";
}
} else {
resetFields();
formRef.value?.clearValidate();
}
content.value = props.data.content;
isUpdate.value = true;
} else {
isUpdate.value = false;
content.value = '';
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>
<style lang="less">
.tab-pane {
min-height: 300px;
min-height: 300px;
}
.form-item-help {
font-size: 12px;
// line-height: 2;
line-height: 1.5;
padding-top: 4px;
min-height: 22px;
// margin-top: -2px;
transition: color 0.3s cubic-bezier(0.215, 0.61, 0.355, 1);
font-size: 12px;
// line-height: 2;
line-height: 1.5;
padding-top: 4px;
min-height: 22px;
// margin-top: -2px;
transition: color 0.3s cubic-bezier(0.215, 0.61, 0.355, 1);
a {
margin: 0 3px;
}
a {
margin: 0 3px;
}
.extra,
small {
color: rgba(0, 0, 0, 0.45);
font-size: 12.5px !important;
}
.extra,
small {
color: rgba(0, 0, 0, 0.45);
font-size: 12.5px !important;
}
.extra {
margin-bottom: 4px !important;
}
.extra {
margin-bottom: 4px !important;
}
}
</style>