This commit is contained in:
weiwei committed 2025-09-12 19:13:05 +08:00
commit 284004a288
5 files changed
+330 -97

No files matched your search

+97 -35
View File
@@ -1,23 +1,28 @@
<template>
<view class="feedback-detail">
<view class="detail-header">
<view class="detail-title">{{ detail.title }}</view>
<view v-if="typeOptions.length && detail.type" class="detail-type">反馈类型:{{ getTypeLabel(detail.type) }}</view>
</view>
<view class="detail-content">
<view class="content-text">{{ detail.description }}</view>
<view v-if="imageList.length" class="image-list">
<image
v-for="(img, idx) in imageList"
:key="idx"
:src="img.startsWith('http') ? img : filePath(img)"
class="feedback-img"
mode="aspectFill"
/>
<view v-if="!detail.title" class="loading-tip">加载中...</view>
<view v-else>
<view class="detail-header">
<view class="detail-title">{{ detail.title || '无标题' }}</view>
<view v-if="detail.type" class="detail-type">
反馈类型:{{ typeOptionsLoaded ? getTypeLabel(detail.type) : '加载中...' }}
</view>
</view>
<view class="detail-content">
<view class="content-text">{{ detail.description || '无描述' }}</view>
<view v-if="imageList.length" class="image-list">
<image
v-for="(img, idx) in imageList"
:key="idx"
:src="img.startsWith('http') ? img : filePath(img)"
class="feedback-img"
mode="aspectFill"
/>
</view>
</view>
<view class="detail-footer">
<text class="create-time">提交时间:{{ formatTime(detail.createTime) }}</text>
</view>
</view>
<view class="detail-footer">
<text class="create-time">提交时间:{{ formatTime(detail.createTime) }}</text>
</view>
</view>
</template>
@@ -25,34 +30,55 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import meetingRoomApi from '@/sheep/api/meetingRoom'
import feedbackApi from '@/sheep/api/feedback'
import { filePath } from '@/sheep/utils/file'
const detail = ref<any>({})
const typeOptions = ref<{ value: string; label: string }[]>([])
const typeOptionsLoaded = ref(false)
async function loadTypeOptions() {
const res: any = await meetingRoomApi.getFeedbackType()
if (res.data && Array.isArray(res.data.data)) {
typeOptions.value = res.data.data.map(item => ({
value: item.id,
label: item.label
}))
try {
const res: any = await feedbackApi.getFeedbackType()
console.log('反馈类型API响应:', res)
if (res.code === 0 && res.data && Array.isArray(res.data)) {
typeOptions.value = res.data.map(item => ({
value: item.id,
label: item.label
}))
console.log('处理后的反馈类型选项:', typeOptions.value)
typeOptionsLoaded.value = true
} else {
console.error('获取反馈类型失败:', res.msg)
typeOptionsLoaded.value = true
}
} catch (e) {
console.error('获取反馈类型异常:', e)
typeOptionsLoaded.value = true
}
}
function getTypeLabel(type) {
if (!typeOptions.value.length || !type) return ''
console.log('获取类型标签 - 输入类型:', type, '类型:', typeof type)
console.log('获取类型标签 - 类型选项:', typeOptions.value)
if (!typeOptions.value.length || !type) {
console.log('获取类型标签 - 无选项或无类型,返回:', type || '')
return type || ''
}
const option = typeOptions.value.find(item => String(item.value) === String(type))
return option ? option.label : type
console.log('获取类型标签 - 找到的选项:', option)
const result = option ? option.label : type
console.log('获取类型标签 - 最终结果:', result)
return result
}
function getTypeClass(type: string) {
const classMap: Record<string, string> = {
'1': 'type-suggestion',
'2': 'type-problem',
'3': 'type-ui',
'4': 'type-other'
'投诉': 'type-complain',
'建议': 'type-suggest',
'bug报告': 'type-bug',
'功能需求': 'type-feature',
'其他': 'type-other'
}
return classMap[type] || 'type-other'
}
@@ -89,15 +115,44 @@ const imageList = computed(() => {
})
onLoad(async (options: any) => {
await loadTypeOptions()
const id = options.id
if (!id) return
console.log('页面参数:', options)
if (!id) {
uni.showToast({ title: '缺少反馈ID', icon: 'none' })
return
}
try {
const response: any = await meetingRoomApi.getFeedbackDetail(id)
if (response.code === 0 && Array.isArray(response.data) && response.data.length > 0) {
detail.value = response.data[0]
uni.showLoading({ title: '加载中...' })
// 先获取反馈详情
const response: any = await feedbackApi.getFeedbackDetail(id)
console.log('反馈详情响应:', response)
if (response.code === 0) {
// 处理不同的数据结构
if (Array.isArray(response.data) && response.data.length > 0) {
detail.value = response.data[0]
} else if (response.data && typeof response.data === 'object') {
detail.value = response.data
} else {
console.error('数据结构异常:', response.data)
uni.showToast({ title: '数据格式错误', icon: 'none' })
return
}
console.log('反馈详情数据:', detail.value)
} else {
uni.showToast({ title: response.msg || '获取详情失败', icon: 'none' })
return
}
// 再获取类型选项
await loadTypeOptions()
uni.hideLoading()
} catch (error) {
console.error('获取详情失败:', error)
uni.hideLoading()
uni.showToast({ title: '获取详情失败', icon: 'none' })
}
})
@@ -110,6 +165,13 @@ onLoad(async (options: any) => {
min-height: 100vh;
}
.loading-tip {
text-align: center;
padding: 60rpx 0;
color: #999;
font-size: 28rpx;
}
.detail-header {
background: #fff;
border-radius: 16rpx;
+65 -28
View File
@@ -15,7 +15,9 @@
<view class="form-item">
<view class="label">反馈类型</view>
<picker @change="onTypeChange" :value="typeIndex" :range="typeOptions" range-key="label">
<view class="picker">{{ typeOptions[typeIndex]?.label || '请选择类型' }}</view>
<view class="picker">
{{ typeOptionsLoaded ? (typeOptions[typeIndex]?.label || '请选择类型') : '加载中...' }}
</view>
</picker>
</view>
<view class="form-item">
@@ -68,21 +70,13 @@
@click="viewDetail(item)"
>
<view class="item-header">
<view class="type-tag" :class="getTypeClass(item.type)">{{ getTypeLabel(item.type) }}</view>
<view class="type-tag" :class="getTypeClass(item.type)">
{{ typeOptionsLoaded ? getTypeLabel(item.type) : '加载中...' }}
</view>
<view class="status-tag" :class="getStatusClass(item.status)">{{ getStatusText(item.status) }}</view>
</view>
<view class="item-content">
<view class="item-title">{{ item.title }}</view>
<view class="item-desc">{{ item.description }}</view>
<view v-if="item.picUrl" class="image-list">
<image
v-for="(img, idx) in item.picUrl.split(',').filter(Boolean)"
:key="idx"
:src="filePath(img)"
class="item-img"
mode="aspectFill"
/>
</view>
</view>
<view class="item-footer">
<text class="time">{{ formatTime(item.createTime) }}</text>
@@ -97,12 +91,13 @@
<script setup lang="ts">
import { ref, onMounted, computed, watch, nextTick } from 'vue'
import meetingRoomApi from '@/sheep/api/meetingRoom'
import feedbackApi from '@/sheep/api/feedback'
import { onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'
import { filePath } from '@/sheep/utils/file'
import { useDefaultHeader } from '@/sheep/hooks/use-token'
const typeOptions = ref<{ value: string; label: string }[]>([])
const typeOptionsLoaded = ref(false)
const typeIndex = ref(0)
const title = ref('')
const description = ref('')
@@ -123,15 +118,24 @@ const canSubmit = computed(() => {
async function loadTypeOptions() {
try {
const res: any = await meetingRoomApi.getFeedbackType()
if (res.data && Array.isArray(res.data.data)) {
typeOptions.value = res.data.data.map(item => ({
value: item.value,
const res: any = await feedbackApi.getFeedbackType()
console.log('反馈类型API响应:', res)
if (res.code === 0 && res.data && Array.isArray(res.data)) {
typeOptions.value = res.data.map(item => ({
value: item.id,
label: item.label
}))
console.log('处理后的反馈类型选项:', typeOptions.value)
typeOptionsLoaded.value = true
} else {
console.error('获取反馈类型失败:', res.msg)
uni.showToast({ title: res.msg || '获取反馈类型失败', icon: 'none' })
typeOptionsLoaded.value = true
}
} catch (e) {
console.error('获取反馈类型异常:', e)
uni.showToast({ title: '获取反馈类型失败', icon: 'none' })
typeOptionsLoaded.value = true
}
}
@@ -140,16 +144,26 @@ function onTypeChange(e: any) {
}
function getTypeLabel(type: string) {
const option = typeOptions.value.find(item => item.value === type)
return option ? option.label : type
console.log('列表页获取类型标签 - 输入类型:', type, '类型:', typeof type)
console.log('列表页获取类型标签 - 类型选项:', typeOptions.value)
if (!typeOptions.value.length || !type) {
console.log('列表页获取类型标签 - 无选项或无类型,返回:', type || '')
return type || ''
}
const option = typeOptions.value.find(item => String(item.value) === String(type))
console.log('列表页获取类型标签 - 找到的选项:', option)
const result = option ? option.label : type
console.log('列表页获取类型标签 - 最终结果:', result)
return result
}
function getTypeClass(type: string) {
const classMap: Record<string, string> = {
'投诉': 'type-complain',
'建议': 'type-suggest',
'BUG报告': 'type-bug',
'功能需求': 'type-feature'
'bug报告': 'type-bug',
'功能需求': 'type-feature',
'其他': 'type-other'
}
return classMap[type] || 'type-other'
}
@@ -197,7 +211,7 @@ async function submitFeedback() {
contact: contact.value.trim(),
picUrl: picUrlArr.join(',')
}
const response: any = await meetingRoomApi.submitFeedback(data)
const response: any = await feedbackApi.submitFeedback(data)
uni.hideLoading()
if (response.code === 0) {
uni.showToast({ title: '提交成功', icon: 'success' })
@@ -229,7 +243,7 @@ async function loadFeedbackList(isLoadMore = false, autoFill = false) {
loading.value = true
error.value = false
try {
const response: any = await meetingRoomApi.getFeedbackList({
const response: any = await feedbackApi.getFeedbackList({
current: page.value,
size: pageSize
})
@@ -275,14 +289,28 @@ function refreshList() {
// 监听页面触底事件,下拉加载更多
onReachBottom(() => {
if (activeTab.value === 1) {
loadFeedbackList(true, false)
// 确保类型选项已加载完成后再加载更多
if (typeOptionsLoaded.value) {
loadFeedbackList(true, false)
} else {
loadTypeOptions().then(() => {
loadFeedbackList(true, false)
})
}
}
})
// 切换tab时重置分页并刷新
watch(activeTab, (val) => {
if (val === 1) {
refreshList()
// 确保类型选项已加载完成后再刷新列表
if (typeOptionsLoaded.value) {
refreshList()
} else {
loadTypeOptions().then(() => {
refreshList()
})
}
}
})
@@ -290,9 +318,18 @@ onPullDownRefresh(() => {
if (activeTab.value === 1) {
page.value = 1
hasMore.value = true
loadFeedbackList(false, true).then(() => {
uni.stopPullDownRefresh()
})
// 确保类型选项已加载完成后再刷新列表
if (typeOptionsLoaded.value) {
loadFeedbackList(false, true).then(() => {
uni.stopPullDownRefresh()
})
} else {
loadTypeOptions().then(() => {
loadFeedbackList(false, true).then(() => {
uni.stopPullDownRefresh()
})
})
}
} else {
uni.stopPullDownRefresh()
}
+76 -14
View File
@@ -2,8 +2,8 @@
<view class="repair-detail">
<view class="detail-header">
<view class="equipment-info">
<text class="equipment-name">{{ detail.equipmentName }}</text>
<text class="location">{{ detail.location }}</text>
<text class="equipment-name">{{ detail.title || detail.equipmentName || '无标题' }}</text>
<text class="location">{{ detail.location || '无位置' }}</text>
</view>
<view class="status-tag" :class="getStatusClass(detail.status)">{{ getStatusText(detail.status) }}</view>
</view>
@@ -11,7 +11,9 @@
<view class="detail-content">
<view class="info-row">
<text class="label">维修类型:</text>
<text class="value">{{ getTypeLabel(detail.type) }}</text>
<text class="value">
{{ typeOptionsLoaded ? getTypeLabel(detail.type || detail.maintainType) : '加载中...' }}
</text>
</view>
<view class="info-row">
<text class="label">紧急程度:</text>
@@ -51,18 +53,13 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import meetingRoomApi from '@/sheep/api/meetingRoom'
import maintenanceApi from '@/sheep/api/maintenance'
import { filePath } from "@/sheep/utils/file"
const detail = ref<any>({})
const typeOptionsLoaded = ref(false)
const typeOptions = [
{ value: '1', label: '电气设备' },
{ value: '2', label: '网络设备' },
{ value: '3', label: '办公设备' },
{ value: '4', label: '空调设备' },
{ value: '5', label: '其他' }
]
const typeOptions = ref<any[]>([])
const urgencyOptions = [
{ value: '1', label: '高' },
@@ -70,8 +67,55 @@ const urgencyOptions = [
{ value: '3', label: '低' }
]
// 加载维修类型选项
async function loadTypeOptions() {
try {
const response: any = await maintenanceApi.getFeedbackType()
console.log('维修类型响应:', response)
if (response.code === 0 && response.data) {
// 处理API返回的数据格式
if (Array.isArray(response.data)) {
typeOptions.value = response.data.map((item: any) => ({
value: String(item.value || item.id),
label: item.label || item.name || item.text
}))
} else {
// 如果返回的是对象,尝试提取选项
typeOptions.value = Object.entries(response.data).map(([key, value]: [string, any]) => ({
value: key,
label: value
}))
}
console.log('处理后的维修类型选项:', typeOptions.value)
typeOptionsLoaded.value = true
} else {
console.error('获取维修类型失败:', response.msg)
// 使用默认选项作为后备
typeOptions.value = [
{ value: '1', label: '电气设备' },
{ value: '2', label: '网络设备' },
{ value: '3', label: '办公设备' },
{ value: '4', label: '空调设备' },
{ value: '5', label: '其他' }
]
typeOptionsLoaded.value = true
}
} catch (error) {
console.error('获取维修类型失败:', error)
// 使用默认选项作为后备
typeOptions.value = [
{ value: '1', label: '电气设备' },
{ value: '2', label: '网络设备' },
{ value: '3', label: '办公设备' },
{ value: '4', label: '空调设备' },
{ value: '5', label: '其他' }
]
typeOptionsLoaded.value = true
}
}
function getTypeLabel(type: string) {
const option = typeOptions.find(item => item.value === type)
const option = typeOptions.value.find(item => item.value === String(type))
return option ? option.label : '其他'
}
@@ -113,15 +157,33 @@ const imageList = computed(() => {
onLoad(async (options: any) => {
const id = options.id
if (!id) return
console.log('维修详情页面参数:', options)
if (!id) {
uni.showToast({ title: '缺少维修ID', icon: 'none' })
return
}
try {
const response: any = await meetingRoomApi.getRepairDetail(id)
uni.showLoading({ title: '加载中...' })
// 先加载维修类型选项
await loadTypeOptions()
// 再加载维修详情
const response: any = await maintenanceApi.getRepairDetail(id)
console.log('维修详情响应:', response)
if (response.code === 0 && response.data) {
detail.value = response.data
console.log('维修详情数据:', detail.value)
} else {
uni.showToast({ title: response.msg || '获取详情失败', icon: 'none' })
}
uni.hideLoading()
} catch (error) {
console.error('获取维修详情失败:', error)
uni.hideLoading()
uni.showToast({ title: '获取详情失败', icon: 'none' })
}
})
+83 -20
View File
@@ -11,7 +11,9 @@
<view class="form-item">
<view class="label">维修类型</view>
<picker @change="onTypeChange" :value="typeIndex" :range="typeOptions" range-key="label">
<view class="picker">{{ typeOptions[typeIndex].label }}</view>
<view class="picker" :class="{ 'placeholder': typeIndex === -1 }">
{{ typeIndex === -1 ? '请选择维修类型' : typeOptions[typeIndex].label }}
</view>
</picker>
</view>
<view class="form-item">
@@ -75,15 +77,16 @@
@click="viewDetail(item)"
>
<view class="item-header">
<view class="type-tag" :class="getTypeClass(item.type)">{{ getTypeLabel(item.type) }}</view>
<view class="type-tag" :class="getTypeClass(item.type)">
{{ typeOptionsLoaded ? getTypeLabel(item.type) : '加载中...' }}
</view>
<view class="status-tag" :class="getStatusClass(item.status)">{{ getStatusText(item.status) }}</view>
</view>
<view class="item-content">
<view class="equipment-info">
<text class="equipment-name">{{ item.equipmentName }}</text>
<text class="equipment-name">{{ item.title || item.equipmentName || '无标题' }}</text>
<text class="location">{{ item.location }}</text>
</view>
<view class="description">{{ item.description }}</view>
</view>
<view class="item-footer">
<text class="time">{{ formatTime(item.createTime) }}</text>
@@ -99,17 +102,12 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'
import meetingRoomApi from '@/sheep/api/meetingRoom'
import maintenanceApi from '@/sheep/api/maintenance'
import { hooks } from '@@/index'
import { filePath } from '@/sheep/utils/file'
const typeOptions = [
{ value: 1, label: '电气设备' },
{ value: 2, label: '网络设备' },
{ value: 3, label: '办公设备' },
{ value: 4, label: '空调设备' },
{ value: 5, label: '其他' }
]
const typeOptions = ref<any[]>([])
const typeOptionsLoaded = ref(false)
const urgencyOptions = [
{ value: 1, label: '高' },
@@ -117,7 +115,7 @@ const urgencyOptions = [
{ value: 3, label: '低' }
]
const typeIndex = ref(0)
const typeIndex = ref(-1)
const urgencyIndex = ref(0)
const title = ref('')
const location = ref('')
@@ -134,7 +132,7 @@ const repairForm = ref<any>(null)
const header = ref({})
const canSubmit = computed(() => {
return title.value.trim() && location.value.trim() && description.value.trim() && dateFound.value
return title.value.trim() && location.value.trim() && description.value.trim() && dateFound.value && typeIndex.value !== -1
})
function onTypeChange(e: any) {
@@ -154,8 +152,55 @@ function beforeComplete(uploadFile: any) {
return res.code == 0
}
// 加载维修类型选项
async function loadTypeOptions() {
try {
const response: any = await maintenanceApi.getFeedbackType()
console.log('维修类型响应:', response)
if (response.code === 0 && response.data) {
// 处理API返回的数据格式
if (Array.isArray(response.data)) {
typeOptions.value = response.data.map((item: any) => ({
value: item.value || item.id,
label: item.label || item.name || item.text
}))
} else {
// 如果返回的是对象,尝试提取选项
typeOptions.value = Object.entries(response.data).map(([key, value]: [string, any]) => ({
value: key,
label: value
}))
}
console.log('处理后的维修类型选项:', typeOptions.value)
typeOptionsLoaded.value = true
} else {
console.error('获取维修类型失败:', response.msg)
// 使用默认选项作为后备
typeOptions.value = [
{ value: 1, label: '电气设备' },
{ value: 2, label: '网络设备' },
{ value: 3, label: '办公设备' },
{ value: 4, label: '空调设备' },
{ value: 5, label: '其他' }
]
typeOptionsLoaded.value = true
}
} catch (error) {
console.error('获取维修类型失败:', error)
// 使用默认选项作为后备
typeOptions.value = [
{ value: 1, label: '电气设备' },
{ value: 2, label: '网络设备' },
{ value: 3, label: '办公设备' },
{ value: 4, label: '空调设备' },
{ value: 5, label: '其他' }
]
typeOptionsLoaded.value = true
}
}
function getTypeLabel(type: number) {
const option = typeOptions.find(item => item.value === type)
const option = typeOptions.value.find(item => item.value === type)
return option ? option.label : '其他'
}
@@ -192,6 +237,13 @@ async function submitRepair() {
uni.showToast({ title: '请填写完整信息', icon: 'none' })
return
}
// 验证维修类型是否已选择
if (typeIndex.value === -1 || !typeOptions.value[typeIndex.value]) {
uni.showToast({ title: '请选择维修类型', icon: 'none' })
return
}
// 收集所有上传成功图片的 url
let picUrl: string[] = []
if (uploadFileUrl.value.length > 0) {
@@ -206,6 +258,7 @@ async function submitRepair() {
}
})
}
try {
uni.showLoading({ title: '提交中...' })
const data = {
@@ -215,9 +268,11 @@ async function submitRepair() {
dateFound: dateFound.value,
location: location.value.trim(),
urgency: String(urgencyOptions[urgencyIndex.value].value),
maintainType: typeOptions[typeIndex.value].value
maintainType: typeOptions.value[typeIndex.value].value
}
const response: any = await meetingRoomApi.submitRepair(data)
console.log('提交的维修数据:', data)
const response: any = await maintenanceApi.submitRepair(data)
console.log('维修提交响应:', response)
uni.hideLoading()
if (response.code === 0) {
uni.showToast({ title: '提交成功', icon: 'success' })
@@ -227,14 +282,17 @@ async function submitRepair() {
description.value = ''
dateFound.value = ''
uploadFileUrl.value = []
typeIndex.value = 0
typeIndex.value = -1
urgencyIndex.value = 0
// 刷新列表
loadRepairList()
if (activeTab.value === 1) {
loadRepairList()
}
} else {
uni.showToast({ title: response.msg || '提交失败', icon: 'none' })
}
} catch (error) {
console.error('维修提交失败:', error)
uni.hideLoading()
uni.showToast({ title: '提交失败', icon: 'none' })
}
@@ -251,7 +309,7 @@ async function loadRepairList(isLoadMore = false, autoFill = false) {
loading.value = true
let requestSuccess = false
try {
const response: any = await meetingRoomApi.getRepairList({
const response: any = await maintenanceApi.getRepairList({
current: page.value,
size: pageSize
})
@@ -315,6 +373,7 @@ watch(activeTab, (val) => {
onMounted(() => {
header.value = hooks.useDefaultHeader()
loadTypeOptions()
if (activeTab.value === 1) {
refreshRepairList()
}
@@ -378,6 +437,10 @@ onMounted(() => {
background: #f7f8fa;
border-radius: 8rpx;
font-size: 28rpx;
&.placeholder {
color: #999;
}
}
.location-input,
+9
View File
@@ -37,6 +37,15 @@ export default {
url: `/integrated/maintenanceOrder/app/my/${id}`,
method: 'GET',
}),
/**
* 获取维修类型选项
*/
getFeedbackType: () =>
request({
url: '/integrated/maintenanceOrder/app/getFeedbackType',
method: 'GET',
}),
}