|
@@ -1,32 +1,82 @@
|
|
|
<template>
|
|
<template>
|
|
|
<view class="page">
|
|
<view class="page">
|
|
|
- <view class="card box">
|
|
|
|
|
|
|
+ <view class="form-section">
|
|
|
<view class="sec-title">反馈类型</view>
|
|
<view class="sec-title">反馈类型</view>
|
|
|
- <view class="types">
|
|
|
|
|
- <view class="type" :class="{ on: form.fbType === '投诉' }" @click="form.fbType = '投诉'">投诉</view>
|
|
|
|
|
- <view class="type" :class="{ on: form.fbType === '表扬' }" @click="form.fbType = '表扬'">表扬</view>
|
|
|
|
|
|
|
+ <view class="field-row">
|
|
|
|
|
+ <view class="label"><text class="req">*</text>类型</view>
|
|
|
|
|
+ <view class="type-switch">
|
|
|
|
|
+ <view class="type" :class="{ on: form.fbType === '投诉' }" @click="form.fbType = '投诉'">投诉</view>
|
|
|
|
|
+ <view class="type" :class="{ on: form.fbType === '表扬' }" @click="form.fbType = '表扬'">表扬</view>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
|
|
|
|
|
+ <view class="form-section">
|
|
|
<view class="sec-title">被反馈人信息(选填)</view>
|
|
<view class="sec-title">被反馈人信息(选填)</view>
|
|
|
- <view class="field"><input v-model="form.targetName" placeholder="被反馈人姓名" /></view>
|
|
|
|
|
- <view class="field"><input v-model="form.targetDept" placeholder="部门" /></view>
|
|
|
|
|
- <view class="field"><input v-model="form.targetPost" placeholder="职务" /></view>
|
|
|
|
|
|
|
+ <view class="field-row">
|
|
|
|
|
+ <view class="label">姓名</view>
|
|
|
|
|
+ <input v-model="form.targetName" placeholder="请输入被反馈人姓名" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="field-row">
|
|
|
|
|
+ <view class="label">单位</view>
|
|
|
|
|
+ <input v-model="form.targetDept" placeholder="请输入所在单位/科室" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="field-row">
|
|
|
|
|
+ <view class="label">职务</view>
|
|
|
|
|
+ <input v-model="form.targetPost" placeholder="请输入职务" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
|
|
|
|
|
+ <view class="form-section">
|
|
|
<view class="sec-title">反馈内容</view>
|
|
<view class="sec-title">反馈内容</view>
|
|
|
- <view class="field">
|
|
|
|
|
- <textarea v-model="form.content" placeholder="请客观具体描述" maxlength="2000" />
|
|
|
|
|
|
|
+ <view class="field-row top">
|
|
|
|
|
+ <view class="label"><text class="req">*</text>内容</view>
|
|
|
|
|
+ <textarea
|
|
|
|
|
+ v-model="form.content"
|
|
|
|
|
+ placeholder="请客观、具体描述相关情况(必填)"
|
|
|
|
|
+ maxlength="2000"
|
|
|
|
|
+ />
|
|
|
</view>
|
|
</view>
|
|
|
-
|
|
|
|
|
- <view class="sec-title">反馈材料(选填)</view>
|
|
|
|
|
- <view class="uploads">
|
|
|
|
|
- <image v-for="(u, i) in form.attachmentUrls" :key="i" :src="u" class="thumb" mode="aspectFill" />
|
|
|
|
|
- <view class="add" @click="pickFiles">+</view>
|
|
|
|
|
|
|
+ <view class="upload-area">
|
|
|
|
|
+ <view class="upload-hint">反馈材料(选填)· 支持图片、视频、文件,可多选</view>
|
|
|
|
|
+ <view class="upload-list">
|
|
|
|
|
+ <view v-for="(item, i) in attachments" :key="i" class="upload-item">
|
|
|
|
|
+ <image v-if="isImage(item)" :src="item.previewUrl" mode="aspectFill" />
|
|
|
|
|
+ <view v-else class="file-chip">
|
|
|
|
|
+ <text class="file-ext">{{ extLabel(item) }}</text>
|
|
|
|
|
+ <text class="file-name">{{ item.fileName || '附件' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="rm" @click="removeFile(i)">×</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="upload-add" @click="pickFiles">
|
|
|
|
|
+ <text class="plus">+</text>
|
|
|
|
|
+ <text>上传</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
|
|
|
- <view class="sec-title">联系方式(选填,可匿名)</view>
|
|
|
|
|
- <view class="field"><input v-model="form.reporterName" placeholder="姓名" /></view>
|
|
|
|
|
- <view class="field"><input v-model="form.reporterPhone" placeholder="手机号" /></view>
|
|
|
|
|
|
|
+ <view class="form-section">
|
|
|
|
|
+ <view class="sec-title">举报人信息(选填)</view>
|
|
|
|
|
+ <view class="field-row">
|
|
|
|
|
+ <view class="label">姓名</view>
|
|
|
|
|
+ <input v-model="form.reporterName" placeholder="可匿名,选填" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="field-row">
|
|
|
|
|
+ <view class="label">单位</view>
|
|
|
|
|
+ <input v-model="form.reporterUnit" placeholder="请输入单位" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="field-row">
|
|
|
|
|
+ <view class="label">职务</view>
|
|
|
|
|
+ <input v-model="form.reporterPost" placeholder="请输入职务" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="field-row">
|
|
|
|
|
+ <view class="label">联系方式</view>
|
|
|
|
|
+ <input v-model="form.reporterPhone" placeholder="手机号 / 微信等" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
|
|
|
|
|
+ <view class="submit-bar">
|
|
|
<view class="btn-primary" :class="{ disabled: submitting }" @click="submit">提交反馈</view>
|
|
<view class="btn-primary" :class="{ disabled: submitting }" @click="submit">提交反馈</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
@@ -34,12 +84,18 @@
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
import { submitFeedback } from '../../api/public.js'
|
|
import { submitFeedback } from '../../api/public.js'
|
|
|
- import { chooseAndUploadImages } from '../../utils/upload.js'
|
|
|
|
|
|
|
+ import {
|
|
|
|
|
+ chooseAndUploadFiles,
|
|
|
|
|
+ fileNameFromUrl,
|
|
|
|
|
+ isImageUrl,
|
|
|
|
|
+ revokePreview
|
|
|
|
|
+ } from '../../utils/upload.js'
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
|
data() {
|
|
data() {
|
|
|
return {
|
|
return {
|
|
|
submitting: false,
|
|
submitting: false,
|
|
|
|
|
+ attachments: [],
|
|
|
form: {
|
|
form: {
|
|
|
fbType: '投诉',
|
|
fbType: '投诉',
|
|
|
targetName: '',
|
|
targetName: '',
|
|
@@ -47,18 +103,40 @@
|
|
|
targetPost: '',
|
|
targetPost: '',
|
|
|
content: '',
|
|
content: '',
|
|
|
reporterName: '',
|
|
reporterName: '',
|
|
|
- reporterPhone: '',
|
|
|
|
|
- attachmentUrls: []
|
|
|
|
|
|
|
+ reporterUnit: '',
|
|
|
|
|
+ reporterPost: '',
|
|
|
|
|
+ reporterPhone: ''
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
+ beforeUnmount() {
|
|
|
|
|
+ ;(this.attachments || []).forEach((a) => revokePreview(a))
|
|
|
|
|
+ },
|
|
|
|
|
+ // #ifndef VUE3
|
|
|
|
|
+ beforeDestroy() {
|
|
|
|
|
+ ;(this.attachments || []).forEach((a) => revokePreview(a))
|
|
|
|
|
+ },
|
|
|
|
|
+ // #endif
|
|
|
methods: {
|
|
methods: {
|
|
|
|
|
+ isImage(item) {
|
|
|
|
|
+ return isImageUrl(item.fileName || item.previewUrl || item.url)
|
|
|
|
|
+ },
|
|
|
|
|
+ extLabel(item) {
|
|
|
|
|
+ const name = item.fileName || fileNameFromUrl(item.url)
|
|
|
|
|
+ const i = name.lastIndexOf('.')
|
|
|
|
|
+ return i >= 0 ? name.slice(i + 1).toUpperCase() : 'FILE'
|
|
|
|
|
+ },
|
|
|
async pickFiles() {
|
|
async pickFiles() {
|
|
|
try {
|
|
try {
|
|
|
- const urls = await chooseAndUploadImages(6, 'feedback')
|
|
|
|
|
- this.form.attachmentUrls = this.form.attachmentUrls.concat(urls)
|
|
|
|
|
|
|
+ const items = await chooseAndUploadFiles('feedback')
|
|
|
|
|
+ this.attachments = this.attachments.concat(items)
|
|
|
} catch (e) {}
|
|
} catch (e) {}
|
|
|
},
|
|
},
|
|
|
|
|
+ removeFile(i) {
|
|
|
|
|
+ const item = this.attachments[i]
|
|
|
|
|
+ if (item) revokePreview(item)
|
|
|
|
|
+ this.attachments.splice(i, 1)
|
|
|
|
|
+ },
|
|
|
async submit() {
|
|
async submit() {
|
|
|
if (!this.form.content || !this.form.content.trim()) {
|
|
if (!this.form.content || !this.form.content.trim()) {
|
|
|
uni.showToast({ title: '请填写反馈内容', icon: 'none' })
|
|
uni.showToast({ title: '请填写反馈内容', icon: 'none' })
|
|
@@ -67,7 +145,12 @@
|
|
|
if (this.submitting) return
|
|
if (this.submitting) return
|
|
|
this.submitting = true
|
|
this.submitting = true
|
|
|
try {
|
|
try {
|
|
|
- await submitFeedback({ ...this.form })
|
|
|
|
|
|
|
+ await submitFeedback({
|
|
|
|
|
+ ...this.form,
|
|
|
|
|
+ attachmentUrls: this.attachments.map((a) => a.url)
|
|
|
|
|
+ })
|
|
|
|
|
+ ;(this.attachments || []).forEach((a) => revokePreview(a))
|
|
|
|
|
+ this.attachments = []
|
|
|
uni.showToast({ title: '提交成功', icon: 'success' })
|
|
uni.showToast({ title: '提交成功', icon: 'success' })
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
uni.reLaunch({ url: '/pages/home/index' })
|
|
uni.reLaunch({ url: '/pages/home/index' })
|
|
@@ -82,50 +165,35 @@
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
.page {
|
|
.page {
|
|
|
- padding: 24rpx;
|
|
|
|
|
|
|
+ padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
|
|
}
|
|
}
|
|
|
- .box {
|
|
|
|
|
- padding: 28rpx;
|
|
|
|
|
- }
|
|
|
|
|
- .types {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ .file-chip {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ padding: 12rpx;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- gap: 16rpx;
|
|
|
|
|
- margin-bottom: 24rpx;
|
|
|
|
|
- }
|
|
|
|
|
- .type {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- text-align: center;
|
|
|
|
|
- padding: 18rpx;
|
|
|
|
|
- border: 1rpx solid var(--line);
|
|
|
|
|
- border-radius: 10rpx;
|
|
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 8rpx;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
}
|
|
}
|
|
|
- .type.on {
|
|
|
|
|
- background: var(--primary-soft);
|
|
|
|
|
- border-color: var(--primary);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ .file-ext {
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
color: var(--primary);
|
|
color: var(--primary);
|
|
|
- font-weight: 600;
|
|
|
|
|
- }
|
|
|
|
|
- textarea {
|
|
|
|
|
- min-height: 180rpx;
|
|
|
|
|
}
|
|
}
|
|
|
- .uploads {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
- gap: 16rpx;
|
|
|
|
|
- margin-bottom: 20rpx;
|
|
|
|
|
- }
|
|
|
|
|
- .thumb,
|
|
|
|
|
- .add {
|
|
|
|
|
- width: 140rpx;
|
|
|
|
|
- height: 140rpx;
|
|
|
|
|
- border-radius: 10rpx;
|
|
|
|
|
- }
|
|
|
|
|
- .add {
|
|
|
|
|
- border: 1rpx dashed #ccc;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- font-size: 48rpx;
|
|
|
|
|
- color: #999;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ .file-name {
|
|
|
|
|
+ font-size: 18rpx;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|