470 lines
14 KiB
Vue
470 lines
14 KiB
Vue
<!-- 用户编辑弹窗 -->
|
|
<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"
|
|
>
|
|
<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>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { ref, reactive, watch, computed } from "vue";
|
|
import { Form, message, Modal } from "ant-design-vue";
|
|
import { useUserStore } from "@/store/modules/user";
|
|
import type { Article } from "@/api/cms/article/model";
|
|
import { addArticle, updateArticle } from "@/api/cms/article";
|
|
import { FILE_SERVER, FILE_THUMBNAIL } from "@/config/setting";
|
|
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
|
|
import { uploadFile } from "@/api/system/file";
|
|
import TinymceEditor from "@/components/TinymceEditor/index.vue";
|
|
import { FormInstance, Rule, RuleObject } from "ant-design-vue/es/form";
|
|
import CategorySelect from "./category-select.vue";
|
|
import { ArticleCategory } from "@/api/cms/category/model";
|
|
import SourceSelect from "../dictionary/source-select.vue";
|
|
import { Goods } from "@/api/goods/model";
|
|
import useFormData from "@/utils/use-form-data";
|
|
|
|
const useForm = Form.useForm;
|
|
const props = defineProps<{
|
|
// 弹窗是否打开
|
|
visible: boolean;
|
|
categoryId?: number;
|
|
// 修改回显的数据
|
|
data?: Article | null;
|
|
categoryList?: ArticleCategory[];
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
(e: "done"): void;
|
|
(e: "update:visible", visible: boolean): void;
|
|
}>();
|
|
|
|
// 是否是修改
|
|
const isUpdate = ref(false);
|
|
const disabled = ref(false);
|
|
// 是否显示最大化切换按钮
|
|
const maxAble = ref(true);
|
|
// 选项卡位置
|
|
const activeKey = ref("1");
|
|
// 已上传数据
|
|
const images = ref<ItemType[]>([]);
|
|
|
|
// 当前用户信息
|
|
const userStore = useUserStore();
|
|
const loginUser = computed(() => userStore.info ?? {});
|
|
// 编辑器内容,双向绑定
|
|
const content = ref<any>("");
|
|
const uploadImgContent = ref<string>("");
|
|
const formRef = ref<FormInstance | null>(null);
|
|
|
|
// 已上传数据, 可赋初始值用于回显
|
|
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: ""
|
|
});
|
|
|
|
/* 更新visible */
|
|
const updateVisible = (value: boolean) => {
|
|
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("请输入文字内容");
|
|
}
|
|
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 onUpload = (item) => {
|
|
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) => {
|
|
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) => {
|
|
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="${result.url}">\n\r`;
|
|
content.value = content.value.replace(uploadImgContent.value, addPath);
|
|
})
|
|
.catch((e) => {
|
|
message.error(e.message);
|
|
});
|
|
hasFile = true;
|
|
}
|
|
}
|
|
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");
|
|
})
|
|
.catch((e) => {
|
|
loading.value = false;
|
|
message.error(e.message);
|
|
});
|
|
})
|
|
.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_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();
|
|
}
|
|
}
|
|
);
|
|
</script>
|
|
<style lang="less">
|
|
.tab-pane {
|
|
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);
|
|
|
|
a {
|
|
margin: 0 3px;
|
|
}
|
|
|
|
.extra,
|
|
small {
|
|
color: rgba(0, 0, 0, 0.45);
|
|
font-size: 12.5px !important;
|
|
}
|
|
|
|
.extra {
|
|
margin-bottom: 4px !important;
|
|
}
|
|
}
|
|
</style>
|