首页更新

This commit is contained in:
hrx 2026-07-08 16:00:30 +08:00
parent 084391fb51
commit 0bba904e9d
11 changed files with 1072 additions and 181 deletions

View File

@ -53,3 +53,16 @@ export function reqCompany(data) {
data
})
}
// 咨询表单选项
export const reqConsultForm = (data) => {
return request({
url: '/product/search_user_inquiry_dict.dspy',
method: 'get',
headers: {
'Content-Type': 'application/json'
},
params: data
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View File

@ -1,9 +1,5 @@
<template>
<el-card class="model-toolbar" shadow="never">
<div class="toolbar-left">
<h3>筛选模型</h3>
<p>按模型名称和类型快速定位目标模型</p>
</div>
<el-form class="toolbar-search" :model="searchForm" inline>
<el-form-item label="模型名称">
<el-input
@ -90,28 +86,12 @@ export default {
/deep/ .el-card__body {
display: flex;
align-items: center;
justify-content: space-between;
justify-content: flex-start;
gap: 16px;
padding: 18px 20px 2px;
}
}
.toolbar-left {
margin-bottom: 16px;
h3 {
margin: 0 0 6px;
color: #1f2d3d;
font-size: 18px;
}
p {
margin: 0;
color: #8a94a6;
font-size: 13px;
}
}
.toolbar-search {
display: flex;
flex-wrap: wrap;

View File

@ -159,6 +159,14 @@ export const constantRoutes = [
title: "智能客服详情", fullPath: "/h5HomePage/service",
},
},
{
path: "consultDialogFull",
title: '全屏咨询弹窗预览',
component: () => import('@/views/H5/consultDialogFullPreview/index.vue'),
meta: {
title: "全屏咨询弹窗预览", fullPath: "/h5HomePage/consultDialogFull",
},
},
]
},
{

View File

@ -0,0 +1,571 @@
<template>
<el-dialog
:visible.sync="dialogVisible"
:fullscreen="true"
custom-class="mobile-consult-fullscreen-dialog"
:append-to-body="true"
:close-on-click-modal="false"
:close-on-press-escape="!submitSuccess"
:show-close="!submitSuccess"
@close="handleClose"
>
<div v-if="submitSuccess" class="submit-success-page">
<div class="success-icon">
<i class="el-icon-check"></i>
</div>
<h2 class="success-title">已完成</h2>
<p class="success-desc">您的咨询信息已提交我们会尽快与您联系</p>
</div>
<div v-else class="mobile-consult-wrap">
<h2 class="mobile-consult-title">{{ title }}</h2>
<p class="mobile-consult-subtitle">期待与您开启AI创新未来</p>
<el-form
ref="consultForm"
:model="formData"
:rules="rules"
label-position="top"
class="mobile-consult-form"
>
<el-form-item label="1. 请填写您的姓名" prop="name">
<el-input
v-model.trim="formData.name"
maxlength="20"
placeholder="请输入您的姓名"
/>
</el-form-item>
<el-form-item label="2. 请填写您的联系方式" prop="phone">
<el-input
v-model.trim="formData.phone"
maxlength="11"
placeholder="请输入您的联系电话"
/>
</el-form-item>
<el-form-item label="3. 请填写您的邮箱" prop="email">
<el-input
v-model.trim="formData.email"
maxlength="60"
placeholder="请输入您的邮箱"
/>
</el-form-item>
<el-form-item label="4. 请填写您的公司名称" prop="company">
<el-input
v-model.trim="formData.company"
maxlength="80"
placeholder="请输入您的公司名称"
/>
</el-form-item>
<el-form-item label="5. 请选择您的企业类型(单选)" prop="enterprise_type">
<el-radio-group v-model="formData.enterprise_type" class="option-grid">
<el-radio
v-for="item in enterpriseOptions"
:key="item.id"
:label="item.id"
class="option-item"
>
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="6. 请选择您所在的省份(单选)" prop="region">
<el-select
v-model="formData.region"
filterable
placeholder="输入省份名称或拼音首字母搜索..."
class="full-select"
>
<el-option
v-for="item in provinceOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="7. 您想咨询的方向(可多选)">
<el-checkbox-group v-model="formData.consult_direction_list" class="direction-list">
<el-checkbox
v-for="item in consultDirectionOptions"
:key="item.id"
:label="item.id"
class="direction-item"
>
<span class="direction-main">{{ item.title }}</span>
<span class="direction-sub">{{ item.desc }}</span>
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="8. 如果您有其他问题需要咨询,请留言">
<el-input
v-model.trim="formData.content"
type="textarea"
:autosize="{ minRows: 3, maxRows: 5 }"
maxlength="500"
show-word-limit
placeholder="请输入您想咨询的内容..."
/>
</el-form-item>
</el-form>
<div class="qrcode-area">
<p>扫码添加微信获取专属服务</p>
<img src="@/assets/image/newkefu.png" alt="官方客服二维码">
</div>
<el-checkbox v-model="formData.checked" class="privacy-checkbox">
<span>
您填写的信息仅用于本次业务对接沟通公司将严格落实信息安全保护机制不泄露不滥用您的任何个人资料
</span>
</el-checkbox>
<el-button
type="primary"
class="submit-btn"
:loading="loading"
@click="handleSubmit"
>
提交咨询
</el-button>
</div>
</el-dialog>
</template>
<script>
import { reqConsultForm, reqProductConsult } from '@/api/H5'
export default {
name: 'MobileFullScreenConsultDialog',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: '开元云科技合作咨询'
},
qrCode: {
type: String,
default: ''
},
currentUrl: {
type: String,
default: ''
},
submitApi: {
type: Function,
default: null
}
},
data() {
const validatePhone = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入联系电话'))
} else if (!/^1[3-9]\d{9}$/.test(value)) {
callback(new Error('请输入正确的手机号'))
} else {
callback()
}
}
return {
loading: false,
formData: this.getInitialFormData(),
enterpriseOptions: [],
provinceOptions: [],
consultDirectionOptions: [],
submitSuccess: false,
rules: {
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
phone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
company: [{ required: true, message: '请输入公司名称', trigger: 'blur' }],
enterprise_type: [{ required: true, message: '请选择企业类型', trigger: 'change' }],
region: [{ required: true, message: '请选择所在省份', trigger: 'change' }]
},
originalOverflow: ''
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(value) {
this.$emit('update:visible', value)
}
}
},
watch: {
visible: {
immediate: true,
handler(newVal) {
if (newVal) {
this.resetForm()
this.fetchConsultOptions()
this.disableBodyScroll()
} else {
this.enableBodyScroll()
}
}
}
},
methods: {
getInitialFormData() {
return {
custom_type: '1',
name: '',
phone: '',
email: '',
company: '',
enterprise_type: '',
region: '',
consult_direction_list: [],
content: '',
checked: false
}
},
resetForm() {
this.formData = this.getInitialFormData()
this.submitSuccess = false
this.$nextTick(() => {
if (this.$refs.consultForm) this.$refs.consultForm.clearValidate()
})
},
disableBodyScroll() {
this.originalOverflow = document.body.style.overflow
document.body.style.overflow = 'hidden'
},
enableBodyScroll() {
document.body.style.overflow = this.originalOverflow || ''
},
handleClose() {
this.dialogVisible = false
this.$emit('close')
},
normalizeDictOptions(list, type) {
return list
.filter(item => item && item.dict_type === type)
.sort((a, b) => Number(a.sort_order || 0) - Number(b.sort_order || 0))
.map(item => ({
id: item.dict_key,
name: item.dict_value
}))
},
normalizeDirectionOptions(list) {
return list
.filter(item => item && item.dict_type === 'direction')
.sort((a, b) => Number(a.sort_order || 0) - Number(b.sort_order || 0))
.map(item => {
const value = item.dict_value || ''
const match = value.match(/^(.+?)(.+?)$/)
return {
id: String(item.dict_key),
name: value,
title: match ? match[1] : value,
desc: match ? match[2] : ''
}
})
},
extractConsultOptionList(res) {
if (res && Array.isArray(res.data)) return res.data
if (res && res.data && Array.isArray(res.data.data)) return res.data.data
return []
},
async fetchConsultOptions() {
try {
const res = await reqConsultForm()
const list = this.extractConsultOptionList(res)
if (!list.length) return
const enterpriseOptions = this.normalizeDictOptions(list, 'enterprise_type')
const provinceOptions = this.normalizeDictOptions(list, 'region')
const consultDirectionOptions = this.normalizeDirectionOptions(list)
if (enterpriseOptions.length) this.enterpriseOptions = enterpriseOptions
if (provinceOptions.length) this.provinceOptions = provinceOptions
if (consultDirectionOptions.length) this.consultDirectionOptions = consultDirectionOptions
} catch (error) {
//
}
},
buildSubmitPayload() {
return {
custom_type: this.formData.custom_type,
name: this.formData.name,
phone: this.formData.phone,
email: this.formData.email,
company: this.formData.company,
enterprise_type: this.formData.enterprise_type,
region: this.formData.region,
consult_direction: this.formData.consult_direction_list.join(','),
content: this.formData.content,
source: '公众号',
url_link: this.currentUrl || window.location.href
}
},
async handleSubmit() {
if (!this.formData.checked) {
this.$message.warning('请先勾选信息安全与隐私保护说明')
return
}
this.$refs.consultForm.validate(async valid => {
if (!valid) return
this.loading = true
try {
const payload = this.buildSubmitPayload()
const response = this.submitApi ? await this.submitApi(payload) : await reqProductConsult(payload)
if (response && (response.status === true || response.status === 'true')) {
this.$emit('success', response)
this.submitSuccess = true
} else {
this.$message.error((response && response.msg) || '提交失败,请稍后再试')
}
} catch (error) {
this.$message.error('提交失败,请稍后再试')
} finally {
this.loading = false
}
})
}
},
beforeDestroy() {
this.enableBodyScroll()
}
}
</script>
<style scoped lang="less">
::v-deep .mobile-consult-fullscreen-dialog {
margin: 0 !important;
width: 100% !important;
max-width: 100% !important;
height: 100vh;
border-radius: 0;
.el-dialog__header {
padding: 14px 14px 8px;
border-bottom: 0;
}
.el-dialog__title {
font-size: 18px;
font-weight: 700;
color: #111827;
}
.el-dialog__body {
height: calc(100vh - 60px);
padding: 12px 14px 18px;
overflow-y: auto;
}
}
.mobile-consult-wrap {
padding-bottom: 8px;
}
.submit-success-page {
min-height: calc(100vh - 110px);
padding: 80px 24px 24px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
box-sizing: border-box;
}
.success-icon {
width: 88px;
height: 88px;
border-radius: 50%;
background: #22c55e;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 14px 28px rgba(34, 197, 94, 0.22);
i {
font-size: 46px;
font-weight: 700;
}
}
.success-title {
margin: 24px 0 10px;
color: #111827;
font-size: 24px;
font-weight: 700;
}
.mobile-consult-title {
margin: 0;
font-size: 24px;
line-height: 1.25;
color: #111827;
}
.mobile-consult-subtitle {
margin: 6px 0 16px;
font-size: 12px;
color: #9ca3af;
}
.mobile-consult-form {
::v-deep .el-form-item {
margin-bottom: 18px;
}
::v-deep .el-form-item__label {
padding-bottom: 6px;
font-size: 13px;
line-height: 1.3;
color: #1f2937;
font-weight: 600;
}
::v-deep .el-form-item__error {
position: static;
padding-top: 6px;
line-height: 1.25;
font-size: 12px;
}
::v-deep .el-input__inner {
height: 40px;
border-radius: 10px;
border: 1px solid #e5e7eb;
}
::v-deep .el-form-item.is-error .el-input__inner,
::v-deep .el-form-item.is-error .el-textarea__inner {
border-color: #f56c6c;
}
::v-deep .el-textarea__inner {
border-radius: 10px;
border: 1px solid #e5e7eb;
}
}
.option-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.option-item {
margin-right: 0 !important;
padding: 9px 8px;
border: 1px solid #e5e7eb;
border-radius: 10px;
white-space: nowrap;
::v-deep .el-radio__label {
padding-left: 6px;
font-size: 12px;
color: #374151;
}
}
.full-select {
width: 100%;
}
.direction-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.direction-item {
margin-right: 0 !important;
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 10px;
display: flex;
align-items: flex-start;
::v-deep .el-checkbox__label {
display: inline-flex;
flex-direction: column;
gap: 2px;
padding-left: 8px;
}
}
.direction-main {
font-size: 14px;
color: #111827;
line-height: 1.2;
}
.direction-sub {
font-size: 12px;
color: #9ca3af;
line-height: 1.2;
}
.qrcode-area {
margin: 16px 0;
text-align: center;
padding: 18px 0;
background: #f7f8fa;
border-radius: 12px;
p {
margin: 0 0 10px;
font-size: 12px;
color: #6b7280;
}
img {
width: 98px;
height: 98px;
border-radius: 8px;
border: 1px dashed #3b82f6;
}
}
.privacy-checkbox {
margin: 0 0 14px;
display: flex;
align-items: flex-start;
::v-deep .el-checkbox__input {
margin-top: 3px;
}
::v-deep .el-checkbox__label {
padding-left: 8px;
color: #6b7280;
font-size: 12px;
line-height: 1.6;
white-space: normal;
}
}
.submit-btn {
width: 100%;
height: 44px;
border: none;
border-radius: 999px;
background: #111827;
color: #fff;
font-size: 16px;
font-weight: 600;
}
</style>

