This commit is contained in:
2025-06-25 20:25:40 +08:00
parent 90d974d739
commit 8e6df5b498
14 changed files with 357 additions and 31 deletions

View File

@@ -24,6 +24,7 @@
"uuid": "^11.1.0",
"vite-plugin-vue-devtools": "^7.7.5",
"vue": "^3.5.13",
"vue-draggable-plus": "^0.6.0",
"vue-router": "^4.5.0"
},
"devDependencies": {

21
pnpm-lock.yaml generated
View File

@@ -50,6 +50,9 @@ importers:
vue:
specifier: ^3.5.13
version: 3.5.13
vue-draggable-plus:
specifier: ^0.6.0
version: 0.6.0(@types/sortablejs@1.15.8)
vue-router:
specifier: ^4.5.0
version: 4.5.0(vue@3.5.13)
@@ -1084,6 +1087,9 @@ packages:
'@types/node@16.18.126':
resolution: {integrity: sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==}
'@types/sortablejs@1.15.8':
resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==}
'@types/validator@13.15.0':
resolution: {integrity: sha512-nh7nrWhLr6CBq9ldtw0wx+z9wKnnv/uTVLA9g/3/TcOYxbpOSZE+MhKPmWqU+K0NvThjhv12uD8MuqijB0WzEA==}
@@ -2643,6 +2649,15 @@ packages:
yaml:
optional: true
vue-draggable-plus@0.6.0:
resolution: {integrity: sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw==}
peerDependencies:
'@types/sortablejs': ^1.15.0
'@vue/composition-api': '*'
peerDependenciesMeta:
'@vue/composition-api':
optional: true
vue-router@4.5.0:
resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==}
peerDependencies:
@@ -3731,6 +3746,8 @@ snapshots:
'@types/node@16.18.126': {}
'@types/sortablejs@1.15.8': {}
'@types/validator@13.15.0': {}
'@vitejs/plugin-legacy@6.1.1(terser@5.39.0)(vite@6.3.2(jiti@2.4.2)(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1))':
@@ -5351,6 +5368,10 @@ snapshots:
terser: 5.39.0
yaml: 2.7.1
vue-draggable-plus@0.6.0(@types/sortablejs@1.15.8):
dependencies:
'@types/sortablejs': 1.15.8
vue-router@4.5.0(vue@3.5.13):
dependencies:
'@vue/devtools-api': 6.6.4

View File

@@ -1,17 +1,23 @@
<script setup>
import {onMounted} from "vue";
import {useSystemStore} from "./pinia/SystemStore/index.js";
import {useRoute} from "vue-router";
import {toPath} from "./utils/index.js";
const SystemStore = useSystemStore();
const route = useRoute();
onMounted(() => {
const isRoot = SystemStore.isRoot;
SystemStore.clearRouter().then(() => {
SystemStore.setRouter(isRoot).then(() => {
toPath(SystemStore.NOW_ROUTER, SystemStore.NOW_ROUTER_QUERY);
if (window.location.href.indexOf('/manage-materials') < 0) {
SystemStore.clearRouter().then(() => {
SystemStore.setRouter(isRoot).then(() => {
toPath(SystemStore.NOW_ROUTER, SystemStore.NOW_ROUTER_QUERY);
});
});
});
} else {
toPath('/manage-materials', SystemStore.NOW_ROUTER_QUERY, true);
}
});
</script>

View File

@@ -40,8 +40,12 @@ const changeInput = debounce((value) => {
}
}, 2000);
const pressEnter = () => {
modelValue.value.push(...input.value.split('#').filter(Boolean).map(item => `#${item}`));
const pressEnter = (_input, e) => {
const temp = input.value.split('#').filter(Boolean).map(item => `#${item}`);
nextTick(() => {
modelValue.value.pop();
modelValue.value.push(...temp);
})
}
</script>

View File

@@ -0,0 +1,28 @@
<script setup>
import {ref} from 'vue';
import {Message} from "@arco-design/web-vue";
const visible = ref(false);
const {srcList} = defineProps({
srcList: {
type: Array,
default: () => [],
}
});
</script>
<template>
<div @click="srcList.length===0?Message.warning('暂无预览'):visible=true">
<slot></slot>
</div>
<a-image-preview-group
v-bind="$attrs"
:srcList="srcList"
v-model:visible="visible">
</a-image-preview-group>
</template>
<style scoped lang="scss">
</style>

View File

@@ -57,7 +57,7 @@ function useTableQuery({
watch(
() => [pagination.current, pagination.pageSize],
() => fetchData(),
(val) => fetchData(),
{deep: true, immediate: immediate}
)

View File

@@ -0,0 +1,217 @@
<script setup>
import Comment from "../../components/Comment/index.vue";
import {computed, reactive, ref} from "vue";
import useTableQuery from "../../hooks/useTableQuery.js";
import Api from "../../api/index.js";
import {useRoute} from "vue-router";
import MaterialLibrary from "../../pages/merchant/components/MaterialLibrary.vue";
import AddMaterial from "../../pages/merchant/components/AddMaterial.vue";
import AddComment from "../../pages/merchant/components/AddComment.vue";
import XImageSmallList from "../../components/XImage/XImageSmallList.vue";
import ViewMaterial from "../../pages/merchant/components/ViewMaterial.vue";
import Talk from "../../components/Talk/index.vue";
import {Message} from "@arco-design/web-vue";
const route = useRoute();
const AddMaterialRef = ref();
const po = reactive({
id: route.query.id,
});
const vo = reactive({
limit: {
title_limit: 0,
desc_limit: 0,
tags_limit: 0,
},
page: '',
rows: [],
total: 0,
pageSize: 9,
});
const columns = computed(() => {
return [
{
title: '子任务编号',
dataIndex: 'uid',
},
{
title: '标题',
dataIndex: 'title',
slotName: 'title',
hidden: vo.limit.title_limit === 0,
},
{
title: '正文',
dataIndex: 'content',
slotName: 'content',
hidden: vo.limit.desc_limit === 0,
},
{
title: '话题',
dataIndex: 'tags',
slotName: 'tags',
hidden: vo.limit.tags_limit === 0,
},
{
title: '素材',
dataIndex: 'material',
slotName: 'material',
},
{
title: '评论',
dataIndex: 'pl',
slotName: 'pl',
},
].filter(v => !v.hidden)
});
const {loading, pagination, fetchData} = useTableQuery({
parameter: po,
api: Api.merchant.getTaskChildrenList,
callback: (data) => {
Object.assign(vo, data);
}
});
const updateTable = async () => {
const {data} = await Api.merchant.getTaskChildrenList(po);
vo.rows.forEach((v, v_index) => {
v.childrenMaterial.forEach((k, k_index) => {
k.comment = data.list[v_index].childrenMaterial[k_index].comment;
});
});
}
const editMaterial = (index) => {
AddMaterialRef.value[index].open();
}
const success = async () => {
const {msg} = await Api.merchant.editChildrenMaterimal({
id: vo.rows[0]?.task_id,
data: vo.rows.filter(v => v.status !== 1).map(v => v.childrenMaterial.map(k => ({
id: k.id,
title: k.title,
material: k.material,
content: k.content,
tags: k.tags,
}))).flat()
});
Message.success(msg);
await fetchData();
}
</script>
<template>
<!-- 批量管理素材 -->
<a-card class="flex-grow text-[14px]">
<div class="flex justify-between">
<MaterialLibrary :id="po.id"></MaterialLibrary>
<a-button type="primary" @click="success">确认修改</a-button>
</div>
<a-table
:data="vo.rows"
:loading="loading"
:columns="columns"
:scroll="{x: 'auto'}"
class="w-full mt-[20px] flex-grow"
@page-change="(e) => pagination.current = e"
:pagination="false">
<template v-slot:title="{record}">
<div class="flex flex-col gap-[12px]">
<a-input
:disabled="record.status !== 0 && record.status !== 2"
v-for="v in record.childrenMaterial"
placeholder="请输入标题"
v-model:model-value="v.title">
</a-input>
</div>
</template>
<template v-slot:content="{record}">
<div class="flex flex-col gap-[12px]">
<a-input
:disabled="record.status !== 0 && record.status !== 2"
v-for="v in record.childrenMaterial"
placeholder="请输入正文"
v-model:model-value="v.content">
</a-input>
</div>
</template>
<template v-slot:tags="{record}">
<div class="flex flex-col gap-[12px]">
<div class="flex gap-[12px]" v-for="v in record.childrenMaterial">
<Talk v-model:model-value="v.tags"
:disabled="record.status !== 0 && record.status !== 2"></Talk>
</div>
</div>
</template>
<template v-slot:material="{record, rowIndex}">
<div class="flex flex-col gap-[12px]">
<div class="flex gap-[12px]" v-for="v in record.childrenMaterial">
<add-material
:disabled="record.status !== 0 && record.status !== 2"
ref="AddMaterialRef"
@success="val => v.material = val"
:id="po.id"
:material="v">
<div
:class="['add-materials', record.status !== 0 && record.status !== 2 ? 'dis-materials' : '']">
<icon-plus/>
<div>添加</div>
</div>
</add-material>
<view-material
:disabled="record.status !== 0 && record.status !== 2"
@edit="editMaterial(rowIndex)"
:material="v"
:id="po.id">
<x-image-small-list
:preview="false"
v-if="v.material.length>0"
:list="v.material">
</x-image-small-list>
</view-material>
</div>
</div>
</template>
<template v-slot:pl="{record}">
<div class="flex flex-col gap-[12px]">
<div v-for="(v, index) in record.childrenMaterial" :key="index" class="flex gap-[12px]">
<add-comment
v-if="record.status === 0 || record.status === 2"
@success="updateTable"
:material="record"
:item="v">
<div class="add-materials">
<icon-plus/>
<div>添加</div>
</div>
</add-comment>
<comment
:hide-delete="record.status !== 0 && record.status !== 2"
@success="updateTable"
:data="v.comment">
</comment>
</div>
</div>
</template>
</a-table>
</a-card>
</template>
<style scoped lang="scss">
.add-materials {
@apply size-[40px] bg-[#F2F3F5] text-[#4E5969] duration-500 flex flex-col justify-center items-center cursor-pointer;
border: 1px dashed rgb(229, 230, 235);
&:hover {
@apply text-[#86909C] bg-[#F2F3F5];
border: 1px dashed rgb(229, 230, 235);
}
}
.dis-materials {
cursor: not-allowed;
}
</style>

View File

@@ -6,13 +6,14 @@ import Api from "../../../api/index.js";
import {v4} from "uuid";
import MaterialSource from "./MaterialSource.vue";
import {useSystemStore} from "../../../pinia/SystemStore/index.js";
import {vDraggable} from 'vue-draggable-plus'
const SystemStore = useSystemStore();
const MaterialSourceRef = ref();
const visible = ref(false);
const targetList = reactive([]);
const emits = defineEmits(['success']);
const {id, material} = defineProps({
const {id, material, disabled} = defineProps({
id: {
type: Number,
default: null,
@@ -20,6 +21,10 @@ const {id, material} = defineProps({
material: {
type: Object,
default: null,
},
disabled: {
type: Boolean,
default: false,
}
});
@@ -57,10 +62,10 @@ const success = async () => {
<template>
<!-- 素材库 -->
<a-button v-if="!$slots.default" type="primary" @click="visible=true">
<a-button v-if="!$slots.default" type="primary" @click="disabled ? null : visible=true">
添加素材
</a-button>
<div @click="visible=true" v-else>
<div @click="disabled ? null : visible=true" v-else>
<slot></slot>
</div>
@@ -72,7 +77,14 @@ const success = async () => {
title="添加素材">
<div class="target">
<div class="text-[#1D2129] text-[14px]">已添加{{ targetList.length }}个素材长摁图片可拖动排序</div>
<div class="grid grid-cols-6 mt-[10px] gap-[16px]">
<div
v-draggable="[
targetList,
{
animation: 150,
}
]"
class="grid grid-cols-6 mt-[10px] gap-[16px]">
<x-image
@delete="targetList.splice(index,1)"
v-for="(v, index) in targetList"

View File

@@ -26,7 +26,7 @@ const form = reactive({
const setTime = () => {
const unit = form.type === 0 ? 'minute' : 'hour';
form.startTime = dayjs(startTime).add(form.diff, unit).format('YYYY-MM-DD HH:mm');
form.endTime = dayjs(endTime).add(form.diff, unit).format('YYYY-MM-DD HH:mm');
form.endTime = dayjs(startTime).add((form.diff * 2), unit).format('YYYY-MM-DD HH:mm');
}
const open = () => {
@@ -55,7 +55,8 @@ const success = () => {
v-model:visible="visible">
<a-form layout="vertical">
<a-form-item label="任务可接时间段">
<a-input disabled v-model:model-value="form.startTime"></a-input>
<a-input disabled
:model-value="`${dayjs(startTime).format('MMDD-HH:mm')} - ${dayjs(endTime).format('MMDD-HH:mm')}`"></a-input>
</a-form-item>
<a-form-item label="在任务可接时段的开始时间和结束时间,各自增加">
<div class="flex w-full gap-3">
@@ -68,8 +69,9 @@ const success = () => {
</a-radio-group>
</div>
</a-form-item>
<a-form-item label="回填时间段为" v-model:model-value="form.endTime">
<a-input disabled v-model:model-value="form.endTime"></a-input>
<a-form-item label="回填时间段为">
<a-input
:model-value="`${dayjs(form.startTime).format('MMDD-HH:mm')} - ${dayjs(form.endTime).format('MMDD-HH:mm')}`"></a-input>
</a-form-item>
</a-form>
</a-modal>

View File

@@ -104,7 +104,7 @@ const confirmTask = async () => {
images: v.images,
})),
});
visible.value = false;
Message.success(msg);
emits('success');
}
@@ -121,7 +121,6 @@ const confirmTask = async () => {
<a-modal
width="800px"
title-align="start"
@ok="confirmTask"
:footer="!yy"
v-model:visible="visible">
<template v-slot:title>
@@ -177,6 +176,12 @@ const confirmTask = async () => {
}}
</div>
</div>
<template v-slot:footer>
<div>
<a-button @click="visible=false">取消</a-button>
<a-button type="primary" @click="confirmTask">确定</a-button>
</div>
</template>
</a-modal>
</template>

View File

@@ -3,9 +3,10 @@ import FormTitle from "../../../../../components/FormTitle/index.vue";
import Backfill from "./Backfill.vue";
import {v4} from "uuid";
import XTimePicker from "../../../../../components/XTimePicker/XTimePicker.vue";
import {useTemplateRef} from "vue";
import {onMounted, reactive, useTemplateRef} from "vue";
import {Message} from "@arco-design/web-vue";
import Api from "../../../../../api/index.js";
import Preview from "../../../../../components/XImage/Preview.vue";
const INDEX = ['一', '二', '三'];
const emits = defineEmits(['success', 'prev']);
@@ -68,6 +69,16 @@ const success = async () => {
}
});
}
const SelectList = reactive([]);
const getSelectList = async () => {
const {data} = await Api.merchant.getChooseContent({id: form.value.platform_id});
SelectList.length = 0;
SelectList.push(...data);
}
onMounted(() => {
getSelectList();
})
</script>
<template>
@@ -109,12 +120,16 @@ const success = async () => {
<Backfill :form="form" :index="index" v-model:po="form.backfill[index]"></Backfill>
<div class="flex gap-[8px]">
<a-button type="outline">
<template #icon>
<icon-bulb/>
</template>
查看指引
</a-button>
<Preview
:srcList="SelectList.find(v => v.id === form.backfill[index].content_id)?.image_arr || []"
infinite>
<a-button type="outline">
<template #icon>
<icon-bulb/>
</template>
查看指引
</a-button>
</Preview>
<a-button
v-if="form.backfill.length > 1"
@click="form.backfill.splice(index, 1)"

View File

@@ -7,7 +7,7 @@ import RefuseModal from "./components/RefuseModal.vue";
import openTerminateTask from "../../../../components/TerminateTask/TerminateTask.js";
import BlackjackExpertModal from "../../components/BlackjackExpertModal.vue";
import {useRoute} from "vue-router";
import {openUrl, toPath} from "../../../../utils/index.js";
import {openPage, openUrl} from "../../../../utils/index.js";
import PreviewTaskMaterialModal from "./components/PreviewTaskMaterialModal.vue";
import {Message} from "@arco-design/web-vue";
import EffectManagementModal from "./components/effectManagementModal.vue";
@@ -121,7 +121,7 @@ const passTask = async (id, task_backfill_id) => {
<a-card class="flex-grow text-[14px]">
<div class="gap-[20px]">
<a-button type="primary" @click="toPath('/home/task-center/manage-materials', {id: po.id})">
<a-button type="primary" @click="openPage('/manage-materials', {id: po.id})">
<template #icon>
<icon-apps/>
</template>
@@ -232,7 +232,7 @@ const passTask = async (id, task_backfill_id) => {
</template>
<template v-slot:payStatus="{record}">
<settlement :task="record" :disabled="record.is_settlement > 0"></settlement>
<settlement :task="record" :disabled="record.is_settlement > 0" @success="fetchData"></settlement>
</template>
<template v-slot:action2="{record}">

View File

@@ -20,12 +20,16 @@ export const useSystemStore = defineStore("SystemStore", () => {
redirect: `/home/${routes[0].path}`,
children: routes
});
router.addRoute({
path: '/manage-materials',
name: 'manage-materials',
component: () => import('../../pages/manage-materials/manage-materials.vue'),
});
await router.replace(router.currentRoute.value.fullPath);
}
const setRouter = async (_isRoot) => {
isRoot.value = _isRoot;
console.log('我看看啥权限', _isRoot);
RoutesTemp.value.length = 0;
@@ -52,6 +56,11 @@ export const useSystemStore = defineStore("SystemStore", () => {
redirect: `/home/${routes[0].path}`,
children: []
});
router.addRoute({
path: '/manage-materials',
name: 'manage-materials',
component: () => import('../../pages/manage-materials/manage-materials.vue'),
});
isRoot.value = false;
await router.replace(router.currentRoute.value.fullPath);
}

View File

@@ -1,16 +1,22 @@
import router from "../router/index.js";
import {useSystemStore} from "../pinia/SystemStore/index.js";
export const toPath = (path, query = {}) => {
export const toPath = (path, query = {}, flag = false) => {
router.push({
path: path, query: query
}).then(() => {
const SystemStore = useSystemStore();
SystemStore.NOW_ROUTER = path;
Object.assign(SystemStore.NOW_ROUTER_QUERY, query);
if (!flag) {
SystemStore.NOW_ROUTER = path;
Object.assign(SystemStore.NOW_ROUTER_QUERY, query);
}
});
}
export const openPage = (path, query = {}) => {
window.open('http://localhost:9050/#/manage-materials', '_blank');
}
export const VITE_TINYMCE_KEY = () => {
return import.meta.env.VITE_TINYMCE_KEY;
}