You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

734 lines
22 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="标题" prop="title">
<el-input
v-model="queryParams.title"
placeholder="请输入标题"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择状态"
clearable
style="width: 100%"
>
<el-option label="不展示" value="0"></el-option>
<el-option label="展示" value="1"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:inherit:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['system:inherit:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:inherit:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:inherit:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="inheritList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="ID" align="center" prop="id" />
<!-- <el-table-column label="门店/校区id" align="center" prop="deptId" >-->
<!-- <template v-for="item in scope.row.deptId">-->
<!-- &lt;!&ndash; <span></span>&ndash;&gt;-->
<!-- <el-tag type="sucess" v-if="item!=''" style="float: left">{{ memberFormat(item) }}</el-tag>-->
<!-- &lt;!&ndash; <span>{{ item }}</span>&ndash;&gt;-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- 门店/校区名称(标签形式) -->
<el-table-column label="门店/校区名称" align="center">
<template #default="scope">
<!-- 调用方法获取名称用span或el-tag展示 -->
<el-tag type="info">{{ getDeptName(scope.row.deptId) }}</el-tag>
<!-- 若不需要样式直接用span<span>{{ getDeptName(scope.row.deptId) }}</span> -->
</template>
</el-table-column>
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="封面" align="center" prop="image" width="100">
<template slot-scope="scope">
<el-image
:src="getFullImgUrl(scope.row.image)"
:preview-src-list="[getFullImgUrl(scope.row.image)]"
style="width: 50px; height: 50px;"
fit="cover"
:lazy="false"
></el-image>
</template>
</el-table-column>
<el-table-column label="阅读量" align="center" prop="readNum" />
<el-table-column label="内容" align="center" prop="content" width="120">
<template slot-scope="scope">
<el-button
type="text"
@click="showContentDialog(scope.row.content)"
size="mini"
>查看内容</el-button>
</template>
</el-table-column>
<el-table-column label="是否展示" align="center" prop="status" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'">
{{ scope.row.status === 1 ? '展示' : '不展示' }}
</el-tag>
</template>
</el-table-column>
<!-- <el-table-column label="创建时间" align="center" prop="startTime" width="180">-->
<!-- <template slot-scope="scope">-->
<!-- <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="更新时间" align="center" prop="modifyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.modifyTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:inherit:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:inherit:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改瑜伽传承对话框 -->
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body @opened="onDialogOpened">
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<!-- 在“标题”表单项后添加 -->
<el-form-item label="门店" prop="deptId">
<el-select
v-model="form.deptId"
placeholder="请选择门店"
clearable
style="width: 100%"
>
<!-- 遍历deptList渲染选项deptId为值deptName为显示文本 -->
<el-option
v-for="dept in deptList"
:key="dept.deptId"
:label="dept.deptName"
:value="dept.deptId"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
<!-- 封面上传区域 -->
<el-form-item label="封面" prop="image">
<el-upload
class="image-uploader"
:action="uploadImgUrl"
:show-file-list="false"
:on-success="handleImageSuccess"
:before-upload="beforeImageUpload"
:headers="uploadHeaders"
:disabled="isUploading"
>
<el-image
v-if="form.image"
:src="getFullImgUrl(form.image)"
style="width: 150px; height: 100px;"
fit="cover"
:lazy="false"
>
<div slot="error" class="image-error">
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
<i v-else class="el-icon-plus image-uploader-icon"></i>
<el-loading
v-if="isUploading"
text="上传中..."
target=".image-uploader"
background="rgba(255,255,255,0.8)"
></el-loading>
</el-upload>
</el-form-item>
<!-- <el-form-item label="阅读量" prop="readNum">-->
<!-- <el-input v-model.number="form.readNum" placeholder="请输入阅读量" />-->
<!-- </el-form-item>-->
<el-form-item label="内容" prop="content">
<div class="editor-container">
<div ref="editor" style="width: 100%; min-height: 300px;"></div>
</div>
</el-form-item>
<el-form-item label="是否展示" prop="status">
<el-radio-group v-model="form.status">
<el-radio label="0">不展示</el-radio>
<el-radio label="1">展示</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
<!-- 内容查看对话框 -->
<el-dialog
title="内容详情"
:visible.sync="contentDialogVisible"
width="800px"
:before-close="handleDialogClose"
>
<div v-html="contentDialogContent" class="content-viewer"></div>
</el-dialog>
</div>
</template>
<script>
import {getInheritBase} from "@/api/columns/inherit";
import { listAppreciate, getAppreciate, delAppreciate, addAppreciate, updateAppreciate } from "@/api/columns/appreciate";
import WangEditor from 'wangeditor';
import { getToken } from '@/utils/auth';
import axios from 'axios';
import Pagination from '@/components/Pagination';
import RightToolbar from '@/components/RightToolbar';
export default {
name: "Practice",
components: {
Pagination,
RightToolbar
},
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 瑜伽传承表格数据
inheritList: [],
deptList:[],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 内容查看对话框
contentDialogVisible: false,
contentDialogContent: "",
// 上传相关配置
uploadHeaders: {
Authorization: getToken()
},
uploadImgUrl: process.env.VUE_APP_BASE_API + "/com/file/upload",
isUploading: false,
baseUrl: process.env.VUE_APP_BASE_API || '',
// 富文本编辑器实例
editor: null,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
title: null,
image: null,
readNum: null,
startTime: null,
content: null,
status: null,
modifyTime: null,
deptId: null,
},
// 表单参数
form: {
status: "0" // 默认不展示
},
// 表单校验
rules: {
deptId: [
{ required: true, message: "请选择门店", trigger: "change" }
],
title: [
{ required: true, message: "请输入标题", trigger: "blur" }
],
image: [
{ required: true, message: "请上传封面图片", trigger: "change" }
],
status: [
{ required: true, message: "请选择状态", trigger: "change" }
]
}
};
},
created() {
this.getList();
this.getBase();
},
methods: {
/** 对话框打开后回调 */
onDialogOpened() {
if (!this.editor) {
this.initEditor();
}
},
getDeptName(deptId) {
if (!deptId) return '未设置';
// 从deptList中查找匹配的部门
// 、、 const dept = this.deptList.find(item => item.deptId === deptId);
// return dept ? dept.deptName : '未知部门';
for(let e of this.deptList){
if(deptId==e.deptId){
return deptId ? e.deptName : '未知部门';
}
}
},
/** 初始化富文本编辑器 */
initEditor() {
if (this.$refs.editor) {
this.editor = new WangEditor(this.$refs.editor);
// 关闭默认网络图片上传
this.editor.config.showLinkImg = false;
// 自定义图片上传逻辑
this.editor.config.customUploadImg = async (resultFiles, insertImgFn) => {
this.isUploading = true;
try {
for (const file of resultFiles) {
const formData = new FormData();
formData.append('file', file);
const response = await axios({
url: this.uploadImgUrl,
method: 'post',
data: formData,
headers: {
...this.uploadHeaders,
'Content-Type': 'multipart/form-data'
},
timeout: 30000
});
if (response.data && response.data.code === 200 && response.data.data) {
// 存储相对路径,显示时拼接基础路径
const relativeUrl = response.data.data;
const fullUrl = this.getFullImgUrl(relativeUrl);
// 插入图片并设置正确路径
insertImgFn(fullUrl, file.name || '图片', 'max-width:100%;height:auto;');
// 强制同步内容到表单
this.form.content = this.editor.txt.html();
this.$message.success('图片上传成功');
} else {
this.$message.error('图片上传失败: ' + (response.data?.msg || '服务器返回异常'));
}
}
} catch (error) {
console.error('富文本图片上传错误:', error);
this.$message.error('上传失败,请检查网络或联系管理员');
} finally {
this.isUploading = false;
}
};
// 内容变化时同步到表单
this.editor.config.onchange = (html) => {
const processedHtml = html.replace(/<img/g, '<img style="max-width:100%;height:auto;"');
this.form.content = processedHtml;
};
// 配置菜单栏
this.editor.config.menus = [
'head', 'bold', 'fontSize', 'fontName', 'italic', 'underline',
'strikeThrough', 'foreColor', 'backColor', 'link', 'list',
'justify', 'quote', 'emoticon', 'image', 'table', 'undo', 'redo'
];
// 创建编辑器实例
this.editor.create();
} else {
setTimeout(() => this.initEditor(), 100);
}
},
getBase(){
getInheritBase().then(res=>{
console.log(res)
this.deptList=res.data
})
},
/** 查询瑜伽传承列表 */
getList() {
this.loading = true;
listAppreciate(this.queryParams).then(response => {
this.inheritList = response.rows;
this.total = response.total;
this.loading = false;
}).catch(() => {
this.loading = false;
this.$message.error('获取列表数据失败');
});
},
/** 显示内容对话框 */
showContentDialog(content) {
let processedContent = content || '';
processedContent = processedContent.replace(
/<img/g,
'<img style="max-width:100%;height:auto;margin:10px auto;display:block;"'
);
this.contentDialogContent = processedContent;
this.contentDialogVisible = true;
},
/** 关闭内容对话框 */
handleDialogClose() {
this.contentDialogContent = "";
this.contentDialogVisible = false;
},
/** 处理封面上传成功 */
handleImageSuccess(response) {
this.isUploading = false;
if (response && response.code === 200 && response.data) {
this.form.image = response.data;
this.$nextTick(() => {
this.$message.success('封面上传成功');
});
} else {
this.$message.error('上传失败:' + (response.msg || '返回格式错误'));
}
},
/** 封面上传前校验 */
beforeImageUpload(file) {
this.isUploading = true;
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.isUploading = false;
this.$message.error('只能上传JPG/PNG格式的图片');
return false;
}
if (!isLt2M) {
this.isUploading = false;
this.$message.error('图片大小不能超过2MB');
return false;
}
return true;
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
id: null,
title: null,
image: null,
readNum: null,
startTime: null,
content: null,
status: "0",
modifyTime: null,
deptId: null
};
this.resetForm("form");
if (this.editor) {
this.editor.txt.clear();
}
this.isUploading = false;
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加瑜伽欣赏";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids[0];
if (!id) {
this.$message.warning('请选择一条记录进行修改');
return;
}
getAppreciate(id).then(response => {
this.form = { ...response.data };
this.form.status = String(this.form.status || "0");
// 富文本回显
this.$nextTick(() => {
setTimeout(() => {
if (this.editor && this.form.content) {
const processedHtml = this.form.content.replace(
/<img/g,
'<img style="max-width:100%;height:auto;"'
);
this.editor.txt.html(processedHtml);
}
}, 500);
});
this.open = true;
this.title = "修改瑜伽欣赏";
}).catch(() => {
this.$message.error('获取数据失败');
});
},
/** 提交按钮 */
submitForm() {
// 强制同步富文本内容并校验
if (this.editor) {
const html = this.editor.txt.html();
// 检查是否存在空图片路径
if (html.includes('<img') && html.includes('src=""')) {
this.$message.error('存在无效图片,请重新上传');
return;
}
this.form.content = html;
}
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateAppreciate(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
}).catch(() => {
this.$modal.msgError("修改失败");
});
} else {
addAppreciate(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
}).catch(() => {
this.$modal.msgError("新增失败");
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
if (!ids || (Array.isArray(ids) && ids.length === 0)) {
this.$message.warning('请选择至少一条记录进行删除');
return;
}
this.$modal.confirm('是否确认删除瑜伽欣赏编号为"' + ids + '"的数据项?').then(function() {
return delAppreciate(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('system/inherit/export', {
...this.queryParams
}, `inherit_${new Date().getTime()}.xlsx`)
},
/** 处理图片URL */
getFullImgUrl(url) {
if (!url || typeof url !== 'string') return '';
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
}
return this.baseUrl + (this.baseUrl.endsWith('/') ? '' : '/') + url;
}
},
beforeDestroy() {
if (this.editor) {
this.editor.destroy();
this.editor = null;
}
}
};
</script>
<style scoped>
/* 富文本样式 */
.editor-container {
border: 1px solid #e6e6e6;
border-radius: 4px;
padding: 5px;
}
.editor-container img {
max-width: 100% !important;
height: auto !important;
}
/* 查看内容样式 */
.content-viewer {
line-height: 1.8;
word-break: break-all;
padding: 15px;
max-height: 600px;
overflow-y: auto;
}
.content-viewer img {
max-width: 100% !important;
height: auto !important;
margin: 10px auto !important;
display: block !important;
}
/* 图片上传样式 */
.image-uploader {
position: relative;
display: inline-block;
}
.image-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 4px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.image-uploader .el-upload:hover {
border-color: #409EFF;
}
.image-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 150px;
height: 100px;
line-height: 100px;
text-align: center;
}
/* 图片错误样式 */
.image-error {
width: 150px;
height: 100px;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
color: #8c939d;
}
</style>