feat: 内容稿件审核列表

This commit is contained in:
rd
2025-08-04 17:05:44 +08:00
parent eb76d00a8e
commit e2f2d26d00
14 changed files with 190 additions and 56 deletions

View File

@ -8,6 +8,14 @@ export const getWorksList = (params = {}) => {
export const postWorksBatch = (params = {}) => {
return Http.post('/v1/works/batch', params);
};
// 生成分享链接
export const postShareLinksGenerate = (params = {}) => {
return Http.post('/v1/share-links/generate', params);
};
// 生成写手链接
export const postWriterLinksGenerate = (params = {}) => {
return Http.post('/v1/writer-links/generate', params);
};
// 内容稿件-修改
export const putWorksUpdate = (params = {}) => {
const { id, ...rest } = params as { id: string; [key: string]: any };

View File

@ -45,6 +45,7 @@ export function useTableSelectionWithPagination(options: UseTableSelectionWithPa
// 全选/取消全选
const handleSelectAll = (checked: boolean) => {
console.log('handleSelectAll', checked)
const currentPageRows = dataSource.value;
const currentPageKeys = currentPageRows.map((v) => v[rowKey]);

View File

@ -81,6 +81,20 @@ const COMPONENTS: AppRouteRecordRaw[] = [
},
component: () => import('@/views/creative-generation-workshop/manuscript/check-list/index.vue'),
},
{
path: 'check-list/detail/:id',
name: 'ManuscriptCheckListDetail',
meta: {
locale: '内容稿件审核详情',
requiresAuth: false,
requireLogin: false,
hideFooter: true,
hideInMenu: true,
roles: ['*'],
activeMenu: 'ManuscriptCheckList',
},
component: () => import('@/views/creative-generation-workshop/manuscript/detail/index.vue'),
},
{
path: 'check/:id',
name: 'ManuscriptCheck',

View File

@ -15,4 +15,8 @@
&.arco-input-disabled {
background-color: var(--BG-200, #F2F3F5) !important;
}
.arco-input-prefix {
padding-right: 0 !important;
margin-right: 4px;
}
}

View File

@ -9,6 +9,7 @@
}
.arco-table-container {
border: none !important;
height: 100%;
.arco-table-element {
thead {
.arco-table-tr {

View File

@ -10,8 +10,8 @@
<span>确认删除 {{ projectName }} 这个稿件吗</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px" status="danger" size="large" @click="onDelete"
<a-button size="medium" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px" status="danger" size="medium" @click="onDelete"
>确认删除</a-button
>
</template>

View File

@ -8,7 +8,11 @@
:scroll="{ x: '100%' }"
class="flex-1 manuscript-table w-100%"
bordered
:row-selection="rowSelection"
:selected-row-keys="selectedRowKeys"
@sorter-change="handleSorterChange"
@select="(selectedKeys, rowKeyValue, record) => emits('select', selectedKeys, rowKeyValue, record)"
@select-all="(check) => emits('selectAll', check)"
>
<template #empty>
<NoData text="暂无稿件" />
@ -35,8 +39,10 @@
</div>
</template>
<template v-if="column.dataIndex === 'create_at'" #cell="{ record }">
{{ exactFormatTime(record.create_at) }}
<template v-if="column.dataIndex === 'compliance_degree'" #cell="{ record }">
<span class="cts num !color-#6D4CFE">{{
record.compliance_degree ? `${record.compliance_degree}%` : '-'
}}</span>
</template>
<template v-else-if="column.dataIndex === 'customer_opinion'" #cell="{ record }">
<p
@ -44,7 +50,7 @@
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
>
<span class="cts" :class="getCustomerOpinionInfo(record.customer_opinion)?.color">{{
getCustomerOpinionInfo(record.customer_opinion)?.label
getCustomerOpinionInfo(record.customer_opinion)?.label ?? '-'
}}</span>
</p>
</template>
@ -76,7 +82,7 @@
['updated_at', 'last_modified_at', 'audit_started_at', 'audit_passed_at'].includes(column.dataIndex)
"
>
{{ exactFormatTime(record[column.dataIndex]) }}
<span class="cts num">{{ exactFormatTime(record[column.dataIndex]) }}</span>
</template>
<template v-else-if="column.dataIndex === 'cover'" #cell="{ record }">
<a-image :width="64" :height="64" :src="record.cover" class="!rounded-6px">
@ -88,14 +94,17 @@
<template v-else-if="column.dataIndex === 'operation'" #cell="{ record }">
<div class="flex items-center">
<img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" />
<a-button type="outline" size="mini" @click="onShare(record)" v-if="audit_status === AuditStatus.Passed"
>分享</a-button
>
<a-button
type="outline"
size="mini"
@click="onShare(record)"
v-if="audit_status === AuditStatus.Passed"
>分享</a-button
@click="onCheck(record)"
v-else-if="audit_status === AuditStatus.Pending"
>审核</a-button
>
<a-button type="outline" size="mini" @click="onCheck(record)" v-else>审核</a-button>
<a-button type="outline" size="mini" @click="onCheck(record)" v-else>查看</a-button>
</div>
</template>
<template v-else #cell="{ record }">
@ -104,6 +113,8 @@
</a-table-column>
</template>
</a-table>
<ShareModal ref="shareModalRef" />
</template>
<script setup>
@ -117,13 +128,14 @@ import {
} from '@/views/creative-generation-workshop/manuscript/check-list/constants';
import TextOverTips from '@/components/text-over-tips';
import ShareModal from '@/views/creative-generation-workshop/manuscript/components/share-manuscript-modal/share-modal.vue';
import icon1 from '@/assets/img/media-account/icon-delete.png';
import icon2 from '@/assets/img/creative-generation-workshop/icon-photo.png';
import icon3 from '@/assets/img/creative-generation-workshop/icon-video.png';
import icon4 from '@/assets/img/error-img.png';
const emits = defineEmits(['edit', 'sorterChange', 'delete']);
const emits = defineEmits(['edit', 'sorterChange', 'delete', 'select', 'selectAll']);
const router = useRouter();
const props = defineProps({
@ -135,12 +147,21 @@ const props = defineProps({
type: Array,
default: () => [],
},
rowSelection: {
type: Array,
default: () => [],
},
selectedRowKeys: {
type: Array,
default: () => [],
},
audit_status: {
type: String,
}
},
});
const tableRef = ref(null);
const shareModalRef = ref(null);
const handleSorterChange = (column, order) => {
emits('sorterChange', column, order === 'ascend' ? 'asc' : 'desc');
@ -149,15 +170,15 @@ const onDelete = (item) => {
emits('delete', item);
};
const onShare = (item) => {
console.log('onShare', item);
shareModalRef.value?.open([item.id]);
};
const onCheck = (item) => {
router.push(`/manuscript/detail/${item.id}`);
router.push(`/manuscript/check/${item.id}`);
};
const onDetail = (item) => {
router.push(`/manuscript/detail/${item.id}`);
router.push(`/manuscript/check-list/detail/${item.id}?source=check&audit_status=${props.audit_status}`);
};
const getCustomerOpinionInfo = (value = 1) => {
const getCustomerOpinionInfo = (value) => {
return CUSTOMER_OPINION.find((item) => item.value === value);
};
</script>

View File

@ -6,6 +6,9 @@
font-style: normal;
font-weight: 400;
line-height: 22px;
&.num {
font-family: $font-family-manrope-regular;
}
}
:deep(.title) {
cursor: pointer;

View File

@ -88,6 +88,11 @@ export const TABLE_COLUMNS2 = [
dataIndex: 'platform',
width: 120,
},
{
title: '合规程度',
dataIndex: 'compliance_degree',
width: 120,
},
// {
// title: '合规程度',
// dataIndex: 'platform',

View File

@ -2,9 +2,9 @@
<div class="manuscript-check-wrap">
<div class="filter-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid mb-16px">
<a-tabs v-model="query.audit_status" @tab-click="handleTabClick">
<a-tab-pane title="待审核" v-for="item in AUDIT_STATUS_LIST" :key="item.value">{{ item.label }}</a-tab-pane>
<a-tab-pane :title="item.label" v-for="item in AUDIT_STATUS_LIST" :key="item.value"></a-tab-pane>
<template #extra>
<a-button type="outline" size="medium" @click="handleOpenAddProjectModal">分享内容稿件</a-button>
<a-button type="outline" size="medium" @click="handleShareModal">分享内容稿件</a-button>
</template>
</a-tabs>
<FilterBlock
@ -17,13 +17,37 @@
<div
class="table-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid px-24px py-24px flex-1 flex flex-col"
>
<div class="flex justify-end mb-12px" v-if="[AuditStatus.Pending, AuditStatus.Auditing].includes(query.audit_status)">
<a-button
type="outline"
class="w-fit"
size="medium"
@click="handleBatchCheck"
v-if="query.audit_status === AuditStatus.Pending"
>批量审核</a-button
>
<a-button
type="outline"
class="w-fit"
size="medium"
@click="handleBatchView"
v-if="query.audit_status === AuditStatus.Auditing"
>批量查看</a-button
>
</div>
<ManuscriptCheckTable
:key="query.audit_status"
:tableColumns="tableColumns"
:rowSelection="rowSelection"
:selectedRowKeys="selectedRowKeys"
:dataSource="dataSource"
:audit_status="query.audit_status"
@sorterChange="handleSorterChange"
@delete="handleDelete"
@edit="handleEdit"
@select="handleSelect"
@selectAll="handleSelectAll"
/>
<div v-if="pageInfo.total > 0" class="pagination-box">
<a-pagination
@ -41,19 +65,22 @@
</div>
<DeleteManuscriptModal ref="deleteManuscriptModalRef" />
<ShareManuscriptModal ref="shareManuscriptModalRef" />
</div>
</template>
<script lang="jsx" setup>
import { defineComponent } from 'vue';
import { Button } from '@arco-design/web-vue';
import { Button, Message as AMessage } from '@arco-design/web-vue';
import FilterBlock from './components/filter-block';
import ManuscriptCheckTable from './components/manuscript-check-table';
import DeleteManuscriptModal from './components/manuscript-check-table/delete-manuscript-modal.vue';
import ShareManuscriptModal from '@/views/creative-generation-workshop/manuscript/components/share-manuscript-modal';
import { getWorkAuditsPage } from '@/api/all/generationWorkshop.ts';
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
import { getProjects } from '@/api/all/propertyMarketing';
import {
AuditStatus,
INITIAL_QUERY,
AUDIT_STATUS_LIST,
TABLE_COLUMNS1,
@ -61,14 +88,18 @@ import {
TABLE_COLUMNS3,
} from '@/views/creative-generation-workshop/manuscript/check-list/constants';
console.log({AUDIT_STATUS_LIST})
const {
dataSource,
pageInfo,
rowSelection,
onPageChange,
onPageSizeChange,
resetPageInfo,
selectedRowKeys,
selectedRows,
handleSelect,
handleSelectAll,
DEFAULT_PAGE_INFO,
} = useTableSelectionWithPagination({
onPageChange: () => {
@ -78,10 +109,13 @@ const {
getData();
},
});
const router = useRouter();
const tableColumns = ref([]);
const query = ref(cloneDeep(INITIAL_QUERY));
const addManuscriptModalRef = ref(null);
const deleteManuscriptModalRef = ref(null);
const shareManuscriptModalRef = ref(null);
const getData = async () => {
const { page, page_size } = pageInfo.value;
@ -112,7 +146,21 @@ const handleSorterChange = (column, order) => {
query.value.sort_order = order;
reload();
};
const handleBatchCheck = () => {
if (!selectedRows.value.length) {
AMessage.warning('请选择需审核的内容稿件');
return;
}
const ids = selectedRowKeys.value.join(',');
router.push(`/manuscript/check/${ids}`);
};
const handleBatchView = () => {
if (!selectedRows.value.length) {
AMessage.warning('请选择需查看的内容稿件');
return;
}
};
const handleTabClick = (key) => {
query.value = cloneDeep(INITIAL_QUERY);
dataSource.value = [];
@ -124,13 +172,13 @@ const handleTabClick = (key) => {
getData();
};
const handleOpenAddProjectModal = () => {
console.log('handleOpenAddProjectModal');
const handleShareModal = () => {
shareManuscriptModalRef.value.open();
};
const handleDelete = (item) => {
const { id, name } = item;
deleteManuscriptModalRef.value?.open({ id, name: `${name}` });
const { id, title } = item;
deleteManuscriptModalRef.value?.open({ id, name: `${title}` });
};
const handleEdit = (item) => {
// addManuscriptModalRef.value?.open(item.id);

View File

@ -12,7 +12,7 @@ import {
} from '@arco-design/web-vue';
import CommonSelect from '@/components/common-select';
import TextOverTips from '@/components/text-over-tips';
import ShareModal from './share-modal';
import ShareModal from '@/views/creative-generation-workshop/manuscript/components/share-manuscript-modal/share-modal';
import { INITIAL_FORM, TABLE_COLUMNS } from './constants';
import { formatTableField, exactFormatTime } from '@/utils/tools';
@ -116,7 +116,7 @@ export default {
};
const onShare = () => {
shareModalRef.value?.open();
shareModalRef.value?.open(selectedRowKeys.value);
};
const handleSorterChange = (column, order) => {
query.value.sort_column = column;
@ -145,13 +145,13 @@ export default {
已选择 <span class="cts color-#211F24 bold">{selectedRows.value.length}</span>
</p>
<div class="flex items-center">
<Button size="large" onClick={onClose}>
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button
type="primary"
class="ml-16px"
size="large"
size="medium"
onClick={onShare}
disabled={!selectedRows.value.length}
>

View File

@ -3,11 +3,12 @@ import { Modal, Form, FormItem, Input, Button, Message as AMessage } from '@arco
import CommonSelect from '@/components/common-select';
import { useClipboard } from '@vueuse/core';
import { postShareLinksGenerate } from '@/api/all/generationWorkshop';
const INITIAL_FORM = {
link: '',
data: 1,
target: '',
work_ids: [],
days: 1,
receiver: '',
};
const OPTIONS = [
{
@ -42,10 +43,12 @@ export default {
const visible = ref(false);
const formRef = ref(null);
const formData = ref(cloneDeep(INITIAL_FORM));
const loading = ref(false);
const { copy } = useClipboard({ source: formData.value.link });
const reset = () => {
loading.value = false;
formData.value = cloneDeep(INITIAL_FORM);
formRef.value?.resetFields?.();
formRef.value?.clearValidate?.();
@ -56,13 +59,22 @@ export default {
reset();
};
const onCopy = () => {
onClose();
copy(formData.value.link);
AMessage.success('复制成功!');
emit('close');
const onGenerateLink = async () => {
try {
loading.value = true;
const { code, data } = await postShareLinksGenerate(formData.value);
if (!code) {
onClose();
copy(data.code);
AMessage.success('复制成功!');
emit('close');
}
} finally {
loading.value = false;
}
};
const open = () => {
const open = (workIds) => {
formData.value.work_ids = workIds;
visible.value = true;
};
@ -79,23 +91,20 @@ export default {
v-slots={{
footer: () => (
<>
<Button size="large" onClick={onClose}>
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button type="primary" class="ml-16px" size="large" onClick={onCopy}>
复制链接
<Button type="primary" class="ml-16px" size="medium" onClick={onGenerateLink} disabled={loading.value}>
{loading.value ? '生成中...' : '生成链接'}
</Button>
</>
),
}}
>
<Form ref={formRef} model={formData.value} auto-label-width>
<FormItem label="分享地址" prop="link">
<Input v-model={formData.value.link} size="large" placeholder="请输入分享地址" />
</FormItem>
<FormItem label="有效期" prop="data">
<FormItem label="有效期" prop="days">
<CommonSelect
v-model={formData.value.data}
v-model={formData.value.days}
options={OPTIONS}
multiple={false}
placeholder="请选择有效期"
@ -104,8 +113,8 @@ export default {
allClear={false}
/>
</FormItem>
<FormItem label="分享对象" prop="target">
<Input v-model={formData.value.target} class="!w-240px" size="large" placeholder="请输入分享对象" />
<FormItem label="分享对象" prop="receiver">
<Input v-model={formData.value.receiver} class="!w-240px" size="large" placeholder="请输入分享对象" />
</FormItem>
</Form>
</Modal>

View File

@ -1,25 +1,36 @@
<script lang="jsx">
import { Button, Message as AMessage } from '@arco-design/web-vue';
import { useRouter, useRoute } from 'vue-router';
import { AuditStatus } from '@/views/creative-generation-workshop/manuscript/check-list/constants';
const DEFAULT_SOURCE_INFO = {
title: '内容稿件列表',
routeName: 'ManuscriptList',
};
const SOURCE_MAP = new Map([['check', { title: '内容稿件审核', routeName: 'ManuscriptCheckList' }]]);
export default {
setup(props, { emit, expose }) {
const router = useRouter();
const route = useRoute();
const workId = ref(route.params.id);
const { source, audit_status } = route.query;
const sourceInfo = computed(() => SOURCE_MAP.get(source) ?? DEFAULT_SOURCE_INFO);
console.log({ source });
// 视频播放相关状态
const isPlaying = ref(false);
const videoRef = ref(null);
const videoUrl = ref('');
const coverImageUrl = ref('');
const isVideoLoaded = ref(false);
const videoUrl = ref('');
const coverImageUrl = ref('');
const isVideoLoaded = ref(false);
const contentType = ref('video');
const isVideo = computed(() => contentType.value === 'video');
const onBack = () => {
router.push({ name: 'ManuscriptList' });
router.push({ name: sourceInfo.value.routeName });
};
const renderMainImg = () => {
@ -61,6 +72,15 @@ export default {
};
const renderFooterRow = () => {
const isPassed = audit_status === AuditStatus.Passed;
const _fn = () => {
if (isPassed) {
console.log('审核详情');
} else {
router.push(`/manuscript/check/${workId.value}`);
}
};
return (
<>
<Button size="medium" type="outline" class="mr-12px" onClick={onBack}>
@ -74,8 +94,8 @@ export default {
>
编辑
</Button>
<Button type="primary" size="medium" onClick={() => router.push(`/manuscript/check/${workId.value}`)}>
去审核
<Button type="primary" size="medium" onClick={_fn}>
{isPassed ? '审核详情' : '去审核'}
</Button>
</>
);
@ -87,7 +107,7 @@ export default {
<div class="manuscript-detail-wrap">
<div class="flex items-center mb-8px">
<span class="cts color-#4E5969 cursor-pointer" onClick={onBack}>
内容稿件列表
{sourceInfo.value.title}
</span>
<icon-oblique-line size="12" class="color-#C9CDD4 mx-4px" />
<span class="cts bold !color-#1D2129">内容稿件详情</span>

View File

@ -4,7 +4,7 @@
<div class="top flex h-64px px-24px py-10px justify-between items-center">
<p class="text-18px font-400 lh-26px color-#211F24 title">内容稿件列表</p>
<div class="flex items-center">
<a-button type="outline" size="medium" class="mr-12px" @click="handleOpenAddProjectModal">
<a-button type="outline" size="medium" class="mr-12px" @click="handleShareModal">
分享内容稿件
</a-button>
<a-button type="primary" size="medium" @click="openUploadModal">
@ -48,7 +48,7 @@ import FilterBlock from './components/filter-block';
import ManuscriptTable from './components/manuscript-table';
import DeleteManuscriptModal from './components/manuscript-table/delete-manuscript-modal.vue';
import UploadManuscriptModal from './components/upload-manuscript-modal';
import ShareManuscriptModal from '../components/share-manuscript-modal';
import ShareManuscriptModal from '@/views/creative-generation-workshop/manuscript/components/share-manuscript-modal';
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
import { getWorksPage } from '@/api/all/generationWorkshop.ts';
@ -98,7 +98,7 @@ const handleSorterChange = (column, order) => {
reload();
};
const handleOpenAddProjectModal = () => {
const handleShareModal = () => {
shareManuscriptModalRef.value.open()
};
const openUploadModal = () => {