|
@@ -0,0 +1,301 @@
|
|
|
+import * as api from './api';
|
|
|
+import { dict, useCompute, PageQuery, AddReq, DelReq, EditReq, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
|
|
+import { computed, shallowRef } from 'vue';
|
|
|
+import { auth } from '/@/utils/authFunction';
|
|
|
+import { marked } from 'marked';
|
|
|
+import DOMPurify from 'dompurify';
|
|
|
+import MarkdownEditor from './components/MarkdownEditor.vue';
|
|
|
+const { compute } = useCompute();
|
|
|
+
|
|
|
+// 配置marked选项
|
|
|
+marked.setOptions({
|
|
|
+ breaks: true, // 支持换行
|
|
|
+ gfm: true, // 支持GitHub风格的Markdown
|
|
|
+});
|
|
|
+
|
|
|
+// Markdown转换工具函数
|
|
|
+function convertMarkdownToHtml(markdown: string): string {
|
|
|
+ if (!markdown) return '';
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 使用marked解析Markdown
|
|
|
+ const html = marked.parse(markdown) as string;
|
|
|
+ // 使用DOMPurify清理HTML,防止XSS攻击
|
|
|
+ return DOMPurify.sanitize(html);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Markdown解析错误:', error);
|
|
|
+ return markdown; // 如果解析失败,返回原始内容
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function convertHtmlToMarkdown(html: string): string {
|
|
|
+ if (!html) return '';
|
|
|
+
|
|
|
+ // 简单的HTML到Markdown转换
|
|
|
+ let markdown = html
|
|
|
+ // 标题
|
|
|
+ .replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n\n')
|
|
|
+ .replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n\n')
|
|
|
+ .replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n\n')
|
|
|
+ .replace(/<h4[^>]*>(.*?)<\/h4>/gi, '#### $1\n\n')
|
|
|
+ .replace(/<h5[^>]*>(.*?)<\/h5>/gi, '##### $1\n\n')
|
|
|
+ .replace(/<h6[^>]*>(.*?)<\/h6>/gi, '###### $1\n\n')
|
|
|
+ // 粗体和斜体
|
|
|
+ .replace(/<strong[^>]*>(.*?)<\/strong>/gi, '**$1**')
|
|
|
+ .replace(/<b[^>]*>(.*?)<\/b>/gi, '**$1**')
|
|
|
+ .replace(/<em[^>]*>(.*?)<\/em>/gi, '*$1*')
|
|
|
+ .replace(/<i[^>]*>(.*?)<\/i>/gi, '*$1*')
|
|
|
+ // 代码
|
|
|
+ .replace(/<pre[^>]*><code[^>]*>(.*?)<\/code><\/pre>/gi, '```\n$1\n```')
|
|
|
+ .replace(/<code[^>]*>(.*?)<\/code>/gi, '`$1`')
|
|
|
+ // 链接
|
|
|
+ .replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)')
|
|
|
+ // 图片
|
|
|
+ .replace(/<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*>/gi, '')
|
|
|
+ .replace(/<img[^>]*src="([^"]*)"[^>]*>/gi, '')
|
|
|
+ // 列表
|
|
|
+ .replace(/<ul[^>]*>(.*?)<\/ul>/gi, '$1')
|
|
|
+ .replace(/<ol[^>]*>(.*?)<\/ol>/gi, '$1')
|
|
|
+ .replace(/<li[^>]*>(.*?)<\/li>/gi, '* $1\n')
|
|
|
+ // 引用
|
|
|
+ .replace(/<blockquote[^>]*>(.*?)<\/blockquote>/gi, '> $1\n')
|
|
|
+ // 水平线
|
|
|
+ .replace(/<hr[^>]*>/gi, '---\n')
|
|
|
+ // 段落和换行
|
|
|
+ .replace(/<p[^>]*>(.*?)<\/p>/gi, '$1\n\n')
|
|
|
+ .replace(/<br[^>]*>/gi, '\n')
|
|
|
+ .replace(/<div[^>]*>(.*?)<\/div>/gi, '$1\n')
|
|
|
+ // 清理其他HTML标签
|
|
|
+ .replace(/<[^>]+>/g, '')
|
|
|
+ // 清理多余的换行
|
|
|
+ .replace(/\n{3,}/g, '\n\n')
|
|
|
+ .trim();
|
|
|
+
|
|
|
+ return markdown;
|
|
|
+}
|
|
|
+
|
|
|
+export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
|
|
+ const pageRequest = async (query: PageQuery) => {
|
|
|
+ return await api.GetList(query);
|
|
|
+ };
|
|
|
+ const editRequest = async ({ form, row }: EditReq) => {
|
|
|
+ form.id = row.id;
|
|
|
+ return await api.UpdateObj(form);
|
|
|
+ };
|
|
|
+ const delRequest = async ({ row }: DelReq) => {
|
|
|
+ return await api.DelObj(row.id);
|
|
|
+ };
|
|
|
+ const addRequest = async ({ form }: AddReq) => {
|
|
|
+ return await api.AddObj(form);
|
|
|
+ };
|
|
|
+
|
|
|
+ const viewRequest = async ({ row }: { row: any }) => {
|
|
|
+ return await api.GetObj(row.id);
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ crudOptions: {
|
|
|
+ request: {
|
|
|
+ pageRequest,
|
|
|
+ addRequest,
|
|
|
+ editRequest,
|
|
|
+ delRequest,
|
|
|
+ },
|
|
|
+ actionbar: {
|
|
|
+ buttons: {
|
|
|
+ add: {
|
|
|
+ show: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ rowHandle: {
|
|
|
+ fixed: 'right',
|
|
|
+ width: 200,
|
|
|
+ buttons: {
|
|
|
+ edit: {
|
|
|
+ text: '编辑',
|
|
|
+ type: 'text',
|
|
|
+ iconRight: 'Edit',
|
|
|
+ show: true,
|
|
|
+ },
|
|
|
+ view: {
|
|
|
+ text: '查看',
|
|
|
+ type: 'text',
|
|
|
+ iconRight: 'View',
|
|
|
+ show: true,
|
|
|
+ click({ index, row }) {
|
|
|
+ // 打开查看模式,传递只读标识
|
|
|
+ crudExpose.openView({ index, row });
|
|
|
+ },
|
|
|
+ },
|
|
|
+ remove: {
|
|
|
+ iconRight: 'Delete',
|
|
|
+ type: 'text',
|
|
|
+ show: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ columns: {
|
|
|
+ id: {
|
|
|
+ title: 'ID',
|
|
|
+ form: {
|
|
|
+ show: false,
|
|
|
+ },
|
|
|
+ column: {
|
|
|
+ width: 80,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ title: {
|
|
|
+ title: '协议标题',
|
|
|
+ search: {
|
|
|
+ show: true,
|
|
|
+ },
|
|
|
+ type: ['text', 'colspan'],
|
|
|
+ column: {
|
|
|
+ minWidth: 200,
|
|
|
+ },
|
|
|
+ form: {
|
|
|
+ rules: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请输入协议标题',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ component: {
|
|
|
+ span: 24,
|
|
|
+ placeholder: '请输入协议标题'
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ key: {
|
|
|
+ title: '协议标识',
|
|
|
+ search: {
|
|
|
+ show: true,
|
|
|
+ },
|
|
|
+ type: 'text',
|
|
|
+ column: {
|
|
|
+ minWidth: 150,
|
|
|
+ },
|
|
|
+ form: {
|
|
|
+ rules: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请输入协议标识',
|
|
|
+ },
|
|
|
+ /* {
|
|
|
+ pattern: /^[a-z_]+$/,
|
|
|
+ message: '协议标识只能包含小写字母和下划线',
|
|
|
+ }, */
|
|
|
+ ],
|
|
|
+ component: {
|
|
|
+ span: 24,
|
|
|
+ placeholder: '如:terms_of_service'
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ version: {
|
|
|
+ title: '版本号',
|
|
|
+ search: {
|
|
|
+ show: true,
|
|
|
+ },
|
|
|
+ type: 'text',
|
|
|
+ column: {
|
|
|
+ width: 120,
|
|
|
+ },
|
|
|
+ form: {
|
|
|
+ rules: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请输入版本号',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ component: {
|
|
|
+ span: 24,
|
|
|
+ placeholder: '如:1.0'
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ publish_date: {
|
|
|
+ title: '发布日期',
|
|
|
+ type: 'datetime',
|
|
|
+ column: {
|
|
|
+
|
|
|
+ width: 180,
|
|
|
+ },
|
|
|
+ form: {
|
|
|
+ show:false,
|
|
|
+ rules: [
|
|
|
+ {
|
|
|
+ required: false,
|
|
|
+ message: '请选择发布日期',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ component: {
|
|
|
+ span: 24,
|
|
|
+ valueFormat: 'YYYY-MM-DDTHH:mm:ssZ',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ is_active: {
|
|
|
+ title: '是否启用',
|
|
|
+ type: 'dict-select',
|
|
|
+ column: {
|
|
|
+ width: 120,
|
|
|
+ },
|
|
|
+ dict: dict({
|
|
|
+ data: [
|
|
|
+ { label: '启用', value: true, color: 'success' },
|
|
|
+ { label: '禁用', value: false, color: 'danger' },
|
|
|
+ ],
|
|
|
+ }),
|
|
|
+ form: {
|
|
|
+ rules: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择是否启用',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ component: {
|
|
|
+ span: 24,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ content: {
|
|
|
+ title: '协议内容',
|
|
|
+ column: {
|
|
|
+ width: 300,
|
|
|
+ show: false,
|
|
|
+ },
|
|
|
+ type: 'colspan',
|
|
|
+ form: {
|
|
|
+ rules: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请输入协议内容',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ component: {
|
|
|
+ name: shallowRef(MarkdownEditor),
|
|
|
+ vModel: 'modelValue',
|
|
|
+ readonly: compute((context) => {
|
|
|
+ const { mode } = context;
|
|
|
+ console.log('当前模式:', mode, '只读状态:', mode === 'view');
|
|
|
+ return mode === 'view';
|
|
|
+ }),
|
|
|
+ span: 24,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ // 在查看模式下也使用相同的组件配置
|
|
|
+ view: {
|
|
|
+ component: {
|
|
|
+ name: shallowRef(MarkdownEditor),
|
|
|
+ vModel: 'modelValue',
|
|
|
+ readonly: true, // 查看模式始终只读
|
|
|
+ span: 24,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ };
|
|
|
+}
|