View File

@ -1,3 +1,4 @@
<!-- 产品咨询弹窗公众号 -->
<template>
<el-dialog
:title="title"

View File

@ -0,0 +1,43 @@
<template>
<div class="consult-full-preview">
<div class="preview-actions">
<el-button type="primary" @click="dialogVisible = true">打开全屏咨询弹窗</el-button>
</div>
<mobile-full-screen-consult-dialog
:visible.sync="dialogVisible"
:current-url="currentUrl"
/>
</div>
</template>
<script>
import MobileFullScreenConsultDialog from '../components/H5_dialog/MobileFullScreenConsultDialog.vue'
export default {
name: 'ConsultDialogFullPreview',
components: { MobileFullScreenConsultDialog },
data() {
return {
dialogVisible: true
}
},
computed: {
currentUrl() {
return window.location.href
}
}
}
</script>
<style scoped lang="less">
.consult-full-preview {
min-height: 100%;
}
.preview-actions {
padding: 12px;
display: flex;
justify-content: center;
}
</style>

View File

@ -38,7 +38,7 @@
</div>
</el-form>
<el-checkbox class="agreement-checkbox" v-model="checked">
勾选表示您同意<span v-if="JSON.stringify(logoInfoNew)!=='{}'">{{ logoInfoNew.home.bannerTitle }}</span>及其授权的合作伙伴通过您填写的联系方式联系您且数据仅用于与您沟通当您注销平台账号后您的数据会被销毁
勾选表示您同意<span v-if="JSON.stringify(logoInfoNew)!=='{}'">{{ logoInfoNew.home.bannerTitle }}</span>及其授权的合作伙伴通过您填写的联系方式联系您
</el-checkbox>
</div>
<div class="qcode">

