Browse Source

Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发

zhaowenkang/feature-20251028181547-行情页面
hongxilin 4 weeks ago
parent
commit
a7ed01953d
  1. 46
      api/customerServicePlatform/customerServicePlatform.js
  2. 196
      components/FeedbackModal.vue
  3. 28
      pages.json
  4. 687
      pages/customerServicePlatform/csPlatformIndex.vue
  5. 358
      pages/customerServicePlatform/historyRecord.vue
  6. 340
      pages/customerServicePlatform/questionDetail.vue
  7. BIN
      static/customer-service-platform/camera.png
  8. BIN
      static/customer-service-platform/cs-platform-back.png
  9. BIN
      static/customer-service-platform/ellipse-dc-img.png
  10. BIN
      static/customer-service-platform/empty-content.png
  11. BIN
      static/customer-service-platform/fail-icon.png
  12. BIN
      static/customer-service-platform/message.png
  13. BIN
      static/customer-service-platform/refresh-icon.png
  14. BIN
      static/customer-service-platform/robot-head.png
  15. BIN
      static/customer-service-platform/smile-icon.png
  16. BIN
      static/customer-service-platform/success-icon.png

46
api/customerServicePlatform/customerServicePlatform.js

@ -0,0 +1,46 @@
import { http } from '@/utils/http.js'
const baseURL = "http://39.101.133.168:8828"
//图片上传
export const uploadImageApi = (data) => {
return http({
method: 'POST',
url: baseURL +'/hljw/api/aws/upload',
data
})
}
//问题回答
export const getAnswerApi = (data) => {
return http({
method: 'POST',
url: 'http://pbb6edde.natappfree.cc' +'/api/customer/askQuestion',
data
})
}
//获取随机5条猜你想问问题
export const getQuestionApi = (data) => {
return http({
method: 'GET',
url: 'http://pbb6edde.natappfree.cc' +'/api/customer/getQuestion',
})
}
//反馈添加
export const addFeedbackRecordApi = (data) => {
return http({
method: 'POST',
url: baseURL +'/link/third/dcFeedBack/feedback/add',
data
})
}
//反馈历史记录
export const getFeedbackRecordsApi = (data) => {
return http({
method: 'POST',
url: baseURL+'/link/third/dcFeedBack/feedback/select',
data
})
}

196
components/FeedbackModal.vue

@ -0,0 +1,196 @@
<template>
<view>
<view v-if="internalVisible" class="fm-overlay"></view>
<view v-if="internalVisible" class="fm-wrap">
<view class="fm-card" :style="{ width: _width }">
<text class="fm-title">{{ _title }}</text>
<view class="fm-icon-wrap">
<image :src="imgSrcComputed" mode="aspectFit" class="fm-icon-img" />
</view>
<text class="fm-sub">{{ _subtitle }}</text>
<view class="fm-btn-wrap">
<button class="fm-btn" @click="onConfirm">{{ _buttonText }}</button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'FeedbackModal',
props: {
title: {
type: String,
default: '提交成功'
},
subtitle: {
type: String,
default: '— 感谢您的反馈 —'
},
buttonText: {
type: String,
default: '确定'
},
status: {
type: String,
default: 'success'
},
lockScroll: {
type: Boolean,
default: true
},
width: {
type: String,
default: '600rpx'
}
},
data() {
return {
internalVisible: false,
_title: this.title,
_subtitle: this.subtitle,
_buttonText: this.buttonText,
_status: this.status,
_width: this.width,
};
},
computed: {
imgSrcComputed() {
return this._status === 'fail' ?
'/static/customer-service-platform/fail-icon.png' :
'/static/customer-service-platform/success-icon.png';
}
},
methods: {
show(options = {}) {
if (options.title) this._title = options.title;
if (options.subtitle) this._subtitle = options.subtitle;
if (options.buttonText) this._buttonText = options.buttonText;
if (options.status) this._status = options.status;
if (options.width) this._width = options.width;
if (this.lockScroll) this._lockScroll();
this.internalVisible = true;
},
hide() {
this.internalVisible = false;
if (this.lockScroll) this._unlockScroll();
},
onConfirm() {
this.hide();
this.$nextTick(() => {
this.$emit('confirm', {
status: this._status
});
});
},
//
_lockScroll() {
try {
document.body.style.overflow = 'hidden';
} catch (e) {}
},
_unlockScroll() {
try {
document.body.style.overflow = '';
} catch (e) {}
}
},
beforeDestroy() {
if (this.lockScroll) this._unlockScroll();
}
};
</script>
<style scoped>
.fm-overlay {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.35);
z-index: 999;
}
.fm-wrap {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 40rpx;
box-sizing: border-box;
}
.fm-card {
background: #fff;
border-radius: 24rpx;
padding: 40rpx;
box-sizing: border-box;
text-align: center;
box-shadow: 0 6rpx 30rpx rgba(0, 0, 0, 0.12);
}
.fm-title {
display: block;
font-size: 48rpx;
font-weight: 700;
color: #333333;
margin-bottom: 40rpx;
}
.fm-icon-wrap {
width: 200rpx;
height: 200rpx;
margin: 0 auto 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.fm-icon-img {
width: 100%;
height: 100%;
}
.fm-sub {
display: block;
color: #666666;
font-size: 32rpx;
margin-bottom: 40rpx;
font-weight: 500;
}
.fm-btn-wrap {
display: flex;
justify-content: center;
}
.fm-btn {
width: 220rpx;
height: 60rpx;
border-radius: 32rpx;
background: #000;
color: #ffffff;
font-weight: 700;
font-size: 32rpx;
font-style: normal;
border: none;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
}
</style>

28
pages.json

@ -306,7 +306,35 @@
{ {
"navigationBarTitleText" : "创建密码" "navigationBarTitleText" : "创建密码"
} }
},
{
"path": "pages/customerServicePlatform/csPlatformIndex",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/customerServicePlatform/historyRecord",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/customerServicePlatform/questionDetail",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
} }
], ],
"globalStyle": { "globalStyle": {
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",

687
pages/customerServicePlatform/csPlatformIndex.vue

@ -0,0 +1,687 @@
<template>
<view class="main">
<view class="top" :style="{height:iSMT+'px'}"></view>
<!-- 头部导航 -->
<view class="header">
<view class="back-icon">
<image @click="onBack" src="/static/customer-service-platform/cs-platform-back.png"
class="header-icon-image"></image>
</view>
<view class="title">{{headerTitle}}</view>
<view class="notification-icon">
<image src="/static/customer-service-platform/message.png" class="header-icon-image"></image>
</view>
</view>
<!-- 内容区域 - 使用滚动视图 -->
<scroll-view scroll-y class="content-container">
<view class="content-header">
<view class="content-header-area">
<view class="logo">
<image mode="aspectFit" src="/static/customer-service-platform/ellipse-dc-img.png"></image>
</view>
<view class="greeting">
<text class="greet-title">我能为你做点什么</text>
<text class="greet-sub">DeepChart随时为您提供服务</text>
</view>
</view>
</view>
<!--猜你想问卡片部分-->
<view class="card">
<view class="suggest-header">
<text class="suggest-title">猜你想问</text>
<view class="swap" @click="getQuestionList()">
<image class="swap-icon" src="/static/customer-service-platform/refresh-icon.png"></image>
<text class="swap-title">换一换</text>
</view>
</view>
<view class="card-line"></view>
<view class="suggest-list">
<view class="suggest-item" v-for="(q, idx) in showQuestions" :key="idx" @click="onQuestionClick(q)">
<view class="left">
<view :class="['num', 'num-' + ((idx % 5) + 1)]">{{ idx + 1 }}</view>
<text class="q-text">{{ q }}</text>
</view>
<view class="right">
<text class="arrow"></text>
</view>
</view>
</view>
</view>
<!--反馈卡片部分-->
<text class="feedback-card-title">反馈中心</text>
<view class="card">
<view class="suggest-header">
<text class="feedback-title">填写反馈内容</text>
</view>
<view class="card-line"></view>
<textarea class="feedback-input" placeholder="请描述您想反馈的内容 最多可输入200字" maxlength="200"
v-model="feedbackText" />
<view class="meta-row">
<text class="char-count">{{ feedbackText.length }}/200</text>
</view>
<view class="suggest-header">
<text class="upload-img-tip">上传图片</text>
</view>
<view class="upload-row">
<view class="img-slot" v-for="(img, index) in images" :key="index">
<image :src="img" mode="scaleToFill" class="slot-img" />
<button v-if="img" class="remove" @click="removeImage(index)">×</button>
</view>
<view class="img-slot" v-if="images.length < 3">
<view class="slot-empty" @click="chooseImage()">
<image src="/static/customer-service-platform/camera.png" class="camera-icon" />
</view>
</view>
<text class="tip-text" v-if="images.length === 0">最多添加3张图片</text>
</view>
<button class="feedback-btn" @click="onSumbitFeedback()">提交</button>
<feedback-modal ref="feedback" @confirm="onConfirm" />
</view>
<!--历史反馈卡片部分-->
<view class="card">
<text class="feedback-title">历史反馈内容</text>
<view class="card-line"></view>
<button class="feedback-btn" @click="viewHistory">查看</button>
</view>
</scroll-view>
</view>
</template>
<script>
import { useUserStore } from "../../stores/modules/userInfo.js"
import {
getQuestionApi,
addFeedbackRecordApi,
uploadImageApi
} from "../../api/customerServicePlatform/customerServicePlatform";
import FeedbackModal from '@/components/FeedbackModal.vue'
export default {
components: {
FeedbackModal
},
data() {
return {
headerTitle: '智能客服中台',
iSMT: 0,
questions: [
"DeepChart 有免费功能和付费功能的区分吗?具体有哪些?",
"如何参与平台的用户反馈活动?反馈的问题会被采纳吗?",
"我的自选股最多能添加多少只?能否按市场分类管理?",
"注册时必须提供手机号 / 邮箱吗?能否匿名使用?",
"忘记登录密码了,如何找回?",
'如何注册账号?',
'为什么无法注册账户?'
],
showQuestions: [],
feedbackText: '',
images: [],
token:''
}
},
mounted() {
//
this.iSMT = uni.getSystemInfoSync().statusBarHeight;
this.getQuestionList()
const memberStore = useUserStore()
this.token = memberStore.userInfo?.token
},
methods: {
onSuccess() {
this.$refs.feedback.show({
status: 'success',
title: '提交成功',
subtitle: '— 感谢您的反馈 —',
buttonText: '确定',
width: '80%',
});
},
onFail() {
this.$refs.feedback.show({
status: 'fail',
title: '提交失败',
subtitle: '— 请重新提交 —',
buttonText: '确定',
width: '80%',
});
},
onBack() {
if (typeof uni !== 'undefined') uni.navigateBack();
},
async getQuestionList() {
const res = await getQuestionApi()
console.log(res)
if (res.code == 200) {
this.showQuestions = res.data
}
},
onQuestionClick(q) {
if (typeof uni !== 'undefined') uni.navigateTo({
url: `/pages/customerServicePlatform/questionDetail?question=${encodeURIComponent(q)}`
});
},
chooseImage() {
const that = this;
if (typeof uni === 'undefined' || !uni.chooseImage) return;
const remain = 3 - (that.images ? that.images.length : 0);
if (remain <= 0) {
if (typeof uni !== 'undefined') uni.showToast({
title: '最多只能上传3张',
icon: 'none'
});
return;
}
uni.chooseImage({
count: remain,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
const paths = res.tempFilePaths || (res.tempFiles && res.tempFiles.map(f => f.path)) || [];
for (let p of paths) {
if (that.images.length < 3) {
that.images.push(p);
}
}
},
fail(err) {
uni.showToast({
title: `选择图片失败`,
icon: 'none'
});
}
});
},
removeImage(index) {
//
this.images.splice(index, 1);
},
async onSumbitFeedback() {
if (!this.feedbackText.trim()) {
if (typeof uni !== 'undefined') uni.showToast({
title: '请填写反馈内容',
icon: 'none'
});
return;
}
if (typeof uni !== 'undefined') uni.showLoading({
title: '提交中...'
});
try {
let uploadedImages = [];
let imgFlag = true
for (let i = 0; i < this.images.length; i++) {
const f = this.images[i];
await new Promise((resolve, reject) => {
uni.getImageInfo({
src: f,
success: () => resolve(),
fail: reject
});
});
const uploadRes = await new Promise((resolve, reject) => {
uni.uploadFile({
url: 'http://39.101.133.168:8828/hljw/api/aws/upload',
filePath: f,
name: 'file',
formData: {
dir: 'deepchart'
},
success: (res) => {
try {
const data = JSON.parse(res.data);
if (data.code === 200) {
uploadedImages.push(data.data.url);
resolve(data);
} else {
uni.showToast({
title: `${i + 1}张图片上传失败`,
icon: 'none'
});
imgFlag = false;
reject(data);
}
} catch (err) {
imgFlag = false;
reject(err);
}
},
fail: (err) => {
imgFlag = false;
uni.showToast({
title: `${i + 1}张图片上传失败`,
icon: 'none'
});
reject(err);
}
});
});
}
if (!imgFlag) {
return
}
const [image1 = '', image2 = '', image3 = ''] = uploadedImages;
const res = await addFeedbackRecordApi({
token: this.token,
content: this.feedbackText,
image1,
image2,
image3
})
if (res.code == 200) {
this.onSuccess()
} else {
this.onFail()
}
} catch {
this.onFail()
} finally {
uni.hideLoading();
this.feedbackText = '';
this.images = [];
}
},
viewHistory() {
//
if (typeof uni !== 'undefined') uni.navigateTo({
url: '/pages/customerServicePlatform/historyRecord'
});
}
}
}
</script>
<style scoped>
.main {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #ffffff;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
}
.title {
color: #000000;
text-align: center;
font-size: 32rpx;
font-style: normal;
font-weight: 400;
}
.back-icon,
.notification-icon {
width: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header-icon-image {
width: 40rpx;
height: 40rpx;
object-fit: contain;
}
.content-container {
padding: 20rpx;
width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
.content-header {
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
padding: 0 60rpx;
width: 100%;
box-sizing: border-box;
height: 188rpx;
}
.content-header-area {
display: flex;
gap: 20rpx;
}
.logo {
width: 120rpx;
height: 120rpx;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 112rpx;
}
.greeting {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1 1 auto;
}
.greet-title {
color: #000;
font-size: 40rpx;
font-style: normal;
font-weight: 500;
line-height: normal;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.greet-sub {
color: #838383;
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
margin-top: 12rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card {
width: 90%;
margin: 0 auto;
border-radius: 16rpx;
padding: 20rpx 40rpx;
box-sizing: border-box;
border-radius: 12rpx;
border: 4rpx solid #FCC8D4;
background: linear-gradient(180deg, #FCC8D3 0%, #FEF0F3 30%, #FFF 100%);
margin-bottom: 20rpx;
}
.suggest-header {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.suggest-title {
color: #000000;
font-size: 32rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.swap {
display: flex;
align-items: center;
transition: transform 0.1s ease, background-color 0.1s ease;
}
.swap:active {
transform: scale(0.95);
}
.swap-icon {
width: 30rpx;
height: 30rpx;
}
.swap-title {
padding-left: 8rpx;
color: #000000;
font-size: 24rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.suggest-list {
margin-top: 20rpx;
}
.card-line {
margin-top: 20rpx;
width: 100%;
height: 2rpx;
border-radius: 2rpx;
background: #FFF;
}
.suggest-item {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 10rpx;
border-radius: 12rpx;
margin-bottom: 20rpx;
width: 100%;
box-sizing: border-box;
}
.left {
width: 90%;
display: flex;
align-items: center;
}
.num {
font-size: 40rpx;
font-style: normal;
font-weight: 700;
line-height: normal;
}
.num-1 {
color: #df5662;
}
.num-2 {
color: #ec6d4f;
}
.num-3 {
color: #f3ba40;
}
.num-4 {
color: #9296a0;
}
.num-5 {
color: #9296a0;
}
.q-text {
padding-left: 14rpx;
display: block;
color: #333;
font-size: 28rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.right {
width: 48rpx;
display: flex;
align-items: center;
justify-content: flex-end;
}
.arrow {
color: #cfcfcf;
font-size: 36rpx;
}
.suggest-item:active {
background: rgba(255, 77, 128, 0.06);
}
.feedback-card-title {
display: flex;
justify-content: center;
color: #000000;
font-size: 32rpx;
font-weight: 700;
line-height: 40rpx;
width: 100%;
margin-bottom: 20rpx;
}
.feedback-title {
color: #000000;
font-size: 32rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.feedback-input {
width: 100%;
display: flex;
padding: 20rpx;
flex-direction: column;
box-sizing: border-box;
align-items: flex-start;
gap: 12rpx;
align-self: stretch;
border-radius: 12rpx;
border: 2rpx solid #F0F1F1;
margin-top: 20rpx;
display: flex;
background: #FFF;
color: #8a8a8a;
font-size: 24rpx;
font-weight: 700;
line-height: normal;
}
.meta-row {
display: flex;
justify-content: flex-end;
margin-top: 12rpx;
}
.char-count {
color: #999
}
.upload-img-tip {
color: #000000;
font-size: 24rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.upload-row {
display: flex;
justify-content: flex-start;
align-items: flex-start;
align-content: flex-start;
flex-wrap: wrap;
gap: 20rpx;
margin-top: 20rpx;
width: 100%;
}
.img-slot {
width: calc((100% - 2 * 30rpx) / 3);
aspect-ratio: 1 / 1;
border-radius: 6px;
border: 1px solid #F0F1F1;
background: #FFF;
position: relative;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
transition: transform 0.2s ease;
}
.slot-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.camera-icon {
width: 34rpx;
height: 34rpx;
}
.slot-img {
width: 100%;
height: 100%;
border-radius: 16rpx;
}
.remove {
position: absolute;
right: 6rpx;
top: 6rpx;
border-radius: 50%;
background: #fd5c58;
padding: 0;
color: #fff;
width: 36rpx;
height: 36rpx;
font-size: 28rpx;
line-height: 36rpx;
text-align: center;
border: none;
outline: none;
cursor: pointer;
}
.remove:active {
background: rgba(0, 0, 0, 0.75);
}
.image-upload-tip {
display: flex;
align-items: center;
}
.tip-text {
color: #999999;
font-size: 24rpx;
font-style: normal;
font-weight: 400;
line-height: 40rpx;
padding-top: 64rpx;
}
.feedback-btn {
margin-top: 24rpx;
width: 180rpx;
height: 60rpx;
aspect-ratio: 89/30;
border-radius: 30rpx;
background: #090A08;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
font-style: normal;
font-weight: 700;
line-height: normal;
}
</style>

358
pages/customerServicePlatform/historyRecord.vue

@ -0,0 +1,358 @@
<template>
<view class="main">
<view class="top" :style="{height:iSMT+'px'}"></view>
<!-- 头部导航 -->
<view class="header">
<view class="back-icon">
<image @click="goBack()" src="/static/customer-service-platform/cs-platform-back.png"
class="header-icon-image"></image>
</view>
<view class="title">智能客服中台</view>
<view class="notification-icon">
<image src="/static/customer-service-platform/message.png" class="header-icon-image"></image>
</view>
</view>
<!-- 内容区域 - 使用滚动视图 -->
<scroll-view scroll-y class="content-container">
<view class="list-wrapper" v-if="historyList.length > 0">
<view class="content-header">
<text class="content-title">历史反馈内容</text>
</view>
<view class="card-line"></view>
<view v-for="(item, idx) in historyList" :key="item.id" class="history-item card">
<view class="item-line" v-if="idx != 0"></view>
<view class="item-head">
<view class="dot-outer">
<view class="dot-inner"></view>
</view>
<text class="feedback-time">{{ formatTime(item.createdAt) }}</text>
<text class="feedback-status">
{{ statusText }}
<image class="smile-icon" src="/static/customer-service-platform/smile-icon.png"></image>
</text>
</view>
<view class="content-box">
<text class="content-text">{{ item.content }}</text>
<text class="count">{{ item.content.length }}/200</text>
</view>
<view v-if="item.images && item.images.length" class="thumb-row">
<view v-for="(img, i) in item.images" :key="i" class="thumb-slot"
@click="previewImage(item.images, i)">
<image :src="img" mode="scaleToFill" class="thumb-img" />
</view>
</view>
</view>
</view>
<!-- 如果没有历史显示空态 -->
<view v-if="historyList.length === 0" class="empty">
<image mode="aspectFit" class="empty-img" src="/static/customer-service-platform/empty-content.png">
</image>
<text class="empty-tip">暂无内容~</text>
</view>
</scroll-view>
</view>
</template>
<script>
import { useUserStore } from "../../stores/modules/userInfo.js"
import {
getFeedbackRecordsApi,
} from "../../api/customerServicePlatform/customerServicePlatform";
export default {
data() {
return {
iSMT: 0,
statusText: '反馈成功',
historyList: [],
token:''
};
},
mounted() {
this.iSMT = uni.getSystemInfoSync().statusBarHeight;
this.loadHistoryList()
const memberStore = useUserStore()
this.token = memberStore.userInfo?.token
},
methods: {
formatTime(str) {
if (!str) return '';
const d = new Date(str);
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
const hh = String(d.getHours()).padStart(2, '0');
const mi = String(d.getMinutes()).padStart(2, '0');
return `${yyyy}-${mm}-${dd} ${hh}:${mi}`;
},
goBack() {
if (typeof uni !== 'undefined' && uni.navigateBack) {
uni.navigateBack();
} else {
window.history.back();
}
},
async loadHistoryList() {
const res = await getFeedbackRecordsApi({
token: this.token
})
console.log(res)
if (res.code == 200) {
this.historyList = res.data.map(item => {
const images = [item.image1, item.image2, item.image3].filter(img => !!img)
return {
id: item.id,
createdAt: item.createdAt,
content: item.content,
images,
dccode: item.dccode
}
})
}
},
previewImage(list, index) {
if (typeof uni !== 'undefined' && uni.previewImage) {
uni.previewImage({
current: list[index],
urls: list
});
} else {
window.open(list[index], '_blank');
}
}
}
};
</script>
<style scoped>
.main {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #ffffff;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
}
.title {
color: #000000;
text-align: center;
font-size: 32rpx;
font-style: normal;
font-weight: 400;
}
.back-icon,
.notification-icon {
width: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header-icon-image {
width: 40rpx;
height: 40rpx;
object-fit: contain;
}
.content-container {
padding: 20rpx;
padding-top: 0;
width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
/* 列表包装器,居中卡片 */
.list-wrapper {
width: 90%;
margin: 0 auto;
padding: 20rpx 40rpx;
flex-direction: column;
align-items: center;
gap: 20rpx;
box-sizing: border-box;
border-radius: 12rpx;
border: 4rpx solid #FCC8D4;
background: linear-gradient(180deg, #FCC8D3 0%, #FEF0F3 30%, #FFF 100%);
}
.content-header {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.content-title {
color: #000000;
font-size: 32rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.card-line {
margin-top: 20rpx;
width: 100%;
height: 2rpx;
border-radius: 2rpx;
background: #FFF;
}
/* 每一条历史卡片 */
.history-item {
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 12rpx rgba(255, 77, 128, 0.06);
}
.item-line{
margin-bottom: 20rpx;
width: 100%;
height: 2rpx;
border-radius: 2rpx;
background: #D9D9D9;
}
.item-head {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 12rpx;
}
.dot-outer {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background: rgba(255, 214, 230, 0.5);
/*粉色外圈*/
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 0 4rpx #ffffff;
/* 最外层白色 */
}
.dot-inner {
width: 14rpx;
height: 14rpx;
border-radius: 50%;
background: #ff4150;
/* 中心红色 */
}
.feedback-time {
color: #000000;
flex: 1;
font-size: 22rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
padding-left: 26rpx;
}
.feedback-status {
color: #ff4150;
font-size: 12rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
display: flex;
align-items: center;
}
.smile-icon {
width: 32rpx;
height: 32rpx;
}
/* 内容框 */
.content-box {
border: 2rpx solid #f0e6ea;
background: #fff;
border-radius: 8rpx;
padding: 18rpx;
position: relative;
box-sizing: border-box;
min-height: 160rpx;
}
.content-text {
display: block;
white-space: pre-wrap;
word-break: break-word;
color: #8a8a8a;
font-size: 24rpx;
font-style: normal;
font-weight: 700;
line-height: normal;
padding-bottom: 26rpx;
}
.count {
position: absolute;
right: 14rpx;
bottom: 10rpx;
color: #000000;
font-size: 24rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.thumb-row {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
align-content: flex-start;
width: 100%;
box-sizing: border-box;
gap: 20rpx;
margin-top: 14rpx;
background: #F9FAFE;
padding: 20rpx;
}
.thumb-slot {
width: calc((100% - 2 * 25rpx) / 3);
aspect-ratio: 1 / 1;
border-radius: 7rpx;
border: 1.2rpx solid #F0F1F1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.thumb-img {
width: 100%;
height: 100%;
}
.empty {
padding: 50rpx 0;
text-align: center;
color: #afafaf;
font-size: 32rpx;
font-style: normal;
font-weight: 500;
line-height: 48rpx;
}
.empty-img {
width: 100%;
}
</style>

340
pages/customerServicePlatform/questionDetail.vue

@ -0,0 +1,340 @@
<template>
<view class="main">
<view class="top" :style="{ height: iSMT + 'px' }"></view>
<!-- 头部导航 -->
<view class="header">
<view class="back-icon">
<image @click="onBack" src="/static/customer-service-platform/cs-platform-back.png"
class="header-icon-image"></image>
</view>
<view class="title">{{ headerTitle }}</view>
<view class="notification-icon">
<image src="/static/customer-service-platform/message.png" class="header-icon-image"></image>
</view>
</view>
<scroll-view scroll-y class="content-container">
<view class="content-header">
<view class="content-header-area">
<view class="logo">
<image mode="aspectFit" src="/static/customer-service-platform/ellipse-dc-img.png"></image>
</view>
<view class="greeting">
<text class="greet-title">我能为你做点什么</text>
<text class="greet-sub">DeepChart随时为您提供服务</text>
</view>
</view>
</view>
<!-- 卡片部分 -->
<view class="card">
<!-- 问题头部-->
<view class="question-header">
<view class="question-row">
<image class="question-avatar" src="/static/customer-service-platform/robot-head.png"
mode="aspectFill"></image>
<view class="question-title">{{ questionTitle }}</view>
</view>
</view>
<!-- 卡片内容区-->
<view class="card-body">
<image class="card-logo" src="/static/customer-service-platform/ellipse-dc-img.png"
mode="aspectFit"></image>
<view class="card-text">
<text class="card-paragraph">
{{answerContent}}
</text>
</view>
</view>
</view>
<view class="login-row" v-if="showLoginRegister">
<button class="login-btn" @click="toLogin">登录</button>
<button class="register-btn" @click="toRegistration">注册</button>
</view>
</scroll-view>
</view>
</template>
<script>
import { useUserStore } from "../../stores/modules/userInfo.js"
import {
getAnswerApi
} from "../../api/customerServicePlatform/customerServicePlatform";
export default {
data() {
return {
headerTitle: '智能客服中台',
iSMT: 0,
questionTitle: '',
answerContent: '正在思考...',
showLoginRegister:false,
token:''
};
},
mounted() {
this.iSMT = uni.getSystemInfoSync().statusBarHeight || 0;
this.getAnswerContent()
const memberStore = useUserStore()
this.token = memberStore.userInfo?.token
},
onLoad(options) {
if (options.question) {
this.questionTitle = decodeURIComponent(options.question);
if (this.questionTitle.includes("如何注册")) {
this.showLoginRegister = true
} else {
this.showLoginRegister = false
}
}
},
methods: {
async getAnswerContent() {
let conversationId = '';
try {
const cache = uni.getStorageSync('conversationId');
if (cache) conversationId = cache;
} catch (e) {
conversationId = '';
}
const res = await getAnswerApi({
question: this.questionTitle,
conversationId: conversationId,
token:this.token
})
console.log(res)
if (res.code == 200) {
uni.setStorageSync('conversationId', res.data.conversationId);
const answer = res.data.answer
this.answerContent = '';
for (let i = 0; i < answer.length; i++) {
this.answerContent += answer[i];
await this.sleepTime(150);
}
} else {
this.answerContent = '获取回答失败,请重试';
}
},
async sleepTime() {
const ms = Math.floor(Math.random() * (300 - 30 + 1)) + 30;
return new Promise(resolve => setTimeout(resolve, ms));
},
toRegistration() {
uni.redirectTo({
url: "/pages/start/Registration/Registration",
});
},
toLogin() {
uni.redirectTo({
url: "/pages/start/login/login",
});
},
onBack() {
if (typeof uni !== 'undefined') uni.navigateBack();
}
}
};
</script>
<style scoped>
.main {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #ffffff;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
}
.title {
color: #000000;
text-align: center;
font-size: 32rpx;
font-weight: 400;
}
.back-icon,
.notification-icon {
width: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header-icon-image {
width: 40rpx;
height: 40rpx;
object-fit: contain;
}
.content-container {
padding: 20rpx;
width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
.content-header {
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
padding: 0 60rpx;
width: 100%;
box-sizing: border-box;
height: 188rpx;
}
.content-header-area {
display: flex;
gap: 20rpx;
}
.logo {
width: 120rpx;
height: 120rpx;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 112rpx;
}
.greeting {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1 1 auto;
}
.greet-title {
color: #000;
font-size: 40rpx;
font-style: normal;
font-weight: 500;
line-height: normal;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.greet-sub {
color: #838383;
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
margin-top: 12rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card {
width: 90%;
margin: 0 auto 20rpx;
padding: 28rpx;
box-sizing: border-box;
border-radius: 16rpx;
border: 4rpx solid #FF7C99;
background: #fff;
}
/* 问题头部 */
.question-header {
width: 100%;
margin-bottom: 48rpx;
}
.question-row {
display: flex;
align-items: center;
}
.question-avatar {
width: 52rpx;
height: 52rpx;
border-radius: 999rpx;
margin-right: 20rpx;
flex-shrink: 0;
}
.question-title {
color: #000000;
font-size: 34rpx;
}
/* 卡片内部布局 */
.card-body {
display: flex;
gap: 20rpx;
align-items: flex-start;
}
.card-logo {
width: 52rpx;
height: 52rpx;
flex: 0 0 52rpx;
border-radius: 8rpx;
}
.card-text {
flex: 1 1 auto;
}
.card-paragraph {
display: block;
color: #000000;
font-size: 28rpx;
margin-bottom: 14rpx;
font-style: normal;
font-weight: 500;
}
.login-row {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
margin-top: 100rpx;
}
.login-btn {
width: 260rpx;
height: 100rpx;
border-radius: 50rpx;
background: #F3F3F3;
color: #000000;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
margin-right: 20rpx;
}
.register-btn {
width: 260rpx;
height: 100rpx;
border-radius: 60rpx;
background: #000;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
}
</style>

BIN
static/customer-service-platform/camera.png

After

Width: 18  |  Height: 16  |  Size: 428 B

BIN
static/customer-service-platform/cs-platform-back.png

After

Width: 20  |  Height: 20  |  Size: 247 B

BIN
static/customer-service-platform/ellipse-dc-img.png

After

Width: 60  |  Height: 60  |  Size: 2.1 KiB

BIN
static/customer-service-platform/empty-content.png

After

Width: 213  |  Height: 228  |  Size: 18 KiB

BIN
static/customer-service-platform/fail-icon.png

After

Width: 100  |  Height: 100  |  Size: 4.2 KiB

BIN
static/customer-service-platform/message.png

After

Width: 18  |  Height: 18  |  Size: 523 B

BIN
static/customer-service-platform/refresh-icon.png

After

Width: 15  |  Height: 15  |  Size: 339 B

BIN
static/customer-service-platform/robot-head.png

After

Width: 21  |  Height: 19  |  Size: 1.2 KiB

BIN
static/customer-service-platform/smile-icon.png

After

Width: 16  |  Height: 16  |  Size: 440 B

BIN
static/customer-service-platform/success-icon.png

After

Width: 100  |  Height: 100  |  Size: 4.2 KiB

Loading…
Cancel
Save