123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- <script setup>
- import {
- Edit,
- Delete
- } from '@element-plus/icons-vue'
- // 文章编辑器组件
- import { QuillEditor, Quill } from "@vueup/vue-quill";
- import "@vueup/vue-quill/dist/vue-quill.snow.css";
- import { ImageDrop } from 'quill-image-drop-module';
- import imageResize from 'quill-image-resize-module';
- Quill.register('modules/ImageDrop', ImageDrop);
- Quill.register('modules/imageResize', imageResize);
- import { ref } from 'vue'
- //文章标签数据模型
- const categorys = ref([])
- //用户搜索时选中的分类id
- const categoryId=ref('')
- //用户搜索时选中的发布状态
- const state=ref('')
- //文章列表数据模型
- const articles = ref([])
- const productList = ref([
- { id: '1', label: '工时管家' },
- { id: '2', label: '随访管家' },
- { id: '3', label: '项目管家' },
- { id: '4', label: '客户管家' },
- { id: '5', label: '生产车间管家' },
- ])
- //分页条数据模型
- const pageNum = ref(1)//当前页
- const total = ref(20)//总条数
- const pageSize = ref(10)//每页条数
- //当每页条数发生了变化,调用此函数
- const onSizeChange = (size) => {
- pageSize.value = size
- articleList();
- }
- //当前页码发生变化,调用此函数
- const onCurrentChange = (num) => {
- pageNum.value = num
- articleList();
- }
- //回显文章标签
- import {articleCategoryListService, articleListService,articleAddService, articleDeleteService} from '@/api/article.js'
- const articleCategoryList=async()=>{
- let result = await articleCategoryListService();
- categorys.value=result.data;
- }
- articleCategoryList();
- //获取文章列表数据
- const articleList=async()=>{
- let params={
- pageNum:pageNum.value,
- pageSize:pageSize.value,
- categoryId:categoryId.value?categoryId.value:null,
- state:state.value?state.value:null
- }
- let result =await articleListService(params);
- //渲染视图
- total.value=result.data.total;
- articles.value=result.data.items;
- //处理数据,给数据模型扩展一个属性 categoryName ,分类名称
- for(let i=0;i<articles.value.length;i++){
- let article=articles.value[i];
- for(let j=0;j<categorys.value.length;j++){
- if(article.categoryId==categorys.value[j].id){
- article.categoryName=categorys.value[j].categoryName
- }
- }
- }
- }
- articleList()
- // 添加文章功能
- import {Plus} from '@element-plus/icons-vue'
- //控制抽屉是否显示
- const visibleDrawer = ref(false)
- //添加表单数据模型
- const articleModel = ref({
- title: '',
- categoryId: '',
- coverImg: '',
- content:'',
- state:'',
- profile: '',
- productId: ''
- })
- const fileList = ref([])
- const options = ref({
- theme: "snow",
- bounds: document.body,
- debug: "warn",
- modules: {
- // 工具栏配置
- toolbar: [
- ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
- ["blockquote", "code-block"], // 引用 代码块
- [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
- [{ indent: "-1" }, { indent: "+1" }], // 缩进
- [{ size: ["small", false, "large", "huge"] }], // 字体大小
- [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
- [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
- [{ align: [] }], // 对齐方式
- ["clean"], // 清除文本格式
- ["link", "image"] // 链接、图片、视频
- ],
- // ImageDrop: true, // PS:因为QuillEditor自带可拖拽此配置可以不开,开启拖动会复制图片
- // todo 富文本导入图片是否需要缩放拖拽
- imageResize: {
- displayStyles: {
- backgroundColor: 'black',
- border: 'none',
- color: 'white',
- },
- modules: ['Resize', 'DisplaySize', 'Toolbar'],
- },
- },
- placeholder: "请输入内容",
- // readOnly: props.readOnly
- });
- //导入token,方便编辑完文章后保存至服务器
- import {userTokenStore} from '@/stores/token.js'
- const tokenStore=userTokenStore();
- //上传成功的回调函数
- const uploadSuccess=(result)=>{
- //将服务器响应的图片地址赋值给 articleModel 的 coverImg
- articleModel.value.coverImg=result.data;
- console.log(result.data)
- }
- const fileExceedsLimit = () => {
- ElMessage.warning('只能上传一个文章封面')
- }
- // 添加文章
- import {ElMessage, ElMessageBox} from 'element-plus'
- const addArticle=async(clickState)=>{
- // 把发布文章yes或草稿no赋值给数据模型
- articleModel.value.state=clickState;
- const { content, title, profile, categoryId, productId } = articleModel.value
- let str = ''
- if(!content) {
- str += '文章内容不能为空,'
- }
- if(!title) {
- str += '文章标题不能为空,'
- }
- if(!profile) {
- str += '文章封面不能为空,'
- }
- if(!categoryId || categoryId.length <= 0) {
- str += '文章标签不能为空,'
- }
- if(fileList.value.length <= 0) {
- str += '文章封面不能为空,'
- }
- if(!productId || productId == '0') {
- str += '文章所属产品不能为空,'
- }
- if(str) {
- ElMessage.warning(str)
- return
- }
- const formVla = { ...articleModel.value, categoryIds: (articleModel.value.categoryId || []).join(',') }
- delete formVla.categoryId
- const formData = new FormData()
- for (const key in formVla) {
- if(key == 'coverImg') {
- let file = fileList.value[0].raw
- if(!isFile(file)) {
- file = base64ToFile(file, 'image.png')
- }
- formData.append('coverImage', file)
- } else {
- formData.append(key, formVla[key])
- }
- }
- //调用接口
- // let result=await articleAddService(articleModel.value)
- let result=await articleAddService(formData)
- ElMessage.success(result.msg?result.msg:"添加文章成功!")
- //让添加文章的抽屉消失
- visibleDrawer.value = false;
- //刷新当前列表
- articleList()
- }
- const isFile = (obj) => {
- return obj instanceof File;
- }
- const base64ToFile = (base64String, fileName) => {
- const arr = base64String.split(',');
- const mime = arr[0].match(/:(.*?);/)[1];
- const bstr = atob(arr[1]);
- let n = bstr.length;
- const u8arr = new Uint8Array(n);
- while (n--) {
- u8arr[n] = bstr.charCodeAt(n);
- }
- return new File([u8arr], fileName, { type: mime });
- }
- // 修改文章
- const editArticle = (row) => {
- const { categoryIds, content, title, profile, id, coverImg, productId } = row
- articleModel.value = {
- categoryId: categoryIds ? JSON.parse(categoryIds) : [],
- content,
- title,
- profile,
- id,
- coverImg: '',
- productId: productId
- }
- if(coverImg) {
- fileList.value = [{
- name: '图片',
- url: `data:image/jpeg;base64, ${coverImg}`,
- raw: `data:image/jpeg;base64, ${coverImg}`,
- }]
- } else {
- fileList.value = []
- }
-
- visibleDrawer.value = true
- }
- const deleteArticle = async (row) => {
- const { id, title } = row
- ElMessageBox.confirm(
- `确定删除【${title}】文章吗?`,
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }
- ).then(async () => {
- let result=await articleDeleteService({ id })
- ElMessage.success(result.message?result.message:"删除文章成功!")
- articleList()
- })
- }
- </script>
- <template>
- <el-card class="page-container">
- <template #header>
- <div class="header">
- <span>文章管理</span>
- <div class="extra">
- <el-button type="primary" @click="visibleDrawer=true">添加文章</el-button>
- </div>
- </div>
- </template>
- <!-- 搜索表单 -->
- <el-form inline>
- <el-form-item label="文章标签:">
- <el-select placeholder="请选择" v-model="categoryId">
- <el-option
- v-for="c in categorys"
- :key="c.id"
- :label="c.categoryName"
- :value="c.id">
- </el-option>
- </el-select>
- </el-form-item>
- <el-form-item label="发布状态:">
- <el-select placeholder="请选择" v-model="state">
- <el-option label="已发布" value="yes"></el-option>
- <el-option label="草稿" value="no"></el-option>
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="articleList">搜索</el-button>
- <el-button @click="categoryId='';state=''">重置</el-button>
- </el-form-item>
- </el-form>
- <!-- 文章列表 -->
- <el-table :data="articles" style="width: 100%">
- <el-table-column label="文章标题" width="400" prop="title"></el-table-column>
- <el-table-column label="分类" prop="categoryNames"></el-table-column>
- <el-table-column label="发表时间" prop="createTime"> </el-table-column>
- <el-table-column label="状态" prop="state"></el-table-column>
- <el-table-column label="操作" width="100">
- <template #default="{ row }">
- <el-button :icon="Edit" circle plain type="primary" @click="editArticle(row)"></el-button>
- <el-button :icon="Delete" circle plain type="danger" @click="deleteArticle(row)"></el-button>
- </template>
- </el-table-column>
- <template #empty>
- <el-empty description="没有数据" />
- </template>
- </el-table>
- <!-- 分页条 -->
- <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :page-sizes="[3, 5 ,10, 15]"
- layout="jumper, total, sizes, prev, pager, next" background :total="total" @size-change="onSizeChange"
- @current-change="onCurrentChange" style="margin-top: 20px; justify-content: flex-end" />
- <!-- 抽屉 -->
- <el-drawer v-model="visibleDrawer" :title="`${articleModel.id ? '编辑文章' : '添加文章'}`" direction="rtl" size="50%">
- <!-- 添加文章表单 -->
- <el-form :model="articleModel" label-width="100px" >
- <el-form-item label="文章标题" >
- <el-input v-model="articleModel.title" placeholder="请输入标题"></el-input>
- </el-form-item>
- <el-form-item label="文章简介" >
- <el-input type="textarea"
- :autosize="{ minRows: 2, maxRows: 4}" v-model="articleModel.profile" placeholder="请输入简介"></el-input>
- </el-form-item>
- <el-form-item label="文章标签">
- <el-select placeholder="请选择" multiple v-model="articleModel.categoryId" style="width: 100%;">
- <el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id">
- </el-option>
- </el-select>
- </el-form-item>
- <el-form-item label="所属产品">
- <el-select placeholder="请选择" v-model="articleModel.productId" style="width: 100%;">
- <el-option v-for="c in productList" :key="c.id" :label="c.label" :value="c.id">
- </el-option>
- </el-select>
- </el-form-item>
- <el-form-item label="文章封面">
- <!--element-plus 的文件上传组件
- auto-upload:是否自动上传
- action: 服务器接口路径
- name: 上传的文件字段名,也就是参数名称
- headers: 设置上传的请求头
- on-success: 上传成功的回调函数
- -->
- <el-upload
- v-model:file-list="fileList"
- list-type="picture-card"
- :auto-upload="false"
- :limit="1"
- :on-exceed="fileExceedsLimit"
- >
- <el-icon><Plus /></el-icon>
- </el-upload>
- </el-form-item>
- <el-form-item label="文章内容">
- <div class="editor">
- <!--富文本编辑器组件-->
- <quill-editor
- theme="snow"
- v-model:content="articleModel.content"
- contentType="html"
- :options="options"
- >
- </quill-editor>
- </div>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="addArticle('已发布')">发布</el-button>
- <el-button type="info" @click="addArticle('草稿')">草稿</el-button>
- </el-form-item>
- </el-form>
- </el-drawer>
- </el-card>
- </template>
- <style lang="scss" scoped>
- .page-container {
- min-height: 100%;
- box-sizing: border-box;
- .header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- }
- /* 抽屉样式 */
- .avatar-uploader {
- :deep() {
- .avatar {
- width: 178px;
- height: 178px;
- display: block;
- }
- .el-upload {
- border: 1px dashed var(--el-border-color);
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- transition: var(--el-transition-duration-fast);
- }
- .el-upload:hover {
- border-color: var(--el-color-primary);
- }
- .el-icon.avatar-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- width: 178px;
- height: 178px;
- text-align: center;
- }
- }
- }
- .editor {
- width: 100%;
- :deep(.ql-editor) {
- min-height: 200px;
- }
- }
- .avatar-uploader .el-upload {
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- }
- .avatar-uploader .el-upload:hover {
- border-color: #409EFF;
- }
- .avatar-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- width: 178px;
- height: 178px;
- line-height: 178px;
- text-align: center;
- }
- .avatar {
- width: 178px;
- height: 178px;
- display: block;
- }
- </style>
|