123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600 |
- <template>
- <div class="hc-order-service">
- <div class="order-service-content">
- <el-scrollbar ref="scrollbarRef">
- <div class="content-box">
- <div class="comment-card-box" v-for="(item,index) in orderDataList" :key="item.id">
- <div class="user-avatar-box">
- <el-avatar :size="50" :src="item.avatar || avatarPng" />
- </div>
- <div class="card-content-box">
- <div class="user-info-box">
- <div class="text-lg">{{item['createUserName']||'用户名异常'}}</div>
- <div class="text-gray">{{item['createTime']}}</div>
- </div>
- <div class="desc_para" v-html="item['opinionContent']"></div>
- <div class="image_desc" v-if="item['returnFiles']?.length > 0">
- <div class="hc-image-box" v-for="(items,indexs) in item['returnFiles']">
- <HcImg class="hc-image" :src="items" :srcs="item['returnFiles']" :index="indexs"/>
- </div>
- </div>
- <div class="foot-tools-box">
- <div class="icon-box" :class="item['commentsNumber'] >= 1 ? 'active' : ''" @click="commentExpanded(item)">
- <HcIcon name="chat" class="icon"/>
- <span class="badge" v-if="item['commentsNumber'] >= 1">{{item['commentsNumber']}}</span>
- </div>
- <div class="icon-box" :class="item['currentUserGood'] ? 'active' : ''" :data-index="item['expandedName']" @click="likeClick(item)">
- <HcIcon name="thumb_up" class="icon"/>
- <span class="badge" v-if="item['goodNumber'] >= 1">{{item['goodNumber']}}</span>
- </div>
- </div>
- <el-collapse class="hc-collapse-box" v-model="item['expandedName']" accordion>
- <el-collapse-item title="" :name="`commentList-${item['id']}`">
- <div class="collapse-comment-box">
- <div class="comment-reply-content-box">
- <el-input autosize type="textarea" v-model="item['replyContent']" placeholder="我也说一句"/>
- <el-button type="primary" hc-btn @click="saveCommentClick(item)">评论</el-button>
- </div>
- <div class="user-comment-info-box" v-for="items in item['expandedCommentList']" :key="items.id">
- <el-avatar :size="50" :src="items.avatar || avatarPng" />
- <div class="user-comment-box">
- <div class="user-info-box">
- <span class="user-name">{{items['userName']||'用户名异常'}}</span>
- <span class="create-time">{{items['createTime']}}</span>
- </div>
- <div class="user-comment-content-box" v-html="items['replyContent']"></div>
- </div>
- </div>
- </div>
- </el-collapse-item>
- </el-collapse>
- </div>
- <div class="code-status-box" v-if="parseInt(item['isSolve']) === 1">
- <img :src="Web515Png" class="widget" alt=""/>
- </div>
- </div>
- </div>
- </el-scrollbar>
- <div class="page-top-btn" @click="scrollToTop">
- <HcIcon name="vertical_align_top" class="icon"/>
- </div>
- </div>
- <!--我的工单服务-->
- <div class="order-service-data" :style="'width:' + leftWidth + 'px;'">
- <HcCard :scrollbar="false">
- <template #header>
- <el-badge :value="2" class="item-badge">
- <div class="font-bold text-lg">我的工单服务进度</div>
- </el-badge>
- </template>
- <template #extra>
- <el-tooltip effect="dark" content="发起新工单服务" placement="top">
- <el-button type="primary" hc-btn class="hc-add-icon" @click="newOrderServiceClick">
- <HcIcon name="add"/>
- </el-button>
- </el-tooltip>
- </template>
- <div class="mb-5">
- <el-select v-model="nameSelectKey" block placeholder="工单名称" size="large" @change="nameSelectUpdate">
- <el-option v-for="item in nameSelectData" :key="item.id" :label="item?.title" :value="item?.id"/>
- </el-select>
- </div>
- <div class="time-line-box" :class="isCurrentBol?'time-height':''">
- <el-scrollbar>
- <el-timeline class="hc-time-line">
- <template v-for="(item,index) in orderFlowList" :key="index">
- <el-timeline-item :class="item['currentBol']?'success':item['current']?'primary':''" size="large">
- <div class="timeline-item-icon">
- <HcIcon name="done" class="check-icon" v-if="item['currentBol']"/>
- <span v-else>{{index + 1}}</span>
- </div>
- <div class="reply-name">{{item['replyName']}}</div>
- <div class="reply-content" v-html="item['replyContent']"></div>
- </el-timeline-item>
- </template>
- </el-timeline>
- </el-scrollbar>
- </div>
- <div class="evaluation-box" :class="isCurrentBol?'show':''">
- <div class="text-lg font-bold">评价</div>
- <div class="tip-box">请对工单处理评价,若是未解决问题,可进行投诉,平台核实情况,将对相关客服人员绩效考核,并且从新为您自动发起工单解决问题</div>
- <div class="radio-group-box">
- <el-radio-group class="radio-group" v-model="evaluationKey">
- <div class="radio-item" v-for="item in evaluationData" :key="item.value">
- <el-radio :label="item.value" size="large" class="size-xl">{{ item.label }}</el-radio>
- </div>
- </el-radio-group>
- </div>
- <div class="btn-box">
- <el-button type="primary" hc-btn @click="disposeUserFeedback">
- <HcIcon name="check_circle"/>
- <span>提交</span>
- </el-button>
- </div>
- </div>
- </HcCard>
- <!--左右拖动-->
- <div class="horizontal-drag-line" @mousedown="onmousedown"/>
- </div>
- <!--提交工单-->
- <el-dialog v-model="showModal" title="发起新工单服务" width="720px" custom-class="hc-modal-border" :before-close="handleModalClose">
- <div class="title">请选择您需要反馈的问题类型</div>
- <div class="hc-type-tabs my-5">
- <el-radio-group v-model="typeTabKey" size="large" @change="typeTabChange">
- <el-radio-button v-for="item in typeTab" :label="item?.dictValue">{{item?.dictValue}}</el-radio-button>
- </el-radio-group>
- </div>
- <div class="modal-checkbox-box">
- <el-checkbox-group v-model="typeCheckBox[typeTabIndex]">
- <div class="checkbox-item" v-for="item in typeTab[typeTabIndex]?.children" :key="item.id">
- <el-checkbox :label="item['dictValue']">{{item['dictValue']}}</el-checkbox>
- </div>
- </el-checkbox-group>
- </div>
- <div class="mt-5">
- <el-input v-model="opinionContent" :rows="3" type="textarea" placeholder="请输入你宝贵的建议,我们将会跟踪解决"/>
- </div>
- <div class="mt-3 upload-img" v-loading="spinShow">
- <el-upload v-model:file-list="uploadFileList" :action="uploadAction" :headers="getTokenHeader()" :limit="3" :accept="uploadAccept" list-type="picture-card" multiple
- :before-upload="beforeUpload" :on-change="uploadChange" :on-exceed="uploadExceed" :on-preview="handlePreview" :on-remove="removeUpload">
- <HcIcon name="add" class="hc-upload-icon"/>
- </el-upload>
- <el-image-viewer v-if="showViewer" :initial-index="initialIndex" :url-list="previewFileList" @close="showViewer = false"/>
- </div>
- <div class="mt-3">
- <el-alert title="请上传JPG、PNG格式的图片文件,最多上传 3 张图片,文件大小不超过30M" type="error" :closable="false"/>
- </div>
- <template #footer>
- <div class="dialog-footer">
- <el-button size="large" @click="handleModalClose">取消</el-button>
- <el-button type="primary" hc-btn @click="saveClick">提交</el-button>
- </div>
- </template>
- </el-dialog>
- <!--提示框-->
- <el-dialog v-model="showTipModal" title="感谢" width="600px" custom-class="hc-modal-border" :before-close="handleTipModalClose">
- <div class="tip-modal-icon-box">
- <HcIcon name="sentiment_very_satisfied"/>
- </div>
- <div class="tip-modal-text-box">感谢您的仗义直言,大恩不言谢,有事联系我们,我们随时都在</div>
- <template #footer>
- <div class="dialog-footer">
- <el-button size="large" @click="tipModalClick">下次不用感谢了</el-button>
- <el-button type="primary" hc-btn @click="handleTipModalClose">不客气</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import {nextTick, onMounted, ref, watch} from "vue";
- import {useAppStore} from "~src/store/index";
- import orderServe from '~api/other/orderServe';
- import {getTokenHeader} from '~src/api/request/header';
- import avatarPng from '~src/assets/images/avatar.png';
- import Web515Png from '~src/assets/images/Web515.png';
- import {userConfigSave} from "~api/other";
- import {isType, isSize, base64ToFile, getIndex} from "vue-utils-plus"
- import oss from "~api/oss";
- //初始变量
- const { getArrValue, getObjValue, getObjNullValue } = isType()
- const useAppState = useAppStore()
- const projectId = ref(useAppState.getProjectId);
- const contractId = ref(useAppState.getContractId);
- const isScreenShort = ref(useAppState.getScreenShort)
- //是否弹出工单感谢, 0不弹出,1弹出
- const opinionView = ref(useAppState.getOrderServiceTipModal)
- //搜索和分页数据
- const searchForm = ref({current: 1, size: 20})
- const orderDataList = ref([])
- // 工单名称
- const nameSelectKey = ref(null)
- const nameSelectData = ref([])
- //监听
- watch(() => [
- useAppState.getScreenShort
- ], ([ScreenShort]) => {
- isScreenShort.value = ScreenShort
- if( ScreenShort ) {
- let base64 = window.sessionStorage.getItem('screenShort-base64') || '';
- if (base64) uploadImgFile(base64)
- }
- })
- nextTick(() => {
- //截图数据
- if(isScreenShort.value) {
- let base64 = window.sessionStorage.getItem('screenShort-base64') || '';
- if (base64) uploadImgFile(base64)
- }
- })
- onMounted(() => {
- //获取相关数据
- queryUserOpinionPage()
- queryCurrentUserOpinionList()
- })
- //获取列表数据
- const queryUserOpinionPage = async () => {
- const { error, code, data } = await orderServe.queryUserOpinionPage(searchForm.value)
- if (!error && code === 200) {
- orderDataList.value = getArrValue(data['records'])
- } else {
- orderDataList.value = []
- }
- }
- //获取工单服务下拉列表
- const queryCurrentUserOpinionList = async () => {
- const { error, code, data } = await orderServe.queryCurrentUserOpinionList({
- projectId: projectId.value
- })
- if (!error && code === 200) {
- const res = getArrValue(data)
- nameSelectData.value = res
- if (res.length > 0) {
- nameSelectKey.value = res[0].id
- queryUserFlowOpinion()
- } else {
- nameSelectKey.value = null
- }
- } else {
- nameSelectData.value = []
- nameSelectKey.value = null
- }
- }
- //获取当前工单的最新流程
- const isCurrentBol = ref(false)
- const orderFlowList = ref([])
- const queryUserFlowOpinion = async () => {
- let id = nameSelectKey.value || null;
- const { error, code, data } = await orderServe.queryUserFlowOpinion({userOpinionId: id})
- if (!error && code === 200) {
- const res = getArrValue(data)
- orderFlowList.value = res
- if (res.length > 0) {
- const {currentBol, evaluation} = res[res.length-1];
- isCurrentBol.value = !!(currentBol && parseInt(evaluation) === -1);
- }
- } else {
- orderFlowList.value = []
- isCurrentBol.value = false
- }
- }
- //我的工单被切换
- const nameSelectUpdate = () => {
- //获取当前工单的最新流程
- queryUserFlowOpinion().then()
- }
- //评论
- const expandedName = ref('')
- const commentExpanded = (item) => {
- if (item['expandedName']) {
- item['expandedName'] = ''
- } else {
- item['expandedName'] = `commentList-${item.id}`
- queryCommentsList(item)
- }
- }
- //获取评论列表
- const queryCommentsList = async (item) => {
- const { error, code, data } = await orderServe.queryCommentsList({
- userOpinionId: item.id
- })
- if (!error && code === 200) {
- item['expandedCommentList'] = getArrValue(data)
- } else {
- item['expandedCommentList'] = []
- }
- }
- //提交评论
- const saveCommentClick = async (item) => {
- if (!item['replyContent']) {
- window.$message?.warning('请先填写评论内容');
- } else {
- const { error, code } = await orderServe.saveUserComments({
- userOpinionId: item.id,
- replyContent: item['replyContent'],
- projectId: projectId.value,
- contractId: contractId.value,
- })
- if (!error && code === 200) {
- window.$message?.success('评论成功');
- item['replyContent'] = ''
- queryCommentsList(item)
- }
- }
- }
- //点赞
- const likeClick = async (item) => {
- if (item['currentUserGood']) {
- const { error, code } = await orderServe.cancelGood({
- userOpinionId: item.id
- })
- if (!error && code === 200) {
- item['currentUserGood'] = false
- item['goodNumber'] --
- }
- } else {
- const { error, code } = await orderServe.addGoodNumber({
- userOpinionId: item.id,
- good: 1
- })
- if (!error && code === 200) {
- item['currentUserGood'] = true
- item['goodNumber'] ++
- }
- }
- }
- //弹框
- const showModal = ref(false)
- //类型tab数据
- const typeTabKey = ref(null)
- const typeTab = ref([]);
- const typeTabIndex = ref(-1);
- const typeCheckBox = ref([]);
- const typeTabChange = (val) => {
- typeTabKey.value = val;
- typeTabIndex.value = typeTab.value.findIndex(item => item.dictValue === val);
- }
- //发起新工单服务
- const newOrderServiceClick = () => {
- queryDictBizList()
- showModal.value = true
- }
- //关闭
- const handleModalClose = () => {
- showModal.value = false
- }
- //获取字典信息
- const queryDictBizList = async () => {
- const { error, code, data } = await orderServe.queryDictBizList()
- if (!error && code === 200) {
- const res = getArrValue(data)
- typeTab.value = res
- if (res.length > 0) {
- typeTabIndex.value = 0
- typeTabKey.value = res[0]?.dictValue
- }
- } else {
- typeTab.value = []
- }
- }
- //建议内容
- const opinionContent = ref('')
- //上传
- const uploadFileList = ref([])
- const uploadAction = "/api/blade-resource/oss/endpoint/put-file"
- const uploadAccept = "image/png,image/jpg,image/jpeg"
- //上传前
- const beforeUpload = (res) => {
- if (isSize(res?.size,30)) {
- return true;
- } else {
- window?.$message?.warning('文件大小,不能过30M!');
- return false;
- }
- }
- //状态改变
- const uploadChange = (_, uploadFiles) => {
- console.log(uploadFiles)
- //暂时不知道怎么搞。。。
- }
- //超出限制时
- const uploadExceed = () => {
- window?.$message?.warning('请上传JPG、PNG格式的图片文件,最多上传 3 张图片,文件大小不超过30M');
- }
- //预览
- const showViewer = ref(false)
- const previewFileList = ref([])
- const initialIndex = ref(0)
- const handlePreview = (file) => {
- let fileArr = getUploadFileUrl()
- const fileList = uploadFileList.value ?? [];
- const index = getIndex(fileList, 'uid', file?.uid)
- previewFileList.value = fileArr
- initialIndex.value = index
- showViewer.value = true
- }
- //获取文件URL
- const getUploadFileUrl = () => {
- let fileArr = [], fileList = uploadFileList.value ?? [];
- fileList.forEach(item => {
- fileArr.push(item?.response?.data?.link)
- })
- return fileArr
- }
- //删除文件
- const removeUpload = async (file) => {
- const fileName = file?.response?.data?.name
- const { error, code } = await oss.removeFile({
- fileName: fileName
- })
- if (!error && code === 200) {
- return true
- } else {
- return false
- }
- }
- //上传截图文件
- const spinShow = ref(false)
- const uploadImgFile = async (base64) => {
- let fileOfBlob = base64ToFile(base64);
- let formData = new FormData();
- formData.append("file", fileOfBlob);
- //上传文件
- spinShow.value = true
- newOrderServiceClick()
- const { error, code, data } = await oss.putFile(formData, false)
- spinShow.value = false
- if (!error && code === 200) {
- let res = getObjValue(data)
- if (res?.link) {
- uploadFileList.value.push({
- url: res?.link,
- name: res?.name,
- response: {data: res}
- })
- }
- window.sessionStorage.removeItem('screenShort-base64');
- window.$message?.success('文件上传成功');
- spinShow.value = false
- } else {
- window.sessionStorage.removeItem('screenShort-base64');
- window.$message?.warning('文件上传失败');
- }
- }
- //提交工单反馈
- const saveClick = async () => {
- //拼接问题类型
- let problemType = typeTabKey.value, index = typeTabIndex.value, problemVal = '';
- const checkBoxVal = typeCheckBox.value[index] || [];
- checkBoxVal.forEach(item => {problemVal += `-${item}`})
- let filesUrl = getUploadFileUrl()
- //判断数据
- if (!problemVal) {
- window.$message?.warning('请先选择问题类型');
- } else {
- //请求接口
- const { error, code } = await orderServe.saveUserOpinion({
- projectId: projectId.value,
- contractId: contractId.value,
- problemType: problemType + problemVal,
- opinionContent: opinionContent.value,
- returnFiles: filesUrl
- })
- if (!error && code === 200) {
- window.$message?.success('提交成功');
- showModal.value = false;
- //重置表单
- typeCheckBox.value[index] = []
- opinionContent.value = ''
- uploadFileList.value = []
- previewFileList.value = []
- //更新数据
- queryUserOpinionPage()
- queryCurrentUserOpinionList()
- }
- }
- }
- //评价
- const showTipModal = ref(false)
- const evaluationKey = ref('1')
- const evaluationData = [
- {value: "1", label: "满意"},
- {value: "2", label: "不满意并再次提交解决"},
- {value: "3", label: "不满意且投诉"}
- ]
- const disposeUserFeedback = async () => {
- let oldEndFlow = orderFlowList.value[3]?.id || ''
- const { error, code } = await orderServe.disposeUserFeedback({
- oldEndFlow: oldEndFlow,
- type: evaluationKey.value || '',
- userOpinionId: nameSelectKey.value || ''
- })
- if (!error && code === 200) {
- window.$message?.success('提交成功');
- showTipModal.value = parseInt(opinionView.value) === 1
- queryCurrentUserOpinionList()
- }
- }
- //提示框
- const tipModalClick = async () => {
- await userConfigSave({opinionView: 0})
- showTipModal.value = false
- useAppState.setOrderServiceTipModal(0)
- opinionView.value = 0
- }
- const handleTipModalClose = () => {
- showTipModal.value = false
- }
- //滚动到顶部
- const scrollbarRef = ref(null)
- const scrollToTop = () => {
- scrollbarRef.value?.setScrollTop(0)
- }
- //左右拖动,改变树形结构宽度
- const leftWidth = ref(500);
- const onmousedown = () => {
- const clientWidth = document.body.clientWidth
- document.onmousemove = (ve) => {
- let diffVal = clientWidth - (ve.clientX + 24);
- if (diffVal >= 300 && diffVal <= 1000) {
- leftWidth.value = diffVal;
- }
- }
- document.onmouseup = () => {
- document.onmousemove = null;
- document.onmouseup = null;
- }
- }
- </script>
- <style lang="scss" scoped>
- @import "../../styles/other/order-service.scss";
- </style>
- <style lang="scss">
- .item-badge .el-badge__content.is-fixed {
- top: 4px;
- }
- .comment-card-box .card-content-box .hc-collapse-box.el-collapse {
- border: 0;
- .el-collapse-item {
- &:last-child {
- margin-bottom: 0;
- }
- .el-collapse-item__header {
- display: none;
- }
- .el-collapse-item__wrap {
- background-color: initial;
- border-bottom: 0;
- }
- }
- }
- .comment-reply-content-box {
- .el-textarea {
- min-height: 40px;
- margin-right: 10px;
- .el-textarea__inner {
- min-height: 40px !important;
- }
- }
- }
- </style>
|