洞察报告;
This commit is contained in:
@@ -22,7 +22,7 @@ npm config set registry %customizeRegistry% && ^
|
||||
echo ====clear npm cache==== && ^
|
||||
npm cache clear --force && ^
|
||||
echo ====install dependency package==== && ^
|
||||
npm i --force && ^
|
||||
npm ci --force && ^
|
||||
echo ====finished install, switch back to original npm registry==== && ^
|
||||
npm config set registry %originalRegistry% && ^
|
||||
echo ====finish and exit==== && ^
|
||||
|
||||
@@ -38,6 +38,7 @@ function json_to_array(key, jsonData) {
|
||||
* @param data Array,表体数据
|
||||
* @param key Array,字段名
|
||||
* @param title String,标题(会居中显示),即excel表格第一行
|
||||
* @param sheetName
|
||||
* @param filename String,文件名
|
||||
* @param autoWidth Boolean,是否自动根据key自定义列宽度
|
||||
*/
|
||||
@@ -46,6 +47,7 @@ export const exportJsonToExcel = ({
|
||||
data,
|
||||
key,
|
||||
title,
|
||||
sheetName,
|
||||
filename,
|
||||
autoWidth
|
||||
}) => {
|
||||
@@ -64,7 +66,7 @@ export const exportJsonToExcel = ({
|
||||
const arr = json_to_array(key, data)
|
||||
auto_width(ws, arr)
|
||||
}
|
||||
XLSX.utils.book_append_sheet(wb, ws, filename)
|
||||
XLSX.utils.book_append_sheet(wb, ws, sheetName)
|
||||
XLSX.writeFile(wb, filename + '.xlsx')
|
||||
}
|
||||
export default {
|
||||
|
||||
@@ -5,6 +5,7 @@ import TemplateMarket from './route.templateMarket'
|
||||
import Contact from './route.contact'
|
||||
import DocumentLibrary from './route.documentLibrary'
|
||||
import DataStatistics from './route.datastatistics'
|
||||
import SharedRoutes from './route.shared'
|
||||
import { jsonp } from "vue-jsonp";
|
||||
import { jsonpUrl } from "../config.js";
|
||||
import { useStore } from "vuex";
|
||||
@@ -24,6 +25,7 @@ const constantRoutes = [
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
...SharedRoutes,
|
||||
{
|
||||
path: '/:catchAll(.*)',
|
||||
redirect: '/error/404',
|
||||
@@ -471,7 +473,7 @@ const router = createRouter({
|
||||
})
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// token
|
||||
if (!router.options.history.state.back) {
|
||||
if (!to.meta.shared && !router.options.history.state.back) {
|
||||
await getToken();
|
||||
}
|
||||
if (!to.meta.noRedirectLogin) {
|
||||
|
||||
20
src/router/route.shared.js
Normal file
20
src/router/route.shared.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const SharedIndex = () => import(/* webpackChunkName: 'shared' */ '@views/Shared/Index.vue')
|
||||
|
||||
const SharedInsightReport = () => import(/* webpackChunkName: 'shared' */ '@views/DataAnalyse/insight/SharedInsightReport.vue')
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/shared',
|
||||
name: 'Shared',
|
||||
meta: { title: '分享', shared: true },
|
||||
component: SharedIndex,
|
||||
children: [
|
||||
{
|
||||
path: 'insight/:secret',
|
||||
name: 'SharedInsight',
|
||||
component: SharedInsightReport
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
export default routes
|
||||
@@ -10,7 +10,7 @@ import InsightShare from './components/InsightShare.vue'
|
||||
import Report from './report/Report.vue'
|
||||
|
||||
|
||||
import { getInsightReport } from './api'
|
||||
import { getInsightReport, updateInsightReport } from './api'
|
||||
|
||||
|
||||
const route = useRoute()
|
||||
@@ -53,25 +53,53 @@ function onGenerateReport(isInit) {
|
||||
}
|
||||
}
|
||||
|
||||
function onConfirmGenerateReport(isInit) {
|
||||
async function onConfirmGenerateReport(isInit) {
|
||||
if(loading.value) {
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
|
||||
// todo
|
||||
const params = {}
|
||||
const data = await updateInsightReport(params)
|
||||
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="insight-page">
|
||||
<InsightEmpty v-if="false" @generate="onGenerateReport" />
|
||||
<a-spin :spinning="loading" tip="加载中" class="spinning">
|
||||
<div v-if="!loading" class="insight-page">
|
||||
<InsightEmpty v-if="!report?.id" @generate="onGenerateReport" />
|
||||
|
||||
<InsightShare @regenerate="onGenerateReport" />
|
||||
<template v-if="report?.id">
|
||||
<InsightShare :report="report" @regenerate="onGenerateReport" />
|
||||
|
||||
<Report :report="report" />
|
||||
</div>
|
||||
<Report :report="report" />
|
||||
</template>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.insight-page {
|
||||
.spinning {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
:deep(.ant-spin-text) {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.insight-page {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,100 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
|
||||
import SharedAccess from './components/SharedAccess.vue'
|
||||
import SharedExpired from './components/SharedExpired.vue'
|
||||
import Report from './report/Report.vue'
|
||||
|
||||
import { getInsightReportBySecret, getInsightReportInfoSecret } from './api'
|
||||
|
||||
document.title = '伊调研|问卷'
|
||||
|
||||
const route = useRoute()
|
||||
const secret = route.params.secret || ''
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const isExpired = ref(false) // 默认 false
|
||||
const isPassword = ref(true) // 默认 true
|
||||
|
||||
const accessRef = ref(null)
|
||||
|
||||
const report = ref({})
|
||||
|
||||
// getReportInfo()
|
||||
|
||||
async function getReportInfo() {
|
||||
if(loading.value) {
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
|
||||
const params = { secret }
|
||||
const data = await getInsightReportInfoSecret(params)
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function getReport(password) {
|
||||
if(loading.value) {
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
|
||||
const params = { password, secret }
|
||||
const data = await getInsightReportBySecret(params)
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div></div>
|
||||
<div class="shared-insight-report">
|
||||
<SharedExpired v-if="!isExpired" />
|
||||
<SharedAccess v-else-if="isPassword" ref="accessRef" @password="getReport" />
|
||||
|
||||
<div v-else class="wrapper">
|
||||
<Report :readonly="true" />
|
||||
</div>
|
||||
|
||||
<a-spin v-if="loading" :spinning="loading" tip="加载中" class="spinning" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.spinning {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
|
||||
:deep(.ant-spin-text) {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.shared-insight-report {
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
|
||||
/**
|
||||
* 新增/修改洞察报告详情
|
||||
* @param data
|
||||
* @returns {*}
|
||||
*/
|
||||
export function updateInsightReport(data) {
|
||||
return request({
|
||||
url: `/console/insightReport`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取洞察报告详情
|
||||
* @param params
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getInsightReport(params) {
|
||||
return request({
|
||||
url: `/console/insightReport/${ params.sn }`,
|
||||
@@ -11,3 +29,46 @@ export function getInsightReport(params) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 分享洞察报告
|
||||
* @param data
|
||||
* @returns {*}
|
||||
*/
|
||||
export function shareReport(data) {
|
||||
return request({
|
||||
url: `/console/shareReport`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取洞察报告基本信息
|
||||
* @param params
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getInsightReportInfoSecret(params) {
|
||||
return request({
|
||||
url: `/console/insightReport/${ params.sn }`,
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取洞察报告详情 secret
|
||||
* @param params
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getInsightReportBySecret(params) {
|
||||
return request({
|
||||
url: `/console/insightReport/${ params.sn }`,
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
<script setup>
|
||||
import { defineEmits, ref } from 'vue'
|
||||
import { defineEmits, defineProps, ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { InfoCircleOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
import { shareReport } from '../api'
|
||||
|
||||
|
||||
const emits = defineEmits(['regenerate'])
|
||||
const props = defineProps({
|
||||
report: { type: Object, default: () => Object.assign({}) }
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const editable = ref(false)
|
||||
const password = ref('') // 密码
|
||||
const expiredForDays = ref('0') // 有效期天数
|
||||
const password = ref(props.report.password || getRandomStr(4) || '') // 密码
|
||||
const expiredForDays = ref(+props.report.expireAt || 0) // 有效期天数
|
||||
|
||||
function getRandomStr(bit) {
|
||||
const random = '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPSDFGHJKLZXCVBNM'
|
||||
let str = ''
|
||||
for(let i = 0; i < bit; i += 1) {
|
||||
str += random[Math.floor(Math.random() * random.length)]
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
function onEdit(type) {
|
||||
if(loading.value) {
|
||||
@@ -24,8 +40,34 @@ function getPopupContainer(el) {
|
||||
return el?.parentNode || document.body
|
||||
}
|
||||
|
||||
function onCopy() {
|
||||
// todo
|
||||
async function onCopy() {
|
||||
if(loading.value) {
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
|
||||
const params = {
|
||||
id: props.report.id || '',
|
||||
passWord: password.value || '',
|
||||
expireDay: expiredForDays.value ?? 0
|
||||
}
|
||||
const data = await shareReport(params)
|
||||
console.log('data', data)
|
||||
|
||||
loading.value = false
|
||||
|
||||
|
||||
copyText()
|
||||
}
|
||||
|
||||
function copyText(text) {
|
||||
const input = document.createElement('input')
|
||||
input.value = text || ''
|
||||
document.body.appendChild(input)
|
||||
input.select()
|
||||
document.execCommand('Copy')
|
||||
document.body.removeChild(input)
|
||||
message.success('复制成功')
|
||||
}
|
||||
|
||||
function onUpdateReport() {
|
||||
@@ -65,10 +107,10 @@ function onUpdateReport() {
|
||||
<div class="row">
|
||||
<span class="label">链接有效期</span>
|
||||
<a-radio-group v-model:value="expiredForDays" :disabled="loading">
|
||||
<a-radio value="0">永久</a-radio>
|
||||
<a-radio value="30">30天</a-radio>
|
||||
<a-radio value="7">7天</a-radio>
|
||||
<a-radio value="1">1天</a-radio>
|
||||
<a-radio :value="0">永久</a-radio>
|
||||
<a-radio :value="30">30天</a-radio>
|
||||
<a-radio :value="7">7天</a-radio>
|
||||
<a-radio :value="1">1天</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div class="row flex-end">
|
||||
|
||||
102
src/views/DataAnalyse/insight/components/SharedAccess.vue
Normal file
102
src/views/DataAnalyse/insight/components/SharedAccess.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<script setup>
|
||||
import { defineEmits, defineExpose, reactive, ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
const emits = defineEmits(['password'])
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const formRef = ref(null)
|
||||
const formState = reactive({ password: '' })
|
||||
|
||||
const rules = {
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ pattern: /^[0-9a-zA-Z]{4,4}$/g, message: '请输入4位数字或字母', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
if(loading.value) {
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
|
||||
formRef.value.validate().then(() => {
|
||||
|
||||
emits('password', formState.password)
|
||||
|
||||
}).catch((error) => {
|
||||
loading.value = false
|
||||
message.warning(error.errorFields.map((err) => err.errors?.join?.('、')).join('、'))
|
||||
})
|
||||
}
|
||||
|
||||
function reset() {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
defineExpose({ reset })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="shared-access">
|
||||
<img src="../img/icon_lock.png" alt="" class="icon">
|
||||
|
||||
<a-form ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
layout="vertical"
|
||||
class="form">
|
||||
<a-form-item label="请输入密码访问" name="password">
|
||||
<a-input v-model:value="formState.password"
|
||||
:disabled="loading"
|
||||
class="custom-input"
|
||||
placeholder="请输入密码" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="no-margin">
|
||||
<a-button type="primary"
|
||||
:loading="loading"
|
||||
block
|
||||
class="custom-button"
|
||||
@click="onSubmit()">
|
||||
确定
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.shared-access {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
width: 315px;
|
||||
height: 180px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form {
|
||||
width: 358px;
|
||||
padding: 20px 22px 20px 42px;
|
||||
border-radius: 10px;
|
||||
background: #FAFAFA;
|
||||
|
||||
:deep(.ant-form-item-required)::before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.no-margin {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
38
src/views/DataAnalyse/insight/components/SharedExpired.vue
Normal file
38
src/views/DataAnalyse/insight/components/SharedExpired.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="shared-expired">
|
||||
<img src="../img/icon_expire.png" alt="" class="icon">
|
||||
|
||||
<div class="message">抱歉,您访问的链接已过期</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.shared-expired {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
width: 554px;
|
||||
height: 307px;
|
||||
margin: 0 auto 32px;
|
||||
}
|
||||
|
||||
.message {
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
font-family: "Alibaba PuHuiTi 3.0", sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
color: #8C8C8C;
|
||||
}
|
||||
</style>
|
||||
BIN
src/views/DataAnalyse/insight/img/icon_expire.png
Normal file
BIN
src/views/DataAnalyse/insight/img/icon_expire.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
BIN
src/views/DataAnalyse/insight/img/icon_lock.png
Normal file
BIN
src/views/DataAnalyse/insight/img/icon_lock.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -36,6 +36,7 @@ const readonly = computed(() => props.readonly || false)
|
||||
<style scoped lang="scss">
|
||||
.insight-report {
|
||||
width: 100%;
|
||||
padding-bottom: 28px;
|
||||
|
||||
font-family: "Alibaba PuHuiTi 3.0", sans-serif;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import { computed, defineEmits, defineProps, ref } from 'vue'
|
||||
const emits = defineEmits(['active'])
|
||||
const props = defineProps({
|
||||
columns: { type: Array, default: () => [] },
|
||||
dataSource: { type: Array, default: () => [] }
|
||||
dataSource: { type: Array, default: () => [] },
|
||||
rowSelectable: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
@@ -33,6 +34,10 @@ function customRow(record = {}) {
|
||||
}
|
||||
|
||||
function onActiveRow(record) {
|
||||
if (!props.rowSelectable) {
|
||||
return
|
||||
}
|
||||
|
||||
activatedRecord.value = record
|
||||
emits('active', record)
|
||||
}
|
||||
|
||||
@@ -4,23 +4,96 @@ import { computed, defineProps, ref, watch } from 'vue'
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Object, default: () => Object.assign({}) }
|
||||
data: { type: Object, default: () => Object.assign({}) },
|
||||
barTable: { type: Boolean, default: false },
|
||||
standardStyle: {
|
||||
type: Object,
|
||||
default: () => Object.assign({
|
||||
groupColor: '#FFAA00',
|
||||
groupBackgroundColor: 'rgba(255, 178, 0, 0.2)',
|
||||
columnColor: '#434343',
|
||||
columnBackgroundColor: 'rgba(255, 178, 0, 0.05)'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const columns = computed(() => (props.data.headerVOS || []).map((column, columnIndex) => {
|
||||
const tableData = ref([])
|
||||
|
||||
const headers = computed(() => {
|
||||
const list = JSON.parse(JSON.stringify(props.data.headerVOS || []))
|
||||
const rowTitle = list[0]
|
||||
|
||||
// 判断是否需要拆分列。(表格内容的第一列文字,是否包含下划线,根据下划线拆分列)
|
||||
if(!tableData.value.some((data) => {
|
||||
return data[rowTitle.dataIndex].indexOf('_') > -1
|
||||
})) {
|
||||
// 不需要拆分列,返回当前配置即可
|
||||
return list
|
||||
}
|
||||
|
||||
/*
|
||||
* 把含有下划线的文字拆分为两列显示,并且合并列的表头。并且合并显示具有相同文字的连续的行。
|
||||
* 例: 把 “女_非常喜欢+比较喜欢【TOP2】” 拆分未 “女” 和 “非常喜欢+比较喜欢【TOP2】” 两列
|
||||
*/
|
||||
|
||||
// 拆分出来的一列
|
||||
const addedFirstRow = {
|
||||
key: rowTitle.key,
|
||||
dataIndex: rowTitle.dataIndex,
|
||||
title: rowTitle.title,
|
||||
colSpan: 2, // 合并并显示第一列
|
||||
customRender: ({ text, index }) => {
|
||||
let idx = index
|
||||
const rowSpan = text?.split?.('_')[0] === tableData.value[index - 1]?.[rowTitle.dataIndex]?.split?.('_')?.[0] ? 0 : tableData.value.filter((record, recordIndex) => {
|
||||
if(idx === recordIndex && text?.split?.('_')[0] === record[rowTitle.dataIndex]?.split?.('_')?.[0]) {
|
||||
idx++
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}).length
|
||||
|
||||
return {
|
||||
children: text.split('_')[0],
|
||||
props: {
|
||||
colSpan: text.split('_').length > 1 ? 1 : 2, // 由于拆分为两列,所以需要合并没有拆分的列
|
||||
rowSpan // 合并连续行
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rowTitle.colSpan = 0 // 合并并隐藏第二列
|
||||
rowTitle.customRender = ({ text, index }) => {
|
||||
return {
|
||||
children: text.split('_')[1],
|
||||
props: {
|
||||
colSpan: Math.max(0, text.split('_').length - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
addedFirstRow,
|
||||
...list
|
||||
]
|
||||
|
||||
})
|
||||
const columns = computed(() => headers.value.map((column, columnIndex) => {
|
||||
column.align = 'center'
|
||||
column.slots = { customRender: column.dataIndex }
|
||||
column.customHeaderCell = function() {
|
||||
const style = {}
|
||||
if(columnIndex === 1) { // 新品表现,一级表头
|
||||
if(column.title === '新品表现') { // 新品表现,一级表头
|
||||
style.color = '#70B936'
|
||||
style.backgroundColor = 'rgba(112, 185, 54, 0.2)'
|
||||
}
|
||||
if(columnIndex === 2) { // 标杆表现,一级表头
|
||||
style.color = '#FFAA00'
|
||||
style.backgroundColor = 'rgba(255, 178, 0, 0.2)'
|
||||
if(column.title === '标杆表现') { // 标杆表现,一级表头
|
||||
style.color = props.standardStyle.groupColor
|
||||
style.backgroundColor = props.standardStyle.groupBackgroundColor
|
||||
}
|
||||
|
||||
// 表头(第一行)显示不同的文字颜色及背景色
|
||||
return {
|
||||
style
|
||||
}
|
||||
@@ -30,33 +103,38 @@ const columns = computed(() => (props.data.headerVOS || []).map((column, columnI
|
||||
column.children.forEach((child) => {
|
||||
child.align = 'center'
|
||||
child.slots = { customRender: child.dataIndex }
|
||||
child.parentTitle = column.title
|
||||
child.width = '120px'
|
||||
child.customHeaderCell = function() {
|
||||
const style = {}
|
||||
if(columnIndex === 1) { // 新品表现,二级表头
|
||||
if(column.title === '新品表现') { // 新品表现,二级表头
|
||||
style.color = '#434343'
|
||||
style.backgroundColor = 'rgba(112, 185, 54, 0.05)'
|
||||
}
|
||||
if(columnIndex === 2) { // 标杆表现,二级表头
|
||||
style.color = '#434343'
|
||||
style.backgroundColor = 'rgba(255, 178, 0, 0.05)'
|
||||
if(column.title === '标杆表现') { // 标杆表现,二级表头
|
||||
style.color = props.standardStyle.columnColor
|
||||
style.backgroundColor = props.standardStyle.columnBackgroundColor
|
||||
}
|
||||
|
||||
// 表头(第二行)显示不同的文字颜色及背景色
|
||||
return {
|
||||
style
|
||||
}
|
||||
}
|
||||
|
||||
child.customCell = function(record, rowIndex) {
|
||||
const rowClass = rowClassName(record, rowIndex)
|
||||
const style = {}
|
||||
if(columnIndex === 1) { // 新品表现,内容
|
||||
style.color = '#434343'
|
||||
if(column.title === '新品表现') { // 新品表现,内容
|
||||
style.color = rowClass === 'gray-row' ? '#818181' : '#434343'
|
||||
style.backgroundColor = 'rgba(112, 185, 54, 0.05)'
|
||||
}
|
||||
if(columnIndex === 2) { // 标杆表现,内容
|
||||
style.color = '#434343'
|
||||
style.backgroundColor = 'rgba(255, 178, 0, 0.05)'
|
||||
if(column.title === '标杆表现') { // 标杆表现,内容
|
||||
style.color = rowClass === 'gray-row' ? '#818181' : props.standardStyle.columnColor
|
||||
style.backgroundColor = props.standardStyle.columnBackgroundColor
|
||||
}
|
||||
|
||||
// 表格内容显示不同的文字颜色及背景色
|
||||
return {
|
||||
style
|
||||
}
|
||||
@@ -65,24 +143,113 @@ const columns = computed(() => (props.data.headerVOS || []).map((column, columnI
|
||||
}
|
||||
|
||||
return column
|
||||
}).filter((column) => {
|
||||
// 过滤掉空列('新品表现' 和 '标杆表现',只有第一列表头但是没有第二列表头的情况,表格内容为空,隐藏掉)
|
||||
return !['新品表现', '标杆表现'].includes(column.title) || column?.children?.length
|
||||
}))
|
||||
const flatColumns = computed(() => columns.value.flatMap((column) => column.children?.length ? column.children : column))
|
||||
const tableData = ref([])
|
||||
|
||||
const barMax = ref(0)
|
||||
const barMin = ref(0)
|
||||
|
||||
watch(() => props.data, () => {
|
||||
tableData.value = props.data.dataVOS || []
|
||||
|
||||
const list = JSON.parse(JSON.stringify(props.data.dataVOS || []))
|
||||
const tableList = []
|
||||
|
||||
const rowTitle = props.data.headerVOS?.[0]
|
||||
if(rowTitle) {
|
||||
// 重排序, 把具有相同开头文字(用下划线区分,例:“开头文字_xxxxx”)的放到一起,便于后续表格跨行合并。
|
||||
// 例:“女_样本基数”和“女_非常喜欢+比较喜欢【TOP2】”
|
||||
for(let i = 0; i < list.length; i += 1) {
|
||||
const row = list[i]
|
||||
tableList.push(row)
|
||||
const titleArr = row[rowTitle.dataIndex]?.split?.('_')
|
||||
if(titleArr?.length > 1) {
|
||||
for(let j = i + 1; j < list.length; j += 1) {
|
||||
if(list[j][rowTitle.dataIndex]?.split?.('_')?.[0] === titleArr[0]) {
|
||||
tableList.push(list[j])
|
||||
list.splice(j, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tableData.value = tableList
|
||||
} else {
|
||||
tableData.value = list
|
||||
}
|
||||
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
watch([flatColumns, tableData], () => {
|
||||
const numericalColumns = flatColumns.value.map((key) => key.dataIndex).slice(1)
|
||||
|
||||
tableData.value.forEach((record) => {
|
||||
if(['概念编码', '样本基数'].includes(record[headers.value[0]?.dataIndex])) {
|
||||
return
|
||||
}
|
||||
|
||||
// 计算条形图的最大值
|
||||
barMax.value = Math.max(barMax.value, ...numericalColumns.map((key) => +record[key]).filter((value) => !isNaN(value)))
|
||||
barMin.value = Math.min(barMin.value, ...numericalColumns.map((key) => +record[key]).filter((value) => !isNaN(value)))
|
||||
})
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
const barStyle = computed(() => (record, column) => {
|
||||
const value = record[column.dataIndex] ? record[column.dataIndex] : 0
|
||||
const width = (+value * 100 / barMax.value) + '%'
|
||||
|
||||
// 计算条形图的长度,根据最大值计算百分比即可
|
||||
return {
|
||||
width,
|
||||
backgroundColor: column.parentTitle === '新品表现' ? '#70B936' : '#FFB200'
|
||||
}
|
||||
})
|
||||
|
||||
function rowClassName(record) {
|
||||
// 表格中 '概念编码' 和 '样本基数' , 这两行要显示灰色
|
||||
return Object.keys(record).some((key) => ['概念编码', '样本基数'].some((item) => record[key]?.indexOf?.(item) > -1)) ? 'gray-row' : ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="styled-table-wrapper">
|
||||
<a-table :columns="columns" :data-source="tableData" bordered :pagination="false" class="table">
|
||||
<template v-for="(column) in flatColumns"
|
||||
<div class="styled-table-wrapper" :class="{'bar-table': props.barTable}">
|
||||
<a-table :columns="columns" :data-source="tableData" bordered :pagination="false"
|
||||
:row-class-name="rowClassName" class="table">
|
||||
<template v-for="(column, columnIndex) in flatColumns"
|
||||
:key="column.dataIndex"
|
||||
#[column.dataIndex]="{text, record}">
|
||||
<template v-if="true">{{ text }}</template>
|
||||
<template v-if="false">{{ record[column.dataIndex] }}</template>
|
||||
#[column.dataIndex]="{record}">
|
||||
<template v-if="props.barTable" data-desc="显示条形图">
|
||||
<span
|
||||
v-if="[0].includes(columnIndex) || ['概念编码', '样本基数'].includes(record[headers[0]?.dataIndex])">
|
||||
{{ record[column.dataIndex] }}
|
||||
</span>
|
||||
|
||||
<div v-else class="cell-bar" :style="barStyle(record, column)">
|
||||
<span class="text">{{ record[column.dataIndex] }}</span>
|
||||
<span class="danger-text">{{ record[column.dataIndex + 'Type'] || '' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="record[headers[0].dataIndex] === '是否通过概念行动标准'"
|
||||
data-desc="显示是否选择框">
|
||||
<span v-if="record[column.dataIndex] === '是否通过概念行动标准'">
|
||||
{{ record[column.dataIndex] }}
|
||||
</span>
|
||||
<a-select v-else placeholder="请选择" class="custom-select" style="width: 90px;">
|
||||
<a-select-option key="1" value="1">
|
||||
<span style="color: #70B936;">是</span>
|
||||
</a-select-option>
|
||||
<a-select-option key="0" value="0">
|
||||
<span style="color: #FC4545;">否</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template v-else data-desc="显示文字">
|
||||
<span>{{ record[column.dataIndex] }}</span>
|
||||
<span class="danger-text">{{ record[column.dataIndex + 'Type'] || '' }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
@@ -92,6 +259,32 @@ watch(() => props.data, () => {
|
||||
.styled-table-wrapper {
|
||||
$radius: 6px;
|
||||
|
||||
.cell-bar {
|
||||
min-width: 1px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
padding: 0;
|
||||
border-radius: 4px;
|
||||
text-align: right;
|
||||
color: #FFFFFF;
|
||||
|
||||
.text {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.danger-text {
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&.bar-table :deep(.ant-table-wrapper) {
|
||||
.ant-table-tbody > tr > td {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-table-wrapper) {
|
||||
.ant-table-thead > tr > th, .ant-table-tbody > tr > td {
|
||||
padding-top: 10px;
|
||||
@@ -110,6 +303,16 @@ watch(() => props.data, () => {
|
||||
.ant-table-thead > tr:first-child > th:last-child {
|
||||
border-top-right-radius: $radius;
|
||||
}
|
||||
|
||||
.ant-table-body tr.gray-row td {
|
||||
&:not([rowspan="2"]) {
|
||||
color: #818181;
|
||||
}
|
||||
}
|
||||
|
||||
.danger-text {
|
||||
color: #FC4545;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { defineProps } from 'vue'
|
||||
import { defineProps, ref, watch } from 'vue'
|
||||
|
||||
import Tinymce from '@/components/Tinymce.vue'
|
||||
|
||||
@@ -13,12 +13,19 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
|
||||
const richText = ref('')
|
||||
|
||||
watch(() => props.report.coreConclusion, (val) => {
|
||||
richText.value = val || ''
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SectionTitle>核心结论</SectionTitle>
|
||||
<Section class="section">
|
||||
<Tinymce :curtail="false"
|
||||
<Tinymce v-model:editorData="richText"
|
||||
:curtail="false"
|
||||
:curtail-min-height="140"
|
||||
show
|
||||
:open3-d-icon="false"
|
||||
|
||||
@@ -18,55 +18,66 @@ const activeKey = ref('0')
|
||||
const tabList = ref([])
|
||||
|
||||
watch(() => props.report, () => {
|
||||
const total = props.report?.chatVOS?.find?.((data) => data.type.startsWith('决策指标-总体'))
|
||||
const gender = props.report?.chatVOS?.find?.((data) => data.type.startsWith('决策指标-性别'))
|
||||
const age = props.report?.chatVOS?.find?.((data) => data.type.startsWith('决策指标-年龄'))
|
||||
const income = props.report?.chatVOS?.find?.((data) => data.type.startsWith('决策指标-家庭月收入'))
|
||||
const city = props.report?.chatVOS?.find?.((data) => data.type.startsWith('决策指标-城市级别'))
|
||||
|
||||
tabList.value = [
|
||||
{
|
||||
key: '0',
|
||||
name: '总体',
|
||||
name: getTableTypeStr(total),
|
||||
canHide: false,
|
||||
visible: true,
|
||||
tableData: props.report?.chatVOS?.[0] || {},
|
||||
codeStr: getTableCodeRow(props.report?.chatVOS?.[0] || {})
|
||||
tableData: total,
|
||||
codeStr: getTableCodeRow(total)
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
name: '性别',
|
||||
name: getTableTypeStr(gender),
|
||||
canHide: true,
|
||||
visible: true,
|
||||
tableData: props.report?.chatVOS?.[1] || {},
|
||||
codeStr: getTableCodeRow(props.report?.chatVOS?.[1] || {})
|
||||
tableData: gender,
|
||||
codeStr: getTableCodeRow(gender)
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
name: '年龄段',
|
||||
name: getTableTypeStr(age),
|
||||
canHide: true,
|
||||
visible: true,
|
||||
tableData: props.report?.chatVOS?.[2] || {},
|
||||
codeStr: getTableCodeRow(props.report?.chatVOS?.[2] || {})
|
||||
tableData: age,
|
||||
codeStr: getTableCodeRow(age)
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
name: '家庭月收入',
|
||||
name: getTableTypeStr(income),
|
||||
canHide: true,
|
||||
visible: true,
|
||||
tableData: props.report?.chatVOS?.[3] || {},
|
||||
codeStr: getTableCodeRow(props.report?.chatVOS?.[3] || {})
|
||||
tableData: income,
|
||||
codeStr: getTableCodeRow(income)
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
name: '城市级别',
|
||||
name: getTableTypeStr(city),
|
||||
canHide: true,
|
||||
visible: true,
|
||||
tableData: props.report?.chatVOS?.[4] || {},
|
||||
codeStr: getTableCodeRow(props.report?.chatVOS?.[4] || {})
|
||||
tableData: city,
|
||||
codeStr: getTableCodeRow(city)
|
||||
}
|
||||
]
|
||||
].filter((item) => !!item.tableData)
|
||||
}, { immediate: true })
|
||||
|
||||
|
||||
|
||||
function getTableTypeStr(tableData) {
|
||||
return tableData?.type?.split('-')?.[1] || ''
|
||||
}
|
||||
|
||||
function getTableCodeRow(tableData) {
|
||||
const codeRow = tableData?.dataVOS?.find?.((row) => Object.keys(row).find((key) => row[key] === '概念编码'))
|
||||
if(!tableData?.dataVOS?.length) {
|
||||
return ''
|
||||
}
|
||||
const codeRow = tableData.dataVOS.find((row) => Object.keys(row).find((key) => row[key] === '概念编码'))
|
||||
if(!codeRow) {
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -11,11 +11,14 @@ const props = defineProps({
|
||||
readonly: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
const tableData = computed(() => [])
|
||||
const codeStr = computed(() => getTableCodeRow(tableData.value?.chatVOS?.[4] || {}))
|
||||
const tableData = computed(() => props.report?.chatVOS?.find?.((data) => data.type === '其他关键指标-喜欢程度') || {})
|
||||
const codeStr = computed(() => getTableCodeRow(tableData.value))
|
||||
|
||||
function getTableCodeRow(tableData) {
|
||||
const codeRow = tableData?.dataVOS?.find?.((row) => Object.keys(row).find((key) => row[key] === '概念编码'))
|
||||
if(!tableData?.dataVOS?.length) {
|
||||
return ''
|
||||
}
|
||||
const codeRow = tableData.dataVOS.find((row) => Object.keys(row).find((key) => row[key] === '概念编码'))
|
||||
if(!codeRow) {
|
||||
return ''
|
||||
}
|
||||
@@ -38,6 +41,7 @@ function getTableCodeRow(tableData) {
|
||||
<style scoped lang="scss">
|
||||
|
||||
.message {
|
||||
padding-top: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #434343;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup>
|
||||
import { computed, defineProps } from 'vue'
|
||||
import { computed, defineProps, ref } from 'vue'
|
||||
import moment from 'moment'
|
||||
|
||||
import { EyeInvisibleOutlined } from '@ant-design/icons-vue'
|
||||
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
|
||||
import SectionTitle from '../../components/SectionTitle.vue'
|
||||
@@ -17,14 +17,16 @@ const props = defineProps({
|
||||
const createdAt = computed(() => props.report?.createdAt ? moment(props.report.createdAt).format('YYYY年MM月DD日 HH:mm:ss') : '--')
|
||||
const completeAt = computed(() => props.report?.completeAt ? moment(props.report.completeAt).format('YYYY年MM月DD日 HH:mm:ss') : '--')
|
||||
|
||||
const statusStr = computed(() => props.report?.status ?? '--')
|
||||
const statusStr = computed(() => ['未生成', '生成中', '已生成'][props.report?.status] ?? '--')
|
||||
const sampleNum = computed(() => props.report?.sampleNum ?? '--')
|
||||
|
||||
const surveyVersion = computed(() => props.report?.surveyVersion ?? '--')
|
||||
const reportVersion = computed(() => props.report?.reportVersion ?? '--')
|
||||
|
||||
function toggleVisibility() {
|
||||
const visible = ref(false)
|
||||
|
||||
function toggleVisibility() {
|
||||
visible.value = !visible.value
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -32,7 +34,8 @@ function toggleVisibility() {
|
||||
<div class="insight-overview">
|
||||
<SectionTitle>
|
||||
<span class="text">报告概览</span>
|
||||
<EyeInvisibleOutlined class="icon" @click="toggleVisibility" />
|
||||
<EyeOutlined v-if="visible" class="icon" @click="toggleVisibility" />
|
||||
<EyeInvisibleOutlined v-else class="icon" @click.stop="toggleVisibility" />
|
||||
</SectionTitle>
|
||||
|
||||
<Section class="section">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { defineProps } from 'vue'
|
||||
import { defineProps, ref, watch } from 'vue'
|
||||
|
||||
import Tinymce from '@/components/Tinymce.vue'
|
||||
|
||||
@@ -10,6 +10,14 @@ const props = defineProps({
|
||||
report: { type: Object, default: () => Object.assign({}) },
|
||||
readonly: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
|
||||
const richText = ref('')
|
||||
|
||||
watch(() => props.report.decisionIndicators, (val) => {
|
||||
richText.value = val || ''
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -22,7 +30,8 @@ const props = defineProps({
|
||||
<div class="row">
|
||||
<div class="label">决策标准</div>
|
||||
<div class="value tinymce-wrapper">
|
||||
<Tinymce :curtail="false"
|
||||
<Tinymce v-model:editorData="richText"
|
||||
:curtail="false"
|
||||
:curtail-min-height="140"
|
||||
show
|
||||
:open3-d-icon="false"
|
||||
|
||||
@@ -14,57 +14,26 @@ const props = defineProps({
|
||||
|
||||
const conceptTypeEnum = {
|
||||
newest: 1,
|
||||
standard: 2,
|
||||
standard: 0,
|
||||
|
||||
1: 'newest',
|
||||
2: 'standard'
|
||||
0: 'standard'
|
||||
}
|
||||
|
||||
|
||||
const list = computed(() => props.data?.list || [
|
||||
{
|
||||
id: 1,
|
||||
name: '零食跨界-屏幕最佳搭档',
|
||||
img: '',
|
||||
type: conceptTypeEnum.newest
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '极致颜值-满杯布灵',
|
||||
img: '',
|
||||
type: conceptTypeEnum.newest
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '极致健康-一站式早餐解决方案',
|
||||
img: '',
|
||||
type: conceptTypeEnum.newest
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: '零食跨界-屏幕最佳搭档',
|
||||
img: '',
|
||||
type: conceptTypeEnum.newest
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
name: '极致颜值-满杯布灵',
|
||||
img: '',
|
||||
type: conceptTypeEnum.newest
|
||||
},
|
||||
{
|
||||
id: 41,
|
||||
name: '极致健康-一站式早餐解决方案',
|
||||
img: '',
|
||||
type: conceptTypeEnum.newest
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '标杆概念-馋酸奶',
|
||||
img: '',
|
||||
type: conceptTypeEnum.standard
|
||||
},
|
||||
|
||||
const list = computed(() => props.report?.config || [
|
||||
// {
|
||||
// id: 41,
|
||||
// concept_name: '极致健康-一站式早餐解决方案',
|
||||
// concept_url: 'https://test-cxp-public-web-1302259445.cos.ap-beijing.myqcloud.com/uat-yls/theme/undefined/1724639839680_902_%E6%B0%B4%E5%BA%93.jpg',
|
||||
// concept_type: conceptTypeEnum.newest
|
||||
// },
|
||||
// {
|
||||
// id: 3,
|
||||
// concept_name: '标杆概念-馋酸奶',
|
||||
// concept_url: 'https://test-cxp-public-web-1302259445.cos.ap-beijing.myqcloud.com/uat-yls/theme/undefined/1724639839680_902_%E6%B0%B4%E5%BA%93.jpg',
|
||||
// concept_type: conceptTypeEnum.standard
|
||||
// },
|
||||
])
|
||||
|
||||
</script>
|
||||
@@ -76,13 +45,13 @@ const list = computed(() => props.data?.list || [
|
||||
<div v-for="(item) in list"
|
||||
:key="item.id"
|
||||
class="item"
|
||||
:class="{[conceptTypeEnum[item.type]]: true}">
|
||||
:class="{[conceptTypeEnum[item.concept_type]]: true}">
|
||||
|
||||
<div class="name">
|
||||
<div class="text">{{ item.name || '' }}</div>
|
||||
<div class="text">{{ item.concept_name || '' }}</div>
|
||||
</div>
|
||||
|
||||
<img v-if="item.img" :src="item.img" alt="" class="img">
|
||||
<img v-if="item.concept_url" :src="item.concept_url" alt="" class="img">
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
@@ -103,6 +72,7 @@ const list = computed(() => props.data?.list || [
|
||||
}
|
||||
|
||||
.item {
|
||||
overflow: hidden;
|
||||
flex: none;
|
||||
width: 198px;
|
||||
height: 152px;
|
||||
|
||||
@@ -71,7 +71,7 @@ watch(() => props.report, () => {
|
||||
<div class="tab-container">
|
||||
<PeopleLike />
|
||||
<PeopleDislike />
|
||||
<ProductImage />
|
||||
<ProductImage :report="props.report"/>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
<script setup>
|
||||
|
||||
|
||||
import Section from '../../../components/Section.vue'
|
||||
import PeopleLike from './PeopleLike.vue'
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Section class="section">
|
||||
<div class="section-header">
|
||||
<img src="../../../img/icon_like.png" alt="" class="icon">
|
||||
<span>消费者喜欢的方面</span>
|
||||
</div>
|
||||
|
||||
<div class="section-content">
|
||||
<div class="area-wrapper"></div>
|
||||
<div class="table-wrapper"></div>
|
||||
<div class="table-wrapper"></div>
|
||||
</div>
|
||||
|
||||
</Section>
|
||||
<PeopleLike title="消费者不喜欢的方面">
|
||||
<template #title-icon>
|
||||
<img src="../../../img/icon_dislike.png" alt="" class="icon">
|
||||
</template>
|
||||
</PeopleLike>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./style.scss";
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
<script setup>
|
||||
import { computed, defineProps, ref } from 'vue'
|
||||
import { AlignRightOutlined, CloudOutlined, DownloadOutlined } from '@ant-design/icons-vue'
|
||||
import moment from 'moment'
|
||||
|
||||
import Section from '../../../components/Section.vue'
|
||||
import SmallTable from '../../components/SmallTable.vue'
|
||||
|
||||
import { exportJsonToExcel } from '@/composables/exportExcel'
|
||||
|
||||
const props = defineProps({
|
||||
title: { type: String, default: '消费者喜欢的方面' }
|
||||
})
|
||||
|
||||
const chartShown = ref(true)
|
||||
|
||||
const activeRow = ref({})
|
||||
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
@@ -31,13 +42,30 @@ const tableData = ref([
|
||||
{ id: '7', seq: '7', original: 'xxx' }
|
||||
])
|
||||
|
||||
|
||||
function showChart(show) {
|
||||
chartShown.value = !!show
|
||||
}
|
||||
|
||||
function onActiveRow(record) {
|
||||
activeRow.value = record || {}
|
||||
}
|
||||
|
||||
function downloadDetailTable() {
|
||||
exportJsonToExcel({
|
||||
data: [['xxx1', 'xxx111'], ['xxx2', 'xxx2']],
|
||||
filename: `关键词比例_${ props.title }_屏幕最佳搭档_${ moment().format('YYYYMMDDHHmm') }`
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Section class="section">
|
||||
<div class="section-header">
|
||||
<img src="../../../img/icon_like.png" alt="" class="title-icon">
|
||||
<span>消费者喜欢的方面</span>
|
||||
<slot name="title-icon">
|
||||
<img src="../../../img/icon_like.png" alt="" class="title-icon">
|
||||
</slot>
|
||||
<span class="title-text">{{ props.title }}</span>
|
||||
</div>
|
||||
|
||||
<div class="section-content">
|
||||
@@ -46,12 +74,6 @@ const tableData = ref([
|
||||
<div class="action-button">
|
||||
<DownloadOutlined class="icon" />
|
||||
</div>
|
||||
<div class="action-button">
|
||||
<CloudOutlined class="icon" />
|
||||
</div>
|
||||
<div class="action-button">
|
||||
<AlignRightOutlined class="icon" style="rotate: 180deg;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -63,29 +85,27 @@ const tableData = ref([
|
||||
<div class="action-button">
|
||||
<DownloadOutlined class="icon" />
|
||||
</div>
|
||||
<div class="action-button">
|
||||
<CloudOutlined class="icon" />
|
||||
</div>
|
||||
<div class="action-button">
|
||||
<AlignRightOutlined class="icon" style="rotate: 180deg;" />
|
||||
<div class="action-button" @click="showChart(!chartShown)">
|
||||
<CloudOutlined v-if="!chartShown" class="icon" />
|
||||
<AlignRightOutlined v-else class="icon" style="rotate: 180deg;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="chartShown"></div>
|
||||
<SmallTable v-else
|
||||
:data-source="tableData"
|
||||
:columns="columns"
|
||||
:row-selectable="true"
|
||||
@active="onActiveRow" />
|
||||
</div>
|
||||
|
||||
<a-divider type="vertical" class="dashed-divider" />
|
||||
<a-divider type="vertical" class="dashed-divider" />
|
||||
|
||||
<div class="table-wrapper">
|
||||
<div class="button-wrapper">
|
||||
<div class="action-button">
|
||||
<div class="action-button" @click="downloadDetailTable">
|
||||
<DownloadOutlined class="icon" />
|
||||
</div>
|
||||
<div class="action-button">
|
||||
<CloudOutlined class="icon" />
|
||||
</div>
|
||||
<div class="action-button">
|
||||
<AlignRightOutlined class="icon" style="rotate: 180deg;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SmallTable :data-source="tableData" :columns="columns" />
|
||||
|
||||
@@ -4,16 +4,22 @@ import { computed, defineProps } from 'vue'
|
||||
import Section from '../../../components/Section.vue'
|
||||
import StyledTable from '../../components/StyledTable.vue'
|
||||
|
||||
const props = defineProps({})
|
||||
const props = defineProps({
|
||||
report: { type: Object, default: () => Object.assign({}) }
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
const codeStr = computed(() => getTableCodeRow())
|
||||
const tableData = computed(() => props.report?.chatVOS?.find?.((data) => data.type === '概念形象') || {})
|
||||
const codeStr = computed(() => getTableCodeRow(tableData.value))
|
||||
|
||||
|
||||
function getTableCodeRow(tableData) {
|
||||
const codeRow = tableData?.dataVOS?.find?.((row) => Object.keys(row).find((key) => row[key] === '概念编码'))
|
||||
if(!tableData?.dataVOS?.length) {
|
||||
return ''
|
||||
}
|
||||
const codeRow = tableData.dataVOS.find((row) => Object.keys(row).find((key) => row[key] === '概念编码'))
|
||||
if(!codeRow) {
|
||||
return ''
|
||||
}
|
||||
@@ -24,15 +30,17 @@ function getTableCodeRow(tableData) {
|
||||
<template>
|
||||
<Section class="section">
|
||||
<div class="section-header">
|
||||
<img src="../../../img/icon_like.png" alt="" class="title-icon">
|
||||
<span>消费者喜欢的方面</span>
|
||||
<img src="../../../img/icon_light.png" alt="" class="title-icon">
|
||||
<span class="title-text">概念传递产品形象</span>
|
||||
</div>
|
||||
|
||||
<StyledTable></StyledTable>
|
||||
<div class="main-wrapper">
|
||||
<StyledTable :data="tableData" :bar-table="true" />
|
||||
|
||||
<div class="message">
|
||||
<span class="emphasize">{{ codeStr }}</span>
|
||||
<span>意为该数值在90%的显著水平下显著高</span>
|
||||
<div class="message">
|
||||
<span class="emphasize">{{ codeStr }}</span>
|
||||
<span>意为该数值在90%的显著水平下显著高</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Section>
|
||||
@@ -41,8 +49,12 @@ function getTableCodeRow(tableData) {
|
||||
<style scoped lang="scss">
|
||||
@import "./style.scss";
|
||||
|
||||
.main-wrapper {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 10px 16px 16px;
|
||||
padding-top: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #434343;
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: none;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
src/views/Shared/Index.vue
Normal file
11
src/views/Shared/Index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user