Merge remote-tracking branch 'origin/main' into feature/0905_登录注册流程重构

# Conflicts:
#	src/App.vue
#	src/views/components/login/index.vue
#	src/views/components/management/person/index.vue
#	src/views/login/style.scss
This commit is contained in:
rd
2025-09-10 16:16:34 +08:00
233 changed files with 5564 additions and 6102 deletions

View File

@ -7,9 +7,9 @@
</div>
<div class="info-section">
<div class="title-group">
<a-tooltip :content="cozeInfo.name">
<Tooltip :title="cozeInfo.name">
<div class="title">{{ cozeInfo.name }}</div>
</a-tooltip>
</Tooltip>
<div class="tag">
<div>
<img class="status-icon" :src="chatbotIcon" />
@ -18,12 +18,8 @@
</div>
</div>
<div class="usage-info">
<a-space>
<span class="count">{{ cozeInfo.views }}</span>
</a-space>
<a-space>
<span class="label"> 次使用 </span>
</a-space>
<span class="count">{{ cozeInfo.views }}</span>
<span class="label"> 次使用 </span>
</div>
</div>
<div class="description-section">
@ -36,6 +32,7 @@
<script lang="ts" setup>
import { defineProps } from 'vue';
import { Tooltip } from 'ant-design-vue';
import chatbotIcon from '@/assets/svg/chatbot.svg';
const props = defineProps({

View File

@ -36,9 +36,9 @@
<div class="body">
<div class="">
<div class="toggle-btn cursor-pointer" @click="toggleCollapse">
<a-tooltip :content="isCollapsed ? '展开' : '折叠'">
<Tooltip :title="isCollapsed ? '展开' : '折叠'">
<img class="status-icon" :src="isCollapsed ? menuUnfold : menuFold" />
</a-tooltip>
</Tooltip>
</div>
</div>
</div>
@ -55,6 +55,7 @@ import { getChatAgent } from '@/api/all/agent';
import { useRouter } from 'vue-router';
import menuFold from '@/assets/svg/menu-fold.svg';
import menuUnfold from '@/assets/svg/menu-unfold.svg';
import { Tooltip } from 'ant-design-vue';
import { formatNumberShow } from '@/utils/tools';
const router = useRouter();

View File

@ -1,41 +1,42 @@
<template>
<div class="agent-wrap relative h-full pl-16px">
<a-input
v-model="query.name"
<Input
v-model:value="query.name"
@press-enter="getData()"
placeholder="搜索智能体"
size="large"
allow-clear
class="absolute right-0 top-4px !w-400px"
class="absolute right-0 top-4px !w-400px"
>
<template #prefix>
<icon-search @click="getData()" />
</template>
</a-input>
<div v-for="(item, index) in list" :key="index">
</Input>
<div v-for="(item, index) in list" :key="index">
<p class="span-title w-fit mb-16px">{{ item.name }}</p>
<a-row class="grid-demo" :gutter="[20, 16]" v-if="item.agent_products.length > 0">
<a-col :xs="24"
:sm="12"
:md="8"
:lg="5"
:xl="6"
:xxl="4"
v-for="(product, k) in item.agent_products" :key="k">
<Row class="grid-demo" :gutter="[20, 16]" v-if="item.agent_products.length > 0">
<Col :xs="24" :sm="12" :md="8" :lg="5" :xl="6" :xxl="4" v-for="(product, k) in item.agent_products" :key="k">
<div class="card-container cursor-pointer !h-252px" @click="goDetail(product?.type, product?.id)">
<div class="card-image h-120px w-100% bg-cover bg-center mb-8px" v-image-main-color="product.image_url">
<img class="object-contain h-full w-100% " :src="product?.image_url"/>
<div class="card-image h-120px w-100% bg-cover bg-center mb-8px" v-image-main-color="product.image_url">
<img class="object-contain h-full w-100%" :src="product?.image_url" />
</div>
<div class="card-content w-full">
<TextoverTips :context="product.name" class="card-title mb-4px !text-16px"/>
<TextoverTips :context="product.description" class="card-description mb-8px color-#737478 text-14px lh-22px font-400" :line="2" />
<TextOverTips :context="product.name" class="card-title mb-4px !text-16px" />
<TextOverTips
:context="product.description"
class="card-description mb-8px color-#737478 text-14px lh-22px font-400"
:line="2"
/>
</div>
<div class="card-footer">
<div
:class="['status-tag', product.type === 1 ? 'blue-tag' : 'red-tag']"
:style="{ background: product.type === 1 ? 'var(--Functional-Blue-1, #F0EDFF)' : 'var(--Functional-Red-1, #FFE9E7)' }"
:style="{
background:
product.type === 1 ? 'var(--Functional-Blue-1, #F0EDFF)' : 'var(--Functional-Red-1, #FFE9E7)',
}"
data-size="mini-20px"
>
<SvgIcon
@ -48,13 +49,15 @@
<div class="status-text">{{ product.type === 1 ? '对话式' : '工作流' }}</div>
</div>
<div class="usage-info">
<div class="usage-count mr-2px">{{ formatNumberShow({ value: product?.views, showExactValue: true }) }}</div>
<div class="usage-count mr-2px">
{{ formatNumberShow({ value: product?.views, showExactValue: true }) }}
</div>
<div class="usage-label">次使用</div>
</div>
</div>
</div>
</a-col>
</a-row>
</Col>
</Row>
<NoData v-else />
</div>
@ -62,10 +65,11 @@
</template>
<script setup lang="ts">
import { Input, Row, Col } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import { getAgentList } from '@/api/all/agent';
import { formatNumberShow } from "@/utils/tools";
import TextoverTips from "@/components/text-over-tips";
import { formatNumberShow } from '@/utils/tools';
import TextOverTips from '@/components/text-over-tips';
const router = useRouter();

View File

@ -1,29 +1,26 @@
<template>
<div class="form-container">
<a-form :model="formData" ref="formRef" layout="vertical">
<a-form-item
<Form :model="formData" ref="formRef" layout="vertical">
<FormItem
v-for="(field, index) in formFields"
:key="index"
:label="field.props.label"
:field="field.props.name"
:name="field.props.name"
:rules="field.props.rules"
:tooltip="field.props.tip"
>
<a-input
<Input
allowClear
v-if="field.type === 'input'"
v-model="formData[field.props.name]"
v-model:value="formData[field.props.name]"
:placeholder="field?.props?.placeholder"
/>
<a-textarea
<TextArea
v-if="field.type === 'textarea'"
style="width: 500px; height: 200px"
v-model="formData[field.props.name]"
v-model:value="formData[field.props.name]"
:placeholder="field?.props?.placeholder"
/>
<!-- <a-color-picker v-if="field.type === 'color_picker'"
style="width: 500px; height: 200px"
v-model="formData[field.props.name]" /> -->
<ImageUpload
v-if="field.type == 'upload_image'"
v-model="formData[field.props.name]"
@ -34,24 +31,28 @@
v-model="formData[field.props.name]"
:limit="field.props.limit"
></FileUpload>
<a-select
<Select
v-else-if="field.type === 'select'"
v-model="formData[field.props.name]"
v-model:value="formData[field.props.name]"
:placeholder="field.placeholder"
>
<a-option v-for="(option, optIndex) in field.props.options" :key="optIndex" :value="option.value">
<Option v-for="(option, optIndex) in field.props.options" :key="optIndex" :value="option.value">
{{ option.label }}
</a-option>
</a-select>
</a-form-item>
</a-form>
<a-button class="submit-btn" type="primary" :disabled="loading" @click="handleSubmit">提交执行</a-button>
</Option>
</Select>
</FormItem>
</Form>
<Button class="submit-btn" type="primary" :disabled="loading" @click="handleSubmit">提交执行</Button>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
import ImageUpload from '@/components/upload/ImageUpload.vue';
import FileUpload from '@/components/upload/FileUpload.vue';
import { Button, Input, Select, Row, Col, Form } from 'ant-design-vue';
const { TextArea } = Input;
const { Option } = Select;
const { Item: FormItem } = Form;
const props = defineProps({
formFields: {
@ -79,24 +80,25 @@ const handleSubmit = async () => {
<style scoped lang="scss">
.form-container {
:deep(.arco-input-wrapper),
:deep(.arco-textarea-wrapper) {
:deep(.ant-input),
:deep(.ant-input:focus),
:deep(.ant-input-focused) {
border-radius: 4px;
border-color: #d7d7d9;
background-color: #fff;
height: 35px;
width: 300px;
&:focus-within,
&.arco-input-focus {
&:focus,
&:focus-within {
background-color: var(--color-bg-2);
border-color: rgb(var(--primary-6));
box-shadow: 0 0 0 0 var(--color-primary-light-2);
}
}
&.arco-textarea-wrapper {
height: 60px;
}
&.ant-textarea-wrapper {
height: 60px;
}
.submit-btn {

View File

@ -42,15 +42,14 @@
{{ item.title }}
</div>
<div class="trigger-container">
<a-trigger
mouse-leave-delay="200"
position="top"
<Dropdown
:mouseLeaveDelay="200"
placement="top"
trigger="hover"
:auto-fit-position="false"
:unmount-on-close="true"
:overlayStyle="{ width: 'auto' }"
>
<SvgIcon size="12" name="svg-more" class="icon-more" />
<template #content>
<template #overlay>
<div class="">
<div class="history-item-dropdown">
<div class="dropdown-item">
@ -61,18 +60,19 @@
</div>
<div class="dropdown-item">
<SvgIcon size="12" name="svg-delete" class="icon color-#6D4CFE" />
<a-popconfirm
<Popconfirm
content="你确认删除该历史对话吗"
@ok="deleteHistory(item.id, index)"
type="error"
@confirm="deleteHistory(item.id, index)"
ok-text="确定"
cancel-text="取消"
>
<div class="text delete">删除</div>
</a-popconfirm>
</Popconfirm>
</div>
</div>
</div>
</template>
</a-trigger>
</Dropdown>
</div>
</div>
</div>
@ -83,9 +83,9 @@
<div class="body">
<div class="">
<div class="toggle-btn cursor-pointer" @click="toggleCollapse">
<a-tooltip :content="isCollapsed ? '展开' : '折叠'">
<Tooltip :title="isCollapsed ? '展开' : '折叠'">
<img class="status-icon" :src="isCollapsed ? menuUnfold : menuFold" />
</a-tooltip>
</Tooltip>
</div>
</div>
</div>
@ -95,7 +95,7 @@
<DynamicForm :formFields="formFields.form" :formData="formData" :loading="loading" @submit="handleSubmit" />
</div>
<div class="res h-full">
<a-spin v-if="loading" class="spin-center" tip="生成中。。。" />
<Spin v-if="loading" wrapperClassName="spin-center" tip="生成中。。。" />
<div
class="markdown-container"
v-if="workFlowRes.output != '' && loading === false"
@ -107,15 +107,16 @@
</div>
</div>
<a-modal style="width: 500px" v-model:visible="editHistoryVisible">
<Modal style="width: 500px" v-model:open="editHistoryVisible" centered>
<template #title> Title</template>
<div></div>
</a-modal>
</Modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { Modal, Tooltip, message, Popconfirm, Spin, Dropdown } from 'ant-design-vue';
import DynamicForm from './components/DynamicForm.vue';
import {
executeWorkFlow,
@ -214,17 +215,17 @@ const handleTop = async (id, sort, event) => {
//置顶
const topHistory = async (id, sort) => {
const { code, message } = await topWorkflowHistoryApi(id);
const { code, message: msg } = await topWorkflowHistoryApi(id);
if (code === 200) {
AMessage.success(message);
message.success(msg);
getWorkflowHistoryList();
}
};
//取消置顶
const canceltopHistory = async (id, sort) => {
const { code, message } = await cancelTopWorkflowHistoryApi(id);
const { code, message: msg } = await cancelTopWorkflowHistoryApi(id);
if (code === 200) {
AMessage.success(message);
message.success(msg);
getWorkflowHistoryList();
}
};

View File

@ -1,13 +1,15 @@
<template>
<a-select allow-search v-model="selectedValue" placeholder="请选择账号" allow-clear filterable @change="handleChange">
<a-option v-for="account in filteredAccounts" :key="account.id" :value="account.id" :label="account.name">
<Select showSearch v-model:value="selectedValue" placeholder="请选择账号" allowClear filterable @change="handleChange">
<Option v-for="account in filteredAccounts" :key="account.id" :value="account.id" :label="account.name">
{{ account.name }}
</a-option>
</a-select>
</Option>
</Select>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { Select } from 'ant-design-vue';
const { Option } = Select;
import { ref, onMounted } from 'vue';
import { getPlacementAccountsList } from '@/api/all/propertyMarketing';
// 定义账号对象类型

View File

@ -1,13 +1,15 @@
<template>
<a-select allow-search v-model="selectedValue" placeholder="请选择计划" allow-clear filterable @change="handleChange">
<a-option v-for="item in listData" :key="item.id" :value="item.id" :label="item.name">
<Select showSearch v-model:value="selectedValue" placeholder="请选择计划" allowClear filterable @change="handleChange">
<Option v-for="item in listData" :key="item.id" :value="item.id" :label="item.name">
{{ item.name }}
</a-option>
</a-select>
</Option>
</Select>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { Select } from 'ant-design-vue';
const { Option } = Select;
import { ref, computed, onMounted, type PropType } from 'vue';
import { getplacementAccountProjectsLlist } from '@/api/all/propertyMarketing';
interface Account {
@ -17,7 +19,7 @@ interface Account {
const props = defineProps({
modelValue: {
type: Array,
type: Array as PropType<number[]>,
default: () => [],
},
});
@ -25,7 +27,7 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue', 'change']);
// 响应式数据
const selectedValue = ref(props.modelValue);
const selectedValue = ref<number | undefined>(props.modelValue?.[0]);
const allAccounts = ref<Account[]>([]);
const listData = ref<Account[]>([]);
const loading = ref(false);

View File

@ -1,13 +1,13 @@
<template>
<view>
<topHeader ref="topHeaderRef" @search="search"></topHeader>
<a-space direction="vertical" class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px">
<div class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px">
<div class="title-row">
<span class="title mr-4px">行业词云</span>
<a-tooltip>
<template #content>基于行业内内容提取的高频词汇</template>
<Tooltip>
<template #title>基于行业内内容提取的高频词汇</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<div class="multi-row-tag-cloud h-472px">
@ -20,7 +20,7 @@
class="tag-row"
:style="{ justifyContent: row.align || 'center' }"
>
<a-tag
<Tag
v-for="(tag, tagIndex) in row.tags"
:key="tagIndex"
class="cursor-pointer"
@ -39,16 +39,14 @@
@mouseenter="hoverTag = tag"
@mouseleave="hoverTag = null"
>
<a-space>
<a-tooltip :content="`性价比:${Number(tag.rate * 100)}%`" position="tl">
<a-space>{{ tag.term }}</a-space>
</a-tooltip>
</a-space>
</a-tag>
<Tooltip :title="`性价比:${Number(tag.rate * 100)}%`" placement="topLeft">
<span>{{ tag.term }}</span>
</Tooltip>
</Tag>
</div>
</template>
</div>
</a-space>
</div>
</view>
</template>
@ -56,6 +54,7 @@
import topHeader from './topHeader.vue';
import { ref, computed } from 'vue';
import { fetchindustryTerms } from '@/api/all/index';
import { Tooltip, Tag } from 'ant-design-vue';
const topHeaderRef = ref();
// 从topHeader获取统一的状态
@ -170,7 +169,7 @@ const processTagData = (apiData) => {
color: #6d4cfe !important;
border-color: #6d4cfe !important;
}
:deep(.arco-modal-body) {
:deep(.ant-modal-body) {
padding: 0px;
}
@ -199,7 +198,7 @@ const processTagData = (apiData) => {
}
/* 悬停放大效果 */
a-tag:hover {
.ant-tag:hover {
transform: scale(1.1);
z-index: 1;
}

View File

@ -2,88 +2,99 @@
<view>
<topHeader ref="topHeaderRef" @search="search"></topHeader>
<!-- tabel -->
<a-space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
>
<div class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px">
<div class="title-row">
<span class="title mr-4px">行业热门话题洞察</span>
<a-tooltip>
<template #content>基于社交内容平台的行业数据分析用户关注的热门话题与趋势</template>
<Tooltip>
<template #title>基于社交内容平台的行业数据分析用户关注的热门话题与趋势</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table
:columns="columns"
:data="dataList"
:filter-icon-align-left="alignLeft"
:scroll="true"
<Table
:dataSource="dataList"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<Table.Column
v-for="column in columns"
:key="column.dataIndex"
:title="column.title"
:dataIndex="column.dataIndex"
:width="column.width"
:minWidth="column.minWidth"
:sortable="column.sortable"
>
<template v-if="column.slotName === 'rank'" #customRender="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template v-else-if="column.slotName === 'keywords'" #customRender="{ record }">
<Tag
v-for="item in record.keywords"
:key="item"
class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px"
>{{ item }}</Tag
>
</template>
<template v-else-if="column.slotName === 'hot'" #customRender="{ record }">
<img
v-for="i in record.hot"
:key="i"
:src="starImages[i - 1]"
style="width: 16px; height: 16px"
class="mr-2px"
/>
</template>
<template v-else-if="column.slotName === 'sentiment'" #customRender="{ record }">
<img v-if="record.felling == '2'" src="@/assets/img/hottranslation/good.png" class="w-24px h-24px" />
<img v-else-if="record.felling == '1'" src="@/assets/img/hottranslation/normal.png" class="w-24px h-24px" />
<img v-else-if="record.felling == '0'" src="@/assets/img/hottranslation/poor.png" class="w-24px h-24px" />
</template>
<template v-else-if="column.slotName === 'optional'" #customRender="{ record }">
<Button type="primary" ghost @click="gotoDetail(record)">详情</Button>
</template>
<template v-else-if="column.titleSlotName === 'hotTitle'" #title>
<div class="flex items-center">
<span class="mr-8px">热度指数</span>
<Tooltip>
<template #title>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</div>
</template>
<template v-else-if="column.titleSlotName === 'sentimentTitle'" #title>
<div class="flex items-center">
<span class="mr-8px">情感倾向</span>
<Tooltip>
<template #title
>统计该行业下全部内容的情绪分布选取占比最高的情绪类型作为该话题的整体情感倾向</template
>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</div>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #hotTitle>
<a-space>
<span>热度指数</span>
<a-tooltip>
<template #content>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #sentimentTitle>
<a-space>
<span>情感倾向</span>
<a-tooltip>
<template #content
>统计该行业下全部内容的情绪分布选取占比最高的情绪类型作为该话题的整体情感倾向</template
>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #rank="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template #keywords="{ record }">
<a-tag
v-for="item in record.keywords"
:key="item"
class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px"
>{{ item }}</a-tag
>
</template>
<template #hot="{ record }">
<img
v-for="i in record.hot"
:key="i"
:src="starImages[i - 1]"
style="width: 16px; height: 16px"
class="mr-2px"
/>
</template>
<template #sentiment="{ record }">
<img v-if="record.felling == '2'" src="@/assets/img/hottranslation/good.png" class="w-24px h-24px" />
<img v-else-if="record.felling == '1'" src="@/assets/img/hottranslation/normal.png" class="w-24px h-24px" />
<img v-else-if="record.felling == '0'" src="@/assets/img/hottranslation/poor.png" class="w-24px h-24px" />
</template>
<template #optional="{ record }">
<a-button type="outline" class="!rounded-4px" @click="gotoDetail(record)">详情</a-button>
</template>
</a-table>
</a-space>
<a-modal :visible="visible" unmountOnClose modal-class="hot-translation-modal" width="640px" @cancel="handleCancel">
</Table>
</div>
<Modal
v-model:open="visible"
unmountOnClose
centered
wrapClassName="hot-translation-modal"
width="640px"
@cancel="handleCancel"
>
<template #title>
<span style="text-align: left; width: 100%">行业热门话题洞察</span>
</template>
<div>
<a-space direction="vertical">
<Space direction="vertical">
<div class="mb-4px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-48px">话题名称</p>
<span class="cts">{{ topicInfo.name }}</span>
@ -94,11 +105,11 @@
</div>
<div class="mb-4px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-48px">关键词</p>
<a-tag
<Tag
v-for="item in topicInfo.keywords"
:key="item"
class="mr-8px py-10px px-8px rounded-4px bg-#F2F3F5 cts !h-24px"
>{{ item }}</a-tag
>{{ item }}</Tag
>
</div>
<div class="mb-4px flex items-center">
@ -123,25 +134,27 @@
<p class="!mr-16px w-48px cts relative top-2px">原始来源</p>
<div class="flex flex-col">
<div v-for="item in topicInfo.industry_topic_sources" :key="item" class="mb-18px flex items-center">
<a-link style="background-color: initial" :href="item.link" target="_blank" class="!text-12px">{{
<Link :href="item.link" target="_blank" class="!text-12px">{{
item.title
}}</a-link>
}}</Link>
<img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" />
</div>
</div>
</div>
</a-space>
</Space>
</div>
<template #footer>
<a-button size="large" @click="handleCancel">取消</a-button>
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
<Button size="large" @click="handleCancel">取消</Button>
<Button type="primary" size="large" @click="handleOk"> 确定 </Button>
</template>
</a-modal>
</Modal>
</view>
</template>
<script setup>
import topHeader from './topHeader.vue';
import { Modal, Button, Tooltip, Space, Table, Tag, Typography } from 'ant-design-vue';
const { Link } = Typography;
import { ref, computed } from 'vue';
import { fetchIndustriesTree, fetchIndustryTopics, fetchIndustryTopicDetail } from '@/api/all/index';
import star1 from '@/assets/img/hottranslation/star-fill1.png';
@ -251,7 +264,7 @@ onMounted(() => {
const getIndustriesTree = async () => {
const res = await fetchIndustriesTree();
industriesTree.value = res;
selectedIndustry.value = res[0].id;
selectedIndustry.value = res[0]?.id;
getIndustryTopics();
};
@ -320,7 +333,7 @@ const handleOk = () => {
color: #6d4cfe !important;
border-color: #6d4cfe !important;
}
:deep(.arco-modal-body) {
:deep(.ant-modal-body) {
padding: 0px;
}
@ -374,7 +387,7 @@ const handleOk = () => {
}
}
.arco-modal-body {
.ant-modal-body {
padding: 12px 20px 0;
.cts {
color: var(--Text-2, #3c4043);

View File

@ -3,102 +3,122 @@
<view>
<topHeader ref="topHeaderRef" @click="search"></topHeader>
<!-- 重点品牌列表 -->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
>
<div class="title-row">
<span class="title mr-4px">重点品牌列表</span>
<a-tooltip>
<template #content
<Tooltip>
<template #title
>基于该行业中近期提及频次高用户互动活跃的品牌内容筛选出关注度较高的代表性品牌</template
>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table
:columns="columns"
:data="dataList"
:filter-icon-align-left="alignLeft"
:scroll="true"
<Table
:dataSource="dataList"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<Table.Column
v-for="column in columns"
:key="column.dataIndex"
:title="column.title"
:dataIndex="column.dataIndex"
:width="column.width"
:minWidth="column.minWidth"
:sortable="column.sortable"
>
<template v-if="column.slotName === 'rank'" #customRender="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template v-else-if="column.slotName === 'hot'" #customRender="{ record }">
<img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" />
</template>
<template v-else-if="column.slotName === 'trend'" #customRender="{ record }">
<div class="flex items-center" :class="record.trend > 0 ? 'color-#F64B31' : 'color-#25C883'">
<icon-arrow-up v-if="record.trend > 0" size="16" />
<icon-arrow-down v-else size="16" />
{{ `${(record.trend * 100).toFixed(2)}%` }}
</div>
</template>
<template v-else-if="column.slotName === 'volumeRate'" #customRender="{ record }">
<Statistic :value="record.volume_rate * 100" />%
</template>
<template v-else-if="column.titleSlotName === 'hotTitle'" #title>
<Space>
<span>热度指数</span>
<Tooltip>
<template #title>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</Space>
</template>
<template v-else-if="column.titleSlotName === 'trendTitle'" #title>
<Space>
<span>变化幅度</span>
<Tooltip>
<template #title>仅基于品牌出现频次</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</Space>
</template>
<template v-else-if="column.titleSlotName === 'volume_rateTitle'" #title>
<Space>
<span>占总声量比例</span>
<Tooltip>
<template #title>该品牌在当前周期内被提及的内容量占整个行业内容总量的比例</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</Space>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #hotTitle>
<a-space>
<span>热度指数</span>
<a-tooltip>
<template #content>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #trendTitle>
<a-space>
<span>变化幅度</span>
<a-tooltip>
<template #content>仅基于品牌出现频次</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #volume_rateTitle>
<a-space>
<span>占总声量比例</span>
<a-tooltip>
<template #content>该品牌在当前周期内被提及的内容量占整个行业内容总量的比例</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #rank="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template #hot="{ record }">
<img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" />
</template>
<template #trend="{ record }">
<div class="flex items-center" :class="record.trend > 0 ? 'color-#F64B31' : 'color-#25C883'">
<icon-arrow-up v-if="record.trend > 0" size="16" />
<icon-arrow-down v-else size="16" />
{{ `${(record.trend * 100).toFixed(2)}%` }}
</div>
</template>
<template #volumeRate="{ record }"> <a-statistic :value="record.volume_rate * 100" />% </template>
</a-table>
</a-space>
</Table>
</Space>
<!-- 舆情 & 敏感动态-->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
>
<div class="title-row">
<span class="title mr-4px">舆情 & 敏感动态</span>
<a-tooltip>
<template #content
<Tooltip>
<template #title
>基于情绪分析与敏感词识别对行业内容中的负面或争议性话题进行监测辅助判断舆情风险动态</template
>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table :data="otherList" :columns="columns2" :pagination="false" :scroll="true" style="font-size: 12px">
<template #empty>
<Table :dataSource="otherList" :pagination="false" :showSorterTooltip="false" style="font-size: 12px">
<Table.Column
v-for="column in columns2"
:key="column.dataIndex"
:title="column.title"
:dataIndex="column.dataIndex"
:width="column.width"
:minWidth="column.minWidth"
:sortable="column.sortable"
/>
<template #emptyText>
<NoData />
</template>
</a-table>
</a-space>
</Table>
</Space>
</view>
</template>
<script setup>
import topHeader from './topHeader.vue';
import { Tooltip, Space, Table, Statistic } from 'ant-design-vue';
import { fetchFocusBrandsList, fetchEventDynamicsList } from '@/api/all/index';
import { ref, onMounted, computed } from 'vue';
import star1 from '@/assets/img/hottranslation/star-fill1.png';

View File

@ -3,46 +3,46 @@
<view>
<topHeader ref="topHeaderRef" @search="search"></topHeader>
<!-- 关键词热度榜 -->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-20px"
>
<div class="title-row">
<span class="title mr-4px">关键词热度榜</span>
<a-tooltip>
<template #content>基于该行业用户内容中提及频率较高的关键词按热度进行排序反映近期关注焦点</template>
<Tooltip>
<template #title>基于该行业用户内容中提及频率较高的关键词按热度进行排序反映近期关注焦点</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table
<Table
:columns="columns"
:data="dataList"
:filter-icon-align-left="alignLeft"
:scroll="true"
:dataSource="dataList"
:scroll="{ x: true }"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<template #emptyText>
<NoData />
</template>
<template #heatLevel>
<a-space>
<Space>
<span>热度指数</span>
<a-tooltip>
<template #content>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<Tooltip>
<template #title>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</Tooltip>
</Space>
</template>
<template #trendTitle>
<a-space>
<Space>
<span>变化幅度</span>
<a-tooltip>
<template #content>仅基于关键词出现频次</template>
<Tooltip>
<template #title>仅基于关键词出现频次</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</Tooltip>
</Space>
</template>
<template #rank="{ record }">
@ -52,7 +52,7 @@
<span v-else>{{ record.rank }}</span>
</template>
<template #keywords="{ record }">
<a-tag v-for="item in record.keywords" :key="item" style="margin-right: 5px">{{ item }}</a-tag>
<Tag v-for="item in record.keywords" :key="item" style="margin-right: 5px">{{ item }}</Tag>
</template>
<template #hot="{ record }">
<img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" />
@ -69,21 +69,21 @@
{{ `${(record.trend * 100).toFixed(2)}%` }}
</div>
</template>
</a-table>
</a-space>
</Table>
</Space>
<!-- 行业情绪 -->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
>
<div class="title-row">
<span class="title mr-4px">行业情绪</span>
<a-tooltip>
<template #content
<Tooltip>
<template #title
>对该行业下用户内容进行情绪分析按情绪类别统计占比提取占比最高者作为行业情绪代表</template
>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<div class="flex items-center w-100%">
@ -103,17 +103,16 @@
</div>
</div>
</div>
<a-table
<Table
class="flex-1"
:columns="columns2"
:data="sortedRowData"
:span-method="spanMethod"
:filter-icon-align-left="alignLeft"
:scroll="true"
:dataSource="sortedRowData"
:scroll="{ x: true }"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<template #emptyText>
<NoData />
</template>
<template #felling="{ record }">
@ -122,33 +121,33 @@
<span>{{ fellingStatus[record.felling].label }}</span>
</div>
</template>
</a-table>
</Table>
</div>
</a-space>
</Space>
<!-- 新兴关键词 -->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px"
>
<div class="title-row">
<span class="title mr-4px">新兴关键词</span>
<a-tooltip>
<template #content
<Tooltip>
<template #title
>指当前周期中首次出现或相较上一周期词频显著增长的关键词反映近期出现的新关注点</template
>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table
<Table
:columns="columns3"
:data="keywordList"
:filter-icon-align-left="alignLeft"
:scroll="true"
:dataSource="keywordList"
:scroll="{ x: true }"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<template #emptyText>
<NoData />
</template>
<template #rank="{ record }">
@ -181,23 +180,23 @@
<img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" />
</template>
<template #hotTitle="{ record }">
<a-space>
<template #hotTitle>
<Space>
<span>当前热度指数</span>
<a-tooltip>
<template #content>综合关键词出现频次互动表现如点赞收藏评论加权计算的热度得分</template>
<Tooltip>
<template #title>综合关键词出现频次互动表现如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</a-space>
</Tooltip>
</Space>
</template>
<template #trendTitle="{ record }">
<a-space>
<template #trendTitle>
<Space>
<span>变化幅度</span>
<a-tooltip>
<template #content>仅基于关键词出现频次</template>
<Tooltip>
<template #title>仅基于关键词出现频次</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</a-space>
</Tooltip>
</Space>
</template>
<template #tred="{ record }">
<div class="flex items-center" :class="record.trend > 0 ? 'color-#F64B31' : 'color-#25C883'">
@ -207,15 +206,16 @@
</div>
</template>
<template #optional="{ record }">
<a-button type="outline" @click="gotoDetail(record)">详情</a-button>
<Button type="primary" ghost @click="gotoDetail(record)">详情</Button>
</template>
</a-table>
</a-space>
</Table>
</Space>
<!-- modal -->
<a-modal
:visible="visible"
modal-class="keyword-modal"
<Modal
v-model:open="visible"
wrapClassName="keyword-modal"
unmountOnClose
centered
width="640px"
@ok="handleOk"
@cancel="handleCancel"
@ -224,7 +224,7 @@
<span style="text-align: left; width: 100%">新兴关键词</span>
</template>
<div>
<a-space direction="vertical">
<Space direction="vertical">
<div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-83px">话题名称</p>
<span class="cts">{{ topicInfo.name }}</span>
@ -250,25 +250,27 @@
<p class="!mr-16px w-83px cts relative top-2px">原始来源</p>
<div class="flex flex-col">
<div v-for="item in topicInfo.industry_new_keyword_sources" :key="item" class="mb-18px flex items-center">
<a-link style="background-color: initial" :href="item.link" target="_blank" class="!text-12px">{{
<Link :href="item.link" target="_blank" class="!text-12px">{{
item.title
}}</a-link>
}}</Link>
<img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" />
</div>
</div>
</div>
</a-space>
</Space>
</div>
<template #footer>
<a-button size="large" @click="handleCancel">取消</a-button>
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
<Button size="large" @click="handleCancel">取消</Button>
<Button type="primary" size="large" @click="handleOk"> 确定 </Button>
</template>
</a-modal>
</Modal>
</view>
</template>
<script setup>
import topHeader from './topHeader.vue';
import { Modal, Button, Tooltip, Space, Table, Tag, Typography } from 'ant-design-vue';
const { Link } = Typography;
import {
fetchKeywordTrendsList,
fetchIndustryEmotions,
@ -690,7 +692,7 @@ onMounted(() => {
}
}
.arco-modal-body {
.ant-modal-body {
padding: 12px 20px 0;
.cts {
color: var(--Text-2, #3c4043);

View File

@ -1,97 +1,100 @@
<template>
<view>
<!-- 头部 -->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px mb-20px"
style="background-color: #fff; width: 100%; padding: 24px; color: #737478; font-size: 14px"
>
<a-space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<Space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<span style="flex-shrink: 0; line-height: 28px" class="mr-32px">行业大类</span>
<div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start">
<a-tag
<Tag
v-for="item in industriesTree"
:key="item.id"
size="Medium"
:checkable="true"
:checked="selectedIndustry == item.id"
style="padding: 10px 16px; border-radius: 30px; height: 28px"
style="padding: 0 16px; border-radius: 30px; height: 28px"
class="lh-28px cursor-pointer"
:style="
selectedIndustry == item.id
? 'color: #6D4CFE; background-color: #F0EDFF'
: 'color: #3C4043; background-color: #F7F8FA'
"
@check="handleIndustryCheck(item.id)"
>{{ item.name }}</a-tag
@click="handleIndustryCheck(item.id)"
>{{ item.name }}</Tag
>
</div>
</a-space>
</Space>
<!-- 二级类目 -->
<a-space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<Space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<span style="flex-shrink: 0; line-height: 28px" class="mr-32px">二级类目</span>
<div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start">
<a-tag
<Tag
v-for="item in subCategories"
:key="item.id"
size="Medium"
size="small"
:checkable="true"
:checked="selectedSubCategory == item.id"
style="padding: 10px 16px; border-radius: 30px; height: 28px"
style="padding: 0 16px; border-radius: 30px; height: 28px"
class="lh-28px cursor-pointer"
:style="
selectedSubCategory == item.id
? 'color: #6d4cfe; background-color: #f0edff'
: 'color: #3C4043; background-color: #F7F8FA'
"
@check="handleSubCategoryCheck(item.id)"
>{{ item.name }}</a-tag
@click="handleSubCategoryCheck(item.id)"
>{{ item.name }}</Tag
>
</div>
</a-space>
<!-- </a-space> -->
<a-space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
</Space>
<!-- </Space> -->
<Space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<span style="flex-shrink: 0; line-height: 28px" class="mr-32px">时间筛选</span>
<div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start">
<a-tag
<Tag
v-for="item in timePeriods"
:key="item.value"
size="Medium"
:checkable="true"
:checked="selectedTimePeriod == item.value"
style="padding: 10px 16px; border-radius: 30px; height: 28px"
class="lh-28px cursor-pointer"
style="padding: 0 16px; border-radius: 30px; height: 28px"
:style="
selectedTimePeriod == item.value
? 'color: #6d4cfe; background-color: #f0edff'
: 'color: #3C4043; background-color: #F7F8FA'
"
@check="handleTimePeriodCheck(item.value)"
@click="handleTimePeriodCheck(item.value)"
>{{ item.label }}
</a-tag>
</Tag>
</div>
</a-space>
</Space>
<!-- 搜索区域 -->
<a-space style="margin-left: 'auto'">
<a-button type="primary" size="medium" @click="handleSearch">
<Space style="margin-left: 'auto'">
<Button type="primary" @click="handleSearch">
<template #icon>
<icon-search />
<icon-search class="mr-8px"/>
</template>
<!-- Use the default slot to avoid extra spaces -->
<template #default>搜索</template>
</a-button>
<a-button class="w-84px reset-btn" size="medium" @click="handleReset">
</Button>
<Button class="w-84px reset-btn" @click="handleReset">
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px"/>
</template>
<template #default>重置</template>
</a-button>
</a-space>
</a-space>
</Button>
</Space>
</Space>
</view>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { fetchIndustriesTree } from '@/api/all/index';
import { Button, Space, Tag } from 'ant-design-vue';
const emit = defineEmits<(e: 'search') => void>();
// 行业大类
const industriesTree = ref([]);
@ -139,6 +142,7 @@ const handleIndustryCheck = (id) => {
};
const handleSubCategoryCheck = (id) => {
console.log('handleSubCategoryCheck');
selectedSubCategory.value = id;
};
@ -185,7 +189,7 @@ const handleReset = () => {
color: #6d4cfe !important;
border-color: #6d4cfe !important;
}
:deep(.arco-modal-body) {
:deep(.ant-modal-body) {
padding: 0px;
}
.reset-btn {

View File

@ -3,63 +3,71 @@
<topHeader ref="topHeaderRef" @search="search"></topHeader>
<!-- 用户痛点观察 -->
<a-space
<Space
direction="vertical"
style="background-color: #fff; width: 100%; padding: 0 20px"
class="bg-#fff rounded-8px mb-24px"
>
<div class="title-row">
<span class="title mr-4px">用户痛点观察</span>
<a-tooltip>
<template #content
<Tooltip>
<template #title
>基于用户内容中的情绪分析与表达模式提取反复出现的负面倾向主题反映典型使用痛点</template
>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table
:columns="columns"
:data="dataList"
:filter-icon-align-left="alignLeft"
:scroll="true"
<Table
:dataSource="dataList"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<Table.Column
v-for="column in columns"
:key="column.dataIndex"
:title="column.title"
:dataIndex="column.dataIndex"
:width="column.width"
:minWidth="column.minWidth"
:sortable="column.sortable"
>
<template v-if="column.slotName === 'rank'" #customRender="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template v-else-if="column.slotName === 'keywords'" #customRender="{ record }">
<Tag
v-for="item in record.keywords"
:key="item"
class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px"
>{{ item }}</Tag
>
</template>
<template v-else-if="column.slotName === 'frequency'" #customRender="{ record }">
<Tag
:class="`!rounded-2px !px-8px !py-1px !bg-${frequencyStatus[record.frequency].bgColor} !h-22px !color-${
frequencyStatus[record.frequency].color
}`"
>{{ frequencyStatus[record.frequency].label }}</Tag
>
</template>
<template v-else-if="column.slotName === 'optional'" #customRender="{ record }">
<Button type="primary" ghost @click="gotoDetail(record)">详情</Button>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #rank="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template #keywords="{ record }">
<a-tag
v-for="item in record.keywords"
:key="item"
class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px"
>{{ item }}</a-tag
>
</template>
<template #frequency="{ record }">
<a-tag
:class="`!rounded-2px !px-8px !py-1px !bg-${frequencyStatus[record.frequency].bgColor} !h-22px !color-${
frequencyStatus[record.frequency].color
}`"
>{{ frequencyStatus[record.frequency].label }}</a-tag
>
</template>
</Table>
</Space>
<template #optional="{ record }">
<a-button type="outline" class="!rounded-4px" @click="gotoDetail(record)">详情</a-button>
</template>
</a-table>
</a-space>
<a-modal
:visible="visible"
modal-class="user-pain-points-modal"
<Modal
v-model:open="visible"
wrapClassName="user-pain-points-modal"
centered
unmountOnClose
width="640px"
@ok="handleOk"
@ -69,27 +77,27 @@
<span style="text-align: left; width: 100%">用户痛点观察</span>
</template>
<div>
<a-space direction="vertical" style="font-size: 12px">
<Space direction="vertical" style="font-size: 12px">
<div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-60px">痛点</p>
<span class="cts">{{ topicInfo.name }}</span>
</div>
<div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-60px">关键词</p>
<a-tag
<Tag
v-for="item in topicInfo.keywords"
:key="item"
class="mr-8px py-10px px-8px rounded-4px bg-#F2F3F5 cts !h-24px"
>{{ item }}</a-tag
>{{ item }}</Tag
>
</div>
<div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-60px">频次</p>
<a-tag
<Tag
:class="`!rounded-2px !px-8px !py-1px !bg-${
frequencyStatus[topicInfo.frequency].bgColor
} !h-22px !color-${frequencyStatus[topicInfo.frequency].color}`"
>{{ frequencyStatus[topicInfo.frequency].label }}</a-tag
>{{ frequencyStatus[topicInfo.frequency].label }}</Tag
>
</div>
<div class="mb-12px flex items-center">
@ -100,25 +108,27 @@
<p class="cts !mr-16px flex-shrink-0 w-60px">原始来源</p>
<div class="flex flex-col">
<div v-for="item in topicInfo.user_pain_point_sources" :key="item" class="mb-18px flex items-center">
<a-link style="background-color: initial" :href="item.link" target="_blank" class="!text-12px">{{
<Link :href="item.link" target="_blank" class="!text-12px">{{
item.title
}}</a-link>
}}</Link>
<img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" />
</div>
</div>
</div>
</a-space>
</Space>
</div>
<template #footer>
<a-button size="large" @click="handleCancel">取消</a-button>
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
<Button size="large" @click="handleCancel">取消</Button>
<Button type="primary" size="large" @click="handleOk"> 确定 </Button>
</template>
</a-modal>
</Modal>
</view>
</template>
<script setup>
import topHeader from './topHeader.vue';
import { Modal, Button, Tooltip, Space, Table, Tag, Typography } from 'ant-design-vue';
const { Link } = Typography;
import { fetchUserPainPointsDetail, fetchUserPainPointsList } from '@/api/all/index';
import { ref, onMounted, computed } from 'vue';
import top1 from '@/assets/img/captcha/top1.svg';
@ -286,16 +296,16 @@ const search = () => {
</style>
<style lang="scss">
.user-pain-points-modal {
.arco-modal-header {
.ant-modal-header {
border-bottom: none;
height: 56px;
padding: 0 20px;
.arco-modal-title {
.ant-modal-title {
justify-content: flex-start;
}
}
.arco-modal-body {
.ant-modal-body {
padding: 12px 20px 0;
.cts {
color: var(--Text-2, #3c4043);
@ -311,7 +321,7 @@ const search = () => {
}
}
.arco-modal-footer {
.ant-modal-footer {
display: flex;
height: 64px;
padding: 0px 20px;

View File

@ -6,52 +6,52 @@
<div class="bg-#fff rounded-8px w-100% py-0 px-20px w-600px mr-24px">
<div class="title-row">
<span class="title mr-4px">性别分布</span>
<a-tooltip>
<template #content>基于社交内容平台中用户资料互动行为及语义特征进行智能识别与估算</template>
<Tooltip>
<template #title>基于社交内容平台中用户资料互动行为及语义特征进行智能识别与估算</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-space v-if="genderData.length > 0">
<Space v-if="genderData.length > 0">
<div id="container" class="w-300px h-300px"></div>
<a-space direction="vertical" style="font-size: 14px">
<a-space>
<Space direction="vertical" style="font-size: 14px">
<Space>
<span style="width: 8px; height: 8px; background-color: #f64b31; border-radius: 50%"></span>
<span>女性</span>
<span>{{ (girlData.rate * 100).toFixed(2) }}%</span>
<span>TGI</span>
<span>{{ girlData.tgi }}</span>
</a-space>
<a-space>
</Space>
<Space>
<span style="width: 8px; height: 8px; background-color: #2a59f3; border-radius: 50%"></span>
<span>男性</span>
<span>{{ (boyData.rate * 100).toFixed(2) }}%</span>
<span>TGI</span>
<span>{{ boyData.tgi }}</span>
</a-space>
</a-space>
</a-space>
</Space>
</Space>
</Space>
<div v-else>
<NoData class="w-100% h-100%" />
</div>
</div>
<!-- 2. 年龄分布 -->
<div class="bg-#fff rounded-8px w-100% py-0 px-20px flex-1 flex flex-col">
<a-space style="display: flex; justify-content: space-between; width: 100%; font-size: 12px">
<Space style="display: flex; justify-content: space-between; width: 100%; font-size: 12px">
<div class="title-row">
<span class="title mr-4px">年龄分布</span>
<a-tooltip>
<template #content>基于社交平台的公开信息内容偏好与行为模式通过算法进行年龄段归类和统计</template>
<Tooltip>
<template #title>基于社交平台的公开信息内容偏好与行为模式通过算法进行年龄段归类和统计</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-space v-if="ageValueData.length > 0" align="center">
<Space v-if="ageValueData.length > 0" align="center">
<span style="width: 16px; height: 8px; background-color: #6d4cfe; border-radius: 2px"></span>
<span style="color: #6d4cfe">占比</span>
<span style="width: 16px; height: 8px; background-color: #f64b31; border-radius: 2px"></span>
<span style="color: #f64b31">TGI比</span>
</a-space>
</a-space>
</Space>
</Space>
<div v-if="ageValueData.length === 0" class="w-100% flex-1">
<NoData />
</div>
@ -61,17 +61,17 @@
<div class="bg-#fff rounded-8px w-100% py-0 px-20px flex-1 pb-20px">
<div class="title-row">
<span class="title mr-4px">地域分布</span>
<a-tooltip>
<template #content>基于社交平台的IP归属地位置标签内容发布地等数据推测用户活跃区域</template>
<Tooltip>
<template #title>基于社交平台的IP归属地位置标签内容发布地等数据推测用户活跃区域</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<div class="flex">
<a-space direction="vertical">
<Space direction="vertical">
<div id="chinaMap" style="height: 416px; width: 640px"></div>
<a-space direction="vertical" style="font-size: 14px">
<Space direction="vertical" style="font-size: 14px">
<span class="cts">搜索指数</span>
<a-space>
<Space>
<span class="cts"></span>
<span
v-for="item in 5"
@ -86,44 +86,38 @@
}"
></span>
<span class="cts"></span>
</a-space>
</a-space>
</a-space>
</Space>
</Space>
</Space>
<div class="flex flex-col h-486px">
<a-tabs default-active-key="1" class="h-100%" @change="tabChange">
<a-tab-pane key="1" title="省份">
<a-table :data="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }">
<template #empty>
<Tabs defaultActiveKey="1" class="h-100%" @change="tabChange" size="large">
<TabPane key="1" tab="省份">
<Table :dataSource="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }" :showSorterTooltip="false">
<template #emptyText>
<NoData />
</template>
<template #columns>
<a-table-column title="排名" data-index="rank" />
<a-table-column title="省份" data-index="geo" />
<a-table-column title="分布占比" data-index="rate" />
<a-table-column title="TGI指数" data-index="tgi" />
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="2" title="城市">
<a-table :data="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }">
<template #empty>
<Column title="排名" dataIndex="rank" />
<Column title="省份" dataIndex="geo" />
<Column title="分布占比" dataIndex="rate" />
<Column title="TGI指数" dataIndex="tgi" />
</Table>
</TabPane>
<TabPane key="2" tab="城市">
<Table :dataSource="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }" :showSorterTooltip="false">
<template #emptyText>
<NoData />
</template>
<template #columns>
<a-table-column title="排名" data-index="rank" />
<a-table-column title="城市" data-index="geo" />
<a-table-column title="分布占比" data-index="rate">
<template #cell="{ record }">
<span class="cts">{{ (record.rate * 100).toFixed(2) }}%</span>
</template>
</a-table-column>
<a-table-column title="TGI指数" data-index="tgi" />
</template>
</a-table>
</a-tab-pane>
</a-tabs>
<Column title="排名" dataIndex="rank" />
<Column title="城市" dataIndex="geo" />
<Column title="分布占比" dataIndex="rate">
<template #customRender="{ record }">
<span class="cts">{{ (record.rate * 100).toFixed(2) }}%</span>
</template>
</Column>
<Column title="TGI指数" dataIndex="tgi" />
</Table>
</TabPane>
</Tabs>
</div>
</div>
</div>
@ -133,11 +127,15 @@
<script setup>
import topHeader from './topHeader.vue';
import { fetchAgeDistributionsList, fetchGeoDistributionsList, fetchGenderDistributionsList } from '@/api/all/index';
import { ref, onMounted, computed } from 'vue';
import { ref, onMounted, computed, watch, nextTick } from 'vue';
import * as echarts from 'echarts';
import chinaJson from '@/assets/maps/china.json';
echarts.registerMap('china', chinaJson);
import { Tabs, Tooltip, Space, Table } from 'ant-design-vue';
const { TabPane } = Tabs;
const { Column } = Table;
const scope = ref(1); // 地域范围1-省2-市
const chartInstance = (ref < echarts.ECharts) | (null > null);
const topHeaderRef = ref();

View File

@ -2,40 +2,44 @@
<div class="bg-#fff rounded-8px w-100% py-0 px-20px pb-24px">
<div class="title-row">
<span class="title">账号管理</span>
<a-button type="outline" class="add-account-button" @click="handleAddAccount">添加子账号</a-button>
<Button type="primary" ghost class="add-account-button" @click="handleAddAccount">添加子账号</Button>
</div>
<a-table
:columns="columns"
:data="dataSource"
<Table
:dataSource="dataSource"
:pagination="pagination"
:showSorterTooltip="false"
class="mt-8px"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
@change="handleTableChange"
>
<template #empty>
<Table.Column title="手机号" dataIndex="mobile">
<template #customRender="{ record }">
<div class="flex item-center pt-13px pb-13px">
<span class="mr-4px">{{ record.mobile }}</span>
<Tag v-if="record.type === 0" class="primary-account">主账号</Tag>
<Tag v-else class="sub-account">子账号</Tag>
</div>
</template>
</Table.Column>
<Table.Column title="操作" dataIndex="action" width="120">
<template #customRender="{ record }">
<Button
v-if="record.type !== 0"
class="delete-button"
size="small"
type="primary"
ghost
danger
@click="openDeleteModal(record)"
>
删除
</Button>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #mobile="{ record }">
<div class="flex item-center pt-13px pb-13px">
<span class="mr-4px">{{ record.mobile }}</span>
<a-tag v-if="record.type === 0" class="primary-account">主账号</a-tag>
<a-tag v-else class="sub-account">子账号</a-tag>
</div>
</template>
<template #action="{ record }">
<a-button
v-if="record.type !== 0"
class="delete-button"
size="mini"
type="outline"
status="danger"
@click="openDeleteModal(record)"
>
删除
</a-button>
</template>
</a-table>
<Modal v-model:visible="addAccountVisible" width="480px" title="添加子账号" :okText="okText" @ok="handleOk">
</Table>
<Modal v-model:open="addAccountVisible" centered width="480px" title="添加子账号" :okText="okText" @ok="handleOk" >
<div v-if="canAddAccount" class="add-account-container">
<h2 class="add-account-title">生成企业专属链接成员通过访问即可注册并加入企业账号</h2>
<p class="add-account-subtitle">子账号可独立登录权限继承主账号配置</p>
@ -52,15 +56,15 @@
<p class="cannot-add-account-subtitle">如需添加更多子账号您可联系销售人员进行购买和权限扩展</p>
</div>
</Modal>
<CustomerServiceModal v-model:visible="customerServiceVisible" />
<DeleteModal v-model:visible="deleteVisible" :title="deleteTitle" @ok="handleDelete">
<p class="delete-modal-content">删除后该账号将无法登录您的企业</p>
<CustomerServiceModal v-model:open="customerServiceVisible" centered/>
<DeleteModal v-model:open="deleteVisible" centered :content="deleteTitle" @ok="handleDelete" @cancel="deleteVisible = false">
</DeleteModal>
</div>
</template>
<script setup lang="ts">
import Container from '@/components/container.vue';
import { ref, onMounted, reactive, computed } from 'vue';
import { Button, Table, message, Tag } from 'ant-design-vue';
import { fetchSubAccountPage, removeEnterpriseAccount, getEnterpriseInviteCode } from '@/api/all';
import Modal from '@/components/modal.vue';
import DeleteModal from '@/components/delete-modal.vue';
@ -82,10 +86,10 @@ const columns = [
const dataSource = ref([]);
const pagination = reactive({
total: 0,
showPageSize: true,
showTotal: true,
defaultCurrent: 1,
defaultPageSize: 10,
showSizeChanger: true,
showTotal: (total: number, range: [number, number]) => `${total} 条记录`,
current: 1,
pageSize: 10,
});
const params = reactive({
@ -120,13 +124,12 @@ const currentSelectAccount = ref();
const { copy, copied, isSupported } = useClipboard({ source: inviteUrl });
function handlePageChange(current: number) {
params.page = current;
getSubAccount();
}
function handlePageSizeChange(pageSize: number) {
params.page_size = pageSize;
function handleTableChange(paginationInfo: any, filters: any, sorter: any) {
params.page = paginationInfo.current;
params.page_size = paginationInfo.pageSize;
// 更新分页状态
pagination.current = paginationInfo.current;
pagination.pageSize = paginationInfo.pageSize;
getSubAccount();
}
@ -155,13 +158,13 @@ function handleOk() {
return;
}
if (!isSupported) {
AMessage.error('您的浏览器不支持复制,请手动复制!');
message.error('您的浏览器不支持复制,请手动复制!');
}
copy(inviteUrl.value);
if (!copied) {
AMessage.error('复制失败,请手动复制!');
message.error('复制失败,请手动复制!');
}
AMessage.success('复制成功!');
message.success('复制成功!');
}
function openDeleteModal(record: { id: number; mobile: string }) {
@ -172,7 +175,7 @@ function openDeleteModal(record: { id: number; mobile: string }) {
async function handleDelete() {
await removeEnterpriseAccount(currentSelectAccount.value.id);
AMessage.success('移除成功!');
message.success('移除成功!');
}
onMounted(() => {
@ -275,14 +278,7 @@ onMounted(() => {
color: var(--Text-2, rgba(60, 64, 67, 1));
}
}
.delete-modal-content {
margin-left: 34px;
margin-top: 16px;
font-family: $font-family-medium;
font-weight: 400;
font-size: 12px;
color: var(--Text-2, rgba(60, 64, 67, 1));
}
.title-row {
display: flex;
height: 64px;

View File

@ -3,39 +3,53 @@
<div class="title-row">
<span class="title">企业信息</span>
</div>
<a-table :columns="columns" :data="dataSource" :pagination="false" class="mt-8px">
<template #empty>
<Table :dataSource="dataSource" :pagination="false" :showSorterTooltip="false" class="mt-8px">
<Table.Column title="企业信息" dataIndex="info">
<template #customRender="{ record }">
{{ record.name }}
</template>
</Table.Column>
<Table.Column title="操作" dataIndex="action" width="120">
<template #customRender>
<Button class="edit-button" size="small" type="primary" ghost @click="handleUpdate">修改</Button>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #info="{ record }">
{{ record.name }}
</template>
<template #action>
<a-button class="edit-button" size="mini" type="outline" @click="handleUpdate">修改</a-button>
</template>
</a-table>
<Modal v-model:visible="infoVisible" width="480px" title="修改企业名称" :okText="okText" @ok="handleOk">
</Table>
<Modal
v-model:open="infoVisible"
width="480px"
centered
title="修改企业名称"
:okText="okText"
@ok="handleOk"
cancelText="取消"
>
<p class="tips">
企业名称只能修改2次请谨慎操作<span
>剩余{{ enterpriseInfo!.update_name_quota - enterpriseInfo!.used_update_name_count }}
</span>
</p>
<a-form
<Form
:model="form"
:rules="rules"
class="form"
:label-col-props="{ span: 6, offset: 0 }"
:wrapper-col-props="{ span: 18, offset: 0 }"
label-align="left"
>
<a-form-item required field="name" label="新企业名称">
<a-input v-model.trim="form.name" size="small" :disabled="!canUpdate" placeholder="请输入新企业名称" />
</a-form-item>
</a-form>
<FormItem required name="name" label="新企业名称">
<Input v-model:value.trim="form.name" size="small" :disabled="!canUpdate" placeholder="请输入新企业名称" />
</FormItem>
</Form>
</Modal>
<CustomerServiceModal v-model:visible="customerServiceVisible" />
<CustomerServiceModal v-model:open="customerServiceVisible" centered />
</div>
</template>
<script setup lang="ts">
import { Button, Form, FormItem, Input, Table, message } from 'ant-design-vue';
import Container from '@/components/container.vue';
import Modal from '@/components/modal.vue';
import { ref, reactive, computed } from 'vue';
@ -48,6 +62,16 @@ const form = reactive({
name: '',
});
const rules = {
name: [
{
required: true,
message: '请输入新企业名称',
trigger: ['blur'],
},
],
};
const enterpriseInfo = computed(() => {
return store.enterpriseInfo ?? {};
});
@ -98,7 +122,7 @@ async function handleOk() {
await updateEnterpriseName({ name: form.name });
store.setEnterpriseName(form.name);
store.incUsedUpdateNameCount();
AMessage.success('修改成功!');
message.success('修改成功!');
}
</script>

View File

@ -1,32 +1,37 @@
<template>
<div class="bg-#fff rounded-16px w-100% p-36px">
<p class="title mb-32px">个人信息</p>
<a-table :columns="columns" :data="dataSource" :pagination="false" class="mt-8px">
<template #empty>
<Table :dataSource="dataSource" :pagination="false" :showSorterTooltip="false" class="mt-8px">
<Table.Column title="用户信息" dataIndex="info">
<template #customRender="{ record }">
<div class="pt-3px pb-3px">
<Avatar :src="record.head_image" :size="32" />
{{ record.name || '-' }}
<icon-edit size="13" class="ml-8px" @click="openEditInfoModal" />
</div>
</template>
</Table.Column>
<Table.Column title="手机号" dataIndex="mobile">
<template #customRender="{ record }">
{{ record.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}
<icon-edit size="13" class="ml-8px" @click="openEditMobileModal" />
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #info="{ record }">
<div class="pt-3px pb-3px">
<a-avatar :image-url="record.head_image" :size="32" />
{{ record.name || '-' }}
<icon-edit size="13" class="ml-8px" @click="openEditInfoModal" />
</div>
</template>
<template #mobile="{ record }">
{{ record.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}
<icon-edit size="13" class="ml-8px" @click="openEditMobileModal" />
</template>
</a-table>
<Modal v-model:visible="infoVisible" title="修改用户信息" @ok="handleSubmitUserInfo">
<a-form
</Table>
<Modal v-model:open="infoVisible" centered title="修改用户信息" @ok="handleSubmitUserInfo">
<Form
class="form"
:rules="rules"
:model="userInfoForm"
:label-col-props="{ span: 3, offset: 0 }"
:wrapper-col-props="{ span: 21, offset: 0 }"
>
<a-form-item field="head_image" label="头像">
<FormItem name="head_image" label="头像">
<div class="flex items-center">
<a-avatar :image-url="userInfoForm.file_url" :size="48" />
<Avatar :src="userInfoForm.file_url" :size="48" />
<span class="upload-button" @click="triggerFileInput">
<input
ref="uploadInputRef"
@ -35,40 +40,40 @@
style="display: none"
@change="handleFileChange"
/>
<a-button><icon-upload />上传新头像</a-button>
<Button><icon-upload />上传新头像</Button>
</span>
</div>
</a-form-item>
<a-form-item field="name" label="昵称">
<a-input v-model.trim="userInfoForm.name" placeholder="请输入昵称" />
</a-form-item>
</a-form>
</FormItem>
<FormItem name="name" label="昵称">
<Input v-model:value="userInfoForm.name" placeholder="请输入昵称" />
</FormItem>
</Form>
</Modal>
<Modal v-model:visible="imageVisible" title="头像裁剪">
<Modal v-model:open="imageVisible" centered title="头像裁剪">
<VueCropper></VueCropper>
</Modal>
<Modal v-model:visible="mobileVisible" title="修改手机号" @ok="handleUpdateMobile">
<a-form
<Modal v-model:open="mobileVisible" centered title="修改手机号" @ok="handleUpdateMobile">
<Form
ref="formRef"
:model="form"
class="form"
:rules="formRules"
:label-col-props="{ span: 5, offset: 0 }"
:wrapper-col-props="{ span: 19, offset: 0 }"
label-align="left"
labelAlign="right"
:labelCol="{ span: 4 }"
:wrapperCol="{ span: 20 }"
>
<a-form-item required field="mobile" label="新手机号">
<a-input v-model.trim="form.mobile" size="small" placeholder="请输入新的手机号" />
</a-form-item>
<a-form-item required field="captcha" label="获取验证码">
<a-input v-model.trim="form.captcha" size="small" placeholder="请输入验证码">
<FormItem required name="mobile" label="新手机号">
<Input v-model:value="form.mobile" size="small" placeholder="请输入新的手机号" />
</FormItem>
<FormItem required name="captcha" label="获取验证码">
<Input v-model:value="form.captcha" size="small" placeholder="请输入验证码">
<template #suffix>
<span v-if="countdown <= 0" @click="sendCaptcha">发送验证码</span>
<span v-else>{{ countdown }}s</span>
</template>
</a-input>
</a-form-item>
</a-form>
</Input>
</FormItem>
</Form>
<PuzzleVerification
:show="verificationVisible"
@submit="handleVerificationSubmit"
@ -78,6 +83,7 @@
</div>
</template>
<script setup lang="ts">
import { Button, Form, FormItem, Input, Table, message, Avatar } from 'ant-design-vue';
import Container from '@/components/container.vue';
import Modal from '@/components/modal.vue';
import PuzzleVerification from '@/views/login/components/PuzzleVerification.vue';
@ -122,35 +128,32 @@ const dataSource = computed(() => {
const formRules = {
mobile: [
{
required: true,
message: '请填写手机号',
trigger: ['blur', 'change'],
},
{
validator: (value: string, callback: (error?: string) => void) => {
if (!/^1[3-9]\d{9}$/.test(value)) {
callback('手机号格式不正确');
} else {
callback();
validator: (_rule: any, value: string) => {
if (!value) {
return Promise.reject('请填写手机号');
}
if (!/^1[3-9]\d{9}$/.test(value)) {
return Promise.reject('手机号格式不正确');
}
return Promise.resolve();
},
required: true,
trigger: ['blur', 'change'],
},
],
captcha: [
{
required: true,
message: '请填写验证码',
trigger: ['blur', 'change'],
},
{
validator: (value: string, callback: (error?: string) => void) => {
if (!/^\d{6}$/.test(value)) {
callback('验证码必须是6位数字');
} else {
callback();
validator: (rule, value) => {
if (!value) {
return Promise.reject('请填写验证码');
}
if (!/^\d{6}$/.test(value)) {
return Promise.reject('验证码必须是6位数字');
}
return Promise.resolve();
},
trigger: ['blur', 'change'],
},
],
@ -203,7 +206,7 @@ function openEditMobileModal() {
async function handleSubmitUserInfo() {
await updateMyInfo(userInfoForm);
AMessage.success('修改成功!');
message.success('修改成功!');
}
async function sendCaptcha() {
@ -213,7 +216,7 @@ async function sendCaptcha() {
verificationVisible.value = true;
isSendCaptcha.value = true;
}
AMessage.error('请填写正确的手机号!');
message.error('请填写正确的手机号!');
} catch (error) {
console.log('手机号验证失败:', error);
}
@ -231,7 +234,7 @@ function beginCountdown() {
async function handleVerificationSubmit() {
await sendUpdateMobileCaptcha({ mobile: form.mobile });
AMessage.success('发送成功');
message.success('发送成功');
verificationVisible.value = false;
countdown.value = 60;
beginCountdown();
@ -239,13 +242,13 @@ async function handleVerificationSubmit() {
async function handleUpdateMobile() {
if (!isSendCaptcha.value) {
AMessage.error('请先获取验证码!');
message.error('请先获取验证码!');
return false;
}
const res = await formRef.value.validate();
if (res === true || res === undefined) {
await updateMobile(form);
AMessage.success('修改成功!');
message.success('修改成功!');
}
}

View File

@ -1,34 +0,0 @@
<!--
* @Author: 田鑫
* @Date: 2023-03-05 15:13:44
* @LastEditors: 田鑫
* @LastEditTime: 2023-03-05 15:43:18
* @Description: 模拟登录鉴权页
-->
<template>
<a-modal title="登录鉴权页" :visible="true" :footer="false">
<div w100 align-center>
<a-button w-160 @click="login" :loading="loading" type="primary">登录</a-button>
</div>
</a-modal>
</template>
<script lang="ts" setup>
const route = useRoute();
const router = useRouter();
const loading = ref(false);
function login() {
loading.value = true;
localStorage.setItem('satoken', '123asdzxc');
AMessage.success('登录鉴权成功,准备跳转');
setTimeout(() => {
loading.value = false;
router.push({ name: route.query?.redirect ? route.query?.redirect : 'dashboard' });
}, 1500);
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,19 +0,0 @@
<!--
* @Author: 田鑫
* @Date: 2023-03-05 14:27:21
* @LastEditors: 田鑫
* @LastEditTime: 2023-03-05 15:14:15
* @Description:
-->
<template>
<a-modal title="选择企业:" :visible="true">
<a-select v-model="enterprise" placeholder="请选择您的企业"></a-select>
</a-modal>
</template>
<script lang="ts" setup>
const enterprise = ref('');
</script>
<style lang="scss" scoped>
</style>

View File

@ -6,7 +6,7 @@
<div class="m-auto mt-24px max-w-1000px">
<Container title="推荐产品" class="container-body">
<div class="grid grid-cols-3 gap-20px">
<Product v-for="product in products" :key="product.id" :product="product" @refresh="getProductList" />
<!-- <Product v-for="product in products" :key="product.id" :product="product" @refresh="getProductList" /> -->
</div>
<NoData v-if="products.length === 0" />
</Container>
@ -20,7 +20,7 @@
</template>
<script setup lang="ts">
import Container from '@/components/container.vue';
import Product from '@/views/components/workplace/modules/product.vue';
// import Product from '@/views/components/workplace/modules/product.vue';
import Case from '@/views/components/workplace/modules/case.vue';
import { fetchProductList, fetchSuccessCaseList } from '@/api/all/index';
import { ref, onMounted } from 'vue';

View File

@ -2,16 +2,15 @@
<div class="container">
<div class="flex arco-row-justify-space-between">
<img class="avatar" :src="props.product.image" :alt="props.product.name" />
<a-tag v-if="props.product.status === Status.Enable" class="status status-enable">已开通</a-tag>
<a-tag v-if="props.product.status === Status.Disable" class="status status-disable">未开通</a-tag>
<a-tag v-if="props.product.status === Status.EXPIRED" class="status status-expired">已到期</a-tag>
<a-tag v-if="props.product.status === Status.TRIAL_ENDS" class="status status-expired">试用结束</a-tag>
<a-countdown
<Tag v-if="props.product.status === Status.Enable" class="status status-enable">已开通</Tag>
<Tag v-if="props.product.status === Status.Disable" class="status status-disable">未开通</Tag>
<Tag v-if="props.product.status === Status.EXPIRED" class="status status-expired">已到期</Tag>
<Tag v-if="props.product.status === Status.TRIAL_ENDS" class="status status-expired">试用结束</Tag>
<Countdown
v-if="props.product.status === Status.ON_TRIAL"
class="status-on-trill"
title="试用中"
:value="1000 * (props.product.expired_at ?? 0)"
:now="now()"
format="D天H时m分s秒"
/>
</div>
@ -22,47 +21,50 @@
</p>
</div>
<div class="footer flex arco-row-justify-start">
<a-button
<Button
v-if="props.product.status === Status.Enable || props.product.status === Status.ON_TRIAL"
type="primary"
size="mini"
size="small"
class="mr-8px"
@click="gotoModule(props.product.id)"
>
进入模块
</a-button>
<a-button
</Button>
<Button
v-if="props.product.status === Status.TRIAL_ENDS || props.product.status === Status.EXPIRED"
size="mini"
size="small"
type="primary"
class="mr-8px"
@click="visible = true"
>
立即购买
</a-button>
<a-button
</Button>
<Button
v-if="props.product.status === Status.ON_TRIAL"
class="mr-8px"
size="mini"
type="outline"
size="small"
type="primary"
ghost
@click="visible = true"
>
升级购买
</a-button>
<a-button
</Button>
<Button
v-if="props.product.status === Status.TRIAL_ENDS || props.product.status === Status.EXPIRED"
class="mr-8px"
size="mini"
type="outline"
size="small"
type="primary"
ghost
@click="visible = true"
>
联系客服
</a-button>
<a-popconfirm focusLock title="试用产品" content="确定试用该产品吗?" @ok="handleTrial(props.product.id)">
<a-button v-if="props.product.status === Status.Disable" size="mini" type="outline"> 免费试用7天 </a-button>
</a-popconfirm>
</Button>
<Popconfirm title="试用产品" ok-text="确定" cancel-text="取消" @confirm="handleTrial(props.product.id)">
<template #description>确定试用该产品吗</template>
<Button v-if="props.product.status === Status.Disable" size="small" type="default" ghost> 免费试用7天 </Button>
</Popconfirm>
</div>
<CustomerServiceModal v-model:visible="visible" />
<CustomerServiceModal v-model:open="visible" centered/>
</div>
</template>
@ -71,6 +73,8 @@ import { now } from '@vueuse/core';
import { trialProduct } from '@/api/all';
import { useRouter } from 'vue-router';
import CustomerServiceModal from '@/components/customer-service-modal.vue';
import { Button, message, Tag, Statistic, Popconfirm } from 'ant-design-vue';
const { Countdown } = Statistic;
import { useSidebarStore } from '@/stores/modules/side-bar';
import { useEnterpriseStore } from '@/stores/modules/enterprise';
@ -110,7 +114,7 @@ const handleTrial = async (id: any) => {
if (code === 200) {
getUserEnterpriseInfo();
AMessage.success('试用成功!');
message.success('试用成功!');
emit('refresh');
}
};
@ -174,7 +178,7 @@ const gotoModule = (menuId: number) => {
border-radius: 4px;
background: rgba(255, 245, 222, 1);
:deep(.arco-statistic-title) {
:deep(.ant-statistic-title) {
font-family: $font-family-medium;
font-weight: 400;
font-size: 12px;
@ -184,7 +188,7 @@ const gotoModule = (menuId: number) => {
padding: 0;
}
:deep(.arco-statistic-value) {
:deep(.ant-statistic-content) {
font-family: $font-family-medium;
font-weight: 400;
font-size: 10px;

View File

@ -1,20 +1,19 @@
<template>
<a-modal v-model:visible="visible" title="删除对话" width="400px" @close="onClose">
<Modal v-model:open="visible" title="删除对话" width="400px" @cancel="onClose" centered>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除对话吗删除后聊天记录将不可恢复</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px !bg-#f64b31 !border-none" status="danger" size="large" @click="onDelete"
>确定</a-button
>
<Button @click="onClose">取消</Button>
<Button type="primary" danger @click="onDelete">确定</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Modal, Button, message } from 'ant-design-vue';
import { deleteHistoryItem } from '@/api/all/chat';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -41,7 +40,7 @@ const open = (record) => {
async function onDelete() {
const { code } = await deleteHistoryItem(conversationId.value);
if (code === 200) {
AMessage.success('删除成功');
message.success('删除成功');
emits('delete', conversationId.value);
onClose();
}

View File

@ -94,7 +94,7 @@ export default {
});
return () => (
<Drawer width={240} rootClassName="ct-history-conversation-drawer" v-model:open={open.value} onClose={onClose}>
<Drawer width={240} rootClassName="ct-history-conversation-drawer" v-model:open={open.value} onClose={onClose}>
<header class="header h-40px px-12px flex justify-between items-center">
<span class="text-12px font-400 color-#211F24 font-family-medium">历史对话</span>
<icon-close size={16} class="color-#211F24 cursor-pointer" onClick={onClose} />

View File

@ -1,18 +1,19 @@
<template>
<a-modal v-model:visible="visible" title="确定删除评论?" width="400px" @close="onClose">
<Modal v-model:open="visible" title="确定删除评论?" width="400px" @cancel="onClose" centered>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>删除的评论将从对话中消失但仍在被引用的评论中可见</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px !border-none" size="large" @click="onDelete">删除</a-button>
<Button @click="onClose">取消</Button>
<Button type="primary" danger @click="onDelete">确定</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Modal, Button } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
const emits = defineEmits(['delete', 'close']);

View File

@ -1,5 +1,6 @@
<script lang="jsx">
import { Image, Spin, Button, Input, Textarea, Affix } from '@arco-design/web-vue';
import { Button, Input, Image } from 'ant-design-vue';
const { TextArea } = Input;
import TextOverTips from '@/components/text-over-tips';
import SvgIcon from '@/components/svg-icon/index.vue';
import DeleteCommentModal from './delete-comment-modal.vue';
@ -125,23 +126,23 @@ export default {
</div>
)}
<Textarea
<TextArea
ref={textAreaRef}
auto-size
class={`max-h-220px overflow-y-auto ${isReplay.value ? 'pt-38px' : ''}`}
autoSize
class={`max-h-220px overflow-y-auto textarea-box ${isReplay.value ? 'pt-38px' : ''}`}
size="large"
placeholder="输入评论"
v-model={comment.value}
v-model:value={comment.value}
onPressEnter={onComment}
/>
</div>
{comment.value && (
<div class="flex justify-end mt-12px">
<Button type="outline" class="mr-12px rounded-8px" size="medium" onClick={onClearComment}>
<Button type="primary" ghost class="cancel-btn mr-12px !rounded-8px" onClick={onClearComment}>
取消
</Button>
<Button type="primary" class="rounded-8px" size="medium" onClick={onComment}>
<Button type="primary" class="!rounded-8px" onClick={onComment}>
发送
</Button>
</div>

View File

@ -19,6 +19,9 @@
font-family: $font-family-medium;
}
}
.cancel-btn {
background-color: transparent !important;
}
.ai-text {
font-family: $font-family-medium;
font-size: 16px;
@ -30,7 +33,7 @@
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
:deep(.arco-textarea-wrapper) {
.textarea-box {
min-height: 38px;
display: flex;
border-color: transparent !important;
@ -39,14 +42,11 @@
background-color: #fff;
color: #211f24 !important;
transition: all 0.3s;
.arco-textarea-mirror,
.arco-textarea {
padding: 8px 16px !important;
}
padding: 8px 16px !important;
&:hover {
border-color: #6d4cfe !important;
}
&.arco-textarea-focus {
&:focus-within {
border-color: #6d4cfe !important;
}
}

View File

@ -1,5 +1,6 @@
<script lang="jsx">
import { Image, Spin, Button } from '@arco-design/web-vue';
import { Button } from 'ant-design-vue';
import { Spin } from 'ant-design-vue';
import AiSuggest from './components/ai-suggest/';
import { getShareWorksList, getShareWorksDetail, patchShareWorksConfirm } from '@/api/all/generationWorkshop.ts';
@ -186,7 +187,7 @@ export default {
下一条
</Button>
)}
<Button type="outline" size="large" class="mr-12px" onClick={onBackList}>
<Button type="primary" ghost size="large" class="mr-12px" onClick={onBackList}>
返回列表
</Button>
{renderConfirmBtn()}
@ -220,7 +221,7 @@ export default {
</div>
</header>
{loading.value ? (
<Spin spinning={loading.value} class="flex-1 w-full flex justify-center items-center" size={60} />
<Spin spinning={loading.value} wrapperClassName="flex-1 w-full flex justify-center items-center" size="large" />
) : (
<section class={`page-wrap relative ${isExpand.value ? 'expand' : ''}`}>
<div class="fold-box cursor-pointer" onClick={() => (isExpand.value = true)}>

View File

@ -1,6 +1,6 @@
<script lang="jsx">
import TextOverTips from '@/components/text-over-tips';
import { Image, Spin } from '@arco-design/web-vue';
import { Image, Spin } from 'ant-design-vue';
import { exactFormatTime } from '@/utils/tools';
import { handleUserHome } from '@/utils/user.ts';
@ -52,7 +52,7 @@ export default {
</header>
<section class="page-wrapper flex justify-center">
{loading.value ? (
<Spin spinning={loading.value} class="w-full flex justify-center items-center" size={60} />
<Spin spinning={loading.value} wrapperClassName="w-full flex justify-center items-center" size="large" />
) : (
<div class="explore-container">
<div class="explore-list-wrap pt-24px pb-28px">

View File

@ -1,5 +1,5 @@
<script lang="tsx">
import { Tabs, TabPane } from 'ant-design-vue';
import { Tabs, TabPane, Button } from 'ant-design-vue';
import ManuscriptList from './manuscript/list/index.vue';
import ManuscriptCheckList from './manuscript/check-list/index.vue';
import ShareManuscriptModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/index.vue';
@ -33,17 +33,17 @@ export default defineComponent({
v-slots={{
rightExtra: () => (
<div class="flex items-center">
<a-button type="outline" size="medium" onClick={handleShareModal}>
<Button type="primary" ghost size="medium" onClick={handleShareModal}>
分享内容稿件
</a-button>
</Button>
{showManuscriptList.value && (
<a-button
<Button
type="primary"
size="medium"
class="ml-12px"
onClick={openUploadModal}
v-slots={{
icon: () => <icon-plus size="16" />,
icon: () => <icon-plus size="16" class="mr-8px" />,
default: () => '上传内容稿件',
}}
/>

View File

@ -8,42 +8,42 @@
<div class="filter-row">
<div class="filter-row-item">
<span class="label">内容稿件标题</span>
<a-space size="medium">
<a-input
v-model="query.title"
<Space size="medium">
<Input
v-model:value="query.title"
class="!w-240px"
placeholder="请输入内容稿件标题"
size="medium"
allow-clear
allowClear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
</Input>
</Space>
</div>
<div class="filter-row-item">
<span class="label">序号</span>
<a-space size="medium">
<a-input
v-model="query.uid"
<Space size="medium">
<Input
v-model:value="query.uid"
class="!w-160px"
placeholder="请输入序号"
size="medium"
allow-clear
allowClear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
</Input>
</Space>
</div>
<div class="filter-row-item" v-if="query.audit_status === AuditStatus.Pending">
<span class="label">上传时间</span>
<a-range-picker
v-model="created_at"
<DatePicker.RangePicker
v-model:value="created_at"
size="medium"
allow-clear
format="YYYY-MM-DD"
@ -54,25 +54,25 @@
<template v-if="[AuditStatus.Auditing, AuditStatus.Passed].includes(query.audit_status)">
<div class="filter-row-item">
<span class="label">审核平台</span>
<a-select
v-model="query.audit_platform"
size="medium"
<Select
v-model:value="query.audit_platform"
size="middle"
placeholder="全部"
allow-clear
allowClear
@change="handleSearch"
class="!w-160px"
>
<a-option v-for="(item, index) in PLATFORMS" :key="index" :value="item.value" :label="item.label">{{
<Option v-for="(item, index) in PLATFORMS" :key="index" :value="item.value" :label="item.label">{{
item.label
}}</a-option>
</a-select>
}}</Option>
</Select>
</div>
<div class="filter-row-item">
<span class="label">审核时间</span>
<a-range-picker
v-model="audit_started_at"
<DatePicker.RangePicker
v-model:value="audit_started_at"
size="medium"
allow-clear
allowClear
format="YYYY-MM-DD"
class="!w-280px"
@change="(value) => onDateChange(value, 'audit_started_at')"
@ -81,27 +81,31 @@
</template>
<div class="filter-row-item">
<a-button type="outline" class="mr-12px" size="medium" @click="handleSearch">
<Button type="primary" ghost class="mr-12px" size="medium" @click="handleSearch">
<template #icon>
<icon-search />
<icon-search class="mr-8px"/>
</template>
<template #default>搜索</template>
</a-button>
<a-button size="medium" @click="handleReset">
</Button>
<Button size="medium" @click="handleReset">
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px" />
</template>
<template #default>重置</template>
</a-button>
</Button>
</div>
</div>
</div>
</template>
<script setup>
import { Button, Input, Select, Space, DatePicker } from 'ant-design-vue';
const { Option } = Select;
import { defineEmits, defineProps } from 'vue';
import { PLATFORMS } from '@/views/material-center/components/finished-products/manuscript/check-list/constants';
import { AuditStatus } from '@/views/material-center/components/finished-products/constants';
import { ref, nextTick } from 'vue';
import dayjs from 'dayjs';
const props = defineProps({
query: {
@ -110,7 +114,7 @@ const props = defineProps({
},
});
const emits = defineEmits('search', 'reset', 'update:query');
const emits = defineEmits(['search', 'reset', 'update:query']);
const created_at = ref([]);
const audit_started_at = ref([]);
@ -137,6 +141,7 @@ const onDateChange = (value, type) => {
const handleReset = () => {
created_at.value = [];
audit_started_at.value = [];
emits('reset');
};
</script>

View File

@ -1,25 +1,27 @@
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
title="删除内容稿件"
width="480px"
@close="onClose"
centered
@cancel="onClose"
>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ projectName }} 这个内容稿件吗</span>
</div>
<template #footer>
<a-button size="medium" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px" status="danger" size="medium" @click="onDelete"
>确认删除</a-button
<Button size="medium" @click="onClose">取消</Button>
<Button type="primary" class="ml-16px" danger size="medium" @click="onDelete"
>确认删除</Button
>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal, message } from 'ant-design-vue';
import { deleteWork } from '@/api/all/generationWorkshop';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -48,7 +50,7 @@ const open = (record) => {
async function onDelete() {
const { code } = await deleteWork(projectId.value);
if (code === 200) {
AMessage.success('删除成功');
message.success('删除成功');
update()
onClose();
}

View File

@ -1,133 +1,117 @@
<template>
<a-table
<Table
ref="tableRef"
:data="dataSource"
row-key="id"
column-resizable
:dataSource="dataSource"
rowKey="id"
:pagination="false"
: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)"
:rowSelection="rowSelection"
:showSorterTooltip="false"
@change="handleTableChange"
>
<template #empty>
<template #emptyText>
<NoData text="暂无稿件" />
</template>
<template #columns>
<a-table-column
v-for="column in tableColumns"
:key="column.dataIndex"
:data-index="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:min-width="column.minWidth"
:sortable="column.sortable"
:align="column.align"
ellipsis
tooltip
>
<template #title>
<div class="flex items-center">
<span class="cts mr-4px">{{ column.title }}</span>
<a-tooltip v-if="column.tooltip" :content="column.tooltip" position="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</a-tooltip>
</div>
</template>
<Column
v-for="column in tableColumns"
:key="column.dataIndex"
:dataIndex="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:minWidth="column.minWidth"
:sorter="column.sortable"
:align="column.align"
:ellipsis="true"
>
<template #title>
<span class="cts mr-4px">{{ column.title }}</span>
<Tooltip v-if="column.tooltip" :title="column.tooltip" placement="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</Tooltip>
</template>
<template v-if="column.dataIndex === 'customer_opinion'" #cell="{ record }">
<p
class="h-28px px-8px flex items-center rounded-2px w-fit"
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
>
<span class="cts" :class="getCustomerOpinionInfo(record.customer_opinion)?.color">{{
getCustomerOpinionInfo(record.customer_opinion)?.label ?? '-'
}}</span>
</p>
</template>
<template v-else-if="column.dataIndex === 'platform'" #cell="{ record }">
<template v-if="!PLATFORMS.find((item) => item.value === record.platform)"> - </template>
<img
v-else
width="24"
height="24"
class="rounded-4px"
:src="PLATFORMS.find((item) => item.value === record.platform)?.icon"
/>
</template>
<template v-else-if="column.dataIndex === 'compliance_level'" #cell="{ record }">
<span class="cts num !color-#6D4CFE">{{
record.ai_review?.compliance_level ? `${record.ai_review?.compliance_level}%` : '-'
}}</span>
</template>
<template v-else-if="column.dataIndex === 'title'" #cell="{ record }">
<TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" />
</template>
<template v-else-if="column.dataIndex === 'type'" #cell="{ record }">
<div class="flex items-center">
<img
:src="record.type === EnumManuscriptType.Image ? icon2 : icon3"
width="16"
height="16"
class="mr-4px"
/>
<span class="cts" :class="record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'">{{
record.type === EnumManuscriptType.Image ? '图文' : '视频'
}}</span>
</div>
</template>
<template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #cell="{ record }">
{{ record[column.dataIndex].name || record[column.dataIndex].mobile }}
</template>
<template
#cell="{ record }"
v-else-if="
['created_at', 'last_modified_at', 'audit_started_at', 'audit_passed_at'].includes(column.dataIndex)
"
<template v-if="column.dataIndex === 'customer_opinion'" #customRender="{ record }">
<p
class="h-28px px-8px flex items-center rounded-2px w-fit"
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
>
<span class="cts num">{{ exactFormatTime(record[column.dataIndex]) }}</span>
</template>
<template v-else-if="column.dataIndex === 'cover'" #cell="{ record }">
<HoverImagePreview :src="record.cover">
<a-image :width="64" :height="64" :src="record.cover" class="!rounded-6px" fit="cover">
<template #error>
<img :src="icon4" class="w-full h-full" />
</template>
</a-image>
</HoverImagePreview>
</template>
<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="onCheck(record)"
v-else-if="audit_status === AuditStatus.Pending"
>审核</a-button
>
<a-button type="outline" size="mini" @click="onScan(record)" v-else>查看</a-button>
</div>
</template>
<template v-else #cell="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</a-table-column>
</template>
</a-table>
<span class="cts" :class="getCustomerOpinionInfo(record.customer_opinion)?.color">{{
getCustomerOpinionInfo(record.customer_opinion)?.label ?? '-'
}}</span>
</p>
</template>
<template v-else-if="column.dataIndex === 'platform'" #customRender="{ record }">
<template v-if="!PLATFORMS.find((item) => item.value === record.platform)"> - </template>
<img
v-else
width="24"
height="24"
class="rounded-4px"
:src="PLATFORMS.find((item) => item.value === record.platform)?.icon"
/>
</template>
<template v-else-if="column.dataIndex === 'compliance_level'" #customRender="{ record }">
<span class="cts num !color-#6D4CFE">{{
record.ai_review?.compliance_level ? `${record.ai_review?.compliance_level}%` : '-'
}}</span>
</template>
<template v-else-if="column.dataIndex === 'title'" #customRender="{ record }">
<TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" />
</template>
<template v-else-if="column.dataIndex === 'type'" #customRender="{ record }">
<div class="flex items-center">
<img :src="record.type === EnumManuscriptType.Image ? icon2 : icon3" width="16" height="16" class="mr-4px" />
<span class="cts" :class="record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'">{{
record.type === EnumManuscriptType.Image ? '图文' : '视频'
}}</span>
</div>
</template>
<template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #customRender="{ record }">
{{ record[column.dataIndex].name || record[column.dataIndex].mobile }}
</template>
<template
#customRender="{ record }"
v-else-if="['created_at', 'last_modified_at', 'audit_started_at', 'audit_passed_at'].includes(column.dataIndex)"
>
<span class="cts num">{{ exactFormatTime(record[column.dataIndex]) }}</span>
</template>
<template v-else-if="column.dataIndex === 'cover'" #customRender="{ record }">
<HoverImagePreview :src="record.cover">
<ImgLazyLoad :width="64" :height="64" :src="record.cover" class="!rounded-6px" />
</HoverImagePreview>
</template>
<template v-else-if="column.dataIndex === 'operation'" #customRender="{ record }">
<div class="flex items-center">
<img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" />
<Button type="primary" ghost size="small" @click="onShare(record)" v-if="audit_status === AuditStatus.Passed"
>分享</Button
>
<Button
type="primary"
ghost
size="small"
@click="onCheck(record)"
v-else-if="audit_status === AuditStatus.Pending"
>审核</Button
>
<Button type="primary" ghost size="small" @click="onScan(record)" v-else>查看</Button>
</div>
</template>
<template v-else #customRender="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</Column>
</Table>
<ShareModal ref="shareModalRef" />
</template>
<script setup>
import { ref } from 'vue';
import { Button, Tooltip, Table, Image } from 'ant-design-vue';
const { Column } = Table;
import { formatTableField, exactFormatTime } from '@/utils/tools';
import { EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants';
import { patchWorkAuditsAudit } from '@/api/all/generationWorkshop';
@ -139,16 +123,17 @@ import { AuditStatus } from '@/views/material-center/components/finished-product
import { slsWithCatch } from '@/utils/stroage.ts';
import TextOverTips from '@/components/text-over-tips';
import TextOverTips from '@/components/text-over-tips'
import ImgLazyLoad from "@/components/img-lazy-load";
import ShareModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/share-modal.vue';
import HoverImagePreview from '@/components/hover-image-preview';
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';
// import icon4 from '@/assets/img/error-img.png';
const emits = defineEmits([ 'sorterChange', 'delete', 'select', 'selectAll']);
const emits = defineEmits(['sorterChange', 'delete', 'select', 'selectAll']);
const router = useRouter();
const props = defineProps({
@ -160,10 +145,6 @@ const props = defineProps({
type: Array,
default: () => [],
},
rowSelection: {
type: Array,
default: () => [],
},
selectedRowKeys: {
type: Array,
default: () => [],
@ -176,8 +157,20 @@ const props = defineProps({
const tableRef = ref(null);
const shareModalRef = ref(null);
const handleSorterChange = (column, order) => {
emits('sorterChange', column, order === 'ascend' ? 'asc' : 'desc');
const handleTableChange = (pagination, filters, sorter) => {
if (sorter && sorter.field) {
emits('sorterChange', sorter.field, sorter.order === 'ascend' ? 'asc' : 'desc');
}
};
const rowSelection = {
selectedRowKeys: computed(() => props.selectedRowKeys),
onSelect: (record, selected) => {
emits('select', record, selected);
},
onSelectAll: (selected) => {
emits('selectAll', selected);
},
};
const onDelete = (item) => {
emits('delete', item);

View File

@ -219,7 +219,7 @@ export const INITIAL_QUERY = {
title: '',
created_at: [],
audit_started_at: [],
audit_platform: '',
audit_platform: undefined,
sort_column: undefined,
sort_order: undefined,
};

View File

@ -13,28 +13,29 @@
class="flex justify-end mb-12px"
v-if="[AuditStatus.Pending, AuditStatus.Auditing].includes(query.audit_status)"
>
<a-button
type="outline"
<Button
type="primary"
ghost
class="w-fit"
size="medium"
@click="handleBatchCheck"
v-if="query.audit_status === AuditStatus.Pending"
>批量审核</a-button
>批量审核</Button
>
<a-button
type="outline"
<Button
type="primary"
ghost
class="w-fit"
size="medium"
@click="handleBatchView"
v-if="query.audit_status === AuditStatus.Auditing"
>批量查看</a-button
>批量查看</Button
>
</div>
<ManuscriptCheckTable
:key="query.audit_status"
:tableColumns="tableColumns"
:rowSelection="rowSelection"
:selectedRowKeys="selectedRowKeys"
:dataSource="dataSource"
:audit_status="query.audit_status"
@ -44,16 +45,15 @@
@selectAll="handleSelectAll"
/>
<div v-if="pageInfo.total > 0" class="pagination-row">
<a-pagination
<Pagination
:total="pageInfo.total"
size="mini"
show-total
show-jumper
show-page-size
size="small"
:showTotal="(total, range) => `共 ${total} 条`"
showSizeChanger
showQuickJumper
:current="pageInfo.page"
:page-size="pageInfo.page_size"
:pageSize="pageInfo.page_size"
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</div>
@ -62,7 +62,7 @@
</div>
</template>
<script lang="jsx" setup>
import { Button, Message as AMessage } from '@arco-design/web-vue';
import { Button, Pagination, message } from 'ant-design-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';
@ -83,22 +83,16 @@ const props = defineProps({
const {
dataSource,
pageInfo,
rowSelection,
onPageChange,
onPageSizeChange,
resetPageInfo,
selectedRowKeys,
selectedRows,
handleSelect,
handleSelectAll,
DEFAULT_PAGE_INFO,
} = useTableSelectionWithPagination({
onPageChange: () => {
getData();
},
onPageSizeChange: () => {
getData();
},
});
const router = useRouter();
const tableColumns = ref([]);
@ -140,7 +134,7 @@ const handleSorterChange = (column, order) => {
};
const handleBatchCheck = () => {
if (!selectedRows.value.length) {
AMessage.warning('请选择需审核的内容稿件');
message.warning('请选择需审核的内容稿件');
return;
}
@ -151,7 +145,7 @@ const handleBatchCheck = () => {
};
const handleBatchView = () => {
if (!selectedRows.value.length) {
AMessage.warning('请选择需查看的内容稿件');
message.warning('请选择需查看的内容稿件');
return;
}
@ -175,7 +169,6 @@ const handleDelete = (item) => {
deleteManuscriptModalRef.value?.open({ id, name: `${title}` });
};
watch(
() => props.audit_status,
(newVal) => {

View File

@ -1,9 +1,10 @@
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
:title="action === 'exit' ? '退出审核' : '切换内容稿件'"
width="480px"
@close="onClose"
centered
@cancel="onClose"
>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
@ -14,16 +15,17 @@
}}</span>
</div>
<template #footer>
<a-button size="medium" @click="onClose">继续编辑</a-button>
<a-button type="primary" class="ml-8px" size="medium" @click="onConfirm">
<Button size="medium" @click="onClose">继续编辑</Button>
<Button type="primary" class="ml-8px" size="medium" @click="onConfirm">
{{ action === 'exit' ? '确认退出' : '确认切换' }}
</a-button>
</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
import { useRouter } from 'vue-router';

View File

@ -1,10 +1,11 @@
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
title="提示"
width="480px"
@close="onClose"
modal-class="upload-success11-modal"
centered
wrapClassName="upload-success11-modal"
:footer="null"
>
<div class="flex items-center flex-col justify-center">
@ -13,14 +14,12 @@
<p class="text-14px lh-22px font-400 color-#737478 ld">想让内容更抓眼球更吸流量吗</p>
<p class="text-14px lh-22px font-400 color-#737478 ld">试试内容稿件分析功能吧</p>
</div>
<!-- <template #footer>
<a-button type="primary" class="ml-8px" size="medium" @click="onConfirm">内容稿件分析</a-button>
</template> -->
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-feedback-success.png';
const router = useRouter();

View File

@ -1,5 +1,5 @@
<script lang="jsx">
import { Drawer, Image } from '@arco-design/web-vue';
import { Drawer, Image } from 'ant-design-vue';
import TextOverTips from '@/components/text-over-tips';
import icon1 from '@/assets/img/error-img.png';
@ -32,12 +32,12 @@ export default {
return () => (
<Drawer
title="审核列表"
visible={visible.value}
v-model:open={visible.value}
width={420}
class="check-list-drawer-xt"
footer={false}
header={false}
onCancel={onClose}
rootClassName="check-list-drawer-xt"
footer={null}
closable={false}
onClose={onClose}
>
<div class="flex justify-between items-center h-56px px-24px">
<div class="flex items-center">
@ -61,13 +61,12 @@ export default {
height={48}
preview={false}
src={item.cover}
class="!rounded-4px mr-8px"
fit="cover"
class="!rounded-4px"
v-slots={{
error: () => <img src={icon1} class="w-full h-full" />,
}}
/>
<div class="flex-1 overflow-hidden flex flex-col items-start">
<div class="flex-1 overflow-hidden flex flex-col items-start ml-8px">
<TextOverTips context={item.title} class={`cts !color-#211F24 title mb-4px !text-14px`} />
<p class="cts !text-14px">{`合规程度:${
item.ai_review?.compliance_level ? `${item.ai_review?.compliance_level}%` : '-'

View File

@ -1,43 +1,44 @@
.check-list-drawer-xt {
.arco-drawer-mask {
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.15);
.ant-drawer-mask {
background-color: transparent;
}
.arco-drawer {
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.15);
.arco-drawer-body {
overflow: hidden;
display: flex;
flex-direction: column;
padding: 0 0 24px;
.cts {
color: var(--Text-1, #939499);
.ant-drawer-header {
display: none;
}
.ant-drawer-body {
overflow: hidden;
display: flex;
flex-direction: column;
padding: 0 0 24px;
.cts {
color: var(--Text-1, #939499);
font-family: $font-family-regular;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
&.bold {
color: var(--Text-1, #211f24);
font-family: $font-family-medium;
}
font-family: $font-family-regular;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
&.bold {
color: var(--Text-1, #211f24);
font-family: $font-family-medium;
}
.card-item {
cursor: pointer;
border: 1px solid transparent;
transition: all;
&:hover {
background-color: #e6e6e8;
}
&:not(:last-child) {
margin-bottom: 12px;
}
&.active {
border-color: #6d4cfe;
background-color: #f0edff;
:deep(.overflow-text) {
font-family: $font-family-medium !important;
}
}
.card-item {
cursor: pointer;
border: 1px solid transparent;
transition: all;
&:hover {
background-color: #e6e6e8;
}
&:not(:last-child) {
margin-bottom: 12px;
}
&.active {
border-color: #6d4cfe;
background-color: #f0edff;
:deep(.overflow-text) {
font-family: $font-family-medium !important;
}
}
}

View File

@ -14,10 +14,10 @@ export const TAB_LIST = [
label: '文本',
value: enumTab.TEXT,
},
// {
// label: '图片',
// value: enumTab.IMAGE,
// },
{
label: '图片',
value: enumTab.IMAGE,
},
];
export enum Enum_Level {

View File

@ -24,6 +24,8 @@
</template>
<script setup lang="ts">
import { Input } from 'ant-design-vue';
const {TextArea} = Input
import { ref, computed, watch, defineProps, defineEmits, onMounted, onUnmounted } from 'vue';
import { escapeRegExp } from './constants';

View File

@ -1,23 +1,13 @@
<script lang="jsx">
import axios from 'axios';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Button, Form, Input, FormItem, Tabs, message, Image, Upload, Spin } from 'ant-design-vue';
import { IconLoading } from '@arco-design/web-vue/es/icon';
import {
Image,
Form,
FormItem,
Input,
Textarea,
Button,
Tabs,
Upload,
TabPane,
Spin,
Message as AMessage,
} from '@arco-design/web-vue';
import TextOverTips from '@/components/text-over-tips';
import HighlightTextarea from './highlight-textarea';
const { TabPane } = Tabs;
import 'swiper/css';
import 'swiper/css/navigation';
import { Navigation } from 'swiper/modules';
@ -87,7 +77,7 @@ export default {
const onAgainCheck = () => {
if (!isTextTab.value && !props.modelValue.files?.length) {
AMessage.warning('请先上传需审核图片');
message.warning('请先上传需审核图片');
return;
}
emit('againCheck');
@ -99,15 +89,7 @@ export default {
activeTab.value = key;
};
const validate = () => {
return new Promise((resolve, reject) => {
formRef.value?.validate((errors) => {
if (errors) {
reject();
} else {
resolve();
}
});
});
return formRef.value?.validate();
};
const reset = () => {
formRef.value?.resetFields?.();
@ -139,24 +121,19 @@ export default {
<Upload
ref={uploadRef}
action="/"
draggable
class="w-fit"
custom-request={(option) => uploadImage(option, action)}
customRequest={(option) => uploadImage(option, action)}
accept=".jpg,.jpeg,.png,.gif,.webp"
show-file-list={false}
showUploadList={false}
multiple
>
{{
'upload-button': () => <UploadBtn />,
}}
<UploadBtn />
</Upload>
);
};
const uploadImage = async (option, action = 'upload') => {
const {
fileItem: { file },
} = option;
const { file } = option;
const { name, size, type } = file;
const response = await getImagePreSignedUrl({ suffix: getFileExtension(name) });
@ -190,11 +167,11 @@ export default {
const renderFooterRow = () => {
return (
<>
<Button class="mr-12px" size="medium" onClick={onAgainCheck} disabled={isDisabled.value}>
<Button class="mr-12px" onClick={onAgainCheck} disabled={isDisabled.value}>
再次审核
</Button>
{isTextTab.value ? (
<Button size="medium" type="outline" class="w-123px check-btn" onClick={onAiReplace} disabled={isDisabled.value}>
<Button type="primary" ghost class="w-123px check-btn" onClick={onAiReplace} disabled={isDisabled.value}>
{aiReplaceLoading.value ? (
<>
<IconLoading size={14} />
@ -210,7 +187,7 @@ export default {
) : (
<div class="w-88px">
{renderUpload(
<Button size="medium" type="outline">
<Button type="primary" ghost>
图片替换
</Button>,
'replaceImage',
@ -223,17 +200,17 @@ export default {
const renderTextForm = () => {
return (
<Form ref={formRef} model={props.modelValue} rules={FORM_RULES} layout="vertical" auto-label-width>
<FormItem label="标题" field="title" required>
<FormItem label="标题" name="title" required>
<Input
v-model={props.modelValue.title}
v-model:value={props.modelValue.title}
placeholder="请输入标题"
size="large"
maxLength={30}
show-word-limit
maxlength={30}
showCount
disabled={isDisabled.value}
/>
</FormItem>
<FormItem label="作品描述" field="content" class="flex-1 content-form-item">
<FormItem label="作品描述" name="content" class="flex-1 content-form-item">
<HighlightTextarea
v-model={props.modelValue.content}
disabled={isDisabled.value}
@ -409,9 +386,9 @@ export default {
<div class="right-box">
<p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p>
<Spin
loading={true}
spinning={true}
tip={`${isTextTab.value ? '文本' : '图片'}检测中`}
size={72}
size="large"
class="h-298px !flex flex-col justify-center items-center"
/>
</div>
@ -421,7 +398,7 @@ export default {
<div class="right-box">
<p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p>
{props.getDataLoading ? (
<Spin loading={true} size={72} class="h-298px !flex justify-center items-center" />
<Spin spinning={true} size="large" class="h-298px !flex justify-center items-center" />
) : (
renderCheckSuccessBox()
)}
@ -440,22 +417,20 @@ export default {
<div class="h-full w-full px-24px pt-16px pb-24px content-wrap flex">
<div class="flex-2 left-box mr-24px flex flex-col">
<div class="flex-1 mb-12px rounded-8px border-1px pt-8px flex flex-col pb-16px bg-#F7F8FA border-#E6E6E8 border-solid">
<Tabs v-model={activeTab.value} onTabClick={handleTabClick} class="mb-16px">
<Tabs activeKey={activeTab.value} onChange={handleTabClick} class="mb-16px">
{TAB_LIST.map((item) => (
<TabPane
key={item.value}
v-slots={{
title: () => (
<div class="flex items-center relative">
<span>{item.label}</span>
{
// activeTab.value === item.value && aiReview.value?.violation_items.length > 0 && (
// <icon-exclamation-circle-fill size={14} class="color-#F64B31 absolute right--10px top-0" />
// )
}
</div>
),
}}
tab={
<div class="flex items-center relative">
<span>{item.label}</span>
{
// activeTab.value === item.value && aiReview.value?.violation_items.length > 0 && (
// <icon-exclamation-circle-fill size={14} class="color-#F64B31 absolute right--10px top-0" />
// )
}
</div>
}
/>
))}
</Tabs>

View File

@ -23,43 +23,32 @@
}
.left-box {
:deep(.arco-tabs) {
.arco-tabs-nav {
.arco-tabs-tab {
height: 40px;
// padding: 0 8px;
margin: 0 16px;
}
&::before {
display: none;
}
}
.arco-tabs-content {
display: none;
}
}
:deep(.arco-form) {
:deep(.ant-form) {
height: 100%;
display: flex;
flex-direction: column;
.arco-form-item {
.ant-form-item {
margin-bottom: 24px;
.arco-form-item-label-col {
.arco-form-item-label {
.ant-form-item-label-col {
.ant-form-item-label {
color: #939499;
}
}
}
.content-form-item {
margin-bottom: 0;
display: flex;
flex-direction: column;
.arco-form-item-wrapper-col {
flex: 1;
.arco-form-item-content-wrapper,
.arco-form-item-content,
.arco-textarea-wrapper {
height: 100%;
.ant-row {
height: 100%;
display: flex;
flex-direction: column;
.ant-form-item-control-input {
flex: 1;
.ant-form-item-control-input-content,
.ant-form-item-control-input-content,
.ant-textarea-wrapper {
height: 100%;
}
}
}
}

View File

@ -1,5 +1,5 @@
<script lang="jsx">
import { Image } from '@arco-design/web-vue';
import { Image } from 'ant-design-vue';
import { Swiper, SwiperSlide } from 'swiper/vue';
import TextOverTips from '@/components/text-over-tips';
@ -56,13 +56,13 @@ export default {
height={48}
preview={false}
src={item.cover}
class="!rounded-4px mr-8px"
class="!rounded-4px"
fit="cover"
v-slots={{
error: () => <img src={icon1} class="w-full h-full" />,
}}
/>
<div class="flex-1 overflow-hidden flex flex-col items-start">
<div class="flex-1 overflow-hidden flex flex-col items-start ml-8px">
<TextOverTips context={item.title} class={`cts !color-#211F24 title mb-4px`} />
<p class="cts">{`合规程度:${item.ai_review?.compliance_level ? `${item.ai_review?.compliance_level}%` : '-'}`}</p>
</div>

View File

@ -1,5 +1,6 @@
<script lang="jsx">
import { Button, Message as AMessage, Spin } from '@arco-design/web-vue';
import { Button, message } from 'ant-design-vue';
import { Spin } from 'ant-design-vue';
import CancelCheckModal from './cancel-check-modal.vue';
import CheckSuccessModal from './check-success-modal.vue';
import HeaderCard from './components/header-card';
@ -135,14 +136,14 @@ export default {
};
const onSave = async () => {
if (!selectCardInfo.value.title) {
AMessage.warning('标题不能为空');
message.warning('标题不能为空');
}
contentCardRef.value?.validate().then(async () => {
const { code, data } = await putWorkAuditsUpdate(selectCardInfo.value);
if (code === 200) {
isSaved.value = true;
AMessage.success('当前内容稿件已保存');
message.success('当前内容稿件已保存');
}
});
};
@ -181,13 +182,13 @@ export default {
const renderFooterRow = () => {
return (
<>
<Button size="medium" type="outline" class="mr-12px" onClick={onExit}>
<Button type="primary" ghost class="mr-12px" onClick={onExit}>
退出
</Button>
<Button size="medium" type="outline" class="mr-12px" onClick={onSave}>
<Button type="primary" ghost class="mr-12px" onClick={onSave}>
保存
</Button>
<Button type="primary" size="medium" onClick={onSubmit} loading={submitLoading.value}>
<Button type="primary" onClick={onSubmit} loading={submitLoading.value}>
{submitLoading.value ? '通过审核中...' : '通过审核'}
</Button>
</>

View File

@ -1,5 +1,5 @@
<template>
<a-form-item field="files">
<FormItem name="files">
<template #label>
<div class="flex items-center">
<span class="cts !color-#211F24 mr-4px">图片</span>
@ -23,32 +23,31 @@
/>
</div>
</div>
<a-upload
<Upload
v-if="files.length < 18"
ref="uploadRef"
action="/"
draggable
:custom-request="(option) => emit('upload', option)"
:customRequest="(option) => emit('upload', option)"
accept=".jpg,.jpeg,.png,.gif,.webp"
:show-file-list="false"
:showUploadList="false"
multiple
class="!flex !items-center"
:limit="18 - files.length"
>
<template #upload-button>
<template #default>
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传图片</span>
</div>
</template>
</a-upload>
</Upload>
</VueDraggable>
</div>
</a-form-item>
</FormItem>
</template>
<script setup>
import { VueDraggable } from 'vue-draggable-plus';
import { FormItem, Upload } from 'ant-design-vue';
const props = defineProps({
files: {

View File

@ -1,6 +1,6 @@
<script lang="jsx">
import axios from 'axios';
import { Form, FormItem, Input, Textarea, Upload, Message as AMessage, Button } from '@arco-design/web-vue';
import { Button, Form, FormItem, Input, message, Upload } from 'ant-design-vue';
// import CommonSelect from '@/components/common-select';
// import { VueDraggable } from 'vue-draggable-plus';
import TextOverTips from '@/components/text-over-tips';
@ -11,6 +11,8 @@ import { formatFileSize, getVideoInfo, formatDuration, formatUploadSpeed } from
import { EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants.ts';
import { getImagePreSignedUrl, getVideoPreSignedUrl } from '@/api/all/common';
const { TextArea } = Input;
// import icon1 from '@/assets/img/creative-generation-workshop/icon-close.png';
// 表单验证规则
@ -128,9 +130,7 @@ export default {
formData.value.videoInfo.uploadStatus = ENUM_UPLOAD_STATUS.UPLOADING;
emit('updateVideoInfo', formData.value.videoInfo);
const {
fileItem: { file },
} = option;
const { file } = option;
setVideoInfo(file);
const response = await getVideoPreSignedUrl({ suffix: getFileExtension(file.name) });
@ -161,13 +161,11 @@ export default {
};
// 文件上传处理
const uploadImage = async (option) => {
const {
fileItem: { file },
} = option;
const { file } = option;
// 验证文件数量
if (formData.value.files?.length >= 18) {
AMessage.error('最多只能上传18张图片');
message.error('最多只能上传18张图片');
return;
}
const { name, size, type } = file;
@ -189,15 +187,7 @@ export default {
};
const validate = () => {
return new Promise((resolve, reject) => {
formRef.value?.validate((errors) => {
if (errors) {
reject(formData.value);
} else {
resolve();
}
});
});
return formRef.value?.validate();
};
const resetForm = () => {
@ -211,25 +201,18 @@ export default {
<Upload
ref={uploadRef}
action="/"
draggable
custom-request={uploadVideo}
customRequest={uploadVideo}
accept=".mp4,.mov,.avi,.flv,.wmv"
show-file-list={false}
showUploadList={false}
>
{{
'upload-button': () => {
if (formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.DEFAULT) {
return (
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传视频</span>
</div>
);
} else {
return <Button type="text">替换视频</Button>;
}
},
}}
{formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.DEFAULT ? (
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传视频</span>
</div>
) : (
<Button type="text">替换视频</Button>
)}
</Upload>
);
};
@ -238,7 +221,7 @@ export default {
const isEnd = formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.END;
return (
<FormItem
field="files"
name="files"
v-slots={{
label: () => (
<div class="flex items-center">
@ -315,30 +298,31 @@ export default {
return () => (
<Form ref={formRef} model={formData.value} rules={props.rules} layout="vertical" auto-label-width>
<FormItem label="标题" field="title" required>
<FormItem label="标题" name="title" required>
<Input
v-model={formData.value.title}
v-model:value={formData.value.title}
onInput={() => {
console.log('onInput');
onChange();
emit('reValidate');
}}
placeholder="请输入标题"
size="large"
class="!w-500px"
maxLength={30}
show-word-limit
maxlength={30}
showCount
/>
</FormItem>
<FormItem label="作品描述" field="content">
<Textarea
v-model={formData.value.content}
<FormItem label="作品描述" name="content">
<TextArea
v-model:value={formData.value.content}
onInput={onChange}
placeholder="请输入作品描述"
size="large"
class="textarea-box !w-784px"
show-word-limit
max-length={1000}
showCount
maxlength={1000}
/>
</FormItem>
{isVideo.value ? (
@ -352,7 +336,7 @@ export default {
/>
)}
{/* <FormItem label="所属项目" field="project_ids">
{/* <FormItem label="所属项目" name="project_ids">
<CommonSelect
v-model={formData.value.project_ids}
onChange={() => emit('change')}

View File

@ -31,8 +31,6 @@
}
}
.textarea-box {
:deep(.arco-textarea) {
height: 140px;
max-height: 298px;
}
height: 140px;
max-height: 298px;
}

View File

@ -1,5 +1,5 @@
export const INITIAL_FORM = {
audit_status: '',
audit_status: undefined,
sort_column: undefined,
sort_order: undefined,
};

View File

@ -1,22 +1,15 @@
<script lang="jsx">
import {
Input,
Table,
Modal,
TableColumn,
Checkbox,
Pagination,
Button,
Tooltip,
Notification,
} from '@arco-design/web-vue';
import { Button, Modal, Tooltip, Table, Pagination } from 'ant-design-vue';
import CommonSelect from '@/components/common-select';
import TextOverTips from '@/components/text-over-tips';
import ShareModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/share-modal';
import { INITIAL_FORM, TABLE_COLUMNS } from './constants';
import { formatTableField, exactFormatTime } from '@/utils/tools';
import { CHECK_STATUS, EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants';
import {
CHECK_STATUS,
EnumManuscriptType,
} from '@/views/material-center/components/finished-products/manuscript/list/constants';
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
import { getWorksPage, getWriterLinksGenerate } from '@/api/all/generationWorkshop.ts';
@ -31,8 +24,6 @@ export default {
dataSource,
pageInfo,
onPageChange,
onPageSizeChange,
rowSelection,
handleSelect,
handleSelectAll,
DEFAULT_PAGE_INFO,
@ -40,9 +31,6 @@ export default {
onPageChange: () => {
getData();
},
onPageSizeChange: () => {
getData();
},
});
const visible = ref(false);
const query = ref(cloneDeep(INITIAL_FORM));
@ -86,42 +74,44 @@ export default {
reset();
};
const renderColumn = () => {
return TABLE_COLUMNS.map((column) => (
<TableColumn
key={column.dataIndex}
data-index={column.dataIndex}
fixed={column.fixed}
width={column.width}
min-width={column.minWidth}
sortable={column.sortable}
align={column.align}
ellipsis
tooltip
v-slots={{
title: () => (
<div class="flex items-center">
<span class="cts mr-4px">{column.title}</span>
{column.tooltip && (
<Tooltip content={column.tooltip} position="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip>
)}
</div>
),
cell: ({ record }) => renderCell(record),
}}
/>
));
};
// const renderColumn = () => {
// return TABLE_COLUMNS.map((column) => (
// <TableColumn
// key={column.dataIndex}
// data-index={column.dataIndex}
// fixed={column.fixed}
// width={column.width}
// min-width={column.minWidth}
// sortable={column.sortable}
// align={column.align}
// ellipsis
// tooltip
// v-slots={{
// title: () => (
// <>
// <span class="cts mr-4px">{column.title}</span>
// {column.tooltip && (
// <Tooltip title={column.tooltip} placement="top">
// <IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
// </Tooltip>
// )}
// </>
// ),
// cell: ({ record }) => renderCell(record),
// }}
// />
// ));
// };
const onShare = () => {
shareModalRef.value?.open(selectedRowKeys.value);
};
const handleSorterChange = (column, order) => {
query.value.sort_column = column;
query.value.sort_order = order === 'ascend' ? 'asc' : 'desc';
reload();
const handleSorterChange = (pagination, filters, sorter) => {
if (sorter && !Array.isArray(sorter) && sorter.columnKey) {
query.value.sort_column = sorter.columnKey;
query.value.sort_order = sorter.order === 'ascend' ? 'asc' : 'desc';
reload();
}
};
const getStatusInfo = (audit_status) => {
return CHECK_STATUS.find((v) => v.id === audit_status) ?? {};
@ -132,12 +122,13 @@ export default {
return () => (
<>
<Modal
v-model:visible={visible.value}
v-model:open={visible.value}
title="分享内容稿件"
width="920px"
onClose={onClose}
unmount-on-close
modal-class="share-manuscript-modal"
onCancel={onClose}
centered
destroyOnClose
wrapClassName="share-manuscript-modal"
v-slots={{
footer: () => (
<div class="flex justify-between w-full items-center">
@ -145,16 +136,8 @@ export default {
已选择 <span class="cts color-#211F24 bold">{selectedRows.value.length}</span>
</p>
<div class="flex items-center">
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button
type="primary"
class="ml-16px"
size="medium"
onClick={onShare}
disabled={!selectedRows.value.length}
>
<Button onClick={onClose}>取消</Button>
<Button type="primary" class="ml-16px" onClick={onShare} disabled={!selectedRows.value.length}>
分享
</Button>
</div>
@ -176,109 +159,99 @@ export default {
</div>
<Table
ref={tableRef}
data={dataSource.value}
row-key="id"
column-resizable
row-selection={rowSelection.value}
selected-keys={selectedRowKeys.value}
dataSource={dataSource.value}
rowKey="id"
rowSelection={{
selectedRowKeys: selectedRowKeys.value,
onSelect: handleSelect,
onSelectAll: handleSelectAll,
}}
pagination={false}
scroll={{ x: '100%', y: '100%' }}
class="overflow-hidden"
bordered
onSorterChange={handleSorterChange}
onSelect={handleSelect}
onSelectAll={handleSelectAll}
showSorterTooltip={false}
onChange={handleSorterChange}
v-slots={{
empty: () => <NoData />,
columns: () => (
<>
{TABLE_COLUMNS.map((column) => (
<TableColumn
key={column.dataIndex}
data-index={column.dataIndex}
fixed={column.fixed}
width={column.width}
min-width={column.minWidth}
sortable={column.sortable}
align={column.align}
ellipsis
tooltip
v-slots={{
title: () => (
<div class="flex items-center">
<span class="cts mr-4px bold color-#211F24">{column.title}</span>
{column.tooltip && (
<Tooltip content={column.tooltip} position="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip>
)}
</div>
),
cell: ({ record }) => {
if (column.dataIndex === 'audit_status') {
return (
<div
class="flex items-center w-fit h-24px px-8px rounded-2px"
style={{ backgroundColor: getStatusInfo(record.audit_status).backgroundColor }}
>
<span class="cts s1 bold" style={{ color: getStatusInfo(record.audit_status).color }}>
{getStatusInfo(record.audit_status).name}
</span>
</div>
);
} else if (column.dataIndex === 'title') {
return <TextOverTips context={record.title} />;
} else if (column.dataIndex === 'type') {
return (
<div class="flex items-center">
<img
src={record.type === EnumManuscriptType.Image ? icon2 : icon3}
width="16"
height="16"
class="mr-4px"
/>
<span
class={`cts !text-14px !lh-22px ${
record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'
}`}
>
{record.type === EnumManuscriptType.Image ? '图文' : '视频'}
</span>
</div>
);
} else if (column.dataIndex === 'last_modified_at') {
return (
<span class="cts num">
{exactFormatTime(
record.last_modified_at,
'YYYY-MM-DD HH:mm:ss',
'YYYY-MM-DD HH:mm:ss',
)}
</span>
);
} else {
return formatTableField(column, record, true);
}
},
}}
/>
))}
</>
),
emptyText: () => <NoData />,
}}
/>
>
{TABLE_COLUMNS.map((column) => (
<Table.Column
key={column.dataIndex}
dataIndex={column.dataIndex}
fixed={column.fixed}
width={column.width}
minWidth={column.minWidth}
sorter={column.sortable}
align={column.align}
ellipsis
title={() => (
<>
<span class="cts mr-4px bold color-#211F24">{column.title}</span>
{column.tooltip && (
<Tooltip title={column.tooltip} placement="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip>
)}
</>
)}
customRender={({ record }) => {
if (column.dataIndex === 'audit_status') {
return (
<div
class="flex items-center w-fit h-24px px-8px rounded-2px"
style={{ backgroundColor: getStatusInfo(record.audit_status).backgroundColor }}
>
<span class="cts s1 bold" style={{ color: getStatusInfo(record.audit_status).color }}>
{getStatusInfo(record.audit_status).name}
</span>
</div>
);
} else if (column.dataIndex === 'title') {
return <TextOverTips context={record.title} class="!text-12px" />;
} else if (column.dataIndex === 'type') {
return (
<div class="flex items-center">
<img
src={record.type === EnumManuscriptType.Image ? icon2 : icon3}
width="16"
height="16"
class="mr-4px"
/>
<span
class={`cts !text-14px !lh-22px ${
record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'
}`}
>
{record.type === EnumManuscriptType.Image ? '图文' : '视频'}
</span>
</div>
);
} else if (column.dataIndex === 'last_modified_at') {
return (
<span class="cts num">
{exactFormatTime(record.last_modified_at, 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss')}
</span>
);
} else {
return formatTableField(column, record, true);
}
}}
/>
))}
</Table>
{pageInfo.value.total > 0 && (
<div class="flex justify-end mt-16px">
<Pagination
total={pageInfo.value.total}
size="mini"
show-total
show-jumper
show-page-size
size="small"
showTotal={(total, range) => `${total}`}
showQuickJumper
showSizeChanger
current={pageInfo.value.page}
page-size={pageInfo.value.page_size}
pageSize={pageInfo.value.page_size}
onChange={onPageChange}
onPageSizeChange={onPageSizeChange}
/>
</div>
)}

View File

@ -1,5 +1,5 @@
<script lang="jsx">
import { Modal, Form, FormItem, Input, Button, Message as AMessage } from '@arco-design/web-vue';
import { Button, Modal, Form, FormItem, Input, Tooltip, message } from 'ant-design-vue';
import CommonSelect from '@/components/common-select';
import { useClipboard } from '@vueuse/core';
@ -65,24 +65,22 @@ export default {
};
const onGenerateLink = () => {
formRef.value.validate().then(async (errors) => {
if (!errors) {
try {
loading.value = true;
const { code, data } = await postShareLinksGenerate(formData.value);
if (code === 200) {
onClose();
formRef.value.validate().then(async () => {
try {
loading.value = true;
const { code, data } = await postShareLinksGenerate(formData.value);
if (code === 200) {
onClose();
const url = router.resolve({
path: `/explore/list/${data.code}`,
}).href;
copy(generateFullUrl(url));
AMessage.success('链接已复制!');
emit('close');
}
} finally {
loading.value = false;
const url = router.resolve({
path: `/explore/list/${data.code}`,
}).href;
copy(generateFullUrl(url));
message.success('链接已复制!');
emit('close');
}
} finally {
loading.value = false;
}
});
};
@ -96,27 +94,33 @@ export default {
});
return () => (
<Modal
v-model:visible={visible.value}
v-model:open={visible.value}
title="分享内容稿件"
width="480px"
onClose={onClose}
unmount-on-close
onCancel={onClose}
destroyOnClose
centered
auto-label-width
v-slots={{
footer: () => (
<>
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button type="primary" class="ml-16px" size="medium" onClick={onGenerateLink} disabled={loading.value}>
<Button onClick={onClose}>取消</Button>
<Button type="primary" class="ml-16px" onClick={onGenerateLink} disabled={loading.value}>
{loading.value ? '生成中...' : '生成链接'}
</Button>
</>
),
}}
>
<Form ref={formRef} rules={rules} model={formData.value} auto-label-width>
<FormItem label="有效期" prop="days" row-class="!items-center">
<Form
ref={formRef}
rules={rules}
model={formData.value}
labelAlign="right"
labelCol={{ span: 5 }}
wrapperCol={{ span: 19 }}
>
<FormItem label="有效期" name="days" row-class="!items-center">
<CommonSelect
v-model={formData.value.days}
options={OPTIONS}
@ -128,21 +132,20 @@ export default {
/>
</FormItem>
<FormItem
label="分享对象"
prop="receiver"
name="receiver"
row-class="!items-center"
v-slots={{
label: () => (
<div class="flex items-center">
<span>分享对象</span>
<a-tooltip content="可填写客户名称、昵称等,非必填" position="top">
<Tooltip title="可填写客户名称、昵称等,非必填" placement="top">
<icon-question-circle class="tooltip-icon color-#737478 ml-4px" size="14" />
</a-tooltip>
</Tooltip>
</div>
),
}}
>
<Input v-model={formData.value.receiver} class="!w-240px" size="large" placeholder="请输入分享对象" />
<Input v-model:value={formData.value.receiver} class="!w-240px" size="large" placeholder="请输入分享对象" />
</FormItem>
</Form>
</Modal>

View File

@ -18,49 +18,36 @@
font-size: 14px;
}
}
.arco-modal-body {
.ant-modal-body {
height: 464px;
display: flex;
flex-direction: column;
overflow: hidden;
.arco-scrollbar-track {
.ant-scrollbar-track {
display: none !important;
}
.arco-table {
.arco-table-container {
.arco-table-element {
thead {
.arco-table-tr {
.arco-table-th {
.arco-table-cell {
padding: 10px 16px !important;
}
}
}
}
tbody {
.arco-table-tr {
.arco-table-td {
.arco-table-cell {
padding: 6px 16px;
.arco-table-cell-content,
.arco-table-td-content {
font-size: 12px;
line-height: 20px;
}
}
}
}
.ant-table {
.ant-table-thead {
.ant-table-cell {
padding: 10px 16px !important;
}
}
.ant-table-body {
.ant-table-cell {
padding: 6px 16px !important;
.ant-table-cell-content {
font-size: 12px;
line-height: 20px;
}
}
}
}
.arco-pagination {
.arco-pagination-total,
.arco-pagination-jumper-prepend {
.ant-pagination {
.ant-pagination-total-text,
.ant-pagination-options-quick-jumper {
font-size: 14px;
}
.arco-pagination-jumper-prepend {
.ant-pagination-options-quick-jumper {
font-family: $font-family-regular;
}
}

View File

@ -1,16 +1,5 @@
<script lang="jsx">
import {
Modal,
Form,
FormItem,
Input,
RadioGroup,
Radio,
Upload,
Button,
Message as AMessage,
Textarea,
} from '@arco-design/web-vue';
import { Modal, Button, Form, FormItem, RadioGroup, Radio, Input, message, Upload } from 'ant-design-vue';
import { useClipboard } from '@vueuse/core';
import { getWriterLinksGenerate, getTemplateUrl, postWorksByLink, postWorksByFile } from '@/api/all/generationWorkshop';
import { generateFullUrl } from '@/utils/tools';
@ -18,6 +7,9 @@ import { slsWithCatch } from '@/utils/stroage.ts';
import TextOverTips from '@/components/text-over-tips';
import icon1 from '@/assets/img/media-account/icon-feedback-fail.png';
import icon2 from '@/assets/img/media-account/icon-download.png';
const { TextArea } = Input;
// 状态枚举
const TASK_STATUS = {
@ -41,7 +33,7 @@ const INITIAL_FORM = {
export default {
setup(props, { emit, expose }) {
const update = inject('update');
// const update = inject('update');
const router = useRouter();
// 响应式状态
@ -119,15 +111,13 @@ export default {
handleHandwriteSubmit();
return;
}
formRef.value?.validate(async (errors) => {
if (!errors) {
taskStatus.value = TASK_STATUS.LOADING;
const { link } = form.value;
const { code, data } = await postWorksByLink({ link });
if (code === 200) {
taskStatus.value = TASK_STATUS.SUCCESS;
works.value = data ? [data] : [];
}
formRef.value?.validate().then(async () => {
taskStatus.value = TASK_STATUS.LOADING;
const { link } = form.value;
const { code, data } = await postWorksByLink({ link });
if (code === 200) {
taskStatus.value = TASK_STATUS.SUCCESS;
works.value = data ? [data] : [];
}
});
}, 300);
@ -140,27 +130,25 @@ export default {
// 手写提交处理
const handleHandwriteSubmit = () => {
if (!form.value.writerLink) {
AMessage.warning('请输入上传链接!');
message.warning('请输入上传链接!');
return;
}
copy(form.value.writerLink);
AMessage.success('复制成功!');
message.success('复制成功!');
onClose();
};
// 取消上传
const onCancelUpload = () => {
taskStatus.value = TASK_STATUS.DEFAULT;
AMessage.info('已取消上传');
message.info('已取消上传');
};
// 文件上传处理
const handleUpload = async (option) => {
taskStatus.value = TASK_STATUS.LOADING;
const {
fileItem: { file },
} = option;
const { file } = option;
const formData = new FormData();
formData.append('file', file);
@ -222,9 +210,9 @@ export default {
// 渲染链接上传表单
const renderLinkForm = () => (
<FormItem label="链接地址" field="link" required>
<Textarea
v-model={form.value.link}
<FormItem label="链接地址" name="link" required>
<TextArea
v-model:value={form.value.link}
size="large"
placeholder="请输入飞书链接地址"
autoSize={{ minRows: 5, maxRows: 8 }}
@ -234,8 +222,8 @@ export default {
// 渲染手写上传表单
const renderHandwriteForm = () => (
<FormItem label="上传链接" field="writerLink">
<Input v-model={form.value.writerLink} placeholder="请输入上传链接" disabled size="large" />
<FormItem label="上传链接" name="writerLink">
<Input v-model:value={form.value.writerLink} placeholder="请输入上传链接" disabled size="large" />
</FormItem>
);
@ -245,24 +233,20 @@ export default {
<div class="flex flex-col w-full">
<Upload
action="/"
draggable
multiple
customRequest={handleUpload}
class="w-full"
accept=".xlsx,.xls,.docx,.doc,.mp4,.mov,.avi,.flv,.wmv,.m4v"
show-file-list={false}
showUploadList={false}
>
{{
'upload-button': () => (
<div class="upload-box">
<icon-plus size="14" class="mb-16px" />
<span class="text mb-4px">点击或拖拽文件到此处上传</span>
<span class="tip">支持文档文本+, 视频批量上传</span>
</div>
),
}}
<div class="upload-box">
<icon-plus size="14" class="mb-16px" />
<span class="text mb-4px">点击或拖拽文件到此处上传</span>
<span class="tip">支持文档文本+, 视频批量上传</span>
</div>
</Upload>
<div class="flex items-center cursor-pointer mt-8px" onClick={handleDownloadTemplate}>
<icon-download size="14" class="mr-4px !color-#6D4CFE" />
<img src={icon2} width="16" height="16" class="mr-4px" />
<span class="cts color-#6D4CFE">下载示例文档</span>
</div>
</div>
@ -332,36 +316,30 @@ export default {
const renderFooterButtons = () => {
const buttonMap = {
[TASK_STATUS.LOADING]: () => (
<Button type="primary" size="medium" onClick={onCancelUpload}>
<Button type="primary" onClick={onCancelUpload}>
取消上传
</Button>
),
[TASK_STATUS.DEFAULT]: () => (
<>
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button type="primary" size="medium" onClick={onSubmit}>
<Button onClick={onClose}>取消</Button>
<Button type="primary" onClick={onSubmit}>
{isHandwrite.value ? '复制链接' : '确认'}
</Button>
</>
),
[TASK_STATUS.FAILED]: () => (
<>
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button type="primary" size="medium" onClick={onClose}>
<Button onClick={onClose}>取消</Button>
<Button type="primary" onClick={onClose}>
重新上传
</Button>
</>
),
[TASK_STATUS.SUCCESS]: () => (
<>
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button type="primary" size="medium" onClick={goUpload}>
<Button onClick={onClose}>取消</Button>
<Button type="primary" onClick={goUpload}>
确认
</Button>
</>
@ -375,17 +353,16 @@ export default {
return () => (
<Modal
v-model:visible={visible.value}
centered
v-model:open={visible.value}
title={getTitle()}
modal-class="upload-manuscript-modal"
wrapClassName="upload-manuscript-modal"
width="500px"
mask-closable={false}
unmount-on-close
onClose={onClose}
footer={!(isDefault.value && isLocal.value)}
v-slots={{
footer: () => renderFooterButtons(),
}}
maskClosable={false}
destroyOnClose
centered
onCancel={onClose}
footer={isDefault.value && isLocal.value ? null : renderFooterButtons()}
>
<Form
ref={formRef}
@ -398,7 +375,7 @@ export default {
>
{isDefault.value && (
<FormItem label="上传方式">
<RadioGroup v-model={uploadType.value} onChange={onUploadTypeChange}>
<RadioGroup v-model:value={uploadType.value} onChange={onUploadTypeChange}>
<Radio value={UPLOAD_TYPE.LINK}>链接上传</Radio>
<Radio value={UPLOAD_TYPE.LOCAL}>本地上传</Radio>
<Radio value={UPLOAD_TYPE.HANDWRITE}>写手上传</Radio>

View File

@ -28,4 +28,8 @@
border: 1px dashed var(--Border-1, #d7d7d9);
background: var(--BG-200, #f2f3f5);
}
.ant-upload {
cursor: pointer;
width: 100%;
}
}

View File

@ -1,5 +1,5 @@
<script lang="jsx">
import { Button, Message as AMessage, Spin } from '@arco-design/web-vue';
import { Button, Spin, message } from 'ant-design-vue';
import { useRouter, useRoute } from 'vue-router';
import { AuditStatus } from '@/views/material-center/components/finished-products/constants';
@ -119,12 +119,12 @@ export default {
return (
<>
<Button size="medium" type="outline" class="mr-12px" onClick={onBack}>
<Button type="primary" ghost class="mr-12px" onClick={onBack}>
退出
</Button>
<Button
size="medium"
type="outline"
type="primary"
ghost
class="mr-12px"
onClick={() =>
router.push({
@ -138,7 +138,7 @@ export default {
编辑
</Button>
{audit_status !== AuditStatus.Passed && (
<Button type="primary" size="medium" onClick={_fn}>
<Button type="primary" onClick={_fn}>
去审核
</Button>
)}
@ -157,7 +157,7 @@ export default {
});
return () => (
<Spin loading={loading.value} class="manuscript-detail-wrap" size={50}>
<Spin spinning={loading.value} wrapperClassName="manuscript-detail-wrap" size="large">
<div class="h-full w-full flex flex-col">
<div class="flex items-center mb-8px">
<span class="cts color-#4E5969 cursor-pointer" onClick={onBack}>

View File

@ -1,18 +1,19 @@
<template>
<a-modal v-model:visible="visible" title="退出编辑" width="480px" @close="onClose">
<Modal v-model:open="visible" title="退出编辑" centered width="480px" @cancel="onClose">
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>内容已修改尚未保存若退出编辑本次修改将不保存</span>
</div>
<template #footer>
<a-button size="medium" @click="onClose">继续编辑</a-button>
<a-button type="primary" class="ml-8px" size="medium" @click="onConfirm">确认退出</a-button>
<Button size="medium" @click="onClose">继续编辑</Button>
<Button type="primary" class="ml-8px" size="medium" @click="onConfirm">确认退出</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
const router = useRouter();

View File

@ -1,5 +1,5 @@
<script lang="jsx">
import { Button, Message as AMessage } from '@arco-design/web-vue';
import { Button, message } from 'ant-design-vue';
import EditForm, { ENUM_UPLOAD_STATUS, INITIAL_VIDEO_INFO } from '../components/edit-form';
import CancelEditModal from './cancel-edit-modal.vue';
@ -43,14 +43,14 @@ export default {
const onSave = async (check = false) => {
formRef.value?.validate().then(async () => {
if (dataSource.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.UPLOADING) {
AMessage.warning('有视频正在上传中,请等待上传完成后再提交');
message.warning('有视频正在上传中,请等待上传完成后再提交');
return;
}
const filteredWorks = omit(dataSource.value, 'videoInfo');
const { code, data } = await putWorksUpdate({ id: workId.value, ...filteredWorks });
if (code === 200) {
AMessage.success('保存成功');
message.success('保存成功');
isSaved.value = true;
if (check) {

View File

@ -3,18 +3,17 @@
<div class="filter-row">
<div class="filter-row-item">
<span class="label">内容稿件标题</span>
<a-input
v-model="query.title"
<Input
v-model:value="query.title"
class="!w-240px"
placeholder="请输入内容稿件标题"
size="medium"
allow-clear
allowClear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</Input>
</div>
<!-- <div class="filter-row-item">
<span class="label">所属项目</span>
@ -28,20 +27,17 @@
</div> -->
<div class="filter-row-item">
<span class="label">序号</span>
<a-space size="medium">
<a-input
v-model="query.uid"
class="!w-160px"
placeholder="请输入序号"
size="medium"
allow-clear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
<Input
v-model:value="query.uid"
class="!w-160px"
placeholder="请输入序号"
allowClear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</Input>
</div>
<div class="filter-row-item">
<span class="label">审核状态</span>
@ -56,8 +52,8 @@
</div>
<div class="filter-row-item">
<span class="label">上传时间</span>
<a-range-picker
v-model="created_at"
<DatePicker.RangePicker
v-model:value="created_at"
size="medium"
allow-clear
format="YYYY-MM-DD"
@ -66,24 +62,25 @@
/>
</div>
<div class="filter-row-item">
<a-button type="outline" class="mr-12px" size="medium" @click="handleSearch">
<Button type="primary" ghost class="mr-12px" size="medium" @click="handleSearch">
<template #icon>
<icon-search />
<icon-search class="mr-8px" />
</template>
<template #default>搜索</template>
</a-button>
<a-button size="medium" @click="handleReset">
</Button>
<Button size="medium" @click="handleReset">
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px" />
</template>
<template #default>重置</template>
</a-button>
</Button>
</div>
</div>
</div>
</template>
<script setup>
import { Button, Input, DatePicker } from 'ant-design-vue';
import { defineEmits, defineProps } from 'vue';
import { CHECK_STATUS } from '@/views/material-center/components/finished-products/manuscript/list/constants';
import CommonSelect from '@/components/common-select';
@ -96,7 +93,7 @@ const props = defineProps({
},
});
const emits = defineEmits('search', 'reset', 'update:query');
const emits = defineEmits(['search', 'reset', 'update:query']);
const created_at = ref([]);
// const projects = ref([]);

View File

@ -67,7 +67,7 @@ export const TABLE_COLUMNS = [
{
title: '操作',
dataIndex: 'operation',
width: 180,
width: 200,
fixed: 'right',
},
];

View File

@ -1,25 +1,19 @@
<template>
<a-modal
v-model:visible="visible"
title="删除稿件"
width="480px"
@close="onClose"
>
<Modal v-model:open="visible" title="删除稿件" centered width="480px" @cancel="onClose">
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ projectName }} 这个稿件吗</span>
<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
>
<Button size="large" @click="onClose">取消</Button>
<Button type="primary" class="ml-16px" danger size="large" @click="onDelete">确认删除</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal, message } from 'ant-design-vue';
import { deleteWork } from '@/api/all/generationWorkshop';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -48,8 +42,8 @@ const open = (record) => {
async function onDelete() {
const { code } = await deleteWork(projectId.value);
if (code === 200) {
AMessage.success('删除成功');
update()
message.success('删除成功');
update();
onClose();
}
}

View File

@ -1,123 +1,114 @@
<template>
<a-table
<Table
ref="tableRef"
:data="dataSource"
row-key="id"
column-resizable
:dataSource="dataSource"
rowKey="id"
:pagination="false"
:scroll="{ x: '100%' }"
class="manuscript-table w-100% flex-1"
bordered
@sorter-change="handleSorterChange"
:showSorterTooltip="false"
@change="handleTableChange"
>
<template #empty>
<template #emptyText>
<NoData text="暂无稿件" />
</template>
<template #columns>
<a-table-column
v-for="column in TABLE_COLUMNS"
:key="column.dataIndex"
:data-index="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:min-width="column.minWidth"
:sortable="column.sortable"
:align="column.align"
ellipsis
tooltip
>
<template #title>
<div class="flex items-center">
<span class="cts mr-4px">{{ column.title }}</span>
<a-tooltip v-if="column.tooltip" :content="column.tooltip" position="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</a-tooltip>
</div>
</template>
<Column
v-for="column in TABLE_COLUMNS"
:key="column.dataIndex"
:dataIndex="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:minWidth="column.minWidth"
:sorter="column.sortable"
:align="column.align"
:ellipsis="true"
>
<template #title>
<span class="cts mr-4px">{{ column.title }}</span>
<Tooltip v-if="column.tooltip" :title="column.tooltip" placement="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</Tooltip>
</template>
<template v-if="column.dataIndex === 'create_at'" #cell="{ record }">
{{ exactFormatTime(record.create_at) }}
</template>
<template v-else-if="column.dataIndex === 'customer_opinion'" #cell="{ record }">
<p
class="h-28px px-8px flex items-center rounded-2px w-fit"
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
>
<span class="cts" :class="getCustomerOpinionInfo(record.customer_opinion)?.color">{{
getCustomerOpinionInfo(record.customer_opinion)?.label ?? '-'
}}</span>
</p>
</template>
<template v-else-if="column.dataIndex === 'title'" #cell="{ record }">
<TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" />
</template>
<template v-else-if="column.dataIndex === 'audit_status'" #cell="{ record }">
<div
class="flex items-center w-fit h-28px px-8px rounded-2px"
:style="{ backgroundColor: getStatusInfo(record.audit_status).backgroundColor }"
>
<span class="cts s1" :style="{ color: getStatusInfo(record.audit_status).color }">{{
getStatusInfo(record.audit_status).name
}}</span>
</div>
</template>
<template v-else-if="column.dataIndex === 'type'" #cell="{ record }">
<div class="flex items-center">
<img
:src="record.type === EnumManuscriptType.Image ? icon2 : icon3"
width="16"
height="16"
class="mr-4px"
/>
<span class="cts" :class="record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'">{{
record.type === EnumManuscriptType.Image ? '图文' : '视频'
}}</span>
</div>
</template>
<template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #cell="{ record }">
{{ record[column.dataIndex].name || record[column.dataIndex].mobile }}
</template>
<template v-else-if="['created_at', 'last_modified_at'].includes(column.dataIndex)" #cell="{ record }">
{{ exactFormatTime(record[column.dataIndex]) }}
</template>
<template v-else-if="column.dataIndex === 'cover'" #cell="{ record }">
<HoverImagePreview :src="record.cover">
<a-image :width="64" :height="64" :src="record.cover" class="!rounded-6px" fit="cover">
<template #error>
<img :src="icon4" class="w-full h-full" />
</template>
</a-image>
</HoverImagePreview>
</template>
<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" class="mr-8px" @click="onEdit(record)">编辑</a-button>
<a-button type="outline" size="mini" @click="onDetail(record)">详情</a-button>
</div>
</template>
<template v-else #cell="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</a-table-column>
</template>
</a-table>
<template v-if="column.dataIndex === 'create_at'" #customRender="{ record }">
{{ exactFormatTime(record.create_at) }}
</template>
<template v-else-if="column.dataIndex === 'customer_opinion'" #customRender="{ record }">
<p
class="h-28px px-8px flex items-center rounded-2px w-fit"
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
>
<span class="cts" :class="getCustomerOpinionInfo(record.customer_opinion)?.color">{{
getCustomerOpinionInfo(record.customer_opinion)?.label ?? '-'
}}</span>
</p>
</template>
<template v-else-if="column.dataIndex === 'title'" #customRender="{ record }">
<TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" />
</template>
<template v-else-if="column.dataIndex === 'audit_status'" #customRender="{ record }">
<div
class="flex items-center w-fit h-28px px-8px rounded-2px"
:style="{ backgroundColor: getStatusInfo(record.audit_status).backgroundColor }"
>
<span class="cts s1" :style="{ color: getStatusInfo(record.audit_status).color }">{{
getStatusInfo(record.audit_status).name
}}</span>
</div>
</template>
<template v-else-if="column.dataIndex === 'type'" #customRender="{ record }">
<div class="flex items-center">
<img :src="record.type === EnumManuscriptType.Image ? icon2 : icon3" width="16" height="16" class="mr-4px" />
<span class="cts" :class="record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'">{{
record.type === EnumManuscriptType.Image ? '图文' : '视频'
}}</span>
</div>
</template>
<template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #customRender="{ record }">
{{ record[column.dataIndex].name || record[column.dataIndex].mobile }}
</template>
<template v-else-if="['created_at', 'last_modified_at'].includes(column.dataIndex)" #customRender="{ record }">
{{ exactFormatTime(record[column.dataIndex]) }}
</template>
<template v-else-if="column.dataIndex === 'cover'" #customRender="{ record }">
<HoverImagePreview :src="record.cover">
<ImgLazyLoad :width="64" :height="64" :src="record.cover" class="!rounded-6px" />
</HoverImagePreview>
</template>
<template v-else-if="column.dataIndex === 'operation'" #customRender="{ record }">
<div class="flex items-center">
<img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" />
<Button type="primary" ghost size="small" class="mr-8px" @click="onEdit(record)">编辑</Button>
<Button type="primary" ghost size="small" @click="onDetail(record)">详情</Button>
</div>
</template>
<template v-else #customRender="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</Column>
</Table>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Tooltip, Table, Image } from 'ant-design-vue';
const { Column } = Table;
import { formatTableField, exactFormatTime } from '@/utils/tools';
import { TABLE_COLUMNS } from './constants';
import { CHECK_STATUS, EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants';
import {
CHECK_STATUS,
EnumManuscriptType,
} from '@/views/material-center/components/finished-products/manuscript/list/constants';
import { CUSTOMER_OPINION } from '@/views/material-center/components/finished-products/manuscript/check-list/constants';
import TextOverTips from '@/components/text-over-tips';
import HoverImagePreview from '@/components/hover-image-preview';
import ImgLazyLoad from "@/components/img-lazy-load";
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 router = useRouter();
@ -131,8 +122,10 @@ const props = defineProps({
const tableRef = ref(null);
const handleSorterChange = (column, order) => {
emits('sorterChange', column, order === 'ascend' ? 'asc' : 'desc');
const handleTableChange = (pagination, filters, sorter) => {
if (sorter && sorter.field) {
emits('sorterChange', sorter.field, sorter.order === 'ascend' ? 'asc' : 'desc');
}
};
const onDelete = (item) => {
emits('delete', item);

View File

@ -2,7 +2,7 @@ export const INITIAL_QUERY = {
title: '',
// project_ids: [],
uid: '',
audit_status: '',
audit_status: undefined,
created_at: [],
sort_column: undefined,
sort_order: undefined,
@ -11,13 +11,13 @@ export const INITIAL_QUERY = {
export enum EnumCheckStatus {
All = '',
Wait = 1,
Checking = 2,
Checking = 2,
Passed = 3,
}
export enum EnumManuscriptType {
All = '',
Image = 0,
Video = 1,
Video = 1,
}
export const CHECK_STATUS = [
@ -25,19 +25,19 @@ export const CHECK_STATUS = [
name: '待审核',
id: EnumCheckStatus.Wait,
backgroundColor: '#F2F3F5',
color: '#3C4043'
color: '#3C4043',
},
{
name: '审核中',
id: EnumCheckStatus.Checking,
backgroundColor: '#FFF7E5',
color: '#FFAE00'
color: '#FFAE00',
},
{
name: '已通过',
id: EnumCheckStatus.Passed,
backgroundColor: '#EBF7F2',
color: '#25C883'
color: '#25C883',
},
];
export const MANUSCRIPT_TYPE = [
@ -53,4 +53,4 @@ export const MANUSCRIPT_TYPE = [
label: '视频',
value: EnumManuscriptType.Video,
},
]
];

View File

@ -8,16 +8,15 @@
>
<ManuscriptTable :dataSource="dataSource" @sorterChange="handleSorterChange" @delete="handleDelete" />
<div v-if="pageInfo.total > 0" class="pagination-row">
<a-pagination
<Pagination
:total="pageInfo.total"
size="mini"
show-total
show-jumper
show-page-size
size="small"
:showTotal="(total, range) => `共 ${total} 条`"
showSizeChanger
showQuickJumper
:current="pageInfo.page"
:page-size="pageInfo.page_size"
:pageSize="pageInfo.page_size"
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</div>
@ -27,7 +26,7 @@
</template>
<script lang="jsx" setup>
import { defineComponent } from 'vue';
import { Button } from '@arco-design/web-vue';
import { Button, Pagination } from 'ant-design-vue';
import FilterBlock from './components/filter-block';
import ManuscriptTable from './components/manuscript-table';
import DeleteManuscriptModal from './components/manuscript-table/delete-manuscript-modal.vue';
@ -36,13 +35,10 @@ import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPa
import { getWorksPage } from '@/api/all/generationWorkshop.ts';
import { INITIAL_QUERY, EnumCheckStatus } from '@/views/material-center/components/finished-products/manuscript/list/constants.ts';
const { dataSource, pageInfo, onPageChange, onPageSizeChange, resetPageInfo } = useTableSelectionWithPagination({
const { dataSource, pageInfo, onPageChange, resetPageInfo } = useTableSelectionWithPagination({
onPageChange: () => {
getData();
},
onPageSizeChange: () => {
getData();
},
});
const query = ref(cloneDeep(INITIAL_QUERY));
const addManuscriptModalRef = ref(null);

View File

@ -1,18 +1,19 @@
<template>
<a-modal v-model:visible="visible" title="确认提示" width="480px" @close="onClose">
<Modal v-model:open="visible" centered title="确认提示" width="480px" @cancel="onClose">
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认取消上传这 {{ num }} 个文件吗此操作不可恢复</span>
</div>
<template #footer>
<a-button size="medium" @click="onClose">继续编辑</a-button>
<a-button type="primary" class="ml-8px" size="medium" @click="onConfirm">确认取消</a-button>
<Button size="medium" @click="onClose">继续编辑</Button>
<Button type="primary" class="ml-8px" size="medium" @click="onConfirm">确认取消</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
const router = useRouter();

View File

@ -1,5 +1,5 @@
<script lang="jsx">
import { Button, Message as AMessage } from '@arco-design/web-vue';
import { Button, message } from 'ant-design-vue';
import TextOverTips from '@/components/text-over-tips';
import EditForm, { ENUM_UPLOAD_STATUS, INITIAL_VIDEO_INFO } from '../components/edit-form';
import CancelUploadModal from './cancel-upload-modal.vue';
@ -45,7 +45,7 @@ export default {
(item) => item.videoInfo?.uploadStatus === ENUM_UPLOAD_STATUS.UPLOADING,
);
if (uploadingVideos.length > 0) {
AMessage.warning(`${uploadingVideos.length} 个视频正在上传中,请等待上传完成后再提交`);
message.warning(`${uploadingVideos.length} 个视频正在上传中,请等待上传完成后再提交`);
return;
}
@ -59,7 +59,7 @@ export default {
}
if (errorDataCards.value.length > 0) {
AMessage.warning(`${errorDataCards.value.length} 个必填信息未填写,请检查`);
message.warning(`${errorDataCards.value.length} 个必填信息未填写,请检查`);
setTimeout(() => {
const el = document.getElementById(`card-${errorDataCards.value[0]?.id}`);
@ -79,7 +79,7 @@ export default {
if (action === 'batchUpload') {
uploadSuccessModal.value?.open(data);
} else {
AMessage.success('上传成功');
message.success('上传成功');
if (action === 'uploadAndCheck') {
slsWithCatch('manuscriptCheckIds', data);
router.push({ name: 'ManuscriptCheck' });
@ -145,10 +145,10 @@ export default {
if (works.value.length > 1) {
return (
<>
<Button size="medium" type="outline" onClick={onCancel} class="mr-12px">
<Button type="primary" ghost onClick={onCancel} class="mr-12px">
取消上传
</Button>
<Button type="primary" size="medium" onClick={() => onUpload('batchUpload')} loading={uploadLoading.value}>
<Button type="primary" onClick={() => onUpload('batchUpload')} loading={uploadLoading.value}>
{uploadLoading.value ? '批量上传中...' : '批量上传'}
</Button>
</>
@ -156,12 +156,13 @@ export default {
} else {
return (
<>
<Button size="medium" type="outline" onClick={onCancel} class="mr-12px">
<Button type="primary" ghost onClick={onCancel} class="mr-12px">
取消上传
</Button>
<Button
size="medium"
type="outline"
type="primary"
ghost
onClick={() => onUpload('singleUpload')}
class="mr-12px"
loading={uploadLoading.value}
@ -311,7 +312,7 @@ export default {
>
{renderFooterRow()}
</footer>
<CancelUploadModal ref={cancelUploadModal} />
<UploadSuccessModal ref={uploadSuccessModal} />
</>

View File

@ -1,5 +1,12 @@
<template>
<a-modal v-model:visible="visible" title="提示" width="480px" @close="onClose" modal-class="upload-success11-modal">
<Modal
v-model:open="visible"
title="提示"
centered
width="480px"
@cancel="onClose"
wrapClassName="upload-success11-modal"
>
<div class="flex items-center flex-col justify-center">
<img :src="icon1" width="80" height="80" class="mb-16px" />
<span class="text-18px lh-26px font-400 color-#211F24 md">上传成功</span>
@ -7,14 +14,15 @@
<p class="text-14px lh-22px font-400 color-#737478 ld">检测是否存在违规内容</p>
</div>
<template #footer>
<a-button size="medium" @click="onBack">回到列表</a-button>
<a-button type="primary" class="ml-8px" size="medium" @click="onConfirm">批量审核</a-button>
<Button size="medium" @click="onBack">回到列表</Button>
<Button type="primary" class="ml-8px" size="medium" @click="onConfirm">批量审核</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal } from 'ant-design-vue';
import { slsWithCatch } from '@/utils/stroage.ts';
import icon1 from '@/assets/img/media-account/icon-feedback-success.png';

View File

@ -3,51 +3,30 @@
<div class="filter-row">
<div class="filter-row-item">
<span class="label">文件名称</span>
<a-input
v-model="query.name"
<Input
v-model:value="query.name"
class="!w-240px"
placeholder="请输入文件名称"
size="medium"
allow-clear
allowClear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</Input>
</div>
<div class="filter-row-item">
<span class="label">序号</span>
<a-space size="medium">
<a-input
v-model="query.uid"
class="!w-160px"
placeholder="请输入序号"
size="medium"
allow-clear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
<Input v-model:value="query.uid" class="!w-160px" placeholder="请输入序号" allowClear @change="handleSearch">
<template #prefix>
<icon-search />
</template>
</Input>
</div>
<!-- <div class="filter-row-item">
<span class="label">来源</span>
<CommonSelect
placeholder="请选择所属项目"
:options="CHECK_STATUS"
v-model="query.audit_status"
class="!w-160px"
:multiple="false"
@change="handleSearch"
/>
</div> -->
<div class="filter-row-item">
<span class="label">上传时间</span>
<a-range-picker
v-model="created_at"
<DatePicker.RangePicker
v-model:value="created_at"
size="medium"
allow-clear
format="YYYY-MM-DD"
@ -56,26 +35,27 @@
/>
</div>
<div class="filter-row-item">
<a-button type="outline" class="mr-12px" size="medium" @click="handleSearch">
<Button type="primary" ghost class="mr-12px" size="medium" @click="handleSearch">
<template #icon>
<icon-search />
<icon-search class="mr-8px" />
</template>
<template #default>搜索</template>
</a-button>
<a-button size="medium" @click="handleReset">
</Button>
<Button size="medium" @click="handleReset">
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px" />
</template>
<template #default>重置</template>
</a-button>
</Button>
</div>
</div>
</div>
</template>
<script setup>
import { defineEmits, defineProps } from 'vue';
// import CommonSelect from '@/components/common-select';
import { defineEmits, defineProps, ref, nextTick } from 'vue';
import { Button, Input, DatePicker } from 'ant-design-vue';
import dayjs from 'dayjs';
const props = defineProps({
query: {
@ -84,7 +64,7 @@ const props = defineProps({
},
});
const emits = defineEmits('search', 'reset', 'update:query');
const emits = defineEmits(['search', 'reset', 'update:query']);
const created_at = ref([]);
@ -112,10 +92,8 @@ const onDateChange = (value) => {
handleSearch();
};
const handleReset = () => {
created_at.value = [];
emits('reset');
};
</script>

View File

@ -1,18 +1,19 @@
<template>
<a-modal v-model:visible="visible" title="删除文件" width="480px" @close="onClose">
<Modal v-model:open="visible" title="删除文件" centered width="480px" @cancel="onClose">
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ fileName }} 文件吗</span>
</div>
<template #footer>
<a-button size="medium" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px" status="danger" size="medium" @click="onDelete">确认删除</a-button>
<Button size="medium" @click="onClose">取消</Button>
<Button type="primary" class="ml-16px" danger size="medium" @click="onDelete">确认删除</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal, message } from 'ant-design-vue';
import { deleteRawMaterial, batchDeleteRawMaterials } from '@/api/all/generationWorkshop';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -43,7 +44,7 @@ async function onDelete() {
const _params = isBatch.value ? { ids: fileId.value } : fileId.value;
const { code } = await _fn(_params);
if (code === 200) {
AMessage.success('删除成功');
message.success('删除成功');
isBatch.value ? emits('batchUpdate') : emits('update');
onClose();

View File

@ -1,101 +1,92 @@
<template>
<a-table
<Table
ref="tableRef"
:data="dataSource"
row-key="id"
column-resizable
:dataSource="dataSource"
rowKey="id"
:pagination="false"
: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)"
:rowSelection="rowSelection"
:showSorterTooltip="false"
@change="handleTableChange"
>
<template #empty>
<template #emptyText>
<NoData text="暂无文件" />
</template>
<template #columns>
<a-table-column
v-for="column in tableColumns"
:key="column.dataIndex"
:data-index="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:min-width="column.minWidth"
:sortable="column.sortable"
:align="column.align"
ellipsis
tooltip
>
<template #title>
<div class="flex items-center">
<span class="cts mr-4px">{{ column.title }}</span>
<a-tooltip v-if="column.tooltip" :content="column.tooltip" position="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</a-tooltip>
<Column
v-for="column in tableColumns"
:key="column.dataIndex"
:dataIndex="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:minWidth="column.minWidth"
:sorter="column.sortable"
:align="column.align"
:ellipsis="true"
>
<template #title>
<span class="cts mr-4px">{{ column.title }}</span>
<Tooltip v-if="column.tooltip" :title="column.tooltip" placement="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</Tooltip>
</template>
<template v-if="column.dataIndex === 'name'" #customRender="{ record }">
<div class="flex items-center">
<HoverImagePreview :src="record.cover">
<ImgLazyLoad :width="64" :height="64" :src="record.cover" class="!rounded-6px mr-16px" />
</HoverImagePreview>
<div class="flex-1 flex flex-col overflow-hidden">
<TextOverTips :context="record.name" :line="1" class="cts mb-4px regular" />
<TextOverTips :context="`序号:${record.uid}`" :line="1" class="cts !color-#737478 regular" />
</div>
</template>
<template v-if="column.dataIndex === 'name'" #cell="{ record }">
<div class="flex items-center">
<HoverImagePreview :src="record.cover">
<a-image :width="64" :height="64" :src="record.cover" class="!rounded-8px mr-16px" fit="cover">
<template #error>
<img :src="icon4" class="w-full h-full" />
</template>
</a-image>
</HoverImagePreview>
<div class="flex-1 flex flex-col overflow-hidden">
<TextOverTips :context="record.name" :line="1" class="cts mb-4px regular" />
<TextOverTips :context="`序号:${record.uid}`" :line="1" class="cts !color-#737478 regular" />
</div>
</div>
</template>
<template v-else-if="column.dataIndex === 'type'" #cell="{ record }">
{{ TABS_LIST.find((item) => item.value === record.type)?.label ?? '-' }}
</template>
<template v-else-if="column.dataIndex === 'size'" #cell="{ record }">
<span class="cts num">{{ formatFileSize(record.size) }}</span>
</template>
<template v-else-if="column.dataIndex === 'origin'" #cell="{ record }">
{{ ORIGIN_LIST.find((item) => item.value === record.origin)?.label ?? '-' }}
</template>
<template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #cell="{ record }">
{{ record[column.dataIndex].name || record[column.dataIndex].mobile }}
</template>
<template #cell="{ record }" v-else-if="['created_at'].includes(column.dataIndex)">
<span class="cts num">{{ exactFormatTime(record[column.dataIndex]) }}</span>
</template>
<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="onDownload(record)">下载</a-button>
</div>
</template>
<template v-else #cell="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</a-table-column>
</template>
</a-table>
</div>
</template>
<template v-else-if="column.dataIndex === 'type'" #customRender="{ record }">
{{ TABS_LIST.find((item) => item.value === record.type)?.label ?? '-' }}
</template>
<template v-else-if="column.dataIndex === 'size'" #customRender="{ record }">
<span class="cts num">{{ formatFileSize(record.size) }}</span>
</template>
<template v-else-if="column.dataIndex === 'origin'" #customRender="{ record }">
{{ ORIGIN_LIST.find((item) => item.value === record.origin)?.label ?? '-' }}
</template>
<template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #customRender="{ record }">
{{ record[column.dataIndex].name || record[column.dataIndex].mobile }}
</template>
<template #customRender="{ record }" v-else-if="['created_at'].includes(column.dataIndex)">
<span class="cts num">{{ exactFormatTime(record[column.dataIndex]) }}</span>
</template>
<template v-else-if="column.dataIndex === 'operation'" #customRender="{ record }">
<div class="flex items-center">
<img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" />
<Button type="primary" ghost size="small" @click="onDownload(record)">下载</Button>
</div>
</template>
<template v-else #customRender="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</Column>
</Table>
</template>
<script setup>
import { ref } from 'vue';
import { ref, computed } from 'vue';
import { Button, Tooltip, Table, Image } from 'ant-design-vue';
const { Column } = Table;
import { formatTableField, exactFormatTime, formatFileSize, downloadByUrl } from '@/utils/tools';
import { slsWithCatch } from '@/utils/stroage.ts';
import { TABS_LIST, ORIGIN_LIST } from '../../constants';
import TextOverTips from '@/components/text-over-tips';
import ShareModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/share-modal.vue';
// import ShareModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/share-modal.vue';
import HoverImagePreview from '@/components/hover-image-preview';
import ImgLazyLoad from '@/components/img-lazy-load';
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';
// 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(['sorterChange', 'delete', 'select', 'selectAll']);
const router = useRouter();
@ -109,10 +100,6 @@ const props = defineProps({
type: Array,
default: () => [],
},
rowSelection: {
type: Object,
default: () => {},
},
selectedRowKeys: {
type: Array,
default: () => [],
@ -121,8 +108,20 @@ const props = defineProps({
const tableRef = ref(null);
const handleSorterChange = (column, order) => {
emits('sorterChange', column, order === 'ascend' ? 'asc' : 'desc');
const handleTableChange = (pagination, filters, sorter) => {
if (sorter && sorter.field) {
emits('sorterChange', sorter.field, sorter.order === 'ascend' ? 'asc' : 'desc');
}
};
const rowSelection = {
selectedRowKeys: computed(() => props.selectedRowKeys),
onSelect: (record, selected) => {
emits('select', record, selected);
},
onSelectAll: (selected) => {
emits('selectAll', selected);
},
};
const onDelete = (item) => {
emits('delete', item);

View File

@ -1,6 +1,6 @@
<script lang="tsx">
import { provide } from 'vue';
import { Tabs, TabPane } from 'ant-design-vue';
import { Tabs, TabPane, Button, Pagination } from 'ant-design-vue';
import { TABS_LIST, RawMaterialType, INITIAL_QUERY, TABLE_COLUMNS } from './constants';
import FilterBlock from './components/filter-block/index.vue';
import RawMaterialTable from './components/table/index.vue';
@ -14,9 +14,7 @@ export default defineComponent({
const {
dataSource,
pageInfo,
rowSelection,
onPageChange,
onPageSizeChange,
selectedRowKeys,
selectedRows,
handleSelect,
@ -26,9 +24,6 @@ export default defineComponent({
onPageChange: () => {
getData();
},
onPageSizeChange: () => {
getData();
},
});
const deleteRawMaterialModalRef = ref(null);
@ -106,14 +101,20 @@ export default defineComponent({
</div>
<div class="table-wrap bg-#fff rounded-8px px-24px py-24px flex flex-col">
<div class="flex justify-end mb-12px">
<a-button type="outline" class="w-fit" size="medium" onClick={handleBatchDelete}>
<Button
type="primary"
ghost
class="w-fit"
size="medium"
onClick={handleBatchDelete}
disabled={!selectedRows.value.length}
>
批量删除
</a-button>
</Button>
</div>
<RawMaterialTable
tableColumns={TABLE_COLUMNS}
rowSelection={rowSelection}
selectedRowKeys={selectedRowKeys.value}
dataSource={dataSource.value}
onSorterChange={handleSorterChange}
@ -123,16 +124,15 @@ export default defineComponent({
/>
{pageInfo.value.total > 0 && (
<div class="pagination-row">
<a-pagination
<Pagination
total={pageInfo.value.total}
size="mini"
show-total
show-jumper
show-page-size
size="small"
showTotal={(total: number) => `${total} 条记录`}
showSizeChanger
showQuickJumper
current={pageInfo.value.page}
pageSize={pageInfo.value.page_size}
onChange={onPageChange}
onPageSizeChange={onPageSizeChange}
/>
</div>
)}

View File

@ -1,10 +1,10 @@
<template>
<div class="brand-wrap">
<div class="filter-wrap bg-#fff rounded-8px ">
<div class="filter-wrap bg-#fff rounded-8px">
<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 class="add-btn" type="primary" @click="handleAdd">+ 添加品牌</a-button>
<Button type="primary" class="add-btn" @click="handleAdd">+ 添加品牌</Button>
</div>
</div>
@ -12,32 +12,33 @@
<div class="filter-row flex mb-20px">
<div class="filter-row-item flex items-center">
<span class="label">品牌名称</span>
<a-space size="medium">
<a-input v-model="query.name" class="w-240px" placeholder="请搜索..." size="medium" allow-clear>
<Space size="medium">
<Input v-model:value="query.name" class="w-240px" placeholder="请搜索..." size="medium" allowClear>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
</Input>
</Space>
</div>
<div class="filter-row flex">
<a-button type="outline" class="mr-12px" size="medium" @click="handleSearch">
<Button type="outline" ghost class="mr-12px" @click="handleSearch">
<template #icon>
<icon-search />
<icon-search class="mr-8px" />
</template>
<template #default>搜索</template>
</a-button>
<a-button size="medium" @click="handleReset">
</Button>
<Button @click="handleReset">
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px" />
</template>
<template #default>重置</template>
</a-button>
</Button>
</div>
</div>
<a-modal
v-model:visible="modalVisible"
:mask-closable="false"
<Modal
v-model:open="modalVisible"
:maskClosable="false"
centered
:esc-to-close="false"
width="510px"
@cancel="handleModalCancel"
@ -45,78 +46,77 @@
<template #title>
<span class="modal-title">{{ form.id > 0 ? '编辑品牌' : '添加品牌' }}</span>
</template>
<a-form :model="form" :rules="formRule" ref="formRef" layout="horizontal" auto-label-width>
<a-form-item field="name" label="品牌名称">
<a-input v-model="form.name" class="h-36px" placeholder="请输入..." />
</a-form-item>
<a-form-item field="logo" class="form-item-logo" label="标准版Logo">
<a-space>
<Form
:model="form"
:rules="formRule"
ref="formRef"
layout="horizontal"
labelAlign="right"
:labelCol="{ span: 4 }"
:wrapperCol="{ span: 20 }"
>
<FormItem name="name" label="品牌名称">
<Input v-model:value="form.name" class="h-36px" placeholder="请输入..." />
</FormItem>
<FormItem name="logo" class="form-item-logo" label="标准版Logo">
<div class="inline-flex">
<ImageUpload v-model="form.logo" :limit="1"></ImageUpload>
</a-space>
<a-space>
</div>
<div class="inline-flex">
<span class="form-tip">品牌常规展示使用支持PNGJPG格式</span>
</a-space>
</a-form-item>
<a-form-item field="otherLogos" class="form-item-logo" label="其他Logo">
</div>
</FormItem>
<FormItem name="otherLogos" class="form-item-logo" label="其他Logo">
<ImageUpload v-model="form.other_logos" :limit="3"></ImageUpload>
</a-form-item>
<a-form-item field="slogan" label="Slogan">
<a-textarea v-model="form.slogan" placeholder="请输入..." :max-length="50" show-word-limit />
</a-form-item>
</a-form>
</FormItem>
<FormItem name="slogan" label="Slogan">
<TextArea v-model:value="form.slogan" placeholder="请输入..." :maxlength="50" showCount />
</FormItem>
</Form>
<template #footer>
<a-button @click="handleModalCancel">取消</a-button>
<a-button type="primary" @click="handleModalOk">{{ btn_str }}</a-button>
<Button @click="handleModalCancel">取消</Button>
<Button type="primary" @click="handleModalOk">{{ btn_str }}</Button>
</template>
</a-modal>
</Modal>
</div>
</div>
<div
class="table-wrap bg-#fff rounded-8px px-24px py-24px flex flex-col"
>
<a-table :data="tableData" ref="tableRef" :pagination="false">
<template #columns>
<a-table-column title="品牌名称" data-index="name" />
<a-table-column title="品牌logo" data-index="logo">
<template #cell="{ record }">
<img :src="record.logo" style="width: 50px; height: 50px" />
</template>
</a-table-column>
<a-table-column title="Slogan" data-index="slogan" />
<a-table-column width="150" min-widht="150" title="操作" data-index="optional">
<template #cell="{ record }">
<a-space size="medium">
<a-space>
<a-popconfirm
content="确定删除吗?"
type="warning"
ok-text="确认删除"
cancel-text="取消"
@ok="deleteBrand(record.id)"
>
<icon-delete></icon-delete>
</a-popconfirm>
</a-space>
<a-space>
<a-button class="edit-btn" type="outline" @click="handleEdit(record.id)">编辑</a-button>
</a-space>
</a-space>
</template>
</a-table-column>
</template>
</a-table>
<div class="table-wrap bg-#fff rounded-8px px-24px py-24px flex flex-col">
<Table :dataSource="tableData" ref="tableRef" :pagination="false" :showSorterTooltip="false">
<Table.Column title="品牌名称" dataIndex="name" />
<Table.Column title="品牌logo" dataIndex="logo">
<template #customRender="{ record }">
<img :src="record.logo" style="width: 50px; height: 50px" />
</template>
</Table.Column>
<Table.Column title="Slogan" dataIndex="slogan" />
<Table.Column :width="80" title="操作" dataIndex="optional">
<template #customRender="{ record }">
<Space>
<Popconfirm
title="确定删除吗?"
okText="确认删除"
cancelText="取消"
@confirm="deleteBrand(record.id)"
>
<icon-delete></icon-delete>
</Popconfirm>
<Button type="outline" class="edit-btn" size="small" @click="handleEdit(record.id)">编辑</Button>
</Space>
</template>
</Table.Column>
</Table>
<div class="pagination-row">
<a-pagination
<Pagination
:total="pageInfo.total"
size="mini"
show-total
show-jumper
show-page-size
size="small"
:showTotal="(total, range) => `共 ${total} 条`"
showSizeChanger
showQuickJumper
:current="pageInfo.page"
:page-size="pageInfo.pageSize"
:pageSize="pageInfo.pageSize"
@change="onPageChange"
@page-size-change="onPageSizeChange"
:hideOnSinglePage="false"
/>
</div>
</div>
@ -125,8 +125,9 @@
<script setup>
import { ref, computed, reactive, onMounted } from 'vue';
import { Message } from '@arco-design/web-vue';
import { IconDelete } from '@arco-design/web-vue/es/icon';
import { Button, Modal, Space, Form, FormItem, Pagination, Input, Table, message, Popconfirm } from 'ant-design-vue';
const { TextArea } = Input;
import {
addMaterials,
@ -196,19 +197,16 @@ const handleReset = () => {
reload();
};
const onPageChange = (current) => {
const onPageChange = (current, pageSize) => {
pageInfo.page = current;
pageInfo.pageSize = pageSize;
handleSearch();
};
const onPageSizeChange = (pageSize) => {
pageInfo.pageSize = pageSize;
reload();
};
const deleteBrand = (id) => {
console.log(id, 'id');
deleteMaterials(id).then(() => {
Message.success('删除成功');
message.success('删除成功');
handleSearch();
});
};
@ -231,24 +229,22 @@ function handleModalOk() {
formRef.value
.validate()
.then((valid) => {
if (!valid) {
if (form.id) {
updateMaterials(form.id, form).then(() => {
Message.success('修改成功');
handleSearch();
});
} else {
addMaterials(form).then((response) => {
Message.success('新增成功');
handleSearch();
});
}
modalVisible.value = false;
if (form.id) {
updateMaterials(form.id, form).then(() => {
message.success('修改成功');
handleSearch();
});
} else {
addMaterials(form).then((response) => {
message.success('新增成功');
handleSearch();
});
}
modalVisible.value = false;
})
.catch((error) => {
console.error('验证失败:', error);
Message.error('请检查表单填写是否正确');
message.error('请检查表单填写是否正确');
});
}

View File

@ -37,6 +37,9 @@
}
:deep(.ant-popconfirm-buttons) {
display: flex;
}
:deep(.arco-input-wrapper),
:deep(.arco-select-view-single),

View File

@ -1,11 +1,11 @@
.arco-modal {
.arco-modal-body {
.arco-form-item {
.ant-modal {
.ant-modal-body {
.ant-form-item {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
.arco-form-item-label {
.ant-form-item-label {
color: var(--Text-1, #211f24);
font-family: $font-family-regular;
font-size: 14px;

View File

@ -1,6 +1,6 @@
<template>
<div class="business-wrap">
<div class="filter-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid">
<div class="filter-wrap bg-#fff rounded-8px">
<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>
@ -9,54 +9,59 @@
<div class="filter-row flex mb-20px">
<div class="filter-row-item flex items-center">
<span class="label">服务/产品</span>
<a-space size="medium">
<a-input v-model="query.name" class="w-240px" placeholder="请搜索..." size="medium" allow-clear>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
<Input v-model:value="query.name" class="w-240px" placeholder="请搜索..." size="middle" allowClear>
<template #prefix>
<icon-search />
</template>
</Input>
</div>
<div class="filter-row-item flex items-center">
<span class="label">时间筛选</span>
<a-space class="w-240px">
<a-range-picker size="medium" allow-clear format="YYYY-MM-DD HH:mm" class="w-100%" />
</a-space>
<Space class="w-240px">
<DatePicker.RangePicker allowClear format="YYYY-MM-DD HH:mm" class="w-100%" />
</Space>
</div>
<div class="filter-row flex">
<a-button type="outline" class="mr-12px" size="medium">
<Button class="mr-12px outline-btn">
<template #icon>
<icon-search />
</template>
<template #default>搜索</template>
</a-button>
<a-button size="medium">
搜索
</Button>
<Button>
<template #icon>
<icon-refresh />
</template>
<template #default>重置</template>
</a-button>
重置
</Button>
</div>
</div>
</div>
</div>
<div
class="table-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid px-24px py-24px flex-1 flex flex-col"
class="table-wrap bg-#fff rounded-8px px-24px py-24px flex-1 flex flex-col"
>
<a-table :columns="columns" :data="tableData" @change="handleChange" :pagination="false">
</a-table>
<Table :dataSource="tableData" :pagination="false" :showSorterTooltip="false" @change="handleChange">
<Table.Column
v-for="column in columns"
:key="column.dataIndex"
:title="column.title"
:dataIndex="column.dataIndex"
:width="column.width"
:minWidth="column.minWidth"
/>
</Table>
<div class="pagination-row">
<a-pagination
<Pagination
:total="pageInfo.total"
size="mini"
show-total
show-jumper
show-page-size
size="small"
:showTotal="(total, range) => `共 ${total} 条`"
showSizeChanger
showQuickJumper
:current="pageInfo.page"
:page-size="pageInfo.pageSize"
:pageSize="pageInfo.pageSize"
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</div>
@ -65,6 +70,7 @@
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { Button, Input, Space, Table, Pagination, DatePicker } from 'ant-design-vue';
const pageInfo = reactive({
page: 1,
@ -79,17 +85,19 @@ const listResult = reactive({
data: [],
total: 0,
});
const onPageSizeChange = () => {};
const tableData = ref([]);
const handleChange = () => {};
const onPageChange = () => {};
const columns = [
const onPageChange = (page: number, pageSize:number) => {
pageInfo.page = page;
pageInfo.pageSize = pageSize;
};
const columns = [
{
title: '服务/产品',
dataIndex: 'service_name',
slotName: 'rank',
width: 60,
minWidth: 60,
width: 100,
minWidth: 100,
},
{
title: '生成日期',
@ -140,10 +148,12 @@ const columns = [
display: flex;
flex-direction: column;
:deep(.search-btn) {
border-radius: 4px;
border: 1px solid var(--Brand-Brand-6, #6d4cfe);
color: #6d4cfe;
:deep(.outline-btn) {
border: 1px solid #d9d9d9;
&:hover {
color: #6d4cfe;
border-color: #6d4cfe;
}
}
:deep(.edit-btn) {
@ -157,9 +167,9 @@ const columns = [
background: var(--BG-white, #fff);
}
:deep(.arco-input-wrapper),
:deep(.arcoInput-wrapper),
:deep(.arco-select-view-single),
:deep(.arco-textarea-wrapper),
:deep(.ant-input),
:deep(.arco-picker),
:deep(.arco-select-view-multiple) {
border-radius: 4px;
@ -167,7 +177,7 @@ const columns = [
background-color: #fff;
&:focus-within,
&.arco-input-focus {
&.arcoInput-focus {
background-color: var(--color-bg-2);
border-color: rgb(var(--primary-6));
box-shadow: 0 0 0 0 var(--color-primary-light-2);

View File

@ -1,6 +1,6 @@
<template>
<div class="competitive-wrap">
<div class="filter-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid">
<div class="filter-wrap bg-#fff rounded-8px ">
<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>
@ -9,54 +9,89 @@
<div class="filter-row flex mb-20px">
<div class="filter-row-item flex items-center">
<span class="label">服务/产品</span>
<a-space size="medium">
<a-input v-model="query.name" class="w-240px" placeholder="请搜索..." size="medium" allow-clear>
<Space>
<Input v-model:value="query.name" class="w-240px" placeholder="请搜索..." allowClear>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
</Input>
</Space>
</div>
<div class="filter-row-item flex items-center">
<span class="label">时间筛选</span>
<a-space class="w-240px">
<a-range-picker size="medium" allow-clear format="YYYY-MM-DD HH:mm" class="w-100%" />
</a-space>
<Space class="w-240px">
<DatePicker.RangePicker allow-clear format="YYYY-MM-DD HH:mm" class="w-100%" />
</Space>
</div>
<div class="filter-row flex">
<a-button type="outline" class="mr-12px" size="medium">
<Button type="primary" ghost class="mr-12px">
<template #icon>
<icon-search />
<icon-search class="mr-8px" />
</template>
<template #default>搜索</template>
</a-button>
<a-button size="medium">
</Button>
<Button>
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px" />
</template>
<template #default>重置</template>
</a-button>
</Button>
</div>
</div>
</div>
</div>
<div
class="table-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid px-24px py-24px flex-1 flex flex-col"
class="table-wrap bg-#fff rounded-8px px-24px py-24px flex-1 flex flex-col"
>
<a-table :columns="columns" :data="tableData" @change="handleChange" :pagination="false">
</a-table>
<Table :dataSource="tableData" :pagination="false" :showSorterTooltip="false" @change="handleChange">
<Table.Column
title="服务/产品"
dataIndex="service_name"
:width="60"
:minWidth="60"
/>
<Table.Column
title="生成日期"
dataIndex="create_time"
:width="120"
:minWidth="120"
/>
<Table.Column
:width="180"
:minWidth="180"
title="竞争对手"
dataIndex="customer"
/>
<Table.Column
title="最后更新日期"
dataIndex="volumeRate"
:width="180"
:minWidth="180"
/>
<Table.Column
title="操作人"
dataIndex="operator"
:width="120"
:minWidth="120"
/>
<Table.Column
title="操作"
dataIndex="operator"
:width="120"
:minWidth="120"
/>
</Table>
<div class="pagination-row">
<a-pagination
<Pagination
:total="pageInfo.total"
size="mini"
show-total
show-jumper
show-page-size
size="small"
:showTotal="(total, range) => `共 ${total} 条`"
showSizeChanger
showQuickJumper
:current="pageInfo.page"
:page-size="pageInfo.pageSize"
:pageSize="pageInfo.pageSize"
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</div>
@ -64,6 +99,7 @@
</template>
<script setup lang="ts">
import { Button, Input, Space, Pagination, Table, DatePicker } from 'ant-design-vue';
import { reactive, ref } from 'vue';
const pageInfo = reactive({
@ -76,54 +112,14 @@ const query = reactive({
});
const handleChange = () => {};
const onPageChange = () => {};
const onPageChange = (page: number, pageSize:number) => {
pageInfo.page = page;
pageInfo.pageSize = pageSize;
};
const onPageSizeChange = () => {};
const tableData = ref([]);
const columns = [
{
title: '服务/产品',
dataIndex: 'service_name',
slotName: 'rank',
width: 60,
minWidth: 60,
},
{
title: '生成日期',
dataIndex: 'create_time',
width: 120,
minWidth: 120,
},
{
titleSlotName: 'customer',
width: 180,
minWidth: 180,
title: '竞争对手',
dataIndex: 'customer',
slotName: 'hot',
},
{
titleSlotName: 'lasterUpdateTime',
title: '最后更新日期',
dataIndex: 'volumeRate',
width: 180,
minWidth: 180,
slotName: 'lasterUpdateTime',
},
{
title: '操作人',
dataIndex: 'operator',
width: 120,
minWidth: 120,
},
{
title: '操作',
dataIndex: 'operator',
width: 120,
minWidth: 120,
},
];
</script>
<style scoped lang="scss">
@ -151,7 +147,7 @@ const columns = [
:deep(.arco-input-wrapper),
:deep(.arco-select-view-single),
:deep(.arco-textarea-wrapper),
:deep(.ant-input),
:deep(.arco-picker),
:deep(.arco-select-view-multiple) {
border-radius: 4px;

View File

@ -5,151 +5,143 @@
<template>
<div class="action-row mb-12px flex justify-between">
<div>
<a-checkbox
<Checkbox
v-if="dataSource.length > 0"
:model-value="checkedAll"
:checked="checkedAll"
:indeterminate="indeterminate"
class="!pl-13px"
@change="handleSelectAll"
>全选</a-checkbox
@change="(e) => handleSelectAll(e.target.checked)"
>全选</Checkbox
>
</div>
<div class="flex items-center">
<a-button type="outline" class="w-110px mr-12px" size="medium" @click="handleExport">
<template #icon> <icon-download /> </template>
<Button type="outline" class="w-110px mr-12px" size="medium" @click="handleExport">
<template #icon> <icon-download class="mr-8px" /> </template>
<template #default>导出数据</template>
</a-button>
<a-button type="outline" class="w-110px" size="medium" @click="openCustomColumn">
</Button>
<Button type="outline" class="w-110px" size="medium" @click="openCustomColumn">
<template #icon>
<img :src="icon1" width="14" height="14" />
<img :src="icon1" width="14" height="14" class="mr-8px" />
</template>
<template #default>自定义列</template>
</a-button>
</Button>
</div>
</div>
<a-table
<Table
ref="tableRef"
:data="dataSource"
row-key="id"
column-resizable
:row-selection="{
:dataSource="dataSource"
rowKey="id"
:rowSelection="{
type: 'checkbox',
showCheckedAll: true,
width: 48,
selectedRowKeys: selectedItems,
onSelect: handleSelect,
onSelectAll: handleSelectAll,
}"
:selected-keys="selectedItems"
:pagination="false"
:scroll="{ x: '100%' }"
class="account-table w-100% flex-1"
bordered
@sorter-change="handleSorterChange"
@select="handleSelect"
@select-all="handleSelectAll"
:showSorterTooltip="false"
@change="handleTableChange"
>
<template #empty>
<template #emptyText>
<NoData />
</template>
<template #columns>
<a-table-column
v-for="column in tableColumns"
:key="column.dataIndex"
:data-index="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:min-width="column.minWidth"
:sortable="column.sortable"
:align="column.align"
ellipsis
tooltip
>
<template #title>
<div class="flex items-center">
<img v-if="column.dataIndex === 'ai_evaluate'" width="16" height="16" :src="icon5" class="mr-4px" />
<span class="cts mr-4px">{{ column.title }}</span>
<a-tooltip v-if="column.tooltip" :content="column.tooltip" position="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</a-tooltip>
</div>
</template>
<Column
v-for="column in tableColumns"
:key="column.dataIndex"
:dataIndex="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:minWidth="column.minWidth"
:sorter="column.sortable"
:align="column.align"
:ellipsis="true"
>
<template #title>
<img v-if="column.dataIndex === 'ai_evaluate'" width="16" height="16" :src="icon5" class="mr-4px" />
<span class="cts mr-4px">{{ column.title }}</span>
<Tooltip v-if="column.tooltip" :title="column.tooltip" placement="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</Tooltip>
</template>
<template v-if="column.dataIndex === 'platform'" #cell="{ record }">
<img :src="getMediaAccountPlatformLogo(record.platform)" width="20" height="20" class="rounded-4px" />
</template>
<template v-else-if="column.dataIndex === 'status'" #cell="{ record }">
<StatusBox :item="record" class="w-fit h-28px" />
</template>
<template v-else-if="column.dataIndex === 'ai_evaluate'" #cell="{ record }">
<div class="ai-evaluation-row flex">
<template v-if="record.ai_evaluate">
<img
width="16"
height="16"
:src="record.ai_evaluate?.status === 0 ? icon2 : record.ai_evaluate?.status === 1 ? icon3 : icon4"
class="mr-8px icon"
/>
<div>
<p class="cts">{{ `${record.ai_evaluate?.level} | ${record.ai_evaluate?.advise}` }}</p>
<p class="cts text-12px lh-20px !color-#939499">
{{
`观看: ${record[`${getPropPrefix(dateType)}view_rate`]}% 点赞: ${
record[`${getPropPrefix(dateType)}like_rate`]
}%`
}}
</p>
</div>
</template>
<template v-else>
<p class="cts">-</p>
</template>
</div>
</template>
<template v-else-if="column.dataIndex === 'like_collect_number'" #cell="{ record }">
{{
formatNumberShow({
value: `${record[`${getPropPrefix(dateType)}like_number`] ?? 0} + ${
record[`${getPropPrefix(dateType)}collect_number`] ?? 0
}`,
showExactValue: true,
})
}}
</template>
<template v-else-if="column.dataIndex === 'operation'" #cell="{ record }">
<a-button type="outline" size="small" @click="handleDetail(record)">详情</a-button>
</template>
<template v-if="column.dataIndex === 'platform'" #customRender="{ record }">
<img :src="getMediaAccountPlatformLogo(record.platform)" width="20" height="20" class="rounded-4px" />
</template>
<template v-else-if="column.dataIndex === 'status'" #customRender="{ record }">
<StatusBox :item="record" class="w-fit h-28px" />
</template>
<template v-else-if="column.dataIndex === 'ai_evaluate'" #customRender="{ record }">
<div class="ai-evaluation-row flex">
<template v-if="record.ai_evaluate">
<img
width="16"
height="16"
:src="record.ai_evaluate?.status === 0 ? icon2 : record.ai_evaluate?.status === 1 ? icon3 : icon4"
class="mr-8px icon"
/>
<div>
<p class="cts">{{ `${record.ai_evaluate?.level} | ${record.ai_evaluate?.advise}` }}</p>
<p class="cts text-12px lh-20px !color-#939499">
{{
`观看: ${record[`${getPropPrefix(dateType)}view_rate`]}% 点赞: ${
record[`${getPropPrefix(dateType)}like_rate`]
}%`
}}
</p>
</div>
</template>
<template v-else>
<p class="cts">-</p>
</template>
</div>
</template>
<template v-else-if="column.dataIndex === 'like_collect_number'" #customRender="{ record }">
{{
formatNumberShow({
value: `${record[`${getPropPrefix(dateType)}like_number`] ?? 0} + ${
record[`${getPropPrefix(dateType)}collect_number`] ?? 0
}`,
showExactValue: true,
})
}}
</template>
<template v-else-if="column.dataIndex === 'operation'" #customRender="{ record }">
<Button type="outline" size="small" @click="handleDetail(record)">详情</Button>
</template>
<template v-else-if="column.isRateField" #cell="{ record }">
<div class="flex items-center rate-row justify-end" :class="record[column.dataIndex] > 0 ? 'up' : 'down'">
<icon-arrow-up v-if="record[column.dataIndex] > 0" size="16" />
<icon-arrow-down v-else size="16" />
{{ formatTableField(column, record) }}
</div>
</template>
<template v-else-if="column.dataIndex === 'newest_work_title'" #cell="{ record }">
<p class="cts cursor-pointer hover:!color-#6D4CFE">{{ record.newest_work_title }}</p>
<p class="cts text-12px lh-20px !color-#939499">
{{ exactFormatTime(record.newest_work_published_at) }}
</p>
</template>
<template v-else-if="column.dataIndex === 'second_new_work_title'" #cell="{ record }">
<p class="cts cursor-pointer hover:!color-#6D4CFE">{{ record.second_new_work_title }}</p>
<p class="cts text-12px lh-20px !color-#939499">
{{ exactFormatTime(record.second_new_work_published_at) }}
</p>
</template>
<template v-else-if="column.isRateField" #customRender="{ record }">
<div class="flex items-center rate-row justify-end" :class="record[column.dataIndex] > 0 ? 'up' : 'down'">
<icon-arrow-up v-if="record[column.dataIndex] > 0" size="16" />
<icon-arrow-down v-else size="16" />
{{ formatTableField(column, record) }}
</div>
</template>
<template v-else-if="column.dataIndex === 'newest_work_title'" #customRender="{ record }">
<p class="cts cursor-pointer hover:!color-#6D4CFE">{{ record.newest_work_title }}</p>
<p class="cts text-12px lh-20px !color-#939499">
{{ exactFormatTime(record.newest_work_published_at) }}
</p>
</template>
<template v-else-if="column.dataIndex === 'second_new_work_title'" #customRender="{ record }">
<p class="cts cursor-pointer hover:!color-#6D4CFE">{{ record.second_new_work_title }}</p>
<p class="cts text-12px lh-20px !color-#939499">
{{ exactFormatTime(record.second_new_work_published_at) }}
</p>
</template>
<template v-else #cell="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</a-table-column>
<a-table-column data-index="operation" fixed="right" width="100" title="操作">
<template #cell="{ record }">
<a-button type="outline" size="small" @click="handleDetail(record)">详情</a-button>
</template>
</a-table-column>
</template>
</a-table>
<template v-else #customRender="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</Column>
<Column dataIndex="operation" fixed="right" :width="100" title="操作">
<template #customRender="{ record }">
<Button type="outline" size="small" @click="handleDetail(record)">详情</Button>
</template>
</Column>
</Table>
<CustomTableColumnModal
ref="customTableColumnModalRef"
@ -162,6 +154,8 @@
<script setup>
import { ref, computed } from 'vue';
import { useRouter } from 'vue-router';
import { Checkbox, Button, Tooltip, Table } from 'ant-design-vue';
const { Column } = Table;
import { getCustomColumns } from '@/api/all/common';
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
@ -209,7 +203,7 @@ const dateType = computed(() => (props.query.type === 7 ? 'week' : 'month'));
const tableColumns = computed(() => {
const _result = [];
const _columns = getDefaultColumns(dateType.value);
console.log({_columns})
console.log({ _columns });
selectedColumns.value.forEach((item) => {
const _column = _columns.find((_item) => _item.prop === item);
@ -220,30 +214,32 @@ const tableColumns = computed(() => {
return _result;
});
const handleSelectAll = (checked) => {
if (checked) {
selectedItems.value = props.dataSource.map((item) => item.id);
} else {
selectedItems.value = [];
}
emit('selectionChange', checked ? selectedItems.value : []);
};
const handleDetail = (record) => {
router.push(`/media-account/detail/${record.id}?type=${dateType.value}`);
};
// 处理排序变化
const handleSorterChange = (column, order) => {
console.log(column, order);
emit('sorterChange', column, order === 'ascend' ? 'asc' : 'desc');
// 处理表格变化
const handleTableChange = (pagination, filters, sorter) => {
if (sorter && sorter.field) {
emit('sorterChange', sorter.field, sorter.order === 'ascend' ? 'asc' : 'desc');
}
};
const handleSelect = (selectedRowKeys, selectedRows) => {
selectedItems.value = selectedRowKeys;
const handleSelect = (record, selected, selectedRows, nativeEvent) => {
selectedItems.value = selectedRows.map((row) => row.id);
emit('selectionChange', selectedRows);
};
const handleSelectAll = (selected, selectedRows, changeRows) => {
if (selected) {
selectedItems.value = props.dataSource.map((item) => item.id);
emit('selectionChange', props.dataSource);
} else {
selectedItems.value = [];
emit('selectionChange', []);
}
};
const handleExport = () => {
emit('export');
};

View File

@ -3,14 +3,15 @@
* @Date: 2025-06-30 10:54:49
-->
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
title="自定义列"
width="960px"
unmountOnClose
titleAlign="start"
class="custom-table-column-modal-98"
@close="close"
centered
wrapClassName="custom-table-column-modal-98"
@cancel="close"
>
<div class="modal-body">
<!-- 左侧分组 -->
@ -20,16 +21,16 @@
<span class="text">{{ group.label }}</span>
</div>
<div class="fields">
<a-checkbox
<Checkbox
v-for="option in group.columns"
:key="option.value"
:model-value="isCheck(option)"
:checked="isCheck(option)"
:value="option.value"
:disabled="option.is_require === ENUM_STATUS.NO"
@change="(checked) => onCheckChange(checked, option)"
@change="(e) => onCheckChange(e.target.checked, option)"
>
{{ localFields.find((item) => item.prop === option.value)?.title }}
</a-checkbox>
</Checkbox>
</div>
</div>
</div>
@ -64,15 +65,16 @@
</div>
<template #footer>
<div style="text-align: right">
<a-button class="mr-8px" size="medium" @click="close">取消</a-button>
<a-button type="primary" size="medium" @click="onSubmit">确定</a-button>
<div class="flex">
<Button @click="close">取消</Button>
<Button type="primary" @click="onSubmit">确定</Button>
</div>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { Checkbox, Modal, Button } from 'ant-design-vue';
import { ref, defineExpose } from 'vue';
import { VueDraggable } from 'vue-draggable-plus';
@ -169,6 +171,7 @@ const removeCheckedField = (value) => {
// 勾选/取消
const onCheckChange = (checked, option) => {
console.log(checked, option);
if (checked) {
checkColumns.value.push(option.value);
} else {

View File

@ -1,5 +1,5 @@
.custom-table-column-modal-98 {
.arco-modal-body {
.ant-modal-body {
.modal-body {
height: 504px;
border-radius: 8px;

View File

@ -8,18 +8,11 @@
<div class="filter-row flex">
<div class="filter-row-item flex items-center">
<span class="label">账号名称</span>
<a-input
v-model="query.name"
placeholder="请搜索..."
size="medium"
class="!w-240px"
allow-clear
@change="handleSearch"
>
<Input v-model:value="query.name" placeholder="请搜索..." class="!w-240px" allowClear @change="handleSearch">
<template #prefix>
<icon-search />
</template>
</a-input>
</Input>
</div>
<div class="filter-row-item flex items-center">
<span class="label">分组</span>
@ -43,37 +36,40 @@
<div class="filter-row flex">
<div class="filter-row-item flex items-center">
<span class="label">时间筛选</span>
<a-select v-model="query.type" size="medium" placeholder="全部" class="!w-240px" @change="handleSearch">
<template #arrow-icon> <icon-calendar size="16" /> </template>
<a-option :value="7" label="近7天">近7天</a-option>
<!-- <a-option :value="14" label="近14天">近14天</a-option> -->
<a-option :value="30" label="近30天">近30天</a-option>
</a-select>
<Select v-model:value="query.type" size="middle" placeholder="全部" class="!w-240px" @change="handleSearch">
<template #suffixIcon> <icon-calendar size="16" /> </template>
<Option :value="7" label="近7天">近7天</Option>
<!-- <Option :value="14" label="近14天">近14天</Option> -->
<Option :value="30" label="近30天">近30天</Option>
</Select>
</div>
<div class="filter-row-item flex items-center">
<a-button type="outline" class="w-84px mr-12px" size="medium" @click="handleSearch">
<Button type="primary" ghost class="w-84px mr-12px" @click="handleSearch">
<template #icon>
<icon-search />
<icon-search class="mr-8px" />
</template>
<template #default>搜索</template>
</a-button>
<a-button class="w-84px" size="medium" @click="handleReset">
</Button>
<Button class="w-84px" @click="handleReset">
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px" />
</template>
<template #default>重置</template>
</a-button>
</Button>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, defineEmits, defineProps } from 'vue';
import { Button, Input, Select } from 'ant-design-vue';
import { reactive, defineEmits, defineProps, onMounted, nextTick, ref } from 'vue';
import { fetchAccountGroups, fetchAccountOperators } from '@/api/all/propertyMarketing';
import StatusSelect from '@/views/property-marketing/media-account/components/status-select';
import CommonSelect from '@/components/common-select';
const { Option } = Select;
const props = defineProps({
query: {
type: Object,
@ -81,7 +77,7 @@ const props = defineProps({
},
});
const emits = defineEmits('onSearch', 'onReset', 'update:query');
const emits = defineEmits(['onSearch', 'onReset', 'update:query']);
const groups = ref([]);
const operators = ref([]);

View File

@ -37,8 +37,8 @@ export const CARD_FIELDS = [
export const INITIAL_QUERY = {
name: '',
status: '',
operator_id: '',
status: undefined,
operator_id: undefined,
group_ids: [],
type: 7,
column: '',

View File

@ -9,9 +9,9 @@
<div class="top flex h-64px py-10px justify-between items-center">
<div class="flex items-center">
<p class="text-18px font-400 lh-26px color-#211F24 mr-4px title">数据总览</p>
<a-tooltip content="展示所筛选的账号的信息汇总">
<Tooltip title="展示所筛选的账号的信息汇总">
<icon-question-circle size="16" class="color-#737478" />
</a-tooltip>
</Tooltip>
</div>
</div>
<div class="overview-row flex">
@ -19,15 +19,15 @@
<div class="flex items-center mb-8px">
<img :src="item.icon" width="20" height="20" class="mr-8px" />
<p class="label color-#211F24">{{ item.label }}</p>
<a-tooltip v-if="item.tooltip" :content="item.tooltip">
<Tooltip v-if="item.tooltip" :title="item.tooltip">
<img :src="icon1" width="14" height="14" class="ml-4px" />
</a-tooltip>
</Tooltip>
</div>
<span class="value color-#211F24 ml-32px">{{ formatNumberShow(overviewData[item.prop]) }}</span>
</div>
</div>
</div>
<div class=" bg-#fff rounded-8px mb-16px">
<div class="bg-#fff rounded-8px mb-16px">
<FilterBlock v-model:query="query" @onSearch="handleSearch" @onReset="handleReset" />
</div>
<div class="table-wrap bg-#fff rounded-8px px-24px py-24px flex flex-col">
@ -40,16 +40,15 @@
@sorterChange="handleSorterChange"
/>
<div v-if="pageInfo.total > 0" class="pagination-row">
<a-pagination
<Pagination
:total="pageInfo.total"
size="mini"
show-total
show-jumper
show-page-size
size="small"
:showTotal="(total, range) => `共 ${total} 条`"
showSizeChanger
showQuickJumper
:current="pageInfo.page"
:page-size="pageInfo.page_size"
:pageSize="pageInfo.page_size"
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</div>
@ -60,6 +59,7 @@
import FilterBlock from './components/filter-block';
import AccountTable from './components/account-table';
import { Tooltip, Pagination } from 'ant-design-vue';
import { getAccountBoardOverview, getAccountBoardList, postAccountBoardExport } from '@/api/all/propertyMarketing';
import { formatNumberShow } from '@/utils/tools';
import { INITIAL_QUERY, CARD_FIELDS } from './constants';
@ -99,8 +99,9 @@ const getData = async () => {
}
};
const onPageChange = (current) => {
const onPageChange = (current, pageSize) => {
pageInfo.value.page = current;
pageInfo.value.page_size = pageSize;
getData();
};

View File

@ -3,7 +3,7 @@
* @Date: 2025-06-28 12:58:25
-->
<template>
<div class="account-info-wrap bg-#fff rounded-8px px-24px mb-16px">
<div class="account-info-wrap bg-#fff rounded-8px px-24px mb-16px">
<div class="title-row">
<span class="cts !text-18px !lh-26px title">账号信息</span>
</div>
@ -47,9 +47,9 @@
<template v-else>
<div class="flex items-center mb-4px">
<p class="cts !color-#737478 !mr-4px">{{ field.title }}</p>
<a-tooltip v-if="field.tooltip" :content="field.tooltip" position="top">
<Tooltip v-if="field.tooltip" :title="field.tooltip" position="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</a-tooltip>
</Tooltip>
</div>
<p class="cts">
<template v-if="field.type === 'status'">
@ -60,10 +60,10 @@
<div v-for="(tag, index) in detailData.tags.slice(0, 2)" :key="index" class="tag-box">
<span class="text">{{ tag.name }}</span>
</div>
<a-tooltip
<Tooltip
v-if="detailData.tags.length > 2"
position="top"
:content="
:title="
detailData.tags
.slice(2)
.map((v) => v.name)
@ -73,7 +73,7 @@
<div class="tag-box">
<span class="text">{{ `+${detailData.tags.length - 2}` }}</span>
</div>
</a-tooltip>
</Tooltip>
</div>
<span class="cts" v-else>-</span>
</template>
@ -88,7 +88,12 @@
}}
</template>
<template v-else-if="field.dataIndex === 'platform'">
<img :src="getMediaAccountPlatformLogo(detailData.platform)" width="16" height="16" class="rounded-4px" />
<img
:src="getMediaAccountPlatformLogo(detailData.platform)"
width="16"
height="16"
class="rounded-4px"
/>
</template>
<template v-else-if="field.dataIndex === 'last_synced_at'">
{{ exactFormatTime(detailData.last_synced_at, 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss') }}
@ -132,6 +137,7 @@
<script setup>
import { useRoute } from 'vue-router';
import { Tooltip } from 'ant-design-vue';
import { formatTableField, formatNumberShow, exactFormatTime } from '@/utils/tools';
import { getMediaAccountPlatformLogo } from '@/utils/platform';
import { getAccountInfoFields } from '../../constants';

View File

@ -18,7 +18,7 @@ export const TABLE_COLUMNS = [
{
title: '笔记标题',
dataIndex: 'title',
width: 240,
width: 280,
fixed: 'left',
},
{
@ -30,42 +30,42 @@ export const TABLE_COLUMNS = [
{
title: '曝光量',
dataIndex: 'exposure_number',
width: 180,
width: 160,
tooltip: '内容被展示给用户的总次数,不代表用户实际观看。',
align: 'right',
},
{
title: '观看量',
dataIndex: 'view_number',
width: 180,
width: 160,
tooltip: '用户点击内容并实际观看的次数,是内容实际触达的重要指标。',
align: 'right',
},
{
title: '点赞量',
dataIndex: 'like_number',
width: 180,
width: 160,
tooltip: '单篇笔记获得的点赞总数,反映用户喜好程度。',
align: 'right',
},
{
title: '收藏量',
dataIndex: 'collect_number',
width: 180,
width: 160,
tooltip: '用户将内容保存到收藏夹的次数,代表内容被认可为“值得保留”。',
align: 'right',
},
{
title: '评论数',
dataIndex: 'comment_number',
width: 180,
width: 160,
tooltip: '内容下方用户留言的总数,体现用户参与度与讨论热度。',
align: 'right',
},
{
title: '分享量',
dataIndex: 'share_number',
width: 180,
width: 160,
tooltip: '内容被转发或分享至其他平台或私信的次数,代表外扩传播意愿。',
align: 'right',
},

View File

@ -3,121 +3,115 @@
* @Date: 2025-06-28 12:58:09
-->
<template>
<div class="note-table-wrap bg-#fff rounded-8px px-24px flex-1 flex flex-col">
<div class="note-table-wrap bg-#fff rounded-8px px-24px flex-1 flex flex-col">
<div class="title-row">
<div class="flex items-center">
<span class="cts !text-18px !lh-26px mr-4px title">笔记详情</span>
<a-tooltip content="展示笔记层级的详细数据,如曝光、互动等,是内容精细分析入口。">
<Tooltip title="展示笔记层级的详细数据,如曝光、互动等,是内容精细分析入口。">
<icon-question-circle size="16" class="color-#737478" />
</a-tooltip>
</Tooltip>
</div>
</div>
<div class="filter-row flex my-16px">
<div class="filter-row-item flex items-center">
<span class="label">笔记标题</span>
<a-space size="medium" class="w-240px">
<a-input v-model="query.title" placeholder="请搜索..." size="medium" allow-clear @change="handleSearch">
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
<Input v-model:value="query.title" class="!w-240px" placeholder="请搜索..." allowClear @change="handleSearch">
<template #prefix>
<icon-search />
</template>
</Input>
</div>
<div class="filter-row-item flex items-center">
<span class="label">发布日期</span>
<a-space size="medium" class="w-260px">
<a-range-picker
v-model="published_at"
size="medium"
allow-clear
format="YYYY-MM-DD"
class="w-100%"
@change="onDateChange"
/>
</a-space>
<DatePicker.RangePicker
v-model:value="published_at"
size="medium"
class="!w-260px"
allow-clear
format="YYYY-MM-DD"
@change="onDateChange"
/>
</div>
<a-button type="outline" class="w-84px mr-12px" size="medium" @click="handleSearch">
<Button type="primary" ghost class="w-84px mr-12px" @click="handleSearch">
<template #icon>
<icon-search />
<icon-search class="mr-8px" />
</template>
<template #default>搜索</template>
</a-button>
<a-button class="w-84px" size="medium" @click="handleReset">
</Button>
<Button class="w-84px" @click="handleReset">
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px" />
</template>
<template #default>重置</template>
</a-button>
</Button>
</div>
<a-table
:data="dataSource"
row-key="id"
<Table
:dataSource="dataSource"
rowKey="id"
:pagination="false"
:scroll="{ x: '100%' }"
class="w-100%"
bordered
column-resizable
:showSorterTooltip="false"
>
<template #empty>
<Table.Column
v-for="column in TABLE_COLUMNS"
:key="column.dataIndex"
:dataIndex="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:minWidth="column.minWidth"
:sortable="column.sortable"
:align="column.align"
:ellipsis="true"
>
<template #title>
<span class="cts mr-4px">{{ column.title }}</span>
<Tooltip v-if="column.tooltip" :title="column.tooltip" placement="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</Tooltip>
</template>
<template #customRender="{ record }">
<template v-if="column.dataIndex === 'published_at'">
{{ exactFormatTime(record.published_at) }}
</template>
<template v-else-if="column.dataIndex === 'exposure_number'">
{{ formatNumberShow({ value: record.view_number * 10, showExactValue: true }) }}
</template>
<template v-else-if="column.dataIndex === 'title'">
<TextoverTips :context="record.title" />
</template>
<template v-else>
{{ formatTableField(column, record, true) }}
</template>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #columns>
<a-table-column
v-for="column in TABLE_COLUMNS"
:key="column.dataIndex"
:data-index="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:min-width="column.minWidth"
:sortable="column.sortable"
:align="column.align"
ellipsis
tooltip
>
<template #title>
<div class="flex items-center">
<span class="cts mr-4px">{{ column.title }}</span>
<a-tooltip v-if="column.tooltip" :content="column.tooltip" position="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</a-tooltip>
</div>
</template>
<template #cell="{ record }">
<template v-if="column.dataIndex === 'published_at'">
{{ exactFormatTime(record.published_at) }}
</template>
<template v-else-if="column.dataIndex === 'exposure_number'">
{{ formatNumberShow({ value: record.view_number * 10, showExactValue: true }) }}
</template>
<template v-else>
{{ formatTableField(column, record, true) }}
</template>
</template>
</a-table-column>
</template>
</a-table>
</Table>
<div v-if="pageInfo.total > 0" class="pagination-row mb-24px">
<a-pagination
<Pagination
:total="pageInfo.total"
size="mini"
show-total
show-jumper
show-page-size
size="small"
:showTotal="(total, range) => `共 ${total} 条`"
showSizeChanger
showQuickJumper
:current="pageInfo.page"
:page-size="pageInfo.page_size"
:pageSize="pageInfo.page_size"
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</div>
</template>
<script setup>
import { Button, Input, Tooltip, Table, Pagination, DatePicker } from 'ant-design-vue';
import { TABLE_COLUMNS, INITIAL_QUERY, INITIAL_PAGE_INFO } from './constants';
import { useRoute } from 'vue-router';
import { formatTableField, exactFormatTime, formatNumberShow } from '@/utils/tools';
import { getMediaAccountBoardWorks } from '@/api/all/propertyMarketing';
import TextoverTips from '@/components/text-over-tips/index.vue';
const route = useRoute();
const id = route.params.id;
@ -148,8 +142,9 @@ const onDateChange = (value) => {
handleSearch();
};
const onPageChange = (current) => {
const onPageChange = (current, pageSize) => {
pageInfo.value.page = current;
pageInfo.value.page_size = pageSize;
getData();
};

View File

@ -3,27 +3,27 @@
* @Date: 2025-06-26 17:44:16
-->
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
:title="isBatch ? '批量删除账号' : '删除账号'"
width="400px"
modal-class="account-manage-modal"
@close="onClose"
wrapClassName="account-manage-modal"
@cancel="onClose"
centered
>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ accountName }} 这个账号吗</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px danger-btn" status="danger" size="large" @click="onDelete"
>确认删除</a-button
>
<Button @click="onClose">取消</Button>
<Button type="primary" class="ml-16px" danger @click="onDelete">确认删除</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { Modal, Button, message } from 'ant-design-vue';
import { ref } from 'vue';
import { deleteMediaAccount, batchDeleteMediaAccounts } from '@/api/all/propertyMarketing';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -56,7 +56,7 @@ async function onDelete() {
const _params = isBatch.value ? { ids: accountId.value } : accountId.value;
const { code } = await _fn(_params);
if (code === 200) {
AMessage.success('删除成功');
message.success('删除成功');
isBatch.value ? emits('batchUpdate') : emits('update');
onClose();
}

View File

@ -3,7 +3,9 @@ import {
EnumErrorStatus,
getStatusInfo,
} from '@/views/property-marketing/media-account/components/status-select/status-box';
import { Dropdown, Doption, Button, Tooltip } from '@arco-design/web-vue';
import { Dropdown, Menu } from 'ant-design-vue';
const { Item: MenuItem } = Menu;
import { Tooltip, Button } from 'ant-design-vue';
export default defineComponent({
name: 'FooterBtn',
props: {
@ -21,37 +23,37 @@ export default defineComponent({
const renderEditDoption = () => {
return (
<Doption class="color-#211F24" onClick={() => emit('openEdit', props.item)}>
<MenuItem class="color-#211F24" onClick={() => emit('openEdit', props.item)}>
</Doption>
</MenuItem>
);
};
const renderReauthorizeDoption = (text = '重新授权') => {
return (
<Doption class="color-#211F24" onClick={() => emit('handleReauthorize', props.item)}>
<MenuItem class="color-#211F24" onClick={() => emit('handleReauthorize', props.item)}>
{text}
</Doption>
</MenuItem>
);
};
const renderPauseDoption = () => {
return (
<Doption class="color-#211F24" onClick={() => emit('handlePause', props.item)}>
<MenuItem class="color-#211F24" onClick={() => emit('handlePause', props.item)}>
</Doption>
</MenuItem>
);
};
const renderUpdateBtn = () => {
return (
<Button type="outline" size="mini" onClick={() => emit('syncData', props.item)}>
<Button type="primary" ghost size="small" onClick={() => emit('syncData', props.item)}>
</Button>
);
};
const renderDeleteDoption = () => {
return (
<Doption class="color-#F64B31" onClick={() => emit('openDelete', props.item)}>
<MenuItem class="color-#F64B31" onClick={() => emit('openDelete', props.item)}>
</Doption>
</MenuItem>
);
};
const renderNormal = () => {
@ -61,17 +63,17 @@ export default defineComponent({
trigger="hover"
v-slots={{
default: () => (
<Button type="outline" class="mr-8px" size="mini">
<Button type="primary" ghost class="mr-8px" size="small">
</Button>
),
content: () => (
<>
overlay: () => (
<Menu>
{renderEditDoption()}
{renderReauthorizeDoption()}
{renderPauseDoption()}
{renderDeleteDoption()}
</>
</Menu>
),
}}
></Dropdown>
@ -86,19 +88,19 @@ export default defineComponent({
trigger="hover"
v-slots={{
default: () => (
<Button type="outline" class="mr-8px" size="mini">
<Button type="primary" ghost class="mr-8px" size="small">
</Button>
),
content: () => (
<>
overlay: () => (
<Menu>
{renderEditDoption()}
{renderDeleteDoption()}
</>
</Menu>
),
}}
></Dropdown>
<Button type="outline" size="mini" onClick={() => emit('handleReauthorize', props.item)}>
<Button type="primary" ghost size="small" onClick={() => emit('handleReauthorize', props.item)}>
</Button>
</>
@ -115,15 +117,15 @@ export default defineComponent({
return renderUpdateBtn();
} else if ([EnumErrorStatus.REQUEST, EnumErrorStatus.FREEZE].includes(error_status)) {
return (
<Tooltip content={statusInfo.value.disabledBtnTooltip}>
<Button type="outline" size="mini" disabled>
<Tooltip title={statusInfo.value.disabledBtnTooltip}>
<Button type="primary" ghost size="small" disabled>
</Button>
</Tooltip>
);
} else {
return (
<Button type="outline" size="mini" onClick={() => emit('handleReauthorize', props.item)}>
<Button type="primary" ghost size="small" onClick={() => emit('handleReauthorize', props.item)}>
{isUnauthorized ? '去授权' : '重新授权'}
</Button>
);
@ -135,17 +137,17 @@ export default defineComponent({
trigger="hover"
v-slots={{
default: () => (
<Button type="outline" class="mr-8px" size="mini">
<Button type="primary" ghost class="mr-8px" size="small">
</Button>
),
content: () => (
<>
overlay: () => (
<Menu>
{renderEditDoption()}
{isMissing && renderReauthorizeDoption()}
{renderPauseDoption()}
{renderDeleteDoption()}
</>
</Menu>
),
}}
></Dropdown>

View File

@ -4,24 +4,21 @@
-->
<template>
<div class="card-container">
<a-spin
<Spin
v-for="(item, index) in dataSource"
:key="index"
:loading="isSyncing(item)"
:spinning="isSyncing(item)"
tip="更新数据中..."
class="card-item"
:class="{
checked: isSelected(item),
}"
:wrapperClassName="`card-item ${isSelected(item) ? 'checked' : ''}`"
>
<template #icon>
<icon-sync size="24" />
</template>
<a-checkbox :model-value="isSelected(item)" :value="item.id" @change="toggleSelect(item)"></a-checkbox>
<Checkbox :checked="isSelected(item)" :value="item.id" @change="toggleSelect(item)" class="relative top--2px"></Checkbox>
<div class="ml-8px flex-1">
<a-tooltip content="点击查看账号详情">
<Tooltip title="点击查看账号详情">
<p class="name cursor-pointer hover:!color-#6d4cfe" @click="goDetail(item)">{{ item.name || '-' }}</p>
</a-tooltip>
</Tooltip>
<div class="field-row">
<span class="label">状态</span>
<StatusBox :item="item" />
@ -50,10 +47,10 @@
<span class="label">所属项目</span>
<span v-if="!item.projects.length" class="cts">-</span>
<div v-else class="flex items-center">
<a-tooltip
<Tooltip
v-if="item.projects.length > 2"
position="bottom"
:content="
placement="bottom"
:title="
item.projects
.slice(2)
.map((v) => v.name)
@ -63,7 +60,7 @@
<div class="tag-box">
<span class="text">{{ `+${item.projects.length - 2}` }}</span>
</div>
</a-tooltip>
</Tooltip>
<div v-for="(project, index) in item.projects.slice(0, 2)" :key="index" class="tag-box">
<span class="text">{{ project.name }}</span>
@ -78,10 +75,10 @@
<span class="label">标签</span>
<span v-if="!item.tags.length" class="cts">-</span>
<div v-else class="flex items-center">
<a-tooltip
<Tooltip
v-if="item.tags.length > 2"
position="bottom"
:content="
placement="bottom"
:title="
item.tags
.slice(2)
.map((v) => v.name)
@ -91,7 +88,7 @@
<div class="tag-box">
<span class="text">{{ `+${item.tags.length - 2}` }}</span>
</div>
</a-tooltip>
</Tooltip>
<div v-for="(tag, index) in item.tags.slice(0, 2)" :key="index" class="tag-box">
<span class="text">{{ tag.name }}</span>
@ -115,13 +112,13 @@
<span class="name !mb-0">{{ getErrorStatusText(item) }}</span>
</div>
<div class="flex items-center">
<a-button type="outline" class="mr-8px" size="mini" @click="handleCancel(item)">取消</a-button>
<a-button type="outline" size="mini" @click="handleConfirm(item)" v-if="showConfirmBtn(item)">{{
<Button type="primary" ghost class="mr-8px" size="small" @click="handleCancel(item)">取消</Button>
<Button type="primary" ghost size="small" @click="handleConfirm(item)" v-if="showConfirmBtn(item)">{{
getConfirmBtnText(item)
}}</a-button>
}}</Button>
</div>
</div>
</a-spin>
</Spin>
<PauseAccountPatchModal ref="pauseAccountPatchModalRef" @success="emits('update')" />
<ReauthorizeAccountModal ref="reauthorizeAccountModalRef" @update="emits('update')" />
<AuthorizedAccountModal ref="authorizedAccountModalRef" @update="emits('update')" />
@ -130,6 +127,7 @@
<script setup>
import { defineProps, ref, computed, inject } from 'vue';
import { Checkbox, Button, Tooltip, Spin } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import { deleteSyncStatus } from '@/api/all/propertyMarketing';
import { exactFormatTime } from '@/utils/tools';

View File

@ -3,19 +3,27 @@
* @Date: 2025-06-27 14:41:20
-->
<template>
<a-modal v-model:visible="visible" title="暂停同步" width="400px" modal-class="account-manage-modal" @close="onClose">
<Modal
v-model:open="visible"
title="暂停同步"
centered
width="400px"
wrapClassName="account-manage-modal"
@cancel="onClose"
>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认暂停同步 {{ accountName }} 这个账号的数据吗</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px" size="large" @click="onConfirm">确定</a-button>
<Button size="large" @click="onClose">取消</Button>
<Button type="primary" class="ml-16px" size="large" @click="onConfirm">确定</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { Button, Modal, message } from 'ant-design-vue';
import { ref } from 'vue';
import { pausePatchAccount } from '@/api/all/propertyMarketing';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -44,7 +52,7 @@ const open = (record) => {
async function onConfirm() {
const { code } = await pausePatchAccount(accountId.value);
if (code === 200) {
AMessage.success('暂停成功');
message.success('暂停成功');
emits('success');
onClose();
}

View File

@ -4,13 +4,17 @@
// grid-template-rows: repeat(2, 1fr); /* 2行 */
grid-template-columns: repeat(4, 1fr); /* 4列 */
gap: 20px;
:deep(.ant-spin-container) {
height: 100%;
width: 100%;
display: flex;
align-items: flex-start;
}
.card-item {
border-radius: 8px;
// border: 1px solid var(--BG-300, #e6e6e8);
background: var(--BG-white, #fff);
padding: 12px 16px 16px;
display: flex;
align-items: flex-start;
position: relative;
.name {
color: var(--Text-1, #211f24);

View File

@ -5,20 +5,19 @@
<script lang="jsx">
import { ref, computed } from 'vue';
import {
Button,
Modal,
Form,
FormItem,
Input,
RadioGroup,
Radio,
Upload,
Button,
Switch,
Input,
message,
Tooltip,
Notification,
Message as AMessage,
Textarea,
} from '@arco-design/web-vue';
Upload,
Switch,
} from 'ant-design-vue';
const { TextArea } = Input;
import AuthorizedAccountModal from '../authorized-account-modal';
// import ImportPromptModal from '../import-prompt-modal';
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
@ -88,15 +87,14 @@ export default {
mobile: [
{
required: true,
message: '请填写手机号',
trigger: ['blur', 'change'],
},
{
validator: (value, callback) => {
validator: (_rule, value) => {
if (!value) {
return Promise.reject('请填写手机号');
}
if (!/^1[3-9]\d{9}$/.test(value)) {
callback('手机号格式不正确');
return Promise.reject('手机号格式不正确');
} else {
callback();
return Promise.resolve();
}
},
trigger: ['blur', 'change'],
@ -135,10 +133,9 @@ export default {
}
};
function handleUpload(option) {
const { fileItem } = option;
uploadStatus.value = UploadStatus.WAITING;
file.value = fileItem.file;
fileName.value = fileItem.name;
file.value = option.file;
fileName.value = option.file.name;
}
function removeFile() {
fileName.value = '';
@ -188,7 +185,7 @@ export default {
const handleBatchImport = async () => {
try {
if (!file.value) {
AMessage.warning('请上传要导入的文件');
message.warning('请上传要导入的文件');
return;
}
@ -234,7 +231,7 @@ export default {
const handleEditAccount = async () => {
const { code } = await putMediaAccounts({ id: id.value, ...form.value });
if (code === 200) {
AMessage.success('修改成功');
message.success('修改成功');
emit('update');
onClose();
}
@ -244,14 +241,12 @@ export default {
handleBatchImport();
return;
}
formRef.value.validate(async (errors) => {
if (!errors) {
if (isCustomCookie.value && !form.value.cookie) {
AMessage.warning('请填写Cookie值');
return;
}
isEdit.value ? handleEditAccount() : handleAddAccount();
formRef.value.validate().then(async () => {
if (isCustomCookie.value && !form.value.cookie) {
message.warning('请填写Cookie值');
return;
}
isEdit.value ? handleEditAccount() : handleAddAccount();
});
}
const startAuthorized = (id, platform) => {
@ -278,18 +273,27 @@ export default {
return () => (
<Modal
v-model:visible={visible.value}
v-model:open={visible.value}
title={isEdit.value ? '编辑账号' : '添加账号'}
modal-class="add-account-modal"
wrapClassName="add-account-modal"
width="500px"
mask-closable={false}
onClose={onClose}
centered
maskClosable={false}
onCancel={onClose}
footer={null}
>
<Form ref={formRef} model={form.value} rules={rules} layout="horizontal" auto-label-width>
<Form
ref={formRef}
model={form.value}
rules={rules}
layout="horizontal"
labelAlign="right"
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
>
{!isEdit.value && (
<FormItem label="上传方式" required>
<RadioGroup v-model={uploadType.value}>
<RadioGroup v-model:value={uploadType.value}>
<Radio value="manual">手动添加账号</Radio>
<Radio value="batch">批量导入账号</Radio>
</RadioGroup>
@ -302,19 +306,14 @@ export default {
<Upload
ref={uploadRef}
action="/"
draggable
custom-request={handleUpload}
customRequest={handleUpload}
accept=".xlsx,.xls"
show-file-list={false}
showUploadList={false}
>
{{
'upload-button': () => (
<div class="upload-box">
<span class="text mb-4px">点击或拖拽文件到此处上传</span>
<span class="tip">支持 xls, xlsx格式</span>
</div>
),
}}
<div class="upload-box">
<span class="text mb-4px">点击或拖拽文件到此处上传</span>
<span class="tip">支持 xls, xlsx格式</span>
</div>
</Upload>
) : (
<div class="flex items-center">
@ -346,35 +345,35 @@ export default {
<>
{isEdit.value && (
<>
<FormItem label="账号名称" field="name">
<Input v-model={form.value.name} placeholder="请输入..." size="large" disabled />
<FormItem label="账号名称" name="name">
<Input v-model:value={form.value.name} placeholder="请输入..." size="large" disabled />
</FormItem>
<FormItem label="账号ID" field="account_id">
<Input v-model={form.value.account_id} placeholder="请输入..." size="large" disabled />
<FormItem label="账号ID" name="account_id">
<Input v-model:value={form.value.account_id} placeholder="请输入..." size="large" disabled />
</FormItem>
<FormItem label="状态" field="status">
<FormItem label="状态" name="status">
<StatusBox item={form.value} />
</FormItem>
</>
)}
<FormItem label="手机号码" field="mobile" required>
<Input v-model={form.value.mobile} placeholder="请输入..." size="large" />
<FormItem label="手机号码" name="mobile" required>
<Input v-model:value={form.value.mobile} placeholder="请输入..." size="large" />
</FormItem>
<FormItem label="运营人员" field="operator_name" required>
<Input v-model={form.value.operator_name} placeholder="请输入..." class="w-240px" size="large" />
<FormItem label="运营人员" name="operator_name" required>
<Input v-model:value={form.value.operator_name} placeholder="请输入..." class="w-240px" size="large" />
</FormItem>
<FormItem label="运营平台" required={!isEdit.value}>
{isEdit.value ? (
<img src={form.value.platform === 0 ? icon3 : icon4} width="24" height="24" />
) : (
<RadioGroup v-model={form.value.platform}>
<RadioGroup v-model:value={form.value.platform}>
<Radio value={1}>小红书</Radio>
<Radio value={0}>抖音</Radio>
</RadioGroup>
)}
</FormItem>
<FormItem label="号码持有人" field="holder_name">
<Input v-model={form.value.holder_name} placeholder="请输入..." class="w-240px" size="large" />
<FormItem label="号码持有人" name="holder_name">
<Input v-model:value={form.value.holder_name} placeholder="请输入..." class="w-240px" size="large" />
</FormItem>
<FormItem label="所属项目">
<CommonSelect
@ -395,21 +394,26 @@ export default {
</FormItem>
<FormItem label="选择标签">
<CommonSelect v-model={form.value.tag_ids} options={tagOptions.value} placeholder="请选择…" size="large" />
<CommonSelect
v-model={form.value.tag_ids}
options={tagOptions.value}
placeholder="请选择…"
size="large"
/>
</FormItem>
<FormItem
label="笔记链接"
field="end_work_link"
name="end_work_link"
v-slots={{
label: () =>
renderLabel('笔记链接', '平台将从该笔记“之后”的内容开始同步,该笔记及更早的数据均不采集'),
}}
>
<Textarea
v-model={form.value.end_work_link}
<TextArea
v-model:value={form.value.end_work_link}
placeholder="请输入..."
size="large"
auto-size={{ minRows: 3, maxRows: 5 }}
autoSize={{ minRows: 3, maxRows: 5 }}
/>
</FormItem>
<FormItem
@ -421,12 +425,12 @@ export default {
<Switch v-model={isCustomCookie.value} size="large" />
</FormItem>
{isCustomCookie.value && (
<FormItem label="" field="cookie">
<Textarea
v-model={form.value.cookie}
<FormItem label=" " name="cookie">
<TextArea
v-model:value={form.value.cookie}
placeholder="请输入..."
size="large"
auto-size={{ minRows: 5, maxRows: 8 }}
autoSize={{ minRows: 5, maxRows: 8 }}
/>
</FormItem>
)}

View File

@ -4,7 +4,7 @@
.w-240px {
width: 240px !important;
}
.arco-modal-body {
.ant-modal-body {
.upload-block {
width: 100%;
.dt {
@ -36,15 +36,8 @@
}
}
}
.arco-upload-drag {
height: 120px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.arco-icon {
margin-bottom: 16px;
}
.ant-upload {
width: 100%;
}
.upload-box {
display: flex;
@ -77,13 +70,13 @@
}
}
}
.upload-dragger {
border: 1px dashed #d9d9d9;
padding: 24px 0;
text-align: center;
background: #fafafa;
cursor: pointer;
}
// .upload-dragger {
// border: 1px dashed #d9d9d9;
// padding: 24px 0;
// text-align: center;
// background: #fafafa;
// cursor: pointer;
// }
.upload-error {
color: #f53f3f;
margin-left: 8px;

View File

@ -3,24 +3,25 @@
* @Date: 2025-06-25 17:51:46
-->
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
width="480px"
title="授权账号"
modal-class="authorized-account-modal"
:mask-closable="false"
:footer="modalState !== MODAL_STATE.LOADING"
@close="close"
centered
wrapClassName="authorized-account-modal"
:maskClosable="false"
:footer="modalState === MODAL_STATE.LOADING ? null : footer"
@cancel="close"
>
<div class="flex flex-col items-center">
<!-- 加载中状态 -->
<template v-if="modalState === MODAL_STATE.LOADING">
<a-progress
<Progress
:percent="progress"
color="#6D4CFE"
trackColor="#E6E6E8"
size="large"
:stroke-width="4"
strokeColor="#6D4CFE"
trailColor="#E6E6E8"
size="default"
:strokeWidth="4"
type="circle"
/>
<p class="s2 mt-16px">数据同步和初始化中请勿关闭窗口</p>
@ -40,7 +41,7 @@
<!-- 二维码加载中或失败 -->
<template v-if="modalState === MODAL_STATE.QR_LOADING || modalState === MODAL_STATE.QR_FAILED">
<div class="relative w-160px h-160px">
<a-image :src="icon1" width="160" height="160" />
<Image :src="icon1" :width="160" :height="160" />
<div class="absolute top-0 left-0 z-2 w-full h-full flex flex-col items-center justify-center">
<img
v-if="modalState === MODAL_STATE.QR_FAILED"
@ -62,7 +63,7 @@
</div>
</template>
<!-- 正常二维码 -->
<a-image v-else :src="qrCodeUrl" width="160" height="160" />
<Image v-else :src="qrCodeUrl" :width="160" :height="160" />
<!-- 二维码失效遮罩 -->
<div v-if="modalState === MODAL_STATE.QR_EXPIRED" class="mask cursor-pointer" @click="handleRefreshQrCode">
<icon-refresh size="24" class="mb-13px" />
@ -76,24 +77,24 @@
</div>
<template #footer>
<a-button v-if="modalState === MODAL_STATE.QR_READY" size="large" @click="handleRefreshQrCode">
<Button v-if="modalState === MODAL_STATE.QR_READY" size="large" @click="handleRefreshQrCode">
重新生成
</a-button>
<a-button v-if="[MODAL_STATE.SUCCESS, MODAL_STATE.FAILED].includes(modalState)" size="large" @click="close">
</Button>
<Button v-if="[MODAL_STATE.SUCCESS, MODAL_STATE.FAILED].includes(modalState)" size="large" @click="close">
取消
</a-button>
<a-button type="primary" size="large" @click="handleOk">
</Button>
<Button type="primary" size="large" @click="handleOk">
{{ confirmBtnText }}
</a-button>
</Button>
</template>
</a-modal>
</Modal>
<SyncDataModal ref="syncDataModalRef" />
</template>
<script setup>
import { defineExpose, ref, computed } from 'vue';
import { Message as AMessage } from '@arco-design/web-vue';
import { Button, Modal, message, Image, Progress } from 'ant-design-vue';
import { getAuthorizedImage, getMediaAccountsAuthorizedStatus } from '@/api/all/propertyMarketing';
import SyncDataModal from '../sync-data-modal';
@ -241,12 +242,12 @@ const getAuthorizedStatus = async () => {
const startFakeProgressPolling = () => {
clearFakeProgressTimer();
progressTimer = setInterval(() => {
if (modalState.value === MODAL_STATE.LOADING && progress.value < 0.99) {
const step = Math.random() * 0.04 + 0.01;
progress.value = Math.min(progress.value + step, 0.99);
if (modalState.value === MODAL_STATE.LOADING && progress.value < 99) {
const step = Math.random() * 4 + 1;
progress.value = Math.min(progress.value + step, 99);
progress.value = Number(progress.value.toFixed(2));
} else if (modalState.value === MODAL_STATE.LOADING && progress.value >= 0.99) {
progress.value = 0.99; // 卡在99%
} else if (modalState.value === MODAL_STATE.LOADING && progress.value >= 99) {
progress.value = 99; // 卡在99%
} else {
clearFakeProgressTimer();
clearStatusPollingTimer();
@ -284,7 +285,7 @@ const handleOk = () => {
// 二维码还在加载中
if (modalState.value === MODAL_STATE.QR_LOADING) {
AMessage.warning('二维码生成中,请稍等');
message.warning('二维码生成中,请稍等');
return;
}

View File

@ -3,73 +3,87 @@
* @Date: 2025-06-27 09:35:49
-->
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
title="批量分组"
modal-class="batch-group-modal"
wrapClassName="batch-group-modal"
width="800px"
:mask-closable="false"
:maskClosable="false"
centered
>
<div class="mb-16px t1">
{{ `已选${accountGroupList.length}个账号` }}
</div>
<a-form ref="formRef" :model="form" layout="horizontal" auto-label-width>
<a-form-item label="编辑方式" required>
<a-radio-group v-model="editType">
<a-radio value="all">
<Form ref="formRef" :model="form" layout="horizontal">
<FormItem label="编辑方式" required>
<Radio.Group v-model:value="editType">
<Radio value="all">
<div class="flex items-center">
<span>统一编辑</span>
<a-tooltip content="原分组将被清除,统一加入新分组。">
<Tooltip title="原分组将被清除,统一加入新分组。">
<img :src="icon1" alt="icon" class="ml-2px" width="14" height="14" />
</a-tooltip>
</Tooltip>
</div>
</a-radio>
<a-radio value="each">分别编辑</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="选择分组" required>
</Radio>
<Radio value="each">分别编辑</Radio>
</Radio.Group>
</FormItem>
<FormItem label="选择分组" required>
<template v-if="editType === 'all'">
<div class="flex items-center w-100%">
<CommonSelect v-model="form.group_id" :options="groupOptions" :multiple="false" class="flex-1" />
</div>
</template>
</a-form-item>
</FormItem>
<!-- 分别编辑 -->
<template v-if="editType === 'each'">
<a-table :data="accountGroupList" :pagination="false" row-key="id" class="w-100%" column-resizable>
<template #columns>
<a-table-column title="账号名称" data-index="name" width="200">
<template #cell="{ record }">
<span>{{ record.name || '-' }}</span>
</template>
</a-table-column>
<a-table-column title="选择分组" data-index="group_id">
<template #cell="{ record }">
<div class="flex items-center w-100%">
<CommonSelect v-model="record.group_id" :options="groupOptions" :multiple="false" />
</div>
</template>
</a-table-column>
<Table
:dataSource="accountGroupList"
:columns="columns"
:pagination="false"
rowKey="id"
bordered
:showSorterTooltip="false"
@resizeColumn="handleResizeColumn"
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'name'">
<span>{{ record.name || '-' }}</span>
</template>
<template v-else-if="column.dataIndex === 'group_id'">
<div class="flex items-center w-100%">
<CommonSelect v-model="record.group_id" :options="groupOptions" :multiple="false" class="w-full" />
</div>
</template>
</template>
</a-table>
</Table>
</template>
</a-form>
</Form>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" size="large" @click="onSubmit">确定</a-button>
<Button @click="onClose">取消</Button>
<Button type="primary" @click="onSubmit">确定</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ref, reactive, computed } from 'vue';
import { Modal, Form, FormItem, Radio, Tooltip, Button, Table, message } from 'ant-design-vue';
import { fetchAccountGroups, batchPutGroup } from '@/api/all/propertyMarketing';
import CommonSelect from '@/components/common-select';
import icon1 from '@/assets/img/icon-question.png';
// [{id, name, group_id: null}]
const columns = ref([
{ title: '账号名称', dataIndex: 'name', width: 100, minWidth: 100, resizable: true },
{ title: '选择分组', dataIndex: 'group_id' },
]);
const handleResizeColumn = (w, col) => {
const idx = columns.value.findIndex((c) => c.dataIndex === col.dataIndex);
if (idx !== -1) columns.value[idx].width = w;
};
const emits = defineEmits(['update']);
const visible = ref(false);
@ -102,18 +116,17 @@ const getTags = async () => {
const onClose = () => {
visible.value = false;
form.group_id = null;
};
const onSubmit = async () => {
if (isAllEdit.value) {
if (form.group_id === null) {
AMessage.error('请选择分组');
message.error('请选择分组');
return;
}
} else {
if (accountGroupList.value.some((item) => item.group_id === null)) {
AMessage.error('请选择分组');
message.error('请选择分组');
return;
}
}
@ -126,7 +139,7 @@ const onSubmit = async () => {
// 这里处理批量标签的提交逻辑
const { code } = await batchPutGroup({ media_accounts });
if (code === 200) {
AMessage.success('设置分组成功');
message.success('设置分组成功');
emits('update');
visible.value = false;
}

View File

@ -2,7 +2,7 @@
.batch-group-modal {
border-radius: 8px;
.arco-modal-body {
.ant-modal-body {
// min-height: 200px;
.t1 {
color: var(--Text-3, #737478);

View File

@ -3,87 +3,85 @@
* @Date: 2025-06-27 09:35:49
-->
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
title="批量标签"
modal-class="batch-tag-modal"
wrapClassName="batch-tag-modal"
width="800px"
:mask-closable="false"
centered
:maskClosable="false"
>
<div class="mb-16px t1">
{{ `已选${accountTagList.length}个账号` }}
</div>
<a-form ref="formRef" :model="form" layout="horizontal" auto-label-width>
<a-form-item label="编辑方式" required>
<a-radio-group v-model="editType">
<a-radio value="all">
<Form ref="formRef" :model="form" layout="horizontal" auto-label-width>
<FormItem label="编辑方式" required>
<RadioGroup v-model:value="editType">
<Radio value="all">
<div class="flex items-center">
<span>统一编辑</span>
<a-tooltip content="原标签将被清除,统一为新标签。">
<Tooltip title="原标签将被清除,统一为新标签。">
<img :src="icon1" alt="icon" class="ml-2px" width="14" height="14" />
</a-tooltip>
</Tooltip>
</div>
</a-radio>
<a-radio value="each">分别编辑</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="选择标签" required>
</Radio>
<Radio value="each">分别编辑</Radio>
</RadioGroup>
</FormItem>
<FormItem label="选择标签" required>
<template v-if="editType === 'all'">
<div class="flex items-center w-100%">
<a-select
v-model="form.tags"
<Select
v-model:value="form.tags"
:options="tagOptions"
multiple
allow-create
mode="tags"
placeholder="请输入标签,回车键可直接添加..."
:limit="5"
:max-tag-count="5"
class="flex-1"
@create="handleCreateTag"
@search="handleCreateTag"
/>
<span class="ml-12px">{{ `${form.tags.length}/5` }}</span>
</div>
</template>
</a-form-item>
</FormItem>
<!-- 分别编辑 -->
<template v-if="editType === 'each'">
<a-table :data="accountTagList" :pagination="false" row-key="id" class="w-100%" column-resizable>
<template #columns>
<a-table-column title="账号名称" data-index="name" width="200">
<template #cell="{ record }">
<span>{{ record.name || '-' }}</span>
</template>
</a-table-column>
<a-table-column title="选择标签" data-index="tags">
<template #cell="{ record, rowIndex }">
<div class="flex items-center w-100%">
<a-select
v-model="record.tags"
:options="tagOptions"
multiple
allow-create
placeholder="请输入标签,回车键可直接添加..."
:limit="5"
style="width: 90%"
@create="(val) => handleCreateTag(val, rowIndex)"
/>
<span class="tag-count ml-8px">{{ record.tags.length }}/5</span>
</div>
</template>
</a-table-column>
</template>
</a-table>
<Table :dataSource="accountTagList" :pagination="false" rowKey="id" class="w-100%">
<Table.Column title="账号名称" dataIndex="name" :width="200">
<template #customRender="{ record }">
<span>{{ record.name || '-' }}</span>
</template>
</Table.Column>
<Table.Column title="选择标签" dataIndex="tags">
<template #customRender="{ record, index }">
<div class="flex items-center w-100%">
<Select
v-model:value="record.tags"
:options="tagOptions"
mode="tags"
placeholder="请输入标签,回车键可直接添加..."
:max-tag-count="5"
class="!w-full"
@search="(val) => handleCreateTag(val, index)"
/>
<span class="tag-count ml-8px">{{ record.tags.length }}/5</span>
</div>
</template>
</Table.Column>
</Table>
</template>
</a-form>
</Form>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" size="large" @click="onSubmit">确定</a-button>
<Button size="large" @click="onClose">取消</Button>
<Button type="primary" size="large" @click="onSubmit">确定</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { Button, Form, FormItem, Modal, Radio, RadioGroup, Select, Table, Tooltip, message } from 'ant-design-vue';
import { fetchAccountTags, batchPutTag } from '@/api/all/propertyMarketing';
import icon1 from '@/assets/img/icon-question.png';
@ -143,12 +141,12 @@ const onClose = () => {
const onSubmit = async () => {
if (isAllEdit.value) {
if (form.tags.length === 0) {
AMessage.error('请输入标签');
message.error('请输入标签');
return;
}
} else {
if (accountTagList.value.some((item) => item.tags.length === 0)) {
AMessage.error('请输入标签');
message.error('请输入标签');
return;
}
}
@ -160,7 +158,7 @@ const onSubmit = async () => {
console.log({ media_accounts });
const { code } = await batchPutTag({ media_accounts });
if (code === 200) {
AMessage.success('设置标签成功');
message.success('设置标签成功');
emits('update');
visible.value = false;
}

View File

@ -2,7 +2,7 @@
.batch-tag-modal {
border-radius: 8px;
.arco-modal-body {
.ant-modal-body {
// min-height: 200px;
.t1 {
color: var(--Text-3, #737478);

View File

@ -8,86 +8,73 @@
<div class="filter-row flex">
<div class="filter-row-item">
<span class="label">账号名称/ID/手机号</span>
<a-space size="medium">
<a-input
v-model="query.search"
class="w-240px"
placeholder="请搜索..."
size="medium"
allow-clear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
<Input v-model:value="query.search" class="w-240px" placeholder="请搜索..." allowClear @change="handleSearch">
<template #prefix>
<icon-search />
</template>
</Input>
</div>
<div class="filter-row-item">
<span class="label">状态</span>
<a-space class="w-180px">
<StatusSelect v-model="query.status" @change="handleSearch" />
</a-space>
<StatusSelect v-model="query.status" @change="handleSearch" class="w-180px" />
</div>
<div class="filter-row-item">
<span class="label">平台</span>
<a-space class="w-160px">
<a-select v-model="query.platform" size="medium" placeholder="全部" allow-clear @change="handleSearch">
<a-option
v-for="(item, index) in MEDIA_ACCOUNT_PLATFORMS"
:key="index"
:value="item.value"
:label="item.label"
>{{ item.label }}</a-option
>
</a-select>
</a-space>
<Select
v-model:value="query.platform"
class="w-160px"
size="middle"
placeholder="全部"
allowClear
@change="handleSearch"
>
<Option
v-for="(item, index) in MEDIA_ACCOUNT_PLATFORMS"
:key="index"
:value="item.value"
:label="item.label"
>{{ item.label }}</Option
>
</Select>
</div>
<div class="filter-row-item">
<span class="label">运营人员</span>
<a-space class="w-160px">
<CommonSelect
v-model="query.operator_id"
allowSearch
:multiple="false"
:options="operators"
@change="handleSearch"
/>
</a-space>
<CommonSelect
class="w-160px"
v-model="query.operator_id"
allowSearch
:multiple="false"
:options="operators"
@change="handleSearch"
/>
</div>
</div>
<div class="filter-row">
<div class="filter-row-item">
<span class="label">分组</span>
<a-space class="w-200px">
<CommonSelect v-model="query.group_ids" multiple :options="groups" @change="handleSearch" />
</a-space>
<CommonSelect v-model="query.group_ids" multiple :options="groups" @change="handleSearch" class="!w-200px" />
</div>
<div class="filter-row-item">
<span class="label">所属项目</span>
<a-space class="w-200px">
<CommonSelect v-model="query.project_ids" :options="projects" @change="handleSearch" />
</a-space>
<CommonSelect v-model="query.project_ids" :options="projects" @change="handleSearch" class="!w-200px" />
</div>
<div class="filter-row-item">
<span class="label">标签</span>
<a-space class="w-320px">
<CommonSelect v-model="query.tag_ids" :options="tags" @change="handleSearch" />
</a-space>
<CommonSelect v-model="query.tag_ids" :options="tags" @change="handleSearch" class="!w-320px" />
</div>
<div class="filter-row-item">
<a-button type="outline" class="w-84px mr-12px" size="medium" @click="handleSearch">
<Button type="primary" ghost class="w-84px mr-12px" @click="handleSearch">
<template #icon>
<icon-search />
<icon-search class="mr-8px" />
</template>
<template #default>搜索</template>
</a-button>
<a-button class="w-84px" size="medium" @click="handleReset">
</Button>
<Button class="w-84px" @click="handleReset">
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px" />
</template>
<template #default>重置</template>
</a-button>
</Button>
</div>
</div>
</div>
@ -95,6 +82,8 @@
<script setup>
import { reactive, defineEmits, defineProps } from 'vue';
import { Button, Input, Select } from 'ant-design-vue';
const { Option } = Select;
import {
fetchAccountTags,
getProjectList,
@ -114,7 +103,7 @@ const props = defineProps({
},
});
const emits = defineEmits('onSearch', 'onReset', 'update:query');
const emits = defineEmits(['onSearch', 'onReset', 'update:query']);
const tags = ref([]);
const groups = ref([]);

View File

@ -3,27 +3,29 @@
* @Date: 2025-06-26 11:44:17
-->
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
:title="isEdit ? '编辑分组' : '添加新分组'"
modal-class="account-manage-modal"
wrapClassName="group-manage-modal"
width="400px"
@close="onClose"
centered
@cancel="onClose"
>
<a-form ref="formRef" :model="form" :rules="rules" layout="horizontal" auto-label-width>
<a-form-item :label="isEdit ? '分组名称' : '新分组名称'" field="name" required>
<a-input v-model="form.name" placeholder="请输入…" />
</a-form-item>
</a-form>
<Form ref="formRef" :model="form" :rules="rules" layout="horizontal" auto-label-width>
<FormItem :label="isEdit ? '分组名称' : '新分组名称'" name="name" required>
<Input v-model:value="form.name" placeholder="请输入…" />
</FormItem>
</Form>
<template #footer>
<a-button @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px" @click="onSubmit">确认</a-button>
<Button @click="onClose">取消</Button>
<Button type="primary" class="ml-16px" @click="onSubmit">确认</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref, watch, nextTick } from 'vue';
import { Button, Modal, Form, FormItem, Input, message } from 'ant-design-vue';
import { postAccountGroups, putGroup } from '@/api/all/propertyMarketing';
const emits = defineEmits(['success', 'close']);
@ -61,16 +63,14 @@ const open = (record = {}) => {
};
async function onSubmit() {
formRef.value.validate(async (errors) => {
if (!errors) {
const _fn = isEdit.value ? putGroup : postAccountGroups;
const _params = isEdit.value ? { id: groupId.value, ...form.value } : form.value;
const { code } = await _fn(_params);
if (code === 200) {
AMessage.success(isEdit.value ? '编辑成功' : '添加成功');
emits('success');
onClose();
}
formRef.value.validate().then(async () => {
const _fn = isEdit.value ? putGroup : postAccountGroups;
const _params = isEdit.value ? { id: groupId.value, ...form.value } : form.value;
const { code } = await _fn(_params);
if (code === 200) {
message.success(isEdit.value ? '编辑成功' : '添加成功');
emits('success');
onClose();
}
});
}

View File

@ -3,22 +3,28 @@
* @Date: 2025-06-26 11:45:05
-->
<template>
<a-modal v-model:visible="visible" title="删除分组" width="400px" modal-class="account-manage-modal" @close="onClose">
<Modal
v-model:open="visible"
title="删除分组"
centered
width="400px"
wrapClassName="account-manage-modal"
@cancel="onClose"
>
<div class="flex items-center mb-24px">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 "{{ groupName }}" 这个分组吗</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px danger-btn" status="danger" size="large" @click="onDelete"
>确认删除</a-button
>
<Button size="large" @click="onClose">取消</Button>
<Button type="primary" class="ml-16px" danger size="large" @click="onDelete">确认删除</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal, message } from 'ant-design-vue';
import { deleteGroup } from '@/api/all/propertyMarketing';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -46,7 +52,7 @@ const open = (record) => {
async function onDelete() {
const { code } = await deleteGroup(groupId.value);
if (code === 200) {
AMessage.success('删除成功');
message.success('删除成功');
emits('success');
onClose();
}

View File

@ -3,84 +3,103 @@
* @Date: 2025-06-25 17:51:46
-->
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
width="900px"
modal-class="account-manage-modal"
:footer="false"
wrapClassName="account-manage-modal"
:footer="null"
centered
title="分组管理"
:mask-closable="false"
@close="close"
:maskClosable="false"
@cancel="close"
>
<div class="flex items-center justify-between mb-16px">
<div class="filter-row-item flex items-center">
<span class="s1 !color-#211F24 mr-12px">分组名称</span>
<a-space size="medium" class="w-240px">
<a-input v-model="query.name" placeholder="请搜索..." size="medium" allow-clear @change="reload">
<Space size="medium" class="w-240px">
<Input v-model:value="query.name" placeholder="请搜索..." size="middle" allowClear @change="reload">
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
</Input>
</Space>
</div>
<a-button type="primary" size="medium" @click="openAdd"
<Button type="primary" size="middle" @click="openAdd"
><template #icon>
<icon-plus size="16" />
<icon-plus size="16" class="mr-8px" />
</template>
<template #default>添加新分组</template>
</a-button>
</Button>
</div>
<a-table
column-resizable
:columns="columns"
:data="list"
row-key="id"
<Table
:dataSource="list"
rowKey="id"
:loading="loading"
:scroll="{ y: 500 }"
class="h-500px"
:pagination="false"
@sorter-change="handleSorterChange"
:showSorterTooltip="false"
@change="
(pagination, filters, sorter) => {
if (sorter && sorter.columnKey) {
handleSorterChange(sorter.columnKey, sorter.order);
}
}
"
>
<template #empty>
<Table.Column title="分组名称" dataIndex="name" />
<Table.Column title="创建人" dataIndex="creator">
<template #customRender="{ record }">
{{ record.creator?.name || '-' }}
</template>
</Table.Column>
<Table.Column title="创建日期" dataIndex="created_at" :width="160" key="created_at" :sorter="true">
<template #customRender="{ record }">
{{ exactFormatTime(record.created_at) }}
</template>
</Table.Column>
<Table.Column title="操作" :align="'center'" :width="120">
<template #customRender="{ record }">
<div class="flex items-center">
<img :src="icon1" width="16" height="16" class="mr-8px cursor-pointer" @click="openDelete(record)" />
<Button type="primary" size="small" @click="openEdit(record)">编辑</Button>
</div>
</template>
</Table.Column>
<template #emptyText>
<NoData>
<span class="s1 mb-16px">暂无分组</span>
<a-button type="primary" class="mb-16px" size="medium" @click="openAdd"
<Button type="primary" class="mb-16px" size="middle" @click="openAdd"
><template #icon>
<icon-plus size="16"/>
<icon-plus size="16" class="mr-8px" />
</template>
<template #default>去添加</template>
</a-button>
</Button>
</NoData>
</template>
<template #action="{ record }">
<div class="flex items-center">
<img :src="icon1" width="16" height="16" class="mr-8px cursor-pointer" @click="openDelete(record)" />
<a-button type="primary" @click="openEdit(record)">编辑</a-button>
</div>
</template>
</a-table>
</Table>
<div v-if="pageInfo.total > 0" class="pagination-row flex justify-end">
<a-pagination
<Pagination
:total="pageInfo.total"
size="mini"
show-total
show-jumper
show-page-size
size="small"
:showTotal="(total, range) => `共 ${total} 条`"
showSizeChanger
showQuickJumper
:current="pageInfo.page"
:page-size="pageInfo.pageSize"
:pageSize="pageInfo.pageSize"
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
<AddGroup ref="addGroupRef" @success="update" />
<DeleteGroup ref="deleteGroupRef" @success="update" />
</a-modal>
</Modal>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { Button, Input, Modal, Space, Table, Pagination } from 'ant-design-vue';
import { getAccountGroup } from '@/api/all/propertyMarketing';
import { exactFormatTime } from '@/utils/tools';
@ -115,25 +134,6 @@ const loading = ref(false);
const query = ref(cloneDeep(INITIAL_QUERY));
const pageInfo = ref(cloneDeep(INITIAL_PAGE_INFO));
const columns = [
{ title: '分组名称', dataIndex: 'name' },
{
title: '创建人',
dataIndex: 'creator',
render: ({ record }) => record.creator?.name || '-',
},
{
title: '创建日期',
dataIndex: 'created_at',
width: 160,
render: ({ record }) => exactFormatTime(record.created_at),
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{ title: '操作', slotName: 'action', align: 'center', width: 120 },
];
function open() {
visible.value = true;
getData();
@ -188,8 +188,9 @@ const reload = () => {
getData();
};
const onPageChange = (current) => {
const onPageChange = (current, pageSize) => {
pageInfo.value.page = current;
pageInfo.value.pageSize = pageSize;
getData();
};
const onPageSizeChange = (pageSize) => {

View File

@ -2,9 +2,9 @@
.account-manage-modal {
border-radius: 8px;
.arco-modal-body {
.arco-btn {
.arco-btn-icon {
.ant-modal-body {
.ant-btn {
.ant-btn-icon {
line-height: 16px;
}
}

Some files were not shown because too many files have changed in this diff Show More