|
@@ -1,89 +1,104 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="container">
|
|
|
|
|
- <Breadcrumb />
|
|
|
|
|
- <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 class="store-page service-page">
|
|
|
|
|
+ <div class="service-page__inner">
|
|
|
|
|
+ <Breadcrumb />
|
|
|
|
|
+
|
|
|
|
|
+ <a-spin :loading="loading" class="store-spin">
|
|
|
|
|
+ <template v-if="!loading && data.id">
|
|
|
|
|
+ <a-card :bordered="false" class="info-card">
|
|
|
|
|
+ <div class="info-card__head">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <h1 class="info-card__title">{{ data.title }}</h1>
|
|
|
|
|
+ <p class="info-card__sub">工单 #{{ data.id }} · {{ formatTimeFull(data.create_time) }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <WorkOrderStateTag :state="data.state" />
|
|
|
</div>
|
|
</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>
|
|
|
|
|
- <a-tag color="purple" v-else-if="msg.type === 'ai'">
|
|
|
|
|
- <template #icon>
|
|
|
|
|
- <icon-robot />
|
|
|
|
|
- </template>
|
|
|
|
|
- AI回复
|
|
|
|
|
- </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>
|
|
|
|
|
|
|
+ <a-descriptions :column="{ xs: 1, sm: 2 }" size="medium" class="info-desc">
|
|
|
|
|
+ <a-descriptions-item label="通知邮箱">{{ data.email }}</a-descriptions-item>
|
|
|
|
|
+ <a-descriptions-item label="最后更新">{{ formatTimeFull(data.update_time) }}</a-descriptions-item>
|
|
|
|
|
+ </a-descriptions>
|
|
|
|
|
+ <div class="info-card__actions">
|
|
|
|
|
+ <a-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ :loading="buttonLoading"
|
|
|
|
|
+ :disabled="data.state === 2"
|
|
|
|
|
+ class="submit-btn"
|
|
|
|
|
+ @click="handleCloseOrder"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ data.state === 2 ? '已关闭' : '关闭工单' }}
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ <a-button @click="$router.push('/service/orderList')">返回列表</a-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </a-card>
|
|
|
|
|
+
|
|
|
|
|
+ <a-card :bordered="false" class="chat-card">
|
|
|
|
|
+ <template #title>
|
|
|
|
|
+ <span class="chat-card__title">沟通记录</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <div v-if="!data.msg?.length" class="chat-empty">暂无消息</div>
|
|
|
|
|
+ <div v-else class="chat-list">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(msg, index) in data.msg"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="chat-item"
|
|
|
|
|
+ :class="messageClass(msg.type)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <a-avatar :size="36" class="chat-item__avatar">
|
|
|
|
|
+ <icon-user v-if="!data.userInfo?.[msg.uuid]?.avatar" />
|
|
|
|
|
+ <img v-else :src="data.userInfo[msg.uuid].avatar" alt="" />
|
|
|
|
|
+ </a-avatar>
|
|
|
|
|
+ <div class="chat-item__body">
|
|
|
|
|
+ <div class="chat-item__meta">
|
|
|
|
|
+ <span class="chat-item__name">{{ data.userInfo?.[msg.uuid]?.username || '用户' }}</span>
|
|
|
|
|
+ <a-tag size="small" :color="tagColor(msg.type)">{{ tagLabel(msg.type) }}</a-tag>
|
|
|
|
|
+ <span class="chat-item__time">{{ formatTimeFull(msg.time) }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="chat-item__content">{{ msg.content }}</div>
|
|
|
|
|
+ <div v-if="msg.files?.length" class="chat-item__files">
|
|
|
|
|
+ <a-upload
|
|
|
|
|
+ :default-file-list="msg.files"
|
|
|
|
|
+ :show-upload-button="false"
|
|
|
|
|
+ :show-remove-button="false"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- </a-card>
|
|
|
|
|
-
|
|
|
|
|
- <a-card title="回复工单" style="margin-top: 15px;" v-if="data.state !== 2 && hasPermission('action.service.createOrder')">
|
|
|
|
|
- <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-card>
|
|
|
|
|
+
|
|
|
|
|
+ <a-card
|
|
|
|
|
+ v-if="data.state !== 2 && hasPermission('action.service.createOrder')"
|
|
|
|
|
+ :bordered="false"
|
|
|
|
|
+ class="reply-card"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #title>
|
|
|
|
|
+ <span class="chat-card__title">继续回复</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <a-form :model="form" :rules="rules" layout="vertical" @submit-success="handleSubmit">
|
|
|
|
|
+ <a-form-item field="content" label="回复内容">
|
|
|
|
|
+ <a-textarea
|
|
|
|
|
+ v-model="form.content"
|
|
|
|
|
+ placeholder="补充说明或回复客服"
|
|
|
|
|
+ :max-length="500"
|
|
|
|
|
+ :auto-size="{ minRows: 4, maxRows: 12 }"
|
|
|
|
|
+ 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 type="primary" html-type="submit" :loading="formLoading" class="submit-btn">
|
|
|
|
|
+ 发送回复
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </a-form-item>
|
|
|
</a-form>
|
|
</a-form>
|
|
|
- </a-card>
|
|
|
|
|
|
|
+ </a-card>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </a-spin>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
@@ -92,220 +107,231 @@ import { orderDetail, closeOrder, createOrder } from '@/api/workOrder'
|
|
|
import { Notification } from '@arco-design/web-vue'
|
|
import { Notification } from '@arco-design/web-vue'
|
|
|
import { useRoute } from 'vue-router'
|
|
import { useRoute } from 'vue-router'
|
|
|
import { hasPermission } from '@/utils/permission'
|
|
import { hasPermission } from '@/utils/permission'
|
|
|
|
|
+import { formatStoreTimeFull } from '@/utils/storeFormat'
|
|
|
|
|
+import WorkOrderStateTag from '@/components/service/WorkOrderStateTag.vue'
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
const route = useRoute()
|
|
|
-
|
|
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
|
const formLoading = ref(false)
|
|
const formLoading = ref(false)
|
|
|
const buttonLoading = ref(false)
|
|
const buttonLoading = ref(false)
|
|
|
const data = ref({})
|
|
const data = ref({})
|
|
|
-const info = ref([])
|
|
|
|
|
-
|
|
|
|
|
-const form = reactive({
|
|
|
|
|
- content: '',
|
|
|
|
|
- files: [],
|
|
|
|
|
-})
|
|
|
|
|
|
|
|
|
|
|
|
+const form = reactive({ content: '', files: [] })
|
|
|
const rules = {
|
|
const rules = {
|
|
|
- content: [
|
|
|
|
|
- {
|
|
|
|
|
- required: true,
|
|
|
|
|
- message: '请详细说明您遇到的问题',
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
|
|
+ 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
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
|
|
+const formatTimeFull = (time) => formatStoreTimeFull(time)
|
|
|
|
|
+
|
|
|
|
|
+const tagLabel = (type) => {
|
|
|
|
|
+ const map = { system: '系统', user: '我', server: '客服', ai: 'AI' }
|
|
|
|
|
+ return map[type] ?? type
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const tagColor = (type) => {
|
|
|
|
|
+ const map = { system: 'gray', user: 'orangered', server: 'arcoblue', ai: 'purple' }
|
|
|
|
|
+ return map[type] ?? 'gray'
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- form.files = fileList // 更新到表单数据中
|
|
|
|
|
|
|
+const messageClass = (type) => {
|
|
|
|
|
+ if (type === 'user') return 'chat-item--mine'
|
|
|
|
|
+ if (type === 'server' || type === 'ai') return 'chat-item--staff'
|
|
|
|
|
+ return 'chat-item--system'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function getState(state) {
|
|
|
|
|
- switch (state) {
|
|
|
|
|
- case 0:
|
|
|
|
|
- return '待处理'
|
|
|
|
|
- case 1:
|
|
|
|
|
- return '已回复'
|
|
|
|
|
- case 2:
|
|
|
|
|
- return '已关闭'
|
|
|
|
|
- case 3:
|
|
|
|
|
- return 'AI回复'
|
|
|
|
|
|
|
+const handleFileChange = (fileList) => {
|
|
|
|
|
+ fileList.forEach((f) => {
|
|
|
|
|
+ if (f.status === 'done' && f.response?.downurl) {
|
|
|
|
|
+ f.url = f.response?.viewurl ?? f.response.downurl
|
|
|
}
|
|
}
|
|
|
- return '未知'
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ form.files = fileList
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
const handleSubmit = async () => {
|
|
|
- if (!hasPermission('action.service.createOrder')) {
|
|
|
|
|
- Notification.warning({
|
|
|
|
|
- title: '无权限',
|
|
|
|
|
- content: '当前账号暂无回复工单权限'
|
|
|
|
|
- })
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- try {
|
|
|
|
|
- formLoading.value = true
|
|
|
|
|
- const data = {
|
|
|
|
|
- id: route.params.id,
|
|
|
|
|
- ...form
|
|
|
|
|
- }
|
|
|
|
|
- const res = await createOrder(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
|
|
|
|
|
|
|
+ if (!hasPermission('action.service.createOrder')) {
|
|
|
|
|
+ Notification.warning({ title: '无权限', content: '当前账号暂无回复权限' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ formLoading.value = true
|
|
|
|
|
+ const res = await createOrder({ id: route.params.id, ...form })
|
|
|
|
|
+ if (!res || res.code !== 0) {
|
|
|
|
|
+ return Notification.error({ title: '发送失败', content: res?.msg ?? '请稍后再试' })
|
|
|
}
|
|
}
|
|
|
|
|
+ Notification.success({ title: '发送成功' })
|
|
|
|
|
+ form.content = ''
|
|
|
|
|
+ form.files = []
|
|
|
|
|
+ await fetchDetail()
|
|
|
|
|
+ } 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 handleCloseOrder = 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: '工单已关闭' })
|
|
|
|
|
+ await fetchDetail()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Notification.error({ title: '关闭失败', content: error.message || '请稍后再试' })
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ buttonLoading.value = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const getOrderDetail = async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await orderDetail({ 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.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 || '请稍后再试'
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
|
|
+const fetchDetail = async () => {
|
|
|
|
|
+ const res = await orderDetail({ id: route.params.id })
|
|
|
|
|
+ if (!res || res.code !== 0) {
|
|
|
|
|
+ Notification.error({ title: '获取详情失败', content: res?.msg ?? '请稍后再试' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ data.value = res.data ?? {}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
let timer = null
|
|
let timer = null
|
|
|
-
|
|
|
|
|
-// 轮询
|
|
|
|
|
const startPolling = () => {
|
|
const startPolling = () => {
|
|
|
- if (!timer) {
|
|
|
|
|
- timer = setInterval(async () => {
|
|
|
|
|
- await getOrderDetail()
|
|
|
|
|
- }, 2000)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!timer) {
|
|
|
|
|
+ timer = setInterval(() => {
|
|
|
|
|
+ if (data.value?.state !== 2) fetchDetail()
|
|
|
|
|
+ }, 3000)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-// 停止轮询
|
|
|
|
|
const stopPolling = () => {
|
|
const stopPolling = () => {
|
|
|
- if (timer) {
|
|
|
|
|
- clearInterval(timer)
|
|
|
|
|
- timer = null
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (timer) {
|
|
|
|
|
+ clearInterval(timer)
|
|
|
|
|
+ timer = null
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
onMounted(async () => {
|
|
|
- loading.value = true
|
|
|
|
|
- await getOrderDetail()
|
|
|
|
|
- loading.value = false
|
|
|
|
|
- startPolling()
|
|
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ await fetchDetail()
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ if (data.value?.state !== 2) startPolling()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-// 组件销毁时停止轮询
|
|
|
|
|
-onUnmounted(() => {
|
|
|
|
|
- stopPolling()
|
|
|
|
|
-})
|
|
|
|
|
|
|
+onUnmounted(stopPolling)
|
|
|
|
|
+</script>
|
|
|
|
|
|
|
|
-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' })
|
|
|
|
|
|
|
+<style lang="less" scoped>
|
|
|
|
|
+@import '@/styles/store-theme.less';
|
|
|
|
|
+
|
|
|
|
|
+.info-card,
|
|
|
|
|
+.chat-card,
|
|
|
|
|
+.reply-card {
|
|
|
|
|
+ border-radius: @store-radius;
|
|
|
|
|
+ border: 1px solid @store-card-border;
|
|
|
|
|
+ box-shadow: @store-shadow;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
}
|
|
}
|
|
|
-</script>
|
|
|
|
|
|
|
|
|
|
-<style scoped lang="less">
|
|
|
|
|
-.container {
|
|
|
|
|
- padding: 0 20px 20px 20px;
|
|
|
|
|
|
|
+.info-card__head {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- .buttonGroup {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- gap: 20px;
|
|
|
|
|
- margin: 15px;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.info-card__title {
|
|
|
|
|
+ margin: 0 0 6px;
|
|
|
|
|
+ font-size: 1.25rem;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: @store-primary;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.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;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.info-card__sub {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 0.85rem;
|
|
|
|
|
+ color: @store-text-muted;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- .content {
|
|
|
|
|
- margin-top: 10px;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.info-card__actions {
|
|
|
|
|
+ margin-top: 16px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- .filebox {
|
|
|
|
|
- margin-top: -10px;
|
|
|
|
|
- max-width: 500px;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.chat-card__title {
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: @store-primary;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.chat-empty {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ color: @store-text-muted;
|
|
|
|
|
+ padding: 24px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.chat-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.chat-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+
|
|
|
|
|
+ &__meta {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &__name {
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: @store-primary;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &__time {
|
|
|
|
|
+ font-size: 0.8rem;
|
|
|
|
|
+ color: @store-text-muted;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &__content {
|
|
|
|
|
+ padding: 12px 14px;
|
|
|
|
|
+ border-radius: @store-radius-sm;
|
|
|
|
|
+ background: @store-bg;
|
|
|
|
|
+ line-height: 1.65;
|
|
|
|
|
+ white-space: pre-wrap;
|
|
|
|
|
+ color: @store-text-muted;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &__files {
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ max-width: 400px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &--mine .chat-item__content {
|
|
|
|
|
+ background: fade(@store-accent, 12%);
|
|
|
|
|
+ border: 1px solid fade(@store-accent, 25%);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &--staff .chat-item__content {
|
|
|
|
|
+ background: #f0f6ff;
|
|
|
|
|
+ border: 1px solid #d6e4ff;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &--system .chat-item__content {
|
|
|
|
|
+ background: var(--color-fill-2);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.submit-btn {
|
|
|
|
|
+ border-radius: 999px;
|
|
|
|
|
+ background: @store-primary !important;
|
|
|
|
|
+ border-color: @store-primary !important;
|
|
|
}
|
|
}
|
|
|
-</style>
|
|
|
|
|
|
|
+</style>
|