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

@ -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;