first
This commit is contained in:
141
src/views/manga/chapter/index.vue
Normal file
141
src/views/manga/chapter/index.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
highlight-current-row
|
||||
:columns="columns"
|
||||
:request-api="getTableList"
|
||||
:data-callback="dataCallback"
|
||||
>
|
||||
</ProTable>
|
||||
<ImgDialog ref="imgDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx" name="complexProTable">
|
||||
import { reactive, ref } from "vue";
|
||||
import ProTable from "@/components/ProTable/index.vue";
|
||||
import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
|
||||
import { ChapterList } from "@/api/interface/manga/chapter";
|
||||
import { getList } from "@/api/modules/manga/chapter";
|
||||
import ImgDialog from "../../components/imgDialog.vue";
|
||||
// import { fetchAndDecodeImage } from "@/utils/decrypt";
|
||||
|
||||
// ProTable 实例
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
const initParam = reactive({
|
||||
limit: 15,
|
||||
page: 1
|
||||
});
|
||||
|
||||
const dataCallback = (data: any) => {
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
total_page: data.total_pages,
|
||||
page: initParam.page,
|
||||
limit: initParam.limit
|
||||
};
|
||||
};
|
||||
|
||||
const getTableList = async (params: any) => {
|
||||
let newParams = JSON.parse(JSON.stringify(params));
|
||||
initParam.page = newParams.page;
|
||||
initParam.limit = newParams.limit;
|
||||
return getList(newParams);
|
||||
};
|
||||
|
||||
// 表格配置项
|
||||
const columns = reactive<ColumnProps<ChapterList.ResList>[]>([
|
||||
{ type: "selection", fixed: "left", width: 50 },
|
||||
{
|
||||
prop: "key",
|
||||
label: "搜索内容",
|
||||
isShow: false,
|
||||
search: {
|
||||
el: "input"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "mhzj_id",
|
||||
label: "ID",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: "mhzj_pai_xu",
|
||||
label: "章节序号",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: "mhzj_ming_zi",
|
||||
label: "章节标题"
|
||||
},
|
||||
{
|
||||
prop: "mhzj_nei_rong",
|
||||
label: "图册资源",
|
||||
render(scope) {
|
||||
if (scope.row.mhzj_nei_rong) {
|
||||
return <el-tag onClick={() => openImgFun(scope.row)}>查看</el-tag>;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ prop: "mhzj_source_code", label: "源站" },
|
||||
{ prop: "created_at", label: "添加时间" }
|
||||
]);
|
||||
|
||||
// 图册资源
|
||||
const imgDialogRef = ref<InstanceType<typeof ImgDialog> | null>(null);
|
||||
|
||||
// const openImgFun = async (row: Partial<ChapterList.ResList> = {}) => {
|
||||
// row.name = row.mhzj_ming_zi;
|
||||
// row.url = row.mhzj_nei_rong_url;
|
||||
|
||||
// if (!row.url) {
|
||||
// console.error("No URL provided in the row.");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const newArray = row.url.map(item => ({ url: item }));
|
||||
|
||||
// try {
|
||||
// // Process each URL with fetchAndDecodeImage before assigning to fileList
|
||||
// const processedArrayPromises = newArray.map(async item => {
|
||||
// const decodedUrl = await fetchAndDecodeImage(item.url);
|
||||
// console.log(decodedUrl, "decodedUrl");
|
||||
// return { url: decodedUrl };
|
||||
// });
|
||||
|
||||
// // Wait for all promises to resolve
|
||||
// const processedArray = await Promise.all(processedArrayPromises);
|
||||
// row.url = processedArray;
|
||||
|
||||
// console.log(row.url, " row.url");
|
||||
|
||||
// const params = {
|
||||
// title: "图册资源",
|
||||
// row: { ...row },
|
||||
// getTableList: proTable.value?.getTableList
|
||||
// };
|
||||
// imgDialogRef.value?.acceptParams(params as any);
|
||||
// } catch (error) {
|
||||
// console.error("Error processing URLs: ", error);
|
||||
// }
|
||||
// };
|
||||
|
||||
const openImgFun = (row: Partial<ChapterList.ResList> = {}) => {
|
||||
row.name = row.mhzj_ming_zi;
|
||||
row.url = row.mhzj_nei_rong_url;
|
||||
const params = {
|
||||
title: "图册资源",
|
||||
row: { ...row },
|
||||
getTableList: proTable.value?.getTableList
|
||||
};
|
||||
imgDialogRef.value?.acceptParams(params as any);
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.el-upload--picture-card {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
312
src/views/manga/component/chapterDrawer.vue
Normal file
312
src/views/manga/component/chapterDrawer.vue
Normal file
@@ -0,0 +1,312 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" size="720" :title="`${drawerProps.title}`">
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
label-width="90px"
|
||||
label-suffix=" :"
|
||||
:rules="rules"
|
||||
:disabled="drawerProps.isView"
|
||||
:model="drawerProps.row"
|
||||
:hide-required-asterisk="drawerProps.isView"
|
||||
>
|
||||
<el-form-item v-for="e in formColumns" :key="e.label" :label="e.label" :prop="e.prop" :required="e.required">
|
||||
<template v-if="e.type === 'switch'">
|
||||
<el-switch
|
||||
v-model="e.value"
|
||||
:active-value="`${1}`"
|
||||
:inactive-value="`${0}`"
|
||||
active-text="上架"
|
||||
inactive-text="下架"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="e.type === 'switch-index'">
|
||||
<el-switch
|
||||
v-model="e.value"
|
||||
:active-value="`${1}`"
|
||||
:inactive-value="`${0}`"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="e.type === 'radio'">
|
||||
<el-radio-group v-model="e.value" class="ml-4">
|
||||
<el-radio v-for="item in e.options" :key="item.value" :label="item.value" size="large">{{
|
||||
item.label
|
||||
}}</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<template v-if="e.type === 'select'">
|
||||
<el-select filterable v-model="e.value" :placeholder="`请选择${e.label}`" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in e.options"
|
||||
:key="index"
|
||||
:label="getField(item, e.fieldNames?.label)"
|
||||
:value="getField(item, e.fieldNames?.value)"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
<template v-if="e.type === 'text'">
|
||||
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
|
||||
</template>
|
||||
<template v-if="e.type === 'text-i'">
|
||||
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
|
||||
</template>
|
||||
<template v-if="e.type === 'text-num'">
|
||||
<el-input v-model="e.value" type="number" :placeholder="`请输入${e.label}`" clearable></el-input>
|
||||
</template>
|
||||
<template v-if="e.type === 'textarea'">
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="e.value"
|
||||
maxlength="36500"
|
||||
:autosize="{ minRows: 20, maxRows: 21 }"
|
||||
:placeholder="`请输入${e.label}`"
|
||||
show-word-limit
|
||||
/>
|
||||
</template>
|
||||
<template v-if="e.type === 'textarea-i'">
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="e.value"
|
||||
maxlength="36500"
|
||||
:autosize="{ minRows: 8, maxRows: 9 }"
|
||||
:placeholder="`请输入${e.label}`"
|
||||
show-word-limit
|
||||
/>
|
||||
<div style="width: 100%">{{ e.describe }}</div>
|
||||
</template>
|
||||
<template v-if="e.type === 'textarea-i'">
|
||||
<div :class="{ 'img-more-h': fileList.length > 0 }">
|
||||
<UploadImgs disabled v-model:file-list="fileList" :drag="false" border-radius="50%">
|
||||
<template #empty>
|
||||
<el-icon><Picture /></el-icon>
|
||||
<span>请上传照片</span>
|
||||
</template>
|
||||
</UploadImgs>
|
||||
</div>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="drawerVisible = false">取消</el-button>
|
||||
<el-button v-show="!drawerProps.isView" type="primary" @click="handleSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="UserDrawer">
|
||||
import { ref, computed } from "vue";
|
||||
import { ElMessage, FormInstance } from "element-plus";
|
||||
import { ChapterList } from "@/api/interface/manga/chapter";
|
||||
import UploadImgs from "@/components/Upload/Imgs.vue";
|
||||
import { extractURI } from "@/utils/eleValidate";
|
||||
type Options = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
};
|
||||
interface Item {
|
||||
uri: any;
|
||||
code: any;
|
||||
}
|
||||
const getField = (item: any, fieldName: string | undefined) => {
|
||||
return fieldName ? item[fieldName] : "";
|
||||
};
|
||||
type Fields = {
|
||||
label: string;
|
||||
prop: string;
|
||||
type: string;
|
||||
value: any;
|
||||
required: boolean;
|
||||
options?: Options[];
|
||||
fieldNames?: { label: string; value: string };
|
||||
describe?: string;
|
||||
};
|
||||
const generateRules = (fields: Fields[]) => {
|
||||
return fields.reduce(
|
||||
(acc, field) => {
|
||||
const { type, required, label, prop } = field;
|
||||
let message = "";
|
||||
if (type === "select") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "switch") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "switch-index") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "tag") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "radio") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "text") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "textarea-i") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "text-i") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "text-num") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "image") {
|
||||
message = `请上传${label}`;
|
||||
}
|
||||
if (type === "image-more") {
|
||||
message = `请上传${label}`;
|
||||
}
|
||||
acc[prop] = [{ required, message }];
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
};
|
||||
|
||||
const formColumns = computed((): Fields[] => {
|
||||
return [
|
||||
{
|
||||
label: "章节标题",
|
||||
prop: "mhzj_ming_zi",
|
||||
type: "text",
|
||||
get value() {
|
||||
return drawerProps.value.row!.mhzj_ming_zi;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.mhzj_ming_zi = val;
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
label: "章节序号",
|
||||
prop: "mhzj_pai_xu",
|
||||
type: "text-num",
|
||||
get value() {
|
||||
return drawerProps.value.row!.mhzj_pai_xu;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.mhzj_pai_xu = val;
|
||||
},
|
||||
required: true
|
||||
},
|
||||
// {
|
||||
// label: "是否VIP",
|
||||
// prop: "mc_vip",
|
||||
// type: "switch-index",
|
||||
// get value() {
|
||||
// return String(drawerProps.value.row!.mc_vip);
|
||||
// },
|
||||
// set value(val) {
|
||||
// drawerProps.value.row!.mc_vip = val;
|
||||
// },
|
||||
// required: true
|
||||
// },
|
||||
|
||||
{
|
||||
label: "图册资源",
|
||||
prop: "mhzj_nei_rong",
|
||||
type: "textarea-i",
|
||||
describe: "多个用逗号(,)隔开",
|
||||
get value() {
|
||||
return drawerProps.value.row!.mhzj_nei_rong;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.mhzj_nei_rong = val;
|
||||
},
|
||||
required: true
|
||||
}
|
||||
];
|
||||
});
|
||||
const rules = ref();
|
||||
const fileList = ref([] as any);
|
||||
interface DrawerProps {
|
||||
title: string;
|
||||
isView: boolean;
|
||||
row: Partial<ChapterList.ResList>;
|
||||
api?: (params: any) => Promise<any>;
|
||||
getTableList?: () => void;
|
||||
}
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const drawerProps = ref<DrawerProps>({
|
||||
isView: false,
|
||||
title: "",
|
||||
row: {}
|
||||
});
|
||||
|
||||
// 接收父组件传过来的参数
|
||||
const acceptParams = (params: DrawerProps) => {
|
||||
drawerProps.value = params;
|
||||
rules.value = generateRules(formColumns.value);
|
||||
drawerVisible.value = true;
|
||||
fileList.value = [];
|
||||
if (drawerProps.value.row.mhzj_nei_rong) {
|
||||
const data = JSON.parse(drawerProps.value.row.mhzj_nei_rong);
|
||||
drawerProps.value.row.mhzj_nei_rong = data.map((obj: Item) => {
|
||||
obj.uri = obj.uri;
|
||||
drawerProps.value.row.cover_code = obj.code;
|
||||
return obj.uri;
|
||||
});
|
||||
const newArr = data.map(item => ({ url: item }));
|
||||
fileList.value = newArr;
|
||||
}
|
||||
// if (drawerProps.value.row.mhzj_nei_rong_detail) {
|
||||
// const data = drawerProps.value.row.mhzj_nei_rong_detail;
|
||||
// const newArr = data.map(item => ({ url: item }));
|
||||
// drawerProps.value.row.mhzj_nei_rong = data;
|
||||
// fileList.value = newArr;
|
||||
// }
|
||||
};
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
const handleSubmit = () => {
|
||||
ruleFormRef.value!.validate(async valid => {
|
||||
if (!valid) return;
|
||||
try {
|
||||
const params = { ...drawerProps.value.row };
|
||||
if (Array.isArray(params.mhzj_nei_rong)) {
|
||||
params.mhzj_nei_rong = params.mhzj_nei_rong.map(item => ({
|
||||
uri: item,
|
||||
code: params.cover_code
|
||||
}));
|
||||
// params.mhzj_nei_rong = params.mhzj_nei_rong.map(uri => ({ uri: extractURI(uri), code: params.cover_code }));
|
||||
// params.mhzj_nei_rong = params.mhzj_nei_rong;
|
||||
} else {
|
||||
params.mhzj_nei_rong = params.mhzj_nei_rong.split(",");
|
||||
params.mhzj_nei_rong = params.mhzj_nei_rong.map(uri => ({ uri: extractURI(uri), code: params.cover_code }));
|
||||
params.mhzj_nei_rong = params.mhzj_nei_rong;
|
||||
}
|
||||
|
||||
return;
|
||||
await drawerProps.value.api!(params);
|
||||
ElMessage.success({ message: `${drawerProps.value.title}成功!` });
|
||||
drawerProps.value.getTableList!();
|
||||
drawerVisible.value = false;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.el-checkbox-button__inner {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-left-color: #dcdfe6 !important;
|
||||
}
|
||||
.el-upload--picture-card {
|
||||
display: none;
|
||||
}
|
||||
.img-more-h {
|
||||
height: 430px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
175
src/views/manga/component/imgDialog.vue
Normal file
175
src/views/manga/component/imgDialog.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="drawerVisible"
|
||||
:title="drawerProps.title + '--' + drawerProps.name"
|
||||
:destroy-on-close="true"
|
||||
width="1000px"
|
||||
draggable
|
||||
>
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
label-width="0px"
|
||||
label-suffix=" :"
|
||||
:rules="rules"
|
||||
:disabled="drawerProps.isView"
|
||||
:model="drawerProps.row"
|
||||
:hide-required-asterisk="drawerProps.isView"
|
||||
>
|
||||
<el-form-item v-for="e in formColumns" :key="e.label" :label="e.label" :prop="e.prop" :required="e.required">
|
||||
<template v-if="e.type === 'image'">
|
||||
<div class="c-dialog-h">
|
||||
<img v-for="(item, index) in e.value" :key="index" :src="item" alt="" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="e.type === 'textarea-i'">
|
||||
<div :class="{ 'img-more-h': fileList.length > 0 }">
|
||||
<UploadImgs disabled v-model:file-list="fileList" height="120px" width="13.3%" :drag="false">
|
||||
<template #empty>
|
||||
<el-icon><Picture /></el-icon>
|
||||
<span>请上传照片</span>
|
||||
</template>
|
||||
</UploadImgs>
|
||||
</div>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="drawerVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="drawerVisible = false">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="UserDrawer">
|
||||
import { ref, computed } from "vue";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { ChapterList } from "@/api/interface/manga/chapter";
|
||||
import UploadImgs from "@/components/Upload/Imgs.vue";
|
||||
type Options = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
};
|
||||
|
||||
type Fields = {
|
||||
label: string;
|
||||
prop: string;
|
||||
type: string;
|
||||
value: any;
|
||||
required: boolean;
|
||||
options?: Options[];
|
||||
fieldNames?: { label: string; value: string };
|
||||
};
|
||||
const generateRules = (fields: Fields[]) => {
|
||||
return fields.reduce(
|
||||
(acc, field) => {
|
||||
const { type, required, label, prop } = field;
|
||||
let message = "";
|
||||
if (type === "select") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "text") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "textarea") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "textareaMore") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "radio") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
acc[prop] = [{ required, message }];
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
};
|
||||
|
||||
const formColumns = computed((): Fields[] => {
|
||||
return [
|
||||
{
|
||||
label: "",
|
||||
prop: "mhzj_nei_rong",
|
||||
type: "textarea-i",
|
||||
get value() {
|
||||
return drawerProps.value.row!.mhzj_nei_rong;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.mhzj_nei_rong = val;
|
||||
},
|
||||
required: false
|
||||
}
|
||||
];
|
||||
});
|
||||
const rules = ref();
|
||||
const fileList = ref([] as any);
|
||||
interface DrawerProps {
|
||||
title: string;
|
||||
name?: any;
|
||||
isView: boolean;
|
||||
row: Partial<ChapterList.ResList>;
|
||||
api?: (params: any) => Promise<any>;
|
||||
getTableList?: () => void;
|
||||
}
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const drawerProps = ref<DrawerProps>({
|
||||
isView: false,
|
||||
title: "",
|
||||
name: "",
|
||||
row: {}
|
||||
});
|
||||
|
||||
// 接收父组件传过来的参数
|
||||
const acceptParams = async (params: DrawerProps) => {
|
||||
drawerProps.value = params;
|
||||
rules.value = generateRules(formColumns.value);
|
||||
drawerProps.value.name = drawerProps.value.row!.mhzj_ming_zi;
|
||||
drawerVisible.value = true;
|
||||
fileList.value = [];
|
||||
if (drawerProps.value.row.mhzj_nei_rong) {
|
||||
const data = JSON.parse(drawerProps.value.row.mhzj_nei_rong);
|
||||
const newArray = data.map(item => ({ url: item }));
|
||||
// const newArray = data.map(() => ({ url: "https://loremflickr.com/390/122?random=59925" }));
|
||||
fileList.value = newArray;
|
||||
}
|
||||
};
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
|
||||
defineExpose({
|
||||
acceptParams
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.el-dialog .el-dialog__header {
|
||||
padding: 15px 0;
|
||||
}
|
||||
.c-dialog-h {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 1100px;
|
||||
max-height: 600px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.c-dialog-h img {
|
||||
width: 19.05%;
|
||||
height: 150px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.img-more-h .upload .upload-image {
|
||||
object-fit: fill !important;
|
||||
}
|
||||
.img-more-h {
|
||||
height: 450px;
|
||||
margin-top: 15px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.el-dialog {
|
||||
--el-dialog-margin-top: 4vh !important;
|
||||
}
|
||||
</style>
|
||||
200
src/views/manga/component/modifyDrawer.vue
Normal file
200
src/views/manga/component/modifyDrawer.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" size="720" :title="`${drawerProps.title}`">
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
label-width="80px"
|
||||
label-position="top"
|
||||
label-suffix=" :"
|
||||
:rules="rules"
|
||||
:disabled="drawerProps.isView"
|
||||
:model="drawerProps.row"
|
||||
:hide-required-asterisk="drawerProps.isView"
|
||||
>
|
||||
<el-form-item v-for="e in formColumns" :key="e.label" :label="e.label" :prop="e.prop" :required="e.required">
|
||||
<template v-if="e.type === 'switch'">
|
||||
<el-switch
|
||||
v-model="e.value"
|
||||
:active-value="`${1}`"
|
||||
:inactive-value="`${0}`"
|
||||
active-text="上架"
|
||||
inactive-text="下架"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="e.type === 'radio'">
|
||||
<el-radio-group v-model="e.value" class="ml-4">
|
||||
<el-radio v-for="item in e.options" :key="item.value" :value="item.value" size="large">{{
|
||||
item.label
|
||||
}}</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<template v-if="e.type === 'select'">
|
||||
<el-select filterable multiple v-model="e.value" :placeholder="`请选择${e.label}`" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in e.options"
|
||||
:key="index"
|
||||
:label="getField(item, e.fieldNames?.label)"
|
||||
:value="getField(item, e.fieldNames?.value)"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
<template v-if="e.type === 'text'">
|
||||
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
|
||||
</template>
|
||||
<template v-if="e.type === 'text-i'">
|
||||
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
|
||||
</template>
|
||||
<template v-if="e.type === 'text-num'">
|
||||
<el-input v-model="e.value" type="number" :placeholder="`请输入${e.label}`" clearable></el-input>
|
||||
</template>
|
||||
<template v-if="e.type === 'textarea'">
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="e.value"
|
||||
maxlength="3650"
|
||||
:placeholder="`请输入${e.label}`"
|
||||
show-word-limit
|
||||
/>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="drawerVisible = false">取消</el-button>
|
||||
<el-button v-show="!drawerProps.isView" type="primary" @click="handleSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="UserDrawer">
|
||||
import { ref, computed } from "vue";
|
||||
import { ElMessage, FormInstance } from "element-plus";
|
||||
import { ChapterList } from "@/api/interface/manga/chapter";
|
||||
import { arrNoticeSwitch } from "@/utils/serviceDict";
|
||||
type Options = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
};
|
||||
|
||||
const getField = (item: any, fieldName: string | undefined) => {
|
||||
return fieldName ? item[fieldName] : "";
|
||||
};
|
||||
type Fields = {
|
||||
label: string;
|
||||
prop: string;
|
||||
type: string;
|
||||
value: any;
|
||||
required: boolean;
|
||||
options?: Options[];
|
||||
fieldNames?: { label: string; value: string };
|
||||
};
|
||||
const generateRules = (fields: Fields[]) => {
|
||||
return fields.reduce(
|
||||
(acc, field) => {
|
||||
const { type, required, label, prop } = field;
|
||||
let message = "";
|
||||
if (type === "select") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "tag") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "radio") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "text") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "text-i") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "text-num") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "textarea") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "image") {
|
||||
message = `请上传${label}`;
|
||||
}
|
||||
if (type === "image-more") {
|
||||
message = `请上传${label}`;
|
||||
}
|
||||
acc[prop] = [{ required, message }];
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
};
|
||||
|
||||
const formColumns = computed((): Fields[] => {
|
||||
return [
|
||||
{
|
||||
label: "是否VIP",
|
||||
prop: "mc_vip",
|
||||
type: "radio",
|
||||
get value() {
|
||||
return drawerProps.value.row!.mc_vip;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.mc_vip = val;
|
||||
},
|
||||
options: arrNoticeSwitch,
|
||||
fieldNames: { label: "label", value: "value" },
|
||||
required: false
|
||||
}
|
||||
];
|
||||
});
|
||||
const rules = ref();
|
||||
|
||||
interface DrawerProps {
|
||||
title: string;
|
||||
isView: boolean;
|
||||
row: Partial<ChapterList.ResList>;
|
||||
api?: (params: any) => Promise<any>;
|
||||
getTableList?: () => void;
|
||||
clearSelection?: () => void;
|
||||
}
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const drawerProps = ref<DrawerProps>({
|
||||
isView: false,
|
||||
title: "",
|
||||
row: {}
|
||||
});
|
||||
|
||||
// 接收父组件传过来的参数
|
||||
const acceptParams = (params: DrawerProps) => {
|
||||
drawerProps.value = params;
|
||||
rules.value = generateRules(formColumns.value);
|
||||
drawerVisible.value = true;
|
||||
drawerProps.value.row!.mc_vip = 0;
|
||||
};
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
const handleSubmit = () => {
|
||||
ruleFormRef.value!.validate(async valid => {
|
||||
if (!valid) return;
|
||||
try {
|
||||
const params = { ...drawerProps.value.row };
|
||||
await drawerProps.value.api!(params);
|
||||
ElMessage.success({ message: `${drawerProps.value.title}成功!` });
|
||||
drawerProps.value.getTableList!();
|
||||
drawerProps.value.clearSelection!();
|
||||
drawerVisible.value = false;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.el-checkbox-button__inner {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-left-color: #dcdfe6 !important;
|
||||
}
|
||||
</style>
|
||||
224
src/views/manga/components/chapter.vue
Normal file
224
src/views/manga/components/chapter.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" size="80%" title="章节列表" :destroy-on-close="true">
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
highlight-current-row
|
||||
:columns="columns"
|
||||
:request-api="getTableList"
|
||||
:data-callback="dataCallback"
|
||||
:init-param="initParam"
|
||||
:is-displayed="isDisplayed"
|
||||
:is-search-location="isSearchLocation"
|
||||
>
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<el-button type="primary" style="display: none" :icon="CirclePlus" @click="openDrawer(true)">
|
||||
新增漫画章节
|
||||
</el-button>
|
||||
<el-button
|
||||
style="display: none"
|
||||
type="danger"
|
||||
:icon="Delete"
|
||||
plain
|
||||
:disabled="!scope.isSelected"
|
||||
@click="batchDelete(scope.selectedList)"
|
||||
>
|
||||
批量删除章节
|
||||
</el-button>
|
||||
<el-button
|
||||
style="display: none"
|
||||
type="primary"
|
||||
:icon="EditPen"
|
||||
plain
|
||||
:disabled="!scope.isSelected"
|
||||
@click="batchModify(scope.selectedList)"
|
||||
>
|
||||
批量编辑漫画章节
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 表格操作 -->
|
||||
<template #operation="scope">
|
||||
<el-button type="primary" link :icon="EditPen" @click="openDrawer(false, scope.row)">编辑</el-button>
|
||||
<el-button type="danger" style="display: none" link :icon="Delete" @click="deleteAccount(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
<template #footer>
|
||||
<el-button @click="drawerVisible = false">取消</el-button>
|
||||
<el-button @click="drawerVisible = false" type="primary">确定</el-button>
|
||||
</template>
|
||||
<ChapterDrawer ref="chapterDrawerRef" />
|
||||
<ModifyDrawer ref="modifyDrawerRef" />
|
||||
<ImgDialog ref="imgDialogRef" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { ref, reactive } from "vue";
|
||||
import ProTable from "@/components/ProTable/index.vue";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { Delete, EditPen, CirclePlus } from "@element-plus/icons-vue";
|
||||
import { ChapterList } from "@/api/interface/manga/chapter";
|
||||
import { getList, saveData, deleteItem, batchData } from "@/api/modules/manga/chapter";
|
||||
import ChapterDrawer from "../component/chapterDrawer.vue";
|
||||
import ModifyDrawer from "../component/modifyDrawer.vue";
|
||||
import ImgDialog from "../../components/imgDialog.vue";
|
||||
// ProTable 实例
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
|
||||
const initParam = reactive({
|
||||
mhxq_id: 0,
|
||||
limit: 25,
|
||||
page: 1
|
||||
});
|
||||
|
||||
const dataCallback = (data: any) => {
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
total_page: data.total_pages,
|
||||
page: initParam.page,
|
||||
limit: initParam.limit
|
||||
};
|
||||
};
|
||||
|
||||
const getTableList = async (params: any) => {
|
||||
let newParams = JSON.parse(JSON.stringify(params));
|
||||
initParam.page = newParams.page;
|
||||
initParam.limit = newParams.limit;
|
||||
return getList(newParams);
|
||||
};
|
||||
|
||||
const isDisplayed = ref<any>(false);
|
||||
const isSearchLocation = ref<any>(false);
|
||||
|
||||
// 表格配置项
|
||||
const columns = reactive<ColumnProps<ChapterList.ResList>[]>([
|
||||
// { type: "selection", fixed: "left", width: 50 },
|
||||
{
|
||||
prop: "key",
|
||||
label: "搜索内容",
|
||||
isShow: false,
|
||||
search: {
|
||||
el: "input"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "mhzj_id",
|
||||
label: "ID",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: "mhzj_pai_xu",
|
||||
label: "章节序号",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: "mhzj_ming_zi",
|
||||
label: "章节标题"
|
||||
},
|
||||
{
|
||||
prop: "mhzj_nei_rong",
|
||||
label: "图册资源",
|
||||
render(scope) {
|
||||
if (scope.row.mhzj_nei_rong) {
|
||||
return <el-tag onClick={() => openImgFun(scope.row)}>查看</el-tag>;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ prop: "mhzj_source_code", label: "源站" },
|
||||
{ prop: "created_at", label: "添加时间" }
|
||||
// { prop: "operation", label: "操作", fixed: "right", width: 200 }
|
||||
]);
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
// 接收父组件传过来的参数
|
||||
const acceptParams = (data: any) => {
|
||||
drawerVisible.value = true;
|
||||
initParam.mhxq_id = data.mhxq_id;
|
||||
};
|
||||
|
||||
// 删除章节
|
||||
const deleteAccount = async (params: ChapterList.ResList) => {
|
||||
await useHandleData(deleteItem, { mhzj_id: params.mhzj_id, mhxq_id: initParam.mhxq_id }, "删除所选信息");
|
||||
proTable.value?.getTableList();
|
||||
};
|
||||
|
||||
// 批量删除章节
|
||||
const batchDelete = async (id: any[]) => {
|
||||
const ids = id.map((item: ChapterList.ResList) => item.mhzj_id);
|
||||
await useHandleData(deleteItem, { mhzj_id: ids.join(","), mhxq_id: initParam.mhxq_id }, "删除所选信息");
|
||||
proTable.value?.clearSelection();
|
||||
proTable.value?.getTableList();
|
||||
};
|
||||
|
||||
// 打开 drawer(新增、编辑)
|
||||
const chapterDrawerRef = ref<InstanceType<typeof ChapterDrawer> | null>(null);
|
||||
|
||||
const openDrawer = (isEdit: boolean, row: Partial<ChapterList.ResList> = {}) => {
|
||||
row.mhxq_id = initParam.mhxq_id;
|
||||
const params = {
|
||||
title: isEdit ? "新增漫画章节" : "编辑漫画章节",
|
||||
row: { ...row },
|
||||
api: saveData,
|
||||
getTableList: proTable.value?.getTableList
|
||||
};
|
||||
chapterDrawerRef.value?.acceptParams(params as any);
|
||||
};
|
||||
|
||||
// 批量修改视频信息
|
||||
const modifyDrawerRef = ref<InstanceType<typeof ModifyDrawer> | null>(null);
|
||||
const batchModify = async (id: any[], row: Partial<ChapterList.ResList> = {}) => {
|
||||
const ids = id.map((item: ChapterList.ResList) => item.mhxq_id);
|
||||
row.mhzj_id = ids.join(",");
|
||||
row.mhxq_id = initParam.mhxq_id;
|
||||
const params = {
|
||||
title: "批量编辑漫画章节",
|
||||
row: { ...row },
|
||||
api: batchData,
|
||||
getTableList: proTable.value?.getTableList,
|
||||
clearSelection: proTable.value?.clearSelection
|
||||
};
|
||||
modifyDrawerRef.value?.acceptParams(params as any);
|
||||
};
|
||||
|
||||
// 图册资源
|
||||
const imgDialogRef = ref<InstanceType<typeof ImgDialog> | null>(null);
|
||||
const openImgFun = (row: Partial<ChapterList.ResList> = {}) => {
|
||||
row.name = row.mhzj_ming_zi;
|
||||
row.url = row.mhzj_nei_rong_url;
|
||||
const params = {
|
||||
title: "图册资源",
|
||||
row: { ...row },
|
||||
getTableList: proTable.value?.getTableList
|
||||
};
|
||||
imgDialogRef.value?.acceptParams(params as any);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.demo-form-inline {
|
||||
.el-form-item {
|
||||
width: 420px;
|
||||
}
|
||||
:deep(.el-form-item__content) {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: flex-start;
|
||||
.tips {
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-group {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
481
src/views/manga/drawer.vue
Normal file
481
src/views/manga/drawer.vue
Normal file
@@ -0,0 +1,481 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" size="720" :title="`${drawerProps.title}`">
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
label-width="90px"
|
||||
label-suffix=" :"
|
||||
:rules="rules"
|
||||
:disabled="drawerProps.isView"
|
||||
:model="drawerProps.row"
|
||||
:hide-required-asterisk="drawerProps.isView"
|
||||
>
|
||||
<el-form-item v-for="e in formColumns" :key="e.label" :label="e.label" :prop="e.prop" :required="e.required">
|
||||
<template v-if="e.type === 'switch'">
|
||||
<el-switch
|
||||
v-model="e.value"
|
||||
:active-value="`${1}`"
|
||||
:inactive-value="`${0}`"
|
||||
active-text="上架"
|
||||
inactive-text="下架"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="e.type === 'switch-index'">
|
||||
<el-switch
|
||||
v-model="e.value"
|
||||
:active-value="`${1}`"
|
||||
:inactive-value="`${0}`"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="e.type === 'radio'">
|
||||
<el-radio-group v-model="e.value" class="ml-4">
|
||||
<el-radio v-for="item in e.options" :key="item.value" :value="item.value" size="large">{{
|
||||
item.label
|
||||
}}</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<template v-if="e.type === 'select'">
|
||||
<el-select filterable v-model="e.value" :placeholder="`请选择${e.label}`" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in e.options"
|
||||
:key="index"
|
||||
:label="getField(item, e.fieldNames?.label)"
|
||||
:value="getField(item, e.fieldNames?.value)"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
<template v-if="e.type === 'text'">
|
||||
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
|
||||
</template>
|
||||
<template v-if="e.type === 'text-i'">
|
||||
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
|
||||
</template>
|
||||
<template v-if="e.type === 'text-num'">
|
||||
<el-input v-model="e.value" type="number" :placeholder="`请输入${e.label}`" clearable></el-input>
|
||||
</template>
|
||||
<template v-if="e.type === 'textarea'">
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="e.value"
|
||||
:rows="5"
|
||||
maxlength="3650"
|
||||
:placeholder="`请输入${e.label}`"
|
||||
show-word-limit
|
||||
/>
|
||||
</template>
|
||||
<template v-if="e.type === 'tag'">
|
||||
<div class="tag-more-s">
|
||||
<el-input
|
||||
v-model="drawerProps.row.searchText"
|
||||
placeholder="请输入搜索内容"
|
||||
clearable
|
||||
@input="handleSearch"
|
||||
/>
|
||||
<div>
|
||||
<span>选中标签:</span>
|
||||
<div class="tag-more-h">
|
||||
<el-checkbox-group v-model="TagList" @change="placeableCheckedCitiesChange">
|
||||
<el-checkbox-button
|
||||
class="vip-power"
|
||||
v-for="item in matchingObjects"
|
||||
:value="item.t_id"
|
||||
:key="item.t_id"
|
||||
>
|
||||
{{ item.t_name }}
|
||||
</el-checkbox-button>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>未选中标签:</span>
|
||||
<div class="tag-more-h">
|
||||
<el-checkbox-group v-model="TagList" @change="placeableCheckedCitiesChange">
|
||||
<el-checkbox-button
|
||||
class="vip-power"
|
||||
v-for="item in nonMatchingObjects"
|
||||
:value="item.t_id"
|
||||
:key="item.t_id"
|
||||
>
|
||||
{{ item.t_name }}
|
||||
</el-checkbox-button>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="e.type === 'text-i'">
|
||||
<UploadImg disabled v-model:image-url="drawerProps.row!.m_cover" width="135px" height="135px" :file-size="3">
|
||||
<template #empty>
|
||||
<el-icon><Avatar /></el-icon>
|
||||
<span>请上传封面</span>
|
||||
</template>
|
||||
</UploadImg>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="drawerVisible = false">取消</el-button>
|
||||
<el-button v-show="!drawerProps.isView" type="primary" @click="handleSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="UserDrawer">
|
||||
import { ref, computed, reactive } from "vue";
|
||||
import { ElMessage, FormInstance } from "element-plus";
|
||||
import { MangaList } from "@/api/interface/manga/list";
|
||||
import { arrNovelType } from "@/utils/serviceDict";
|
||||
import { getTypeList } from "@/api/modules/tag/list";
|
||||
import UploadImg from "@/components/Upload/Img.vue";
|
||||
// import { extractURI } from "@/utils/eleValidate";
|
||||
type Options = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
};
|
||||
interface Item {
|
||||
t_id: any;
|
||||
uri: any;
|
||||
}
|
||||
const getField = (item: any, fieldName: string | undefined) => {
|
||||
return fieldName ? item[fieldName] : "";
|
||||
};
|
||||
type Fields = {
|
||||
label: string;
|
||||
prop: string;
|
||||
type: string;
|
||||
value: any;
|
||||
required: boolean;
|
||||
options?: Options[];
|
||||
fieldNames?: { label: string; value: string };
|
||||
};
|
||||
const generateRules = (fields: Fields[]) => {
|
||||
return fields.reduce(
|
||||
(acc, field) => {
|
||||
const { type, required, label, prop } = field;
|
||||
let message = "";
|
||||
if (type === "select") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "tag") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "radio") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "textarea") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "text") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "text-i") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "text-num") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "image") {
|
||||
message = `请上传${label}`;
|
||||
}
|
||||
if (type === "image-more") {
|
||||
message = `请上传${label}`;
|
||||
}
|
||||
acc[prop] = [{ required, message }];
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
};
|
||||
|
||||
const TagList = ref([] as any);
|
||||
const arrTagList = ref([] as any);
|
||||
const arrNewTagList = ref([] as any);
|
||||
const matchingObjects = ref([] as any); //选中相同
|
||||
const nonMatchingObjects = ref([] as any); //没有选中的
|
||||
const matchingObjectsA = ref([] as any); //选中相同
|
||||
const nonMatchingObjectsB = ref([] as any); //没有选中的
|
||||
const getTagList = async () => {
|
||||
try {
|
||||
const initParam = reactive({
|
||||
t_type: 1,
|
||||
limit: 3650,
|
||||
page: 1,
|
||||
t_hidden: 1
|
||||
});
|
||||
const siteRes = await getTypeList(initParam);
|
||||
arrTagList.value = siteRes.data.item;
|
||||
arrNewTagList.value = siteRes.data.item;
|
||||
matchingObjects.value = [];
|
||||
matchingObjectsA.value = [];
|
||||
// 遍历第一个数组的每个元素
|
||||
TagList.value.forEach(index => {
|
||||
// 遍历第二个数组的每个元素
|
||||
arrNewTagList.value.forEach(obj => {
|
||||
// 如果第二个数组中的元素的t_id与第一个数组中的元素相同,则将其添加到新数组中
|
||||
if (obj.t_id === index) {
|
||||
matchingObjects.value = [...matchingObjects.value, obj];
|
||||
matchingObjectsA.value = [...matchingObjectsA.value, obj];
|
||||
const matchingIds = matchingObjectsA.value.map(obj => obj.t_id);
|
||||
TagList.value = matchingIds;
|
||||
}
|
||||
});
|
||||
});
|
||||
// 创建一个新数组,包含objectArray中id不存在于arrayA的元素
|
||||
nonMatchingObjects.value = arrNewTagList.value.filter(obj => !TagList.value.includes(obj.t_id));
|
||||
nonMatchingObjectsB.value = arrNewTagList.value.filter(obj => !TagList.value.includes(obj.t_id));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理搜索事件
|
||||
function handleSearch() {
|
||||
// 在输入事件中实时更新过滤后的标签列表
|
||||
filteredTagList();
|
||||
}
|
||||
function filteredTagList() {
|
||||
if (drawerProps.value.row!.searchText.length > 0) {
|
||||
let arrData = arrTagList.value.filter(tag =>
|
||||
tag.t_name.toLowerCase().includes(drawerProps.value.row!.searchText.toLowerCase())
|
||||
);
|
||||
if (arrData.length > 0) {
|
||||
matchingObjects.value = matchingObjects.value.filter(objA => arrData.some(objB => objB.t_id === objA.t_id));
|
||||
nonMatchingObjects.value = nonMatchingObjects.value.filter(objA => arrData.some(objB => objB.t_id === objA.t_id));
|
||||
} else {
|
||||
matchingObjects.value = [];
|
||||
nonMatchingObjects.value = [];
|
||||
}
|
||||
} else {
|
||||
matchingObjects.value = matchingObjectsA.value;
|
||||
nonMatchingObjects.value = nonMatchingObjectsB.value;
|
||||
}
|
||||
}
|
||||
|
||||
//标签数组
|
||||
async function placeableCheckedCitiesChange(value) {
|
||||
matchingObjects.value = [];
|
||||
matchingObjectsA.value = [];
|
||||
drawerProps.value.row.m_tags = value;
|
||||
// 遍历第一个数组的每个元素
|
||||
TagList.value.forEach(index => {
|
||||
// 遍历第二个数组的每个元素
|
||||
arrNewTagList.value.forEach(obj => {
|
||||
// 如果第二个数组中的元素的t_id与第一个数组中的元素相同,则将其添加到新数组中
|
||||
if (obj.t_id === index) {
|
||||
matchingObjects.value = [...matchingObjects.value, obj];
|
||||
matchingObjectsA.value = [...matchingObjectsA.value, obj];
|
||||
const matchingIds = matchingObjectsA.value.map(obj => obj.t_id);
|
||||
TagList.value = matchingIds;
|
||||
}
|
||||
});
|
||||
});
|
||||
nonMatchingObjects.value = arrNewTagList.value.filter(obj => !TagList.value.includes(obj.t_id));
|
||||
nonMatchingObjectsB.value = arrNewTagList.value.filter(obj => !TagList.value.includes(obj.t_id));
|
||||
}
|
||||
|
||||
const formColumns = computed((): Fields[] => {
|
||||
return [
|
||||
{
|
||||
label: "漫画名称",
|
||||
prop: "m_name",
|
||||
type: "text",
|
||||
get value() {
|
||||
return drawerProps.value.row!.m_name;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.m_name = val;
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
label: "漫画作者",
|
||||
prop: "m_author",
|
||||
type: "text",
|
||||
get value() {
|
||||
return drawerProps.value.row!.m_author;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.m_author = val;
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
label: "来源 ID",
|
||||
prop: "m_source_id",
|
||||
type: "text-num",
|
||||
get value() {
|
||||
return drawerProps.value.row!.m_source_id;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.m_source_id = val;
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
label: "阅 读 数",
|
||||
prop: "m_hits",
|
||||
type: "text-num",
|
||||
get value() {
|
||||
return drawerProps.value.row!.m_hits;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.m_hits = val;
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
label: "漫画状态",
|
||||
prop: "m_status",
|
||||
type: "switch",
|
||||
get value() {
|
||||
return String(drawerProps.value.row!.m_status);
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.m_status = val;
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
label: "漫画类型",
|
||||
prop: "m_type",
|
||||
type: "radio",
|
||||
get value() {
|
||||
return drawerProps.value.row!.m_type;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.m_type = val;
|
||||
},
|
||||
options: arrNovelType,
|
||||
fieldNames: { label: "label", value: "value" },
|
||||
required: true
|
||||
},
|
||||
// {
|
||||
// label: "首页轮播状态",
|
||||
// prop: "m_slide_status",
|
||||
// type: "switch-index",
|
||||
// get value() {
|
||||
// return String(drawerProps.value.row!.m_slide_status);
|
||||
// },
|
||||
// set value(val) {
|
||||
// drawerProps.value.row!.m_slide_status = val;
|
||||
// },
|
||||
// required: true
|
||||
// },
|
||||
|
||||
{
|
||||
label: "漫画封面",
|
||||
prop: "m_cover_edit",
|
||||
type: "text",
|
||||
get value() {
|
||||
return drawerProps.value.row!.m_cover_edit;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.m_cover_edit = val;
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
label: "漫画描述",
|
||||
prop: "m_description",
|
||||
type: "textarea",
|
||||
get value() {
|
||||
return drawerProps.value.row!.m_description;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.m_description = val;
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
label: "漫画标签",
|
||||
prop: "m_tags",
|
||||
type: "tag",
|
||||
get value() {
|
||||
return drawerProps.value.row!.m_tags;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.m_tags = val;
|
||||
},
|
||||
options: arrTagList.value,
|
||||
fieldNames: { label: "label", value: "value" },
|
||||
required: true
|
||||
}
|
||||
];
|
||||
});
|
||||
const rules = ref();
|
||||
|
||||
interface DrawerProps {
|
||||
title: string;
|
||||
isView: boolean;
|
||||
row: Partial<MangaList.ResList>;
|
||||
api?: (params: any) => Promise<any>;
|
||||
getTableList?: () => void;
|
||||
}
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const drawerProps = ref<DrawerProps>({
|
||||
isView: false,
|
||||
title: "",
|
||||
row: {}
|
||||
});
|
||||
|
||||
// 接收父组件传过来的参数
|
||||
const acceptParams = (params: DrawerProps) => {
|
||||
drawerProps.value = params;
|
||||
rules.value = generateRules(formColumns.value);
|
||||
drawerVisible.value = true;
|
||||
TagList.value = [];
|
||||
if (drawerProps.value.title === "新增漫画") {
|
||||
drawerProps.value.row!.m_type = 0;
|
||||
}
|
||||
if (drawerProps.value.row.m_cover) {
|
||||
drawerProps.value.row.m_cover_code = drawerProps.value.row.m_cover.code;
|
||||
drawerProps.value.row.m_cover_edit = drawerProps.value.row.m_cover.uri;
|
||||
}
|
||||
if (drawerProps.value.row.m_tags) {
|
||||
TagList.value = drawerProps.value.row.m_tags.map((obj: Item) => obj.t_id).map(Number);
|
||||
}
|
||||
getTagList();
|
||||
};
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
const handleSubmit = () => {
|
||||
ruleFormRef.value!.validate(async valid => {
|
||||
if (!valid) return;
|
||||
try {
|
||||
const params = { ...drawerProps.value.row };
|
||||
let arrData = {
|
||||
uri: params.m_cover_edit,
|
||||
code: params.m_cover_code
|
||||
};
|
||||
params.m_cover = arrData;
|
||||
params.m_tags = TagList.value;
|
||||
await drawerProps.value.api!(params);
|
||||
ElMessage.success({ message: `${drawerProps.value.title}成功!` });
|
||||
drawerProps.value.getTableList!();
|
||||
drawerVisible.value = false;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.el-checkbox-button__inner {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-left-color: #dcdfe6 !important;
|
||||
}
|
||||
.tag-more-s {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid black;
|
||||
box-shadow: 0 0 4px 2px rgb(0 0 0 / 90%);
|
||||
}
|
||||
</style>
|
||||
72
src/views/manga/fenlei/index.vue
Normal file
72
src/views/manga/fenlei/index.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
highlight-current-row
|
||||
:columns="columns"
|
||||
:request-api="getTableList"
|
||||
:data-callback="dataCallback"
|
||||
>
|
||||
</ProTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx" name="complexProTable">
|
||||
import { reactive, ref } from "vue";
|
||||
import { fenLeiList } from "@/api/interface/manga/fenlei";
|
||||
import ProTable from "@/components/ProTable/index.vue";
|
||||
import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
|
||||
import { getList } from "@/api/modules/manga/fenlei";
|
||||
|
||||
// ProTable 实例
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
const initParam = reactive({
|
||||
limit: 15,
|
||||
page: 1
|
||||
});
|
||||
|
||||
const dataCallback = (data: any) => {
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
total_page: data.total_pages,
|
||||
page: initParam.page,
|
||||
limit: initParam.limit
|
||||
};
|
||||
};
|
||||
|
||||
const getTableList = (params: any) => {
|
||||
let newParams = JSON.parse(JSON.stringify(params));
|
||||
initParam.page = newParams.page;
|
||||
initParam.limit = newParams.limit;
|
||||
return getList(newParams);
|
||||
};
|
||||
|
||||
// 表格配置项
|
||||
const columns = reactive<ColumnProps<fenLeiList.ResList>[]>([
|
||||
{ type: "selection", fixed: "left", width: 50 },
|
||||
{
|
||||
prop: "key",
|
||||
label: "搜索内容",
|
||||
isShow: false,
|
||||
search: {
|
||||
el: "input"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "mhfl_id",
|
||||
label: "ID",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: "mhfl_ming_zi",
|
||||
label: "分类名称"
|
||||
},
|
||||
|
||||
{
|
||||
prop: "mhfl_source_code",
|
||||
label: "来源"
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
200
src/views/manga/index.vue
Normal file
200
src/views/manga/index.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
highlight-current-row
|
||||
:columns="columns"
|
||||
:request-api="getTableList"
|
||||
:data-callback="dataCallback"
|
||||
>
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<el-button type="primary" style="display: none" :icon="CirclePlus" @click="openDrawer(true)">新增</el-button>
|
||||
<el-button
|
||||
style="display: none"
|
||||
type="danger"
|
||||
:icon="Delete"
|
||||
plain
|
||||
:disabled="!scope.isSelected"
|
||||
@click="batchDelete(scope.selectedList)"
|
||||
>
|
||||
批量删除漫画
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 表格操作 -->
|
||||
<template #operation="scope">
|
||||
<el-button type="success" link :icon="ChatLineSquare" @click="openChapterDrawer(scope.row)">章节</el-button>
|
||||
<el-button type="primary" style="display: none" link :icon="EditPen" @click="openDrawer(false, scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button type="danger" style="display: none" link :icon="Delete" @click="deleteAccount(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
<Drawer ref="drawerRef" />
|
||||
<ChapterDrawer ref="chapterDrawerRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx" name="complexProTable">
|
||||
import { reactive, ref } from "vue";
|
||||
import { MangaList } from "@/api/interface/manga/list";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import ProTable from "@/components/ProTable/index.vue";
|
||||
import { Delete, EditPen, CirclePlus, ChatLineSquare } from "@element-plus/icons-vue";
|
||||
import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
|
||||
import { getList, saveData, deleteItem } from "@/api/modules/manga/list";
|
||||
import Drawer from "./drawer.vue";
|
||||
import ChapterDrawer from "./components/chapter.vue";
|
||||
import { getAllList } from "@/api/modules/manga/fenlei";
|
||||
import { LOSE_STR_KEY, LOGIN_URL } from "@/config";
|
||||
import router from "@/routers";
|
||||
import { h } from "vue";
|
||||
import { AsyncImageComponent } from "@/utils/decrypt";
|
||||
// ProTable 实例
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
//漫画分类
|
||||
const getFenLeiList = ref([] as any);
|
||||
getAllList().then(res => {
|
||||
if (res.code === LOSE_STR_KEY) {
|
||||
router.replace(LOGIN_URL);
|
||||
return;
|
||||
}
|
||||
getFenLeiList.value = res.data.items;
|
||||
});
|
||||
|
||||
const initParam = reactive({
|
||||
limit: 15,
|
||||
page: 1
|
||||
});
|
||||
|
||||
const dataCallback = (data: any) => {
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
total_page: data.total_pages,
|
||||
page: initParam.page,
|
||||
limit: initParam.limit
|
||||
};
|
||||
};
|
||||
|
||||
const getTableList = async (params: any) => {
|
||||
let newParams = JSON.parse(JSON.stringify(params));
|
||||
initParam.page = newParams.page;
|
||||
initParam.limit = newParams.limit;
|
||||
return await getList(newParams);
|
||||
};
|
||||
|
||||
// 表格配置项
|
||||
const columns = reactive<ColumnProps<MangaList.ResList>[]>([
|
||||
// { type: "selection", fixed: "left", width: 50 },
|
||||
{
|
||||
prop: "key",
|
||||
label: "搜索内容",
|
||||
isShow: false,
|
||||
search: {
|
||||
el: "input"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "mhxq_id",
|
||||
label: "ID",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: "mhxq_ming_zi",
|
||||
label: "名称",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
prop: "https_cover_url",
|
||||
label: "封面",
|
||||
width: 120,
|
||||
render(scope) {
|
||||
return h(AsyncImageComponent, { url: scope.row.https_cover_url });
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "mhxq_zhuang_tai",
|
||||
label: "状态"
|
||||
},
|
||||
{
|
||||
prop: "mhfl_id",
|
||||
label: "分类",
|
||||
width: 100,
|
||||
fieldNames: { label: "mhfl_ming_zi", value: "mhfl_id" },
|
||||
enum: getFenLeiList
|
||||
},
|
||||
{
|
||||
prop: "mhxq_zuo_zhe",
|
||||
label: "作者"
|
||||
},
|
||||
|
||||
{
|
||||
prop: "mhxq_biao_qian",
|
||||
label: "标签",
|
||||
width: 250,
|
||||
render(scope) {
|
||||
if (scope.row.mhxq_biao_qian) {
|
||||
if (Array.isArray(scope.row.mhxq_biao_qian)) {
|
||||
return <span>{scope.row.mhxq_biao_qian.join(", ")}</span>;
|
||||
} else {
|
||||
const data = JSON.parse(scope.row.mhxq_biao_qian);
|
||||
return <span>{data.join(", ")}</span>;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ prop: "mhxq_geng_xin_shi_jian", label: "最后更新时间", width: 120 },
|
||||
{ prop: "mhxq_jian_jie", label: "描述", width: 170 },
|
||||
{ prop: "created_at", label: "添加时间", width: 170 },
|
||||
{ prop: "operation", label: "操作", fixed: "right", width: 120 }
|
||||
]);
|
||||
|
||||
// 删除漫画信息
|
||||
const deleteAccount = async (params: MangaList.ResList) => {
|
||||
await useHandleData(deleteItem, { mhxq_id: params.mhxq_id }, "删除所选信息");
|
||||
proTable.value?.getTableList();
|
||||
};
|
||||
|
||||
// 批量删除漫画信息
|
||||
const batchDelete = async (id: any[]) => {
|
||||
const ids = id.map((item: MangaList.ResList) => item.mhxq_id);
|
||||
await useHandleData(deleteItem, { mhxq_id: ids.join(",") }, "删除所选信息");
|
||||
proTable.value?.clearSelection();
|
||||
proTable.value?.getTableList();
|
||||
};
|
||||
// 打开 drawer(新增、编辑)
|
||||
const drawerRef = ref<InstanceType<typeof Drawer> | null>(null);
|
||||
|
||||
const openDrawer = (isEdit: boolean, row: Partial<MangaList.ResList> = {}) => {
|
||||
const params = {
|
||||
title: isEdit ? "新增漫画" : "编辑漫画",
|
||||
row: { ...row },
|
||||
api: saveData,
|
||||
getTableList: proTable.value?.getTableList
|
||||
};
|
||||
drawerRef.value?.acceptParams(params as any);
|
||||
};
|
||||
|
||||
// 章节
|
||||
const chapterDrawerRef = ref<InstanceType<typeof ChapterDrawer> | null>(null);
|
||||
const openChapterDrawer = (row: Partial<MangaList.ResList> = {}) => {
|
||||
chapterDrawerRef.value?.acceptParams(row as any);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.el-table .warning-row,
|
||||
.el-table .warning-row .el-table-fixed-column--right,
|
||||
.el-table .warning-row .el-table-fixed-column--left {
|
||||
background-color: var(--el-color-warning-light-9);
|
||||
}
|
||||
.el-table .success-row,
|
||||
.el-table .success-row .el-table-fixed-column--right,
|
||||
.el-table .success-row .el-table-fixed-column--left {
|
||||
background-color: var(--el-color-success-light-9);
|
||||
}
|
||||
</style>
|
||||
179
src/views/manga/tagDialog.vue
Normal file
179
src/views/manga/tagDialog.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<el-dialog v-model="drawerVisible" :title="drawerProps.title" :destroy-on-close="true" width="520px" draggable>
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
label-width="50px"
|
||||
label-suffix=" :"
|
||||
:rules="rules"
|
||||
:disabled="drawerProps.isView"
|
||||
:model="drawerProps.row"
|
||||
:hide-required-asterisk="drawerProps.isView"
|
||||
>
|
||||
<el-form-item v-for="e in formColumns" :key="e.label" :label="e.label" :prop="e.prop" :required="e.required">
|
||||
<template v-if="e.type === 'switch'">
|
||||
<el-switch v-model="e.value" />
|
||||
</template>
|
||||
<template v-if="e.type === 'radio'">
|
||||
<el-radio-group v-model="e.value" class="ml-4">
|
||||
<el-radio v-for="item in e.options" :key="item.value" :label="item.value" size="large">{{
|
||||
item.label
|
||||
}}</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<template v-if="e.type === 'select'">
|
||||
<el-select filterable v-model="e.value" :placeholder="`请选择${e.label}`" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in e.options"
|
||||
:key="index"
|
||||
:label="getField(item, e.fieldNames?.label)"
|
||||
:value="getField(item, e.fieldNames?.value)"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
<template v-if="e.type === 'tag'">
|
||||
<el-tag
|
||||
style="margin: 7px; margin-right: 10px"
|
||||
v-for="item in drawerProps.row!.m_tags"
|
||||
:key="item.t_id"
|
||||
:type="item.type"
|
||||
effect="dark"
|
||||
>
|
||||
{{ item.t_name }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="e.type === 'text'">
|
||||
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
|
||||
</template>
|
||||
<template v-if="e.type === 'textarea'">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="10"
|
||||
v-model="e.value"
|
||||
maxlength="500000"
|
||||
:placeholder="`请输入${e.label}`"
|
||||
show-word-limit
|
||||
/>
|
||||
</template>
|
||||
<template v-if="e.type === 'textareaMore'">
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="e.value"
|
||||
maxlength="500000"
|
||||
:placeholder="`请输入${e.label}`"
|
||||
show-word-limit
|
||||
/>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="drawerVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="drawerVisible = false">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="UserDrawer">
|
||||
import { ref, computed } from "vue";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { MangaList } from "@/api/interface/manga/list";
|
||||
type Options = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
};
|
||||
|
||||
const getField = (item: any, fieldName: string | undefined) => {
|
||||
return fieldName ? item[fieldName] : "";
|
||||
};
|
||||
|
||||
type Fields = {
|
||||
label: string;
|
||||
prop: string;
|
||||
type: string;
|
||||
value: any;
|
||||
required: boolean;
|
||||
options?: Options[];
|
||||
fieldNames?: { label: string; value: string };
|
||||
};
|
||||
const generateRules = (fields: Fields[]) => {
|
||||
return fields.reduce(
|
||||
(acc, field) => {
|
||||
const { type, required, label, prop } = field;
|
||||
let message = "";
|
||||
if (type === "select") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
if (type === "text") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "textarea") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "textareaMore") {
|
||||
message = `请输入${label}`;
|
||||
}
|
||||
if (type === "radio") {
|
||||
message = `请选择${label}`;
|
||||
}
|
||||
acc[prop] = [{ required, message }];
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
};
|
||||
|
||||
const formColumns = computed((): Fields[] => {
|
||||
return [
|
||||
{
|
||||
label: "标签",
|
||||
prop: "t_name",
|
||||
type: "tag",
|
||||
get value() {
|
||||
return drawerProps.value.row!.t_name;
|
||||
},
|
||||
set value(val) {
|
||||
drawerProps.value.row!.t_name = val;
|
||||
},
|
||||
options: drawerProps.value.row!.m_tags,
|
||||
fieldNames: { label: "t_name", value: "t_id" },
|
||||
required: false
|
||||
}
|
||||
];
|
||||
});
|
||||
const rules = ref();
|
||||
|
||||
interface DrawerProps {
|
||||
title: string;
|
||||
isView: boolean;
|
||||
row: Partial<MangaList.ResList>;
|
||||
api?: (params: any) => Promise<any>;
|
||||
getTableList?: () => void;
|
||||
}
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const drawerProps = ref<DrawerProps>({
|
||||
isView: false,
|
||||
title: "",
|
||||
row: {}
|
||||
});
|
||||
|
||||
// 接收父组件传过来的参数
|
||||
const acceptParams = async (params: DrawerProps) => {
|
||||
drawerProps.value = params;
|
||||
rules.value = generateRules(formColumns.value);
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
|
||||
defineExpose({
|
||||
acceptParams
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.el-dialog .el-dialog__header {
|
||||
padding: 15px 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user