| | |
| | | list = '/api/robin/list', |
| | | } |
| | | |
| | | const tabPinyinMap: Record<string, string> = { |
| | | deepseek: 'deepseek', |
| | | 豆包: 'doubao', |
| | | 文小言: 'wenxiaoyan', |
| | | 元宝: 'yuanbao', |
| | | 通义: 'tongyi', |
| | | kimi: 'kimi', |
| | | }; |
| | | |
| | | export const normalizeLLMKey = (tab: string): string => { |
| | | const key = (tab || '').trim(); |
| | | if (!key) return ''; |
| | | return tabPinyinMap[key] || key.replace(/\s+/g, '').toLowerCase(); |
| | | }; |
| | | |
| | | /** |
| | | * 获取robin列表数据 |
| | | * @param params 请求参数 |
| | |
| | | sKeyword: string; |
| | | semanticWordId?: string; |
| | | } = { |
| | | sFromLLM: tab, |
| | | sFromLLM: normalizeLLMKey(tab), |
| | | sBookMarkName: bookmarkName, |
| | | sKeyword: semanticWord |
| | | }; |
| | |
| | | sKeyword: string; |
| | | semanticWordId?: string; |
| | | } = { |
| | | sFromLLM: tab, |
| | | sFromLLM: normalizeLLMKey(tab), |
| | | sBookMarkName: bookmarkName, |
| | | sKeyword: semanticWord |
| | | }; |
| | |
| | | : []; |
| | | |
| | | // 获取前五个 result 项的数据 |
| | | const chartDataArray = []; |
| | | const chartDataArray: Array<{ xAxis: string[]; series: Array<number | null> }> = []; |
| | | for (let i = 0; i < 5; i++) { |
| | | const resultItem = resultList[i]; |
| | | |
| | |
| | | export const getBrandChartData = async (tab: string) => { |
| | | try { |
| | | const response = await getRobinList({ |
| | | sFromLLM: tab, |
| | | sFromLLM: normalizeLLMKey(tab), |
| | | sBookMarkName: '吾德研究', |
| | | sKeyword: '品牌力第一证明机构' |
| | | }); |
| | |
| | | label: "客户名称", |
| | | field: "customerName", |
| | | component: 'Input', |
| | | componentProps: { |
| | | style: { width: '150px' } |
| | | }, |
| | | colProps: { style: { marginRight: '-150px' } }, |
| | | }, |
| | | { |
| | | label: "合作月", |
| | |
| | | placeholder: '请输入合作月(正整数)', |
| | | style: { width: '150px' } |
| | | }, |
| | | colProps: { style: { position: 'relative', right: '10px' } }, |
| | | colProps: { style: { marginRight: '-150px' } }, |
| | | }, |
| | | { |
| | | label: "加急类型", |
| | |
| | | ], |
| | | style: { width: '150px'} |
| | | }, |
| | | colProps: { style: { position: 'relative', right: '110px' } }, |
| | | colProps: { style: { marginRight: '-100px' } }, |
| | | }, |
| | | // { |
| | | // label: "审核状态", |
| | |
| | | series: [1, 1, 2, 2, 2, 4, 1], |
| | | }; |
| | | |
| | | const clampedSeries = chartData.series.map((value) => { |
| | | if (typeof value !== 'number') return value; |
| | | return value > 8 ? 8 : value; |
| | | }); |
| | | |
| | | const option = { |
| | | xAxis: { |
| | | type: 'category', |
| | |
| | | yAxis: { |
| | | type: 'value', |
| | | min: 1, |
| | | max: 6, |
| | | max: 8, |
| | | interval: 1, // y 轴刻度间隔 |
| | | inverse: true, // y轴从高到低排列 |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '一周内名次概览', |
| | | data: chartData.series, |
| | | data: clampedSeries, |
| | | type: 'line', |
| | | color: '#ff0000', // 红色直线 |
| | | smooth: false, // 直线,不使用平滑曲线 |
| | |
| | | |
| | | <div class="stats-group"> |
| | | <div class="stat-item"> |
| | | <div class="stat-value">{{ formatValue(cardStats.signRank) }}</div> |
| | | <div class="stat-value">{{ formatValue(cardStats.signRank === 4 ? '保展现' : cardStats.signRank) }}</div> |
| | | <div class="stat-label">签约排名</div> |
| | | </div> |
| | | <div class="stat-item"> |
| | |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, onMounted, watch, computed } from 'vue'; |
| | | import { getRobinList } from '/@/api/demo/robin.api'; |
| | | import { getRobinList, normalizeLLMKey } from '/@/api/demo/robin.api'; |
| | | |
| | | // 定义props |
| | | const props = defineProps<{ |
| | |
| | | outWord?: string; // 露出词,由父组件传递 |
| | | semanticWordId?: string; // 语义词ID |
| | | cardIndex?: number; // 卡片索引,用于区分显示哪个test字段 |
| | | llmTab?: string; // 当前选择的大模型 |
| | | contractPeriod: string; // 签约期限 |
| | | signRank: number; // 签约排名 |
| | | avgRank: number; // 平均排名 |
| | |
| | | |
| | | try { |
| | | const params = { |
| | | sFromLLM: 'deepseek', // 默认使用deepseek |
| | | sFromLLM: normalizeLLMKey(props.llmTab || 'deepseek'), // 根据当前模型映射 |
| | | sBookMarkName: currentOutWord.value, // 使用传入的露出词 |
| | | sKeyword: props.word || '品牌力第一证明机构', // 使用传入的语义词或默认值 |
| | | semanticWordId: props.semanticWordId, |
| | |
| | | |
| | | // 监听props.word和props.outWord的变化 |
| | | watch( |
| | | () => [props.word, props.outWord, props.semanticWordId], |
| | | () => [props.word, props.outWord, props.semanticWordId, props.llmTab], |
| | | ([newWord]) => { |
| | | if (props.queryEnabled && newWord) { |
| | | // 当语义词或露出词变化时,重新获取robin数据 |
| | |
| | | <div class="card-item" v-for="(cardData, index) in cardDataList" :key="index"> |
| | | <div class="card-content"> |
| | | <StatsCard |
| | | :word="isLoadingCards ? '' : selectedSemanticWord" |
| | | :word="isLoadingCards ? '' : (selectedSemanticWord || '')" |
| | | :semantic-word-id="isLoadingCards ? '' : selectedSemanticWordId" |
| | | :out-word="hasQueried ? currentOutWord : ''" |
| | | :card-index="index" |
| | | :llm-tab="currentTab" |
| | | :contract-period="isLoadingCards ? '' : cardData.contractPeriod" |
| | | :sign-rank="cardData.signRank" |
| | | :avg-rank="cardData.avgRank" |
| | |
| | | /> |
| | | <RankingChart |
| | | :chart-data="cardData.chartData" |
| | | :selected-semantic-word="isLoadingCards ? '' : selectedSemanticWord" |
| | | :selected-semantic-word="isLoadingCards ? '' : (selectedSemanticWord || '')" |
| | | :query-enabled="hasQueried" |
| | | :out-word="hasQueried ? currentOutWord : ''" |
| | | /> |
| | |
| | | }> |
| | | >([]); |
| | | |
| | | type ChartData = { xAxis: string[]; series: Array<number | null> }; |
| | | type CardData = { |
| | | contractPeriod: string; |
| | | signRank: number; |
| | | avgRank: number; |
| | | onlineDate: string; |
| | | acceptIndicator: number; |
| | | achievedRate: number; |
| | | chartData: ChartData; |
| | | }; |
| | | |
| | | // 卡片数据列表 |
| | | const cardDataList = ref([ |
| | | const cardDataList = ref<CardData[]>([ |
| | | { |
| | | contractPeriod: '2025-10-08-2026-10-07', |
| | | signRank: 2, |
| | |
| | | if (!currentOutWord.value) { |
| | | currentOutWord.value = await getOutWordBySemanticWord(selectedSemanticWord.value); |
| | | } |
| | | const dataArray = await fetchAllChartData(tab, selectedSemanticWord.value, selectedSemanticWordId.value); |
| | | const dataArray = (await fetchAllChartData( |
| | | tab, |
| | | selectedSemanticWord.value, |
| | | selectedSemanticWordId.value |
| | | )) as ChartData[]; |
| | | // 为每个卡片分配对应的图表数据 |
| | | cardDataList.value.forEach((card, index) => { |
| | | if (dataArray[index]) { |
| | |
| | | // 等待下一个tick,确保props已经更新 |
| | | await nextTick(); |
| | | |
| | | const dataArray = await fetchAllChartData(currentTab.value, selectedSemanticWord.value, selectedSemanticWordId.value); |
| | | const dataArray = (await fetchAllChartData( |
| | | currentTab.value, |
| | | selectedSemanticWord.value, |
| | | selectedSemanticWordId.value |
| | | )) as ChartData[]; |
| | | // 为每个卡片分配对应的图表数据 |
| | | cardDataList.value.forEach((card, index) => { |
| | | if (dataArray[index]) { |
| | |
| | | <div class="card-item" v-for="(cardData, index) in cardDataList" :key="index"> |
| | | <div class="card-content"> |
| | | <StatsCard |
| | | :word="isLoadingCards ? '' : selectedSemanticWord" |
| | | :word="isLoadingCards ? '' : (selectedSemanticWord || '')" |
| | | :semantic-word-id="isLoadingCards ? '' : selectedSemanticWordId" |
| | | :out-word="hasQueried ? currentOutWord : ''" |
| | | :card-index="index" |
| | | :llm-tab="currentTab" |
| | | :contract-period="isLoadingCards ? '' : cardData.contractPeriod" |
| | | :sign-rank="cardData.signRank" |
| | | :avg-rank="cardData.avgRank" |
| | |
| | | /> |
| | | <RankingChart |
| | | :chart-data="cardData.chartData" |
| | | :selected-semantic-word="isLoadingCards ? '' : selectedSemanticWord" |
| | | :selected-semantic-word="isLoadingCards ? '' : (selectedSemanticWord || '')" |
| | | :query-enabled="hasQueried" |
| | | :out-word="hasQueried ? currentOutWord : ''" |
| | | /> |
| | |
| | | <a-descriptions-item v-for="word in semanticWords" :key="word.id" :label="`语义词 ${semanticWords.indexOf(word) + 1}`"> |
| | | <div style="display: flex; flex-direction: column; gap: 12px; width: 100%"> |
| | | <div style="display: flex; gap: 16px; width: 100%"> |
| | | <span style="flex: 1;"><strong>类型:</strong>{{ word.category === '1' ? '品牌' : word.category === '2' ? '地区词' : '全国词' }}</span> |
| | | <span style="flex: 1;"><strong>语义词:</strong>{{ word.word || '-' }}</span> |
| | | <span style="flex: 1;"><strong>露出词:</strong>{{ word.outWord || '-' }}</span> |
| | | <span style="flex: 1;"><strong>价格:</strong>{{ word.price ? `${formatPrice(word.price)}元` : '-' }}</span> |
| | | <span style="flex: 1;"><strong>合作周期:</strong>{{ word.month ? `${word.month}月` : '-' }}</span> |
| | | <span style="flex: 1;"><strong>签约排名:</strong>{{ formatRanking(word.ranking) }}</span> |
| | | <span style="flex: 1;"><strong>验收指标(>=):</strong>{{ word.acceptindicator ? `${word.acceptindicator}%` : '-' }}</span> |
| | | </div> |
| | | </div> |
| | | </a-descriptions-item> |
| | |
| | | <a-descriptions-item v-for="word in semanticWords" :key="word.id" :label="`语义词 ${semanticWords.indexOf(word) + 1}`"> |
| | | <div style="display: flex; flex-direction: column; gap: 12px; width: 100%"> |
| | | <div style="display: flex; gap: 16px; width: 100%"> |
| | | <span style="flex: 1;"><strong>类型:</strong>{{ word.category === '1' ? '品牌' : word.category === '2' ? '地区词' : '全国词' }}</span> |
| | | <span style="flex: 1;"><strong>语义词:</strong>{{ word.word || '-' }}</span> |
| | | <span style="flex: 1;"><strong>露出词:</strong>{{ word.outWord || '-' }}</span> |
| | | <span style="flex: 1;"><strong>价格:</strong>{{ word.price ? `${formatPrice(word.price)}元` : '-' }}</span> |
| | | <span style="flex: 1;"><strong>合作周期:</strong>{{ word.month ? `${word.month}月` : '-' }}</span> |
| | | <span style="flex: 1;"><strong>签约排名:</strong>{{ formatRanking(word.ranking) }}</span> |
| | | <span style="flex: 1;"><strong>验收指标(>=):</strong>{{ word.acceptindicator ? `${word.acceptindicator}%` : '-' }}</span> |
| | | </div> |
| | | </div> |
| | | </a-descriptions-item> |
| | |
| | | @confirm="handleDeleteExistingFile(file, 'contract')" |
| | | > |
| | | <a-button type="link" danger size="small"> |
| | | <template #icon> |
| | | <delete-outlined /> |
| | | </template> |
| | | 删除 |
| | | <delete-outlined /> |
| | | </a-button> |
| | | </a-popconfirm> |
| | | </div> |
| | |
| | | @confirm="handleDeleteExistingFile(file, 'attachment')" |
| | | > |
| | | <a-button type="link" danger size="small"> |
| | | <template #icon> |
| | | <delete-outlined /> |
| | | </template> |
| | | 删除 |
| | | <delete-outlined /> |
| | | </a-button> |
| | | </a-popconfirm> |
| | | </div> |
| | |
| | | <a-radio :value="1">1</a-radio> |
| | | <a-radio :value="2">2</a-radio> |
| | | <a-radio :value="3">3</a-radio> |
| | | <a-radio value="保展现不保排名">保展现不保排名</a-radio> |
| | | <a-radio value="4">保展现不保排名</a-radio> |
| | | </a-radio-group> |
| | | 验收指标: |
| | | 验收指标(>=): |
| | | <a-input-number |
| | | v-model:value="word.acceptindicator" |
| | | placeholder="请输入验收指标" |
| | |
| | | @confirm="handleDeleteExistingFile(file, 'contract')" |
| | | > |
| | | <a-button type="link" danger size="small"> |
| | | <template #icon> |
| | | <delete-outlined /> |
| | | </template> |
| | | 删除 |
| | | <delete-outlined /> |
| | | </a-button> |
| | | </a-popconfirm> |
| | | </div> |
| | |
| | | @confirm="handleDeleteExistingFile(file, 'attachment')" |
| | | > |
| | | <a-button type="link" danger size="small"> |
| | | <template #icon> |
| | | <delete-outlined /> |
| | | </template> |
| | | 删除 |
| | | <delete-outlined /> |
| | | </a-button> |
| | | </a-popconfirm> |
| | | </div> |
| | |
| | | <a-radio :value="1">1</a-radio> |
| | | <a-radio :value="2">2</a-radio> |
| | | <a-radio :value="3">3</a-radio> |
| | | <a-radio value="保展现不保排名">保展现不保排名</a-radio> |
| | | <a-radio :value="4">保展现不保排名</a-radio> |
| | | </a-radio-group> |
| | | 验收指标: |
| | | 验收指标(>=): |
| | | <a-input-number |
| | | v-model:value="word.acceptindicator" |
| | | placeholder="请输入验收指标" |
| | |
| | | <a-radio :value="1">1</a-radio> |
| | | <a-radio :value="2">2</a-radio> |
| | | <a-radio :value="3">3</a-radio> |
| | | <a-radio value="保展现不保排名">保展现不保排名</a-radio> |
| | | <a-radio :value="4">保展现不保排名</a-radio> |
| | | </a-radio-group> |
| | | 验收指标: |
| | | 验收指标(>=): |
| | | <a-input-number |
| | | v-model:value="word.acceptindicator" |
| | | placeholder="请输入验收指标" |
| | |
| | | </template> |
| | | <!-- 验收指标列标题 --> |
| | | <template #acceptIndicatorTitle> |
| | | <span><span style="color: red">*</span> 验收指标</span> |
| | | <span><span style="color: red">*</span> 验收指标(>=)</span> |
| | | </template> |
| | | <!-- 验收指标列 --> |
| | | <template #acceptIndicator="{ record }"> |
| | |
| | | component: 'Input', |
| | | label: '合作周期', |
| | | required: true, |
| | | defaultValue: 3, |
| | | render: ({ model, field }) => { |
| | | return h(Input, { |
| | | placeholder: '请输入正整数', |
| | |
| | | </template> |
| | | <!-- 验收指标列标题 --> |
| | | <template #acceptIndicatorTitle> |
| | | <span><span style="color: red">*</span> 验收指标</span> |
| | | <span><span style="color: red">*</span> 验收指标(>=)</span> |
| | | </template> |
| | | <!-- 验收指标列 --> |
| | | <template #acceptIndicator="{ record }"> |