| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- <template>
- <div class="container">
- <Breadcrumb :items="['工单管理', '工单详情']" />
- <a-card title="工单详情" :loading="loading">
- <a-descriptions :data="info" :column="2" />
- <div class="buttonGroup">
- <a-button type="primary" size="large" :loading="buttonLoading" @click="CloseOrder()"
- :disabled="data.state === 2">{{ data.state === 2 ? '已关闭' :
- '关闭工单' }}</a-button>
- </div>
- </a-card>
- <a-card title="沟通记录" style="margin-top: 15px; padding-bottom: 20px;">
- <div class="message" v-for="(msg, index) in data.msg">
- <a-divider v-if="index !== 0" />
- <div class="head">
- <a-avatar>
- <IconUser v-if="!data.userInfo[msg.uuid]?.avatar" />
- <img alt="" :src="data.userInfo[msg.uuid]?.avatar" v-else />
- </a-avatar>
- <div class="right">
- <div class="username">
- {{ data.userInfo[msg.uuid]?.username }}
- <a-tag color="gray" v-if="msg.type === 'system'">
- <template #icon>
- <icon-desktop />
- </template>
- 系统回复
- </a-tag>
- <a-tag color="orangered" v-else-if="msg.type === 'user'">
- <template #icon>
- <icon-user />
- </template>
- 用户回复
- </a-tag>
- <a-tag color="blue" v-else-if="msg.type === 'server'">
- <template #icon>
- <icon-customer-service />
- </template>
- 客服回复
- </a-tag>
- </div>
- <div class="time">
- {{ stramptoTime(msg.time) }}
- </div>
- </div>
- </div>
- <div class="content" :style="{ whiteSpace: 'pre-wrap' }">
- {{ msg.content }}
- </div>
- <div class="filebox">
- <a-upload :default-file-list="msg.files" :show-upload-button="false" :show-remove-button="false">
- <template #success-icon>
- </template>
- </a-upload>
- </div>
- </div>
- </a-card>
- <a-card title="回复工单" style="margin-top: 15px;" v-if="data.state !== 2">
- <a-form :model="form" :rules="rules" layout="vertical" :style="{ width: '600px' }"
- @submit-success="handleSubmit">
- <a-form-item field="content" label="内容">
- <a-textarea v-model="form.content" placeholder="请详细说明您遇到的问题,详细的阐述有助于我们快速为您解决问题..." :max-length="200"
- :auto-size="{
- minRows: 6,
- maxRows: 20
- }" allow-clear show-word-limit />
- </a-form-item>
- <a-form-item field="files" label="上传附件">
- <a-upload action="/cloud/api.php" :file-list="form.files" @change="handleFileChange" />
- </a-form-item>
- <a-form-item>
- <a-button html-type="submit" :loading="formLoading">提交</a-button>
- </a-form-item>
- </a-form>
- </a-card>
- </div>
- </template>
- <script setup>
- import { ref, reactive, onMounted, onUnmounted } from 'vue'
- import { adminOrderDetail, closeOrder, adminReplyOrder } from '@/api/workOrder'
- import { Notification } from '@arco-design/web-vue'
- import { useRoute } from 'vue-router'
- const route = useRoute()
- const loading = ref(false)
- const formLoading = ref(false)
- const buttonLoading = ref(false)
- const data = ref({})
- const info = ref([])
- const form = reactive({
- content: '',
- files: [],
- })
- const rules = {
- content: [
- {
- required: true,
- message: '请详细说明您遇到的问题',
- },
- ],
- }
- const handleFileChange = (fileList) => {
- fileList.forEach(f => {
- if (f.status === 'done' && f.response?.downurl) {
- f.url = f.response?.viewurl ?? f.response.downurl
- }
- })
- form.files = fileList // 更新到表单数据中
- }
- function getState(state) {
- switch (state) {
- case 0:
- return '待处理'
- case 1:
- return '已回复'
- case 2:
- return '已关闭'
- }
- return '未知'
- }
- const handleSubmit = async () => {
- try {
- formLoading.value = true
- const data = {
- id: route.params.id,
- ...form
- }
- const res = await adminReplyOrder(data)
- if (!res || res.code !== 0)
- return Notification.error({
- title: '回复工单失败!',
- content: res?.msg ?? '请稍后再试'
- })
- Notification.success({
- title: '回复工单成功!'
- })
- form.content = ''
- form.files = []
- getOrderDetail()
- } catch (error) {
- Notification.error({
- title: '回复工单失败!',
- content: error.message || '请稍后再试'
- })
- } finally {
- formLoading.value = false
- }
- }
- const CloseOrder = async () => {
- try {
- buttonLoading.value = true
- const res = await closeOrder({ id: route.params.id })
- if (!res || res.code !== 0)
- return Notification.error({
- title: '关闭工单失败!',
- content: res?.msg ?? '请稍后再试'
- })
- Notification.success({
- title: '关闭工单成功!'
- })
- getOrderDetail()
- } catch (error) {
- Notification.error({
- title: '关闭工单失败!',
- content: error.message || '请稍后再试'
- })
- } finally {
- buttonLoading.value = false
- }
- }
- const getOrderDetail = async () => {
- try {
- const res = await adminOrderDetail({ id: route.params.id })
- if (!res || res.code !== 0)
- return Notification.error({
- title: '获取工单详情失败!',
- content: res?.msg ?? '请稍后再试'
- })
- data.value = res.data
- info.value = [
- { label: '工单标题', value: res.data.title },
- { label: '工单ID', value: res.data.id },
- { label: '发起人', value: res.data.userInfo[res.data.create_user]?.username},
- { label: '通知邮箱', value: res.data.email },
- { label: '创建时间', value: stramptoTime(res.data.create_time) },
- { label: '最后更新时间', value: stramptoTime(res.data.update_time) },
- { label: '当前状态', value: getState(res.data.state) }
- ]
- } catch (error) {
- Notification.error({
- title: '获取路径数据失败!',
- content: error.message || '请稍后再试'
- })
- }
- }
- let timer = null
- // 轮询
- const startPolling = () => {
- if (!timer) {
- timer = setInterval(async () => {
- await getOrderDetail()
- }, 2000)
- }
- }
- // 停止轮询
- const stopPolling = () => {
- if (timer) {
- clearInterval(timer)
- timer = null
- }
- }
- onMounted(async () => {
- loading.value = true
- await getOrderDetail()
- loading.value = false
- startPolling()
- })
- // 组件销毁时停止轮询
- onUnmounted(() => {
- stopPolling()
- })
- const stramptoTime = (time) => {
- return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })
- }
- </script>
- <style scoped lang="less">
- .container {
- padding: 0 20px 20px 20px;
- .buttonGroup {
- display: flex;
- justify-content: center;
- gap: 20px;
- margin: 15px;
- }
- }
- .message {
- display: flex;
- flex-direction: column;
- margin-bottom: 5px;
- .head {
- display: flex;
- .right {
- display: flex;
- flex-direction: column;
- margin-left: 10px;
- .username {
- font-size: 1.2em;
- display: flex;
- gap: 10px
- }
- .time {
- font-size: 0.9em;
- color: #777;
- }
- }
- }
- .content {
- margin-top: 10px;
- }
- .filebox {
- margin-top: -10px;
- max-width: 500px;
- }
- }
- </style>
|