提交 d4b65227 authored 作者: hsx's avatar hsx

feat:企业经营情况详情页

上级 000d30fd
...@@ -129,3 +129,42 @@ export function getSupplyList(params) { ...@@ -129,3 +129,42 @@ export function getSupplyList(params) {
params, params,
}) })
} }
// 企业市值:市值变化
export function getMarketCapList(params) {
return request({
method: 'GET',
url: `/api/enterprisePage/marketCap/${params}`,
})
}
// 企业发展:营收折线图
export function getRevenueList(params) {
return request({
method: 'GET',
url: `/api/enterprisePage/revenue/${params}`,
})
}
// 企业发展:净利润折线图
export function getNetProfitList(params) {
return request({
method: 'GET',
url: `/api/enterprisePage/netProfit/${params}`,
})
}
// 企业发展:人员情况折线图
export function getPersonnelList(params) {
return request({
method: 'GET',
url: `/api/enterprisePage/personnel/${params}`,
})
}
// 企业发展: 市场占比折线图
export function getMarketShareList(params) {
return request({
method: 'GET',
url: `/api/enterprisePage/marketShare/${params}`,
})
}
\ No newline at end of file
@use '@/styles/common.scss';
.radio-group-as-gap-btn {
@extend .text-tip-1;
.el-radio-button {
--el-radio-button-checked-bg-color: var(--bg-white-100);
--el-radio-button-checked-border-color: var(--bg-black-10);
border-radius: 4px;
}
.el-radio-button.is-active {
--el-radio-button-checked-text-color: var(--color-primary-100);
--el-radio-button-checked-bg-color: var(--color-primary-10);
--el-radio-button-checked-border-color: var(--color-primary-5);
border-radius: 4px;
}
}
\ No newline at end of file
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'; import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { getEnterpriseBranch, getEnterpriseKeyPerson } from '@/api/companyPages'; import { getEnterpriseBranch, getEnterpriseKeyPerson } from '@/api/companyPages';
import PersonAvatar from '@/components/base/people/personAvatar.vue'; import PersonAvatar from '@/components/base/people/PersonAvatar.vue';
import { ElDescriptions, ElDescriptionsItem, ElDivider, ElImage, ElSpace } from 'element-plus'; import { ElDescriptions, ElDescriptionsItem, ElDivider, ElImage, ElSpace } from 'element-plus';
import '@/styles/descriptions.scss' import '@/styles/descriptions.scss'
......
<template>
<sanctions-situation :enterprise-info="enterpriseInfo" :line-data="lineData"></sanctions-situation>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getNetProfitList, getPersonnelList, getRevenueList } from '@/api/companyPages';
import SanctionsSituation, { LineDataItem } from './SanctionsSituation.vue';
import { l } from 'vite/dist/node/types.d-aGj9QkWt';
// 定义组件属性
const props = defineProps({
enterpriseInfo: {
type: Object,
default: {}
}
});
const lineData = ref<LineDataItem[]>([])
onMounted(async () => {
await intData()
})
async function intData() {
let { data: revenue } = await getRevenueList(props.enterpriseInfo.id)
revenue = revenue?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.value,
type: '营收',
unit: item.unit ?? '亿元'
})) ?? []
let { data: netProfit } = await getNetProfitList(props.enterpriseInfo.id)
netProfit = netProfit?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.value,
type: '净利润',
unit: item.unit ?? '亿元'
})) ?? []
let { data: personnel } = await getPersonnelList(props.enterpriseInfo.id)
personnel = personnel?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.value,
type: '人员',
unit: item.unit ?? '亿元'
})) ?? []
lineData.value = [...revenue, ...netProfit, ...personnel]
}
</script>
\ No newline at end of file
<template>
<sanctions-situation :enterprise-info="enterpriseInfo" :line-data="lineData"></sanctions-situation>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getMarketShareList } from '@/api/companyPages';
import SanctionsSituation, { LineDataItem } from './SanctionsSituation.vue';
// 定义组件属性
const props = defineProps({
enterpriseInfo: {
type: Object,
default: {}
}
});
const lineData = ref<LineDataItem[]>([])
onMounted(async () => {
await intData()
})
async function intData() {
const { data } = await getMarketShareList(props.enterpriseInfo.id)
lineData.value = data?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.value,
unit: item.unit ?? '亿元'
})) ?? []
}
</script>
\ No newline at end of file
<template>
<sanctions-situation :enterprise-info="enterpriseInfo" :line-data="lineData"></sanctions-situation>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getMarketCapList } from '@/api/companyPages';
import SanctionsSituation, { LineDataItem } from './SanctionsSituation.vue';
// 定义组件属性
const props = defineProps({
enterpriseInfo: {
type: Object,
default: {}
}
});
const lineData = ref<LineDataItem[]>([])
onMounted(async () => {
await intData()
})
async function intData() {
const { data } = await getMarketCapList(props.enterpriseInfo.id)
lineData.value = data?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.value,
type: '市值变化',
unit: item.unit ?? '亿元'
})) ?? []
}
</script>
\ No newline at end of file
<template>
<sanctions-situation :enterprise-info="enterpriseInfo" :line-data="lineData"></sanctions-situation>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getMarketCapList, getStudyList } from '@/api/companyPages';
import SanctionsSituation, { LineDataItem } from './SanctionsSituation.vue';
// 定义组件属性
const props = defineProps({
enterpriseInfo: {
type: Object,
default: {}
}
});
const lineData = ref<LineDataItem[]>([])
onMounted(async () => {
await intData()
})
async function intData() {
const { data } = await getStudyList(props.enterpriseInfo.id)
lineData.value = data?.map(item => ({
time: new Date(item.year, 1, 1),
value: item.currentValue,
type: item.type,
unit: item.unit ?? '亿元'
})) ?? []
}
</script>
\ No newline at end of file
<script setup> <script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'; import { ref, onMounted, onUnmounted, watch } from 'vue';
import { ElSpace, ElRadioGroup, ElRadio, ElRadioButton } from 'element-plus'; import { ElRadioGroup, ElRadioButton } from 'element-plus';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { getStudyList, getSanctionList } from '@/api/companyPages'; import { getSanctionList } from '@/api/companyPages';
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'; import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue';
import AiTipPane from '@/components/base/panes/AiTipPane.vue' import AiTipPane from '@/components/base/panes/AiTipPane.vue'
export interface LineDataItem {
time: Date;
value: number;
type: string;
unit: string;
}
// 定义组件属性 // 定义组件属性
const props = defineProps({ const props = defineProps({
enterpriseInfo: { enterpriseInfo: {
type: Object, type: Object,
default: {} default: {}
},
lineData: {
type: Array<LineDataItem>,
default: () => []
} }
}); });
const studyList = ref([])
const sanctionList = ref([]) const sanctionList = ref([])
const studyTypes = ref([]) const lineTypes = ref([])
const studyType = ref() const lineType = ref()
const chartDom = ref() const chartDom = ref()
let myChart = null let myChart = null
...@@ -28,24 +38,26 @@ onMounted(async () => { ...@@ -28,24 +38,26 @@ onMounted(async () => {
}) })
onUnmounted(() => myChart?.dispose()) onUnmounted(() => myChart?.dispose())
watch(() => [props.lineData, sanctionList.value], async () => {
lineTypes.value = [...new Set(props.lineData.map(t => t.type))]
if (lineTypes.value.length > 0) {
lineType.value = lineTypes.value[0]
}
updateCharts(lineType.value, props.lineData, sanctionList.value)
})
async function intData() { async function intData() {
const { data } = await getStudyList(props.enterpriseInfo.id) const { data } = await getSanctionList(props.enterpriseInfo.id)
studyList.value = data ?? [] sanctionList.value = data ?? []
data.forEach(t => t.time = new Date(t.year, 1, 1))
studyTypes.value = [...new Set(data.map(t => t.type))]
const { data: sanctionData } = await getSanctionList(props.enterpriseInfo.id)
sanctionList.value = sanctionData ?? []
sanctionList.value.forEach(t => t.time = new Date(t.sanctionDate)) sanctionList.value.forEach(t => t.time = new Date(t.sanctionDate))
if (studyTypes.value.length > 0) {
studyType.value = studyTypes.value[0]
}
updateCharts(studyType.value, studyList.value, sanctionList.value)
} }
// 辅助函数:获取制裁年份对应的Y值 // 辅助函数:获取制裁年份对应的Y值
function getSanctionYValue(sanctionDate, filteredList, defaultYValue) { function getSanctionYValue(sanctionDate: Date, filteredList: LineDataItem[], defaultYValue: number) {
const year = sanctionDate.getFullYear() const year = sanctionDate.getFullYear()
const yearData = filteredList.find(d => d.year === year) const yearData = filteredList.find(d => d.time.getFullYear() === year)
return yearData ? yearData.currentValue : defaultYValue return yearData ? yearData.value : defaultYValue
} }
// 辅助函数:格式化文本内容,实现智能换行(考虑中英文混合) // 辅助函数:格式化文本内容,实现智能换行(考虑中英文混合)
...@@ -77,7 +89,95 @@ function formatContent(content, maxWidth = 26) { ...@@ -77,7 +89,95 @@ function formatContent(content, maxWidth = 26) {
return lines.join('\n') return lines.join('\n')
} }
function updateCharts(type, dataStudy, dataSanction) {
// 辅助函数:获取折线图数据
function getLineSeries(filteredList: LineDataItem[]) {
return {
type: 'line',
//从上到下填充颜色
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(128, 181, 255, 0.8)'
},
{
offset: 1,
color: 'rgba(128, 181, 255, 0)'
}
])
},
color: '#80B5FF',
data: filteredList.map(t => [t.time, t.value])
}
}
// 辅助函数:获取散点图数据
function getScatterSeries(filteredList: LineDataItem[], dataSanction: LineDataItem[], yValue: number) {
// 计算散点图数据
const xyData = dataSanction.map(t => {
const currentYValue = getSanctionYValue(t.time, filteredList, yValue) + yValue / 3
return [t.time, currentYValue, t]
})
return {
type: 'scatter',
tooltip: { show: false },
data: xyData,
// 绘制垂直虚线
markLine: {
lineStyle: {
color: '#ff4d4f',
type: 'dashed',
width: 1
},
data: xyData.map(t => {
return [{
coord: [t[0], 0],
symbol: 'none'
}, {
coord: [t[0], t[1]],
symbol: 'none',
}]
})
},
coordinateSystem: 'cartesian2d',
symbolSize: 1,
// 散点图标签
label: {
show: true,
position: 'insideBottomLeft',
formatter: function (params) {
const title = params.data[2].sanctionDate;
const content = params.data[2].content;
const formattedContent = formatContent(content);
return `{title|${title}}\n{content|${formattedContent}}`;
},
rich: {
title: {
fontSize: 16,
lineHeight: 22,
color: '#CE4F51'
},
content: {
fontSize: 15,
fontWeight: 'bold',
width: 200, // 限制宽度
color: '#CE4F51',
lineHeight: 22,
overflow: 'break' // 超出宽度自动换行
}
},
backgroundColor: 'rgba(255, 241, 240, 1)',
borderColor: 'rgba(255, 204, 199, 1)',
borderRadius: 4,
padding: [8, 12],
distance: -1
}
}
}
// 辅助函数:更新图表数据
function updateCharts(type: string, dataStudy: LineDataItem[], dataSanction: LineDataItem[]) {
const filteredList = dataStudy.filter(t => t.type === type) const filteredList = dataStudy.filter(t => t.type === type)
if (!filteredList.length) return if (!filteredList.length) return
...@@ -91,13 +191,15 @@ function updateCharts(type, dataStudy, dataSanction) { ...@@ -91,13 +191,15 @@ function updateCharts(type, dataStudy, dataSanction) {
// y轴单位 // y轴单位
const unit = filteredList[0].unit const unit = filteredList[0].unit
const yValue = Math.max(...filteredList.map(d => d.currentValue)) const yValue = Math.max(...filteredList.map(d => d.value))
// 计算x轴范围,扩大活动空间 // 计算x轴范围,扩大活动空间
const allDates = [ const allDates = [
...filteredList.map(t => t.time), ...filteredList.map(t => t.time.getTime()),
...dataSanction.map(t => t.time) ...dataSanction.map(t => t.time.getTime())
] ]
// 计算x轴范围,扩大活动空间
const minDate = new Date(Math.min(...allDates)) const minDate = new Date(Math.min(...allDates))
const maxDate = new Date(Math.max(...allDates)) const maxDate = new Date(Math.max(...allDates))
...@@ -144,104 +246,38 @@ function updateCharts(type, dataStudy, dataSanction) { ...@@ -144,104 +246,38 @@ function updateCharts(type, dataStudy, dataSanction) {
nameLocation: 'end' nameLocation: 'end'
}, },
series: [ series: [
{ getScatterSeries(filteredList, dataSanction, yValue),
type: 'line', getLineSeries(filteredList),
//从上到下填充颜色 ]
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(128, 181, 255, 0.8)'
},
{
offset: 1,
color: 'rgba(128, 181, 255, 0)'
}
])
},
data: filteredList.map(t => [t.time, t.currentValue])
},
{
type: 'scatter',
tooltip: { show: false },
data: dataSanction.map(t => {
const currentYValue = getSanctionYValue(t.time, filteredList, yValue) + yValue / 3
return [t.time, currentYValue, t]
}),
markLine: {
lineStyle: {
color: '#ff4d4f',
type: 'dashed',
width: 1
},
data: dataSanction.map(t => {
const currentYValue = getSanctionYValue(t.time, filteredList, yValue) + yValue / 3
return [{
coord: [t.time, 0],
symbol: 'none'
}, {
coord: [t.time, currentYValue],
symbol: 'none',
}]
})
},
coordinateSystem: 'cartesian2d',
symbolSize: 1,
label: {
show: true,
position: 'insideBottomLeft',
formatter: function (params) {
const title = params.data[2].sanctionDate;
const content = params.data[2].content;
const formattedContent = formatContent(content);
return `{title|${title}}\n{content|${formattedContent}}`;
},
rich: {
title: {
fontSize: 16,
lineHeight: 22,
color: '#CE4F51'
},
content: {
fontSize: 15,
fontWeight: 'bold',
width: 200, // 限制宽度
color: '#CE4F51',
lineHeight: 22,
overflow: 'break' // 超出宽度自动换行
}
},
backgroundColor: 'rgba(255, 241, 240, 1)',
borderColor: 'rgba(255, 204, 199, 1)',
borderRadius: 4,
padding: [8, 12],
distance: -1
}
}
],
}) })
} }
// 辅助函数:处理研究类型改变事件
function handleStudyTypesChange() { function handleStudyTypesChange() {
updateCharts(studyType.value, studyList.value, sanctionList.value) updateCharts(lineType.value, props.lineData, sanctionList.value)
} }
</script> </script>
<template> <template>
<analysis-box title="被制裁时间轴"> <analysis-box title="被制裁时间轴">
<template v-slot:header-btn> <template v-if="lineTypes.length > 1" v-slot:header-btn>
<el-radio-group v-model="studyType" @change="handleStudyTypesChange"> <el-radio-group class="radio-group-as-gap-btn" v-model="lineType" @change="handleStudyTypesChange">
<el-radio-button v-for="item in studyTypes" :key="item" :label="item">{{ item }}</el-radio-button> <el-radio-button v-for="item in lineTypes" :key="item" :label="item">{{ item }}</el-radio-button>
</el-radio-group> </el-radio-group>
</template> </template>
<div class="flex-display content-box"> <div class="flex-display content-box">
<div ref="chartDom" class="chart-container"></div> <div ref="chartDom" class="chart-container"></div>
<ai-tip-pane>123</ai-tip-pane> <ai-tip-pane
v-if="enterpriseInfo.id === '914403001922038216'">近年来,华为遭遇了严峻的外部技术封锁,其核心源于某些国家的持续制裁。这主要包括被列入“实体清单”,禁止其未经许可从美国公司获取芯片等关键技术;以及遭受更严格的“外国直接产品规则”打击,旨在切断其利用美国工具、软件和技术设计或制造先进芯片的渠道。这些措施不仅限制了华为自身产品的设计与生产,也影响了其全球供应链,使其在获取先进半导体、移动操作系统生态(如GMS)及基础软件工具等方面面临巨大挑战,意图从根本上遏制其技术发展。</ai-tip-pane>
</div> </div>
</analysis-box> </analysis-box>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@use '@/styles/radio.scss';
.content-box { .content-box {
padding: 10px; padding: 10px;
gap: 10px; gap: 10px;
......
<script setup lang="ts"> <script setup lang="ts">
import '@/styles/tabs.scss' import '@/styles/tabs.scss'
import { ElTabPane, ElTabs } from 'element-plus'; import { ElTabPane, ElTabs } from 'element-plus';
import SanctionsSituation from './SanctionsSituation.vue'; import EnterpriseScale from './EnterpriseScale.vue';
import MarketShare from './MarketShare.vue';
import MarketValue from './MarketValue.vue';
import ResearchAndDevelopment from './ResearchAndDevelopment.vue';
// 定义组件属性 // 定义组件属性
const props = defineProps({ const props = defineProps({
...@@ -15,12 +18,18 @@ const props = defineProps({ ...@@ -15,12 +18,18 @@ const props = defineProps({
<template> <template>
<div style="overflow: visible;"> <div style="overflow: visible;">
<el-tabs tabPosition="left" class="disinheritance tabs-nav-no-wrap left-float-nav-tabs"> <el-tabs tabPosition="left" class="disinheritance tabs-nav-no-wrap left-float-nav-tabs">
<el-tab-pane label="企业规模"> <el-tab-pane label="企业规模" lazy>
<sanctions-situation :enterprise-info="enterpriseInfo"></sanctions-situation> <enterprise-scale :enterprise-info="enterpriseInfo"></enterprise-scale>
</el-tab-pane>
<el-tab-pane label="市值变化" lazy>
<market-value :enterprise-info="enterpriseInfo"></market-value>
</el-tab-pane>
<el-tab-pane label="研发投入" lazy>
<research-and-development :enterprise-info="enterpriseInfo"></research-and-development>
</el-tab-pane>
<el-tab-pane label="市场占比" lazy>
<market-share :enterprise-info="enterpriseInfo"></market-share>
</el-tab-pane> </el-tab-pane>
<!-- <el-tab-pane label="市值变化"></el-tab-pane>
<el-tab-pane label="研发投入"></el-tab-pane>
<el-tab-pane label="市场占比"></el-tab-pane> -->
</el-tabs> </el-tabs>
</div> </div>
</template> </template>
\ No newline at end of file
...@@ -30,7 +30,7 @@ import { getSupplyAreaList } from '@/api/companyPages'; ...@@ -30,7 +30,7 @@ import { getSupplyAreaList } from '@/api/companyPages';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { ElSpace } from 'element-plus'; import { ElSpace } from 'element-plus';
// 导入自定义组件 // 导入自定义组件
import CapitalScale from './capitalScale.vue' import CapitalScale from './CapitalScale.vue'
// 响应式数据 // 响应式数据
const areas = ref([]) // 供应区域列表 const areas = ref([]) // 供应区域列表
......
...@@ -29,10 +29,10 @@ ...@@ -29,10 +29,10 @@
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { getEnterprisePageInfo } from '@/api/companyPages'; import { getEnterprisePageInfo } from '@/api/companyPages';
import TitlePane from './component/titlePane.vue'; import TitlePane from './component/TitlePane.vue';
import NewsPane from './component/detailsPages/newsPane.vue'; import NewsPane from './component/DetailsPages/NewsPane.vue';
import BaseInfo from './component/detailsPages/baseInfo.vue'; import BaseInfo from './component/DetailsPages/BaseInfo.vue';
import OperatingPages from './component/operatingPages/index.vue'; import OperatingPages from './component/OperatingPages/index.vue';
import '@/styles/tabs.scss' import '@/styles/tabs.scss'
import '@/styles/container.scss' import '@/styles/container.scss'
import { ElScrollbar, ElSpace, ElTabs, ElTabPane } from 'element-plus'; import { ElScrollbar, ElSpace, ElTabs, ElTabPane } from 'element-plus';
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论