View File

@ -13,13 +13,13 @@
<h2 v-if="$t('home.heroSubtitle')">{{ $t('home.heroSubtitle') }}</h2>
<p>{{ $t('home.heroSlogan') }}</p>
<div class="hero-actions">
<button type="button" class="use-btn">{{ $t('home.solutionsBtn') }}</button>
<button type="button" class="use-btn" @click="goSolution">{{ $t('home.solutionsBtn') }}</button>
<div class="outline-btn" @click="contactSales">{{ $t('home.contactSalesBtn') }}</div>
</div>
</div>
</section>
<!-- AI+解决方案 -->
<section class="ai-solution-section" @mouseenter="stopSolutionCarousel" @mouseleave="startSolutionCarousel">
<section id="ai-solutions-section" class="ai-solution-section" @mouseenter="stopSolutionCarousel" @mouseleave="startSolutionCarousel">
<div class="ai-solution-inner">
<h2 class="section-title">{{ $t('home.aiSolutionsTitle') }}</h2>
<div class="solution-carousel" :class="{ 'is-rotating': isSolutionRotating }">
@ -426,7 +426,12 @@ export default {
})
},
goSolution() {
this.navigateTo('/homePage/opc')
const target = this.$el && this.$el.querySelector('#ai-solutions-section')
if (!target) return
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
})
},
contactSales() {
this.$store.commit('setShowTalk', true)
@ -656,6 +661,7 @@ body.dark-theme .bg-orb {
.ai-solution-section {
position: relative;
padding: 60px 24px 110px;
scroll-margin-top: 90px;
overflow: hidden;
background: transparent;
}

View File

@ -1,26 +1,19 @@
<template>
<div class="model-page">
<!-- 筛选区 -->
<model-filter
<!-- 统计区 -->
<model-stats :stats="modelStats" />
<!-- 筛选区 -->
<model-filter
:search-form="searchForm"
:model-type-options="modelTypeOptions"
:provider-options="providerOptions"
@search="handleSearch"
@reset="resetSearch"
/>
<!-- 统计区 -->
<model-stats :stats="modelStats" />
<!-- 列表区 -->
<el-card class="model-table-card" shadow="never">
<div class="table-header">
<div>
<h3>模型列表</h3>
<p>展示模型基础信息支持上下架排序编辑等操作</p>
</div>
<el-button size="small" icon="el-icon-refresh" @click="fetchModelList">刷新</el-button>
</div>
<el-tabs v-model="activeStatus" class="model-status-tabs" @tab-click="handleTabChange">
<el-tab-pane label="待上架" name="pending" />

View File

@ -1,202 +1,478 @@
<template>
<div class="consulting-container">
<!-- 操作栏 -->
<div class="operation-bar">
<el-button
type="primary"
icon="el-icon-upload2"
size="small"
@click="getTableData('1')"
class="export-btn"
>
全部导出
</el-button>
<div class="page-section active" id="page-consultation-form">
<div class="page-padding">
<div class="title-wrap">
<h1 class="page-title">咨询表单</h1>
</div>
<!-- 表格区域 -->
<div class="table-container">
<div class="stats-grid">
<div class="stats-card">
<p class="stats-label">总咨询量</p>
<p class="stats-value stats-value-dark">{{ formatCount(totalCount) }}</p>
</div>
<div class="stats-card">
<p class="stats-label">微信公众号渠道</p>
<p class="stats-value stats-value-green">{{ formatCount(wechatCount) }}</p>
</div>
<div class="stats-card">
<p class="stats-label">官网渠道</p>
<p class="stats-value stats-value-purple">{{ formatCount(webCount) }}</p>
</div>
<div class="stats-card">
<p class="stats-label">待回复</p>
<p class="stats-value stats-value-orange">{{ formatCount(pendingCount) }}</p>
</div>
</div>
<div class="filter-card">
<el-form :model="queryForm" inline class="filter-form">
<el-form-item label="姓名">
<el-input v-model.trim="queryForm.name" size="small" clearable placeholder="请输入姓名" @keyup.enter.native="handleSearch" />
</el-form-item>
<el-form-item label="手机号">
<el-input v-model.trim="queryForm.phone" size="small" clearable placeholder="请输入手机号" @keyup.enter.native="handleSearch" />
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model.trim="queryForm.email" size="small" clearable placeholder="请输入邮箱" @keyup.enter.native="handleSearch" />
</el-form-item>
<el-form-item label="渠道来源">
<el-select v-model="queryForm.source" size="small" clearable placeholder="全部渠道">
<el-option label="全部渠道" value="" />
<el-option v-for="item in sourceList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="回复状态">
<el-select v-model="queryForm.feedback" size="small" clearable placeholder="全部状态">
<el-option label="全部状态" value="" />
<el-option v-for="item in feedbackOptions" :key="item.id" :label="item.name" :value="String(item.id)" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleSearch">查询</el-button>
<el-button size="small" @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="action-bar">
<el-button
type="success"
icon="el-icon-download"
size="small"
:loading="exportLoading"
@click="getTableData('1')"
>
导出Excel
</el-button>
<div class="record-count"> {{ totalCount }} 条记录</div>
</div>
<div class="table-card">
<el-table
v-loading="tableLoading"
:data="tableData"
border
stripe
style="width: 100%"
class="consulting-table"
empty-text="暂无咨询数据"
:header-cell-style="{background: '#f5f7fa', color: '#606266'}"
>
<el-table-column
prop="name"
label="姓名"
min-width="120"
align="center"
header-align="center"
>
</el-table-column>
<el-table-column
prop="phone"
label="手机号"
min-width="140"
align="center"
header-align="center"
>
</el-table-column>
<el-table-column
prop="email"
label="邮箱"
min-width="180"
align="center"
header-align="center"
>
</el-table-column>
<el-table-column
prop="content"
label="内容"
min-width="200"
align="center"
header-align="center"
show-overflow-tooltip
>
</el-table-column>
<el-table-column
prop="update_time"
label="咨询时间"
min-width="160"
align="center"
header-align="center"
>
</el-table-column>
<el-table-column
label="反馈状态"
min-width="120"
align="center"
header-align="center"
style="width: 100%"
>
<el-table-column label="ID" width="80" align="center">
<template slot-scope="scope">
<el-tag
:type="scope.row.feedback === '1' ? 'success' : 'info'"
size="small"
effect="plain"
>
{{ scope.row.feedback === '1' ? '已回复' : '未回复' }}
{{ getRowNo(scope.$index) }}
</template>
</el-table-column>
<el-table-column prop="name" label="姓名" min-width="90" />
<el-table-column prop="phone" label="手机号" min-width="120" />
<el-table-column prop="email" label="邮箱" min-width="180" show-overflow-tooltip />
<el-table-column prop="company" label="公司名称" min-width="140" show-overflow-tooltip />
<el-table-column prop="source" label="咨询渠道" min-width="120" show-overflow-tooltip />
<el-table-column prop="consult_direction_name" label="咨询方向" min-width="220" show-overflow-tooltip />
<el-table-column prop="update_time" label="提交时间" min-width="160" />
<el-table-column label="回复状态" min-width="110" align="center">
<template slot-scope="scope">
<el-tag size="mini" effect="plain" :type="feedbackTagType(scope.row.feedback)">
{{ feedbackText(scope.row) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="small">查看</el-button>
<el-button type="text" size="small" @click="replyUser(scope.row)">回复</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="pager-wrap">
<div class="pager-info">显示 {{ pageStart }}-{{ pageEnd }} {{ totalCount }} </div>
<el-pagination
background
layout="prev, pager, next"
:total="totalCount"
:page-size="pageSize"
:current-page.sync="page"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</template>
<script>
import { reqSearchUserInquiry } from '@/api/ncmatch'
import XLSX from 'xlsx'
export default {
name: 'ConsultingMangement',
data() {
return {
tableData: []
tableData: [],
tableLoading: false,
exportLoading: false,
page: 1,
pageSize: 10,
totalCount: 0,
sourceList: [],
queryForm: {
name: '',
phone: '',
email: '',
source: '',
feedback: ''
},
feedbackOptions: [
{ id: 0, name: '待回复' },
{ id: 1, name: '沟通中' },
{ id: 2, name: '已关闭' },
{ id: 3, name: '号码错误' }
]
}
},
computed: {
pageStart() {
if (!this.tableData.length) return 0
return (this.page - 1) * this.pageSize + 1
},
pageEnd() {
if (!this.tableData.length) return 0
return (this.page - 1) * this.pageSize + this.tableData.length
},
wechatCount() {
return this.tableData.filter(item => /微信|云宝/.test(String(item.source || ''))).length
},
webCount() {
return this.tableData.filter(item => !/微信|云宝/.test(String(item.source || ''))).length
},
pendingCount() {
return this.tableData.filter(item => String(item.feedback) === '0').length
}
},
created() {
this.getTableData('0')
},
methods: {
formatCount(value) {
return Number(value || 0).toLocaleString('en-US')
},
feedbackText(row) {
if (row.feedback_name) return row.feedback_name
const found = this.feedbackOptions.find(item => String(item.id) === String(row.feedback))
return found ? found.name : '待回复'
},
feedbackTagType(feedback) {
const value = String(feedback)
if (value === '2') return 'success'
if (value === '1') return ''
if (value === '3') return 'info'
return 'warning'
},
getRowNo(index) {
return (this.page - 1) * this.pageSize + index + 1
},
buildSearchParams(to_excel) {
return {
url_link: window.location.href.split('#')[0] || window.location.href,
to_excel: String(to_excel),
page: String(this.page),
page_size: String(this.pageSize),
name: (this.queryForm.name || '').trim(),
phone: (this.queryForm.phone || '').trim(),
email: (this.queryForm.email || '').trim(),
source: this.queryForm.source || '',
feedback: this.queryForm.feedback === null || this.queryForm.feedback === undefined
? ''
: String(this.queryForm.feedback)
}
},
extractRows(res) {
const data = (res && res.data) || {}
if (Array.isArray(res && res.data)) return res.data
if (Array.isArray(data.list)) return data.list
if (Array.isArray(data.data)) return data.data
if (Array.isArray(data.rows)) return data.rows
if (Array.isArray(data.records)) return data.records
return []
},
extractTotal(res, listLength) {
const data = (res && res.data) || {}
const total = Number(
data.total_count ||
data.total ||
data.count ||
data.totalCount ||
res.total_count ||
res.total ||
res.count
)
return Number.isFinite(total) && total >= 0 ? total : listLength
},
extractSourceList(res, rows = []) {
const data = (res && res.data) || {}
const fromApi = data.source_list || data.sourceList || res.source_list || res.sourceList
if (Array.isArray(fromApi) && fromApi.length) return fromApi
return [...new Set(rows.map(item => String(item.source || '').trim()).filter(Boolean))]
},
getTableData(to_excel) {
reqSearchUserInquiry({ url_link: window.location.href, to_excel: to_excel }).then(res => {
console.log(res)
if (res.status) {
const params = this.buildSearchParams(to_excel)
if (to_excel === '0') this.tableLoading = true
if (to_excel === '1') this.exportLoading = true
reqSearchUserInquiry(params).then(res => {
if (!res || !res.status) {
this.$message.warning((res && res.msg) || '暂无咨询数据')
return
}
const rows = this.extractRows(res)
if (to_excel === '0') {
this.tableData = res.data
} else if (to_excel === '1') {
// Workbook
const wb = XLSX.utils.book_new();
// sheet
const ws1 = XLSX.utils.json_to_sheet(res.data);
XLSX.utils.book_append_sheet(wb, ws1, 'sheet1');
// Excel
XLSX.writeFile(wb, '咨询列表.xlsx');
this.tableData = rows
this.totalCount = this.extractTotal(res, rows.length)
this.sourceList = this.extractSourceList(res, rows)
return
}
if (!rows.length) {
this.$message.warning('暂无可导出的数据')
return
}
const wb = XLSX.utils.book_new()
const ws1 = XLSX.utils.json_to_sheet(rows)
XLSX.utils.book_append_sheet(wb, ws1, 'sheet1')
XLSX.writeFile(wb, '咨询列表.xlsx')
this.$message.success('导出成功')
}
}
}).catch(() => {
this.$message.error('咨询列表加载失败,请稍后重试')
}).finally(() => {
this.tableLoading = false
this.exportLoading = false
})
},
handleSearch() {
this.page = 1
this.getTableData('0')
},
resetSearch() {
this.queryForm = {
name: '',
phone: '',
email: '',
source: '',
feedback: ''
}
this.page = 1
this.getTableData('0')
},
handleCurrentChange(page) {
this.page = page
this.getTableData('0')
},
async replyUser(row) {
if (row.email) {
window.location.href = `mailto:${row.email}`
return
}
if (row.phone) {
try {
await navigator.clipboard.writeText(row.phone)
this.$message.success('手机号已复制,可直接联系用户')
} catch (error) {
this.$message.warning('联系方式复制失败,请手动联系')
}
}
}
}
}
</script>
<style scoped>
.consulting-container {
padding: 20px;
background: #f0f2f5;
.page-section {
min-height: calc(100vh - 84px);
background: #eef3f9;
}
.operation-bar {
.page-padding {
padding: 24px;
}
.title-wrap {
margin-bottom: 24px;
}
.page-title {
margin: 0;
color: #1f2937;
font-size: 30px;
font-weight: 700;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stats-card {
background: #fff;
padding: 16px 20px;
margin-bottom: 20px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border: 1px solid #edf0f5;
border-radius: 14px;
padding: 16px;
}
.export-btn {
font-weight: 500;
padding: 8px 15px;
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
transition: all 0.3s ease;
.stats-label {
margin: 0;
color: #6b7280;
font-size: 13px;
}
.export-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(64, 158, 255, 0.4);
.stats-value {
margin: 6px 0 0;
font-size: 34px;
line-height: 1;
font-weight: 700;
}
.table-container {
.stats-value-dark {
color: #1f2937;
}
.stats-value-green {
color: #16a34a;
}
.stats-value-purple {
color: #9333ea;
}
.stats-value-orange {
color: #ea580c;
}
.filter-card {
background: #fff;
padding: 20px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border: 1px solid #edf0f5;
border-radius: 14px;
padding: 16px;
margin-bottom: 24px;
}
.consulting-table {
font-size: 14px;
.filter-form {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
}
.filter-form ::v-deep .el-form-item {
margin-right: 14px;
margin-bottom: 0;
}
.filter-form ::v-deep .el-form-item__label {
color: #4b5563;
font-size: 13px;
}
.filter-form ::v-deep .el-input,
.filter-form ::v-deep .el-select {
width: 180px;
}
.filter-form ::v-deep .el-input__inner {
border-radius: 10px;
}
.filter-form ::v-deep .el-button {
border-radius: 10px;
}
.action-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.record-count {
color: #6b7280;
font-size: 13px;
}
.table-card {
background: #fff;
border: 1px solid #edf0f5;
border-radius: 14px;
overflow: hidden;
}
.consulting-table ::v-deep .el-table__header th {
background: #f9fafb;
color: #4b5563;
font-size: 13px;
font-weight: 600;
}
.consulting-table ::v-deep .el-table__body td {
color: #374151;
font-size: 13px;
}
/* 表格行悬停效果 */
.consulting-table ::v-deep .el-table__body tr:hover > td {
background-color: #f5f7fa !important;
background: #f9fafb;
}
/* 表格边框优化 */
.consulting-table ::v-deep .el-table--border {
border: 1px solid #ebeef5;
.pager-wrap {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 16px;
}
.consulting-table ::v-deep .el-table--border th,
.consulting-table ::v-deep .el-table--border td {
border-right: 1px solid #ebeef5;
.pager-info {
color: #6b7280;
font-size: 13px;
}
/* 表格斑马纹颜色 */
.consulting-table ::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td {
background-color: #fafafa;
@media (max-width: 1200px) {
.stats-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
/* 状态标签样式优化 */
.consulting-table ::v-deep .el-tag {
border-radius: 12px;
padding: 0 10px;
height: 24px;
line-height: 24px;
}
@media (max-width: 768px) {
.page-padding {
padding: 12px;
}
/* 内容过长时的省略号样式 */
.consulting-table ::v-deep .el-tooltip__popper {
max-width: 400px;
line-height: 1.5;
.stats-grid {
grid-template-columns: 1fr;
}
.pager-wrap {
flex-direction: column;
gap: 8px;
align-items: flex-start;
}
}
</style>