'隐私协议'

This commit is contained in:
张磊
2026-04-14 13:29:50 +08:00
parent 486563fbd6
commit 028e869547
4 changed files with 343 additions and 38 deletions
+43
View File
@@ -0,0 +1,43 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
//隐私协议列表
export function privacyList(data): AxiosPromise<any> {
return request({
url: '/admin/v1/agreement/list',
method: 'post',
data
});
}
//协议类型列表
export function agreementTypeList(data): AxiosPromise<any> {
return request({
url: '/admin/v1/agreement/types',
method: 'post',
data
});
}
//隐私协议添加
export function addPrivacy(data): AxiosPromise<any> {
return request({
url: '/admin/v1/agreement/create',
method: 'post',
data
})
}
//隐私协议修改
export function updatePrivacy(data): AxiosPromise<any> {
return request({
url: '/admin/v1/agreement/update',
method: 'post',
data
})
}
//隐私协议删除
export function deletePrivacy(data): AxiosPromise<any> {
return request({
url: '/admin/v1/agreement/delete',
method: 'post',
data
})
}
+39 -38
View File
@@ -10,8 +10,7 @@
<!-- 编辑器 -->
<Editor
:defaultConfig="editorConfig"
v-model="modelValue"
@onChange="handleChange"
v-model="value"
style="height: 450px; overflow-y: hidden"
:mode="mode"
@onCreated="handleCreated"
@@ -20,64 +19,66 @@
</template>
<script setup lang="ts">
import { onBeforeUnmount, shallowRef, reactive, toRefs } from 'vue';
import { onBeforeUnmount, shallowRef, ref, watch } from 'vue';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
import {CODES, Numbers} from "@/utils/code";
// API 引用
import { Numbers } from "@/utils/code";
import useStore from "@/store";
const { my } = useStore();
const props = defineProps({
modelValue: {
type: [String],
type: String,
default: ''
}
});
const emit = defineEmits(['update:modelValue']);
const value = ref(props.modelValue);
watch(() => props.modelValue, (newVal) => {
value.value = newVal;
});
watch(value, (newVal) => {
if (newVal !== props.modelValue) {
emit('update:modelValue', newVal);
}
});
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef();
const state = reactive({
toolbarConfig: {},
editorConfig: {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
// 自定义图片上传
async customUpload(file: any, insertFn: any) {
my.upImage(file, Numbers.imageTypeContent, function (url:string, uri : string){
insertFn(url);
})
}
},
uploadVideo: {
// 自定义图片上传
async customUpload(file: any, insertFn: any) {
my.upImage(file, Numbers.imageTypeContent, function (url:string, uri : string){
insertFn(url);
})
}
const toolbarConfig = {};
const editorConfig = {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
async customUpload(file: any, insertFn: any) {
my.upImage(file, Numbers.imageTypeContent, function (url: string) {
insertFn(url);
})
}
},
uploadVideo: {
async customUpload(file: any, insertFn: any) {
my.upImage(file, Numbers.imageTypeContent, function (url: string) {
insertFn(url);
})
}
}
},
defaultHtml: props.modelValue,
mode: 'default'
});
const { toolbarConfig, editorConfig, defaultHtml, mode } = toRefs(state);
}
};
const mode = 'default';
const handleCreated = (editor: any) => {
editorRef.value = editor; // 记录 editor 实例,重要!
editorRef.value = editor;
if (props.modelValue) {
editor.setHtml(props.modelValue);
}
};
function handleChange(editor: any) {
emit('update:modelValue', editor.getHtml());
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
@@ -0,0 +1,104 @@
<template>
<el-dialog :model-value="visible" :title="dialogTitle" width="800px" append-to-body destroy-on-close
@update:model-value="$emit('update:visible', $event)">
<template v-if="mode === 'view'">
<div v-html="formData.content" style="padding: 10px;"></div>
</template>
<el-form v-else ref="formRef" :model="formData" :rules="rules" label-width="80px">
<el-form-item label="类型" prop="type_id">
<el-select v-model="formData.type_id" placeholder="请选择类型" style="width: 100%">
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="内容" prop="content">
<WangEditor v-model="formData.content" />
</el-form-item>
</el-form>
<template #footer v-if="mode !== 'view'">
<el-button @click="$emit('update:visible', false)">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleSubmit">确定</el-button>
</template>
<template #footer v-else>
<el-button @click="$emit('update:visible', false)">关闭</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { reactive, ref, watch, computed } from 'vue';
import { addPrivacy, updatePrivacy } from '@/api/privacy/index'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import WangEditor from '@/components/WangEditor/index.vue';
interface Props {
visible: boolean;
typeList: any[];
mode: 'add' | 'edit' | 'view';
editId?: number | undefined;
editData?: any;
}
const props = defineProps<Props>();
const emit = defineEmits<{
'update:visible': [val: boolean];
'success': [];
}>();
const formRef = ref<FormInstance>();
const loading = ref(false);
const formData = reactive<{
type_id: number | string;
content: string;
}>({
type_id: '',
content: '',
});
const rules: FormRules = {
type_id: [{ required: true, message: '请选择类型', trigger: 'change' }],
content: [{ required: true, message: '请输入内容', trigger: 'blur' }],
};
const dialogTitle = computed(() => {
if (props.mode === 'view') return '查看';
return props.mode === 'edit' ? '修改' : '添加';
});
const typeName = computed(() => {
const item = props.typeList.find((t: any) => t.id === formData.type_id);
return item ? item.name : '--';
});
watch(() => props.visible, (val) => {
if (val) {
if (props.editId) {
formData.type_id = props.editData.type_id;
formData.content = props.editData.content || '';
} else {
formData.type_id = '';
formData.content = '';
}
formRef.value?.clearValidate();
}
});
const handleSubmit = async () => {
try {
await formRef.value.validate();
loading.value = true;
if (props.editId) {
await updatePrivacy({ id: props.editId, ...formData });
ElMessage.success('修改成功');
} else {
await addPrivacy(formData);
ElMessage.success('添加成功');
}
emit('update:visible', false);
emit('success');
} catch (err: any) {
if (err !== 'cancel') ElMessage.error(err?.message || '操作失败');
} finally {
loading.value = false;
}
};
</script>
+157
View File
@@ -0,0 +1,157 @@
<template>
<div class="app-container">
<el-card class="box-card">
<el-form ref="queryFormRef" :model="queryParams" inline class="form-row">
<el-form-item>
<!-- <el-button type="primary" :icon="Search" @click="loadList">搜索</el-button> -->
<!-- <el-button :icon="Refresh" @click="resetQuery">重置</el-button> -->
</el-form-item>
<el-form-item style="margin-left: auto;">
<el-button type="primary" :icon="Plus" @click="handleAdd">添加</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="storeList" border stripe style="width: 100%">
<el-table-column prop="type_id" label="类型" align="center">
<template #default="{ row }"><el-tag type="primary">{{ getTypeName(row.type_id) }}</el-tag></template>
</el-table-column>
<el-table-column prop="content" label="内容" align="center">
<template #default="{ row }">
<el-button type="primary" link @click="handleView(row)">查看</el-button>
</template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" align="center">
<template #default="{ row }">{{ row.created_at || '--' }}</template>
</el-table-column>
<el-table-column label="操作" align="center" width="120">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">修改</el-button>
<el-button type="primary" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination :total="total" v-model:page="queryParams.page" v-model:limit="queryParams.size"
@pagination="loadList" />
</el-card>
<!-- 添加/编辑弹窗 -->
<PrivacyDialog v-model:visible="dialogVisible" :type-list="typeList" :mode="dialogMode" :edit-id="editId" :edit-data="editRow"
@success="loadList" />
</div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted } from 'vue';
import { privacyList, deletePrivacy, updatePrivacy, addPrivacy, agreementTypeList } from '@/api/privacy/index'
import { ElMessage, ElMessageBox } from 'element-plus';
import { Search, Refresh, Plus } from '@element-plus/icons-vue';
import PrivacyDialog from './components/PrivacyDialog.vue';
// 类型列表
const typeList = ref<any[]>([]);
// 列表
const loading = ref(false);
const total = ref(0);
const storeList = ref<any[]>([]);
const queryFormRef = ref();
const queryParams = reactive({
page: 1,
size: 10,
keyword: '',
});
// 弹窗
const dialogVisible = ref(false);
const dialogMode = ref<'add' | 'edit' | 'view'>('add');
const editId = ref<number | undefined>(undefined);
const editRow = ref<any>(undefined);
const handleAdd = () => {
dialogMode.value = 'add';
editId.value = undefined;
editRow.value = undefined;
dialogVisible.value = true;
};
const handleEdit = (row: any) => {
dialogMode.value = 'edit';
editId.value = row.id;
editRow.value = { ...row };
dialogVisible.value = true;
};
const handleView = (row: any) => {
dialogMode.value = 'view';
editId.value = row.id;
editRow.value = { ...row };
dialogVisible.value = true;
};
function normalizePageData(data: any): { list: any[]; total: number } {
if (Array.isArray(data)) return { list: data, total: data.length };
const list = data?.list ?? data?.data ?? [];
const total = Number(data?.total);
return { list, total: Number.isFinite(total) ? total : list.length };
}
// 加载类型列表
const loadTypeList = () => {
agreementTypeList({}).then(({ data }) => {
typeList.value = Array.isArray(data) ? data : (data?.list ?? []);
});
};
// 获取类型名称
const getTypeName = (typeId: number) => {
const item = typeList.value.find((t: any) => t.id === typeId);
return item ? item.name : '--';
};
// 加载列表
const loadList = () => {
loading.value = true;
privacyList(queryParams)
.then(({ data }) => {
const { list, total: t } = normalizePageData(data);
storeList.value = list;
total.value = t;
})
.catch(() => ElMessage.error('获取列表失败'))
.finally(() => (loading.value = false));
};
// 重置
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.page = 1;
loadList();
};
// 删除
const handleDelete = async (row: any) => {
try {
await ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' });
await deletePrivacy({ id: row.id });
ElMessage.success('删除成功');
loadList();
} catch (err: any) {
if (err !== 'cancel') ElMessage.error(err?.message || '删除失败');
}
};
onMounted(() => {
loadTypeList();
loadList();
});
</script>
<style lang="scss" scoped>
.app-container {
padding: 20px;
}
.form-row {
display: flex;
width: 100%;
}
</style>