hookehuyr

feat(反馈): 添加意见反馈页面和功能

refactor(页面路由): 更新反馈页面路由路径
style(样式): 添加反馈页面样式文件
build(组件): 添加NutOverlay组件类型声明
docs(配置): 更新页面配置文件和导航标题
......@@ -19,6 +19,7 @@ declare module 'vue' {
NutInput: typeof import('@nutui/nutui-taro')['Input']
NutMenu: typeof import('@nutui/nutui-taro')['Menu']
NutMenuItem: typeof import('@nutui/nutui-taro')['MenuItem']
NutOverlay: typeof import('@nutui/nutui-taro')['Overlay']
NutPicker: typeof import('@nutui/nutui-taro')['Picker']
NutPopup: typeof import('@nutui/nutui-taro')['Popup']
NutRadio: typeof import('@nutui/nutui-taro')['Radio']
......
/*
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-03 18:09:07
* @LastEditTime: 2025-07-03 20:40:08
* @FilePath: /jgdl/src/app.config.js
* @Description: 文件描述
*/
......@@ -24,6 +24,8 @@ export default {
'pages/myCar/index',
'pages/myOrders/index',
'pages/myAuthCar/index',
'pages/feedBack/index',
'pages/helpCenter/index',
],
subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去
{
......
/*
* @Date: 2025-07-03 20:15:54
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-03 20:16:13
* @FilePath: /jgdl/src/pages/feedBack/index.config.js
* @Description: 文件描述
*/
export default {
navigationBarTitleText: '意见反馈',
usingComponents: {
},
}
.feedback-page {
min-height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
/* 头部导航 */
.header {
background-color: #f97316;
padding: 32rpx 32rpx 24rpx;
padding-top: calc(32rpx + env(safe-area-inset-top));
display: flex;
align-items: center;
justify-content: space-between;
}
.header-left {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
font-size: 48rpx;
color: white;
}
.header-title {
font-size: 36rpx;
font-weight: 600;
color: white;
flex: 1;
text-align: center;
}
.header-right {
width: 48rpx;
}
/* 内容区域 */
.content {
flex: 1;
padding: 32rpx;
}
/* 提示文字 */
.tip-text {
font-size: 28rpx;
color: #6b7280;
margin-bottom: 32rpx;
line-height: 1.5;
}
/* 反馈分类 */
.category-section {
margin-bottom: 32rpx;
}
.category-grid {
display: flex;
justify-content: space-between;
gap: 16rpx;
}
.category-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
}
.category-icon {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
background-color: #f3f4f6;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
transition: all 0.3s ease;
}
.category-icon.active {
background-color: #fed7aa;
}
.icon-text {
font-size: 48rpx;
}
.category-name {
font-size: 24rpx;
color: #6b7280;
text-align: center;
}
.category-item.active .category-name {
color: #f97316;
}
/* 反馈内容 */
.feedback-section {
margin-bottom: 32rpx;
}
.feedback-textarea {
width: 100%;
height: 256rpx;
padding: 24rpx;
background-color: #f9fafb;
border-radius: 16rpx;
border: none;
font-size: 28rpx;
color: #374151;
line-height: 1.5;
resize: none;
box-sizing: border-box;
}
.feedback-textarea:focus {
outline: none;
background-color: #f3f4f6;
}
.char-count {
display: flex;
justify-content: flex-end;
font-size: 24rpx;
color: #9ca3af;
margin-top: 16rpx;
}
/* 图片上传 */
.image-section {
margin-bottom: 32rpx;
}
.section-label {
font-size: 28rpx;
color: #6b7280;
margin-bottom: 16rpx;
}
.image-upload-grid {
display: flex;
gap: 24rpx;
align-items: center;
}
.image-item {
position: relative;
}
.image-preview {
position: relative;
width: 128rpx;
height: 128rpx;
border-radius: 16rpx;
overflow: hidden;
}
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.delete-btn {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 32rpx;
height: 32rpx;
background-color: #ef4444;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
}
.delete-icon {
font-size: 16rpx;
color: white;
}
.upload-button {
width: 128rpx;
height: 128rpx;
border: 2rpx dashed #d1d5db;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f9fafb;
transition: all 0.3s ease;
}
.upload-button:active {
background-color: #f3f4f6;
border-color: #f97316;
}
.upload-icon {
font-size: 48rpx;
color: #9ca3af;
}
.upload-tip {
font-size: 24rpx;
color: #9ca3af;
margin-top: 16rpx;
text-align: right;
}
/* 联系方式 */
.contact-section {
margin-bottom: 48rpx;
}
.contact-input {
width: 100%;
padding: 28rpx;
background-color: #f9fafb;
border-radius: 16rpx;
border: none;
font-size: 28rpx;
color: #374151;
box-sizing: border-box;
margin-top: 16rpx;
}
.contact-input:focus {
outline: none;
background-color: #f3f4f6;
}
/* 提交按钮 */
.submit-section {
margin-bottom: 32rpx;
}
.submit-btn {
width: 100%;
padding: 12rpx;
background-color: #f97316;
color: white;
border: none;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: 600;
transition: all 0.3s ease;
}
.submit-btn.disabled {
background-color: #fed7aa;
color: white;
}
.submit-btn:not(.disabled):active {
background-color: #ea580c;
}
/* 成功弹窗 */
.success-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.success-content {
background-color: white;
border-radius: 16rpx;
padding: 48rpx;
width: 480rpx;
text-align: center;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
}
.success-icon {
width: 128rpx;
height: 128rpx;
background-color: #dcfce7;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 32rpx;
}
.check-icon {
font-size: 64rpx;
color: #16a34a;
font-weight: bold;
}
.success-title {
font-size: 36rpx;
font-weight: 600;
color: #111827;
margin-bottom: 16rpx;
}
.success-desc {
font-size: 28rpx;
color: #6b7280;
}
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-03 20:25:34
* @FilePath: /jgdl/src/pages/feedBack/index.vue
* @Description: 意见反馈页面
-->
<template>
<view class="feedback-page">
<view class="content">
<!-- 提示文字 -->
<view class="tip-text">
感谢您的宝贵意见,我们将不断完善产品和服务
</view>
<!-- 反馈分类 -->
<view class="category-section">
<view class="category-grid">
<view
v-for="category in categories"
:key="category.id"
class="category-item"
:class="{ active: selectedCategory === category.id }"
@click="selectCategory(category.id)"
>
<view class="category-icon" :class="{ active: selectedCategory === category.id }">
<text class="icon-text">{{ category.icon }}</text>
</view>
<text class="category-name">{{ category.name }}</text>
</view>
</view>
</view>
<!-- 反馈内容 -->
<view class="feedback-section">
<textarea
v-model="feedbackText"
placeholder="请详细描述您遇到的问题或建议..."
class="feedback-textarea"
maxlength="200"
/>
<view class="char-count">
{{ feedbackText.length }}/200
</view>
</view>
<!-- 图片上传 -->
<view class="image-section">
<view class="section-label">上传图片(选填)</view>
<view class="image-upload-grid">
<!-- 已上传的图片 -->
<view
v-for="(image, index) in uploadedImages"
:key="index"
class="image-item"
>
<view class="image-preview" @click="previewImage(image)">
<image :src="image" class="preview-image" mode="aspectFill" />
<view class="delete-btn" @click.stop="deleteImage(index)">
<Close class="delete-icon" />
</view>
</view>
</view>
<!-- 上传按钮 -->
<view
v-if="uploadedImages.length < 3"
class="upload-button"
@click="triggerUpload"
>
<Plus class="upload-icon" />
</view>
</view>
<view class="upload-tip">最多上传3张</view>
</view>
<!-- 联系方式 -->
<view class="contact-section">
<view class="section-label">联系方式(选填)</view>
<nut-input
v-model="contactInfo"
placeholder="请留下您的手机号或微信号"
/>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<button
class="submit-btn"
:class="{ disabled: !canSubmit }"
:disabled="!canSubmit"
@click="handleSubmit"
>
提交反馈
</button>
</view>
</view>
<!-- 图片预览组件 -->
<nut-image-preview
v-model:show="previewVisible"
:images="previewImages"
:init-no="previewIndex"
@close="closePreview"
/>
<!-- 成功提示弹窗 -->
<nut-overlay v-model:visible="showSuccessModal" @click="closeSuccessModal">
<view class="success-modal">
<view class="success-content">
<view class="success-icon">
<text class="check-icon">✓</text>
</view>
<view class="success-title">反馈成功</view>
<view class="success-desc">感谢您的反馈</view>
</view>
</view>
</nut-overlay>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import Taro from '@tarojs/taro'
import { Left, Plus, Close } from '@nutui/icons-vue-taro'
import { Toast } from '@nutui/nutui-taro'
import './index.less'
// 反馈分类
const categories = ref([
{ id: 'feature', name: '功能建议', icon: '💡' },
{ id: 'ui', name: '界面设计', icon: '✖️' },
{ id: 'vehicle', name: '车辆信息', icon: '🚲' },
{ id: 'other', name: '其他问题', icon: '❓' }
])
// 表单数据
const selectedCategory = ref(null)
const feedbackText = ref('')
const contactInfo = ref('')
const uploadedImages = ref([])
// 图片预览相关
const previewVisible = ref(false)
const previewImages = ref([])
const previewIndex = ref(0)
// 成功弹窗
const showSuccessModal = ref(false)
// 计算属性:是否可以提交
const canSubmit = computed(() => {
return selectedCategory.value && feedbackText.value.trim()
})
/**
* 返回上一页
*/
const goBack = () => {
Taro.navigateBack()
}
/**
* 选择反馈分类
*/
const selectCategory = (categoryId) => {
selectedCategory.value = categoryId
}
/**
* 触发图片上传
*/
const triggerUpload = () => {
if (uploadedImages.value.length >= 3) {
Toast.fail('最多只能上传3张图片')
return
}
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: function (res) {
const tempFilePath = res.tempFilePaths[0]
uploadImage(tempFilePath)
},
fail: function () {
Toast.fail('选择图片失败')
}
})
}
/**
* 上传图片到服务器
*/
const uploadImage = (filePath) => {
Taro.showLoading({ title: '上传中', mask: true })
// 模拟上传成功(实际项目中替换为真实的上传逻辑)
setTimeout(() => {
Taro.hideLoading()
// 模拟服务器返回的图片URL
const mockImageUrl = filePath // 在实际项目中,这里应该是服务器返回的URL
// 添加到已上传图片列表
uploadedImages.value.push(mockImageUrl)
Toast.success('上传成功')
}, 1500)
}
/**
* 预览图片
*/
const previewImage = (imageUrl) => {
previewImages.value = [{ src: imageUrl }]
previewIndex.value = 0
previewVisible.value = true
}
/**
* 删除图片
*/
const deleteImage = (index) => {
uploadedImages.value.splice(index, 1)
Toast.success('删除成功')
}
/**
* 关闭图片预览
*/
const closePreview = () => {
previewVisible.value = false
}
/**
* 提交反馈
*/
const handleSubmit = () => {
if (!canSubmit.value) return
Taro.showLoading({ title: '提交中...', mask: true })
// 构建提交数据
const submitData = {
category: selectedCategory.value,
content: feedbackText.value.trim(),
contact: contactInfo.value.trim(),
images: uploadedImages.value
}
// TODO: 调用实际的API接口提交数据
// submitFeedback(submitData)
// 模拟提交
setTimeout(() => {
Taro.hideLoading()
showSuccessModal.value = true
// 2秒后自动关闭并重置表单
setTimeout(() => {
closeSuccessModal()
}, 2000)
}, 1500)
}
/**
* 关闭成功弹窗并重置表单
*/
const closeSuccessModal = () => {
showSuccessModal.value = false
// 重置表单
selectedCategory.value = null
feedbackText.value = ''
contactInfo.value = ''
uploadedImages.value = []
}
</script>
<script>
export default {
name: 'FeedbackPage'
}
</script>
/*
* @Date: 2025-07-03 20:39:30
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-03 20:39:50
* @FilePath: /jgdl/src/pages/help/index.config.js
* @Description: 文件描述
*/
export default {
navigationBarTitleText: '帮助中心',
usingComponents: {
},
}
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-02 14:50:57
* @FilePath: /jgdl/src/pages/demo/index.vue
* @Description: 文件描述
-->
<template>
<div class="red">{{ str }}</div>
</template>
<script setup>
// import '@tarojs/taro/html.css'
import { ref } from "vue";
import "./index.less";
// 定义响应式数据
const str = ref('Demo页面')
</script>
<script>
export default {
name: "demoPage",
};
</script>
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-03 13:25:46
* @LastEditTime: 2025-07-03 20:35:32
* @FilePath: /jgdl/src/pages/myAuthCar/index.vue
* @Description: 我的认证车页面
-->
......@@ -266,7 +266,7 @@ const onItemClick = (item) => {
*/
const handleSellClick = (carId) => {
Taro.navigateTo({
url: `/pages/sell/index?id=${carId}&mode=edit`
url: `/pages/sell/index?id=${carId}&mode=edit&type=auth`
})
}
......
......@@ -158,7 +158,7 @@ const onHelpCenter = () => {
*/
const onFeedback = () => {
Taro.navigateTo({
url: '/pages/feedback/index'
url: '/pages/feedBack/index'
})
}
......
......@@ -224,9 +224,12 @@
<!-- 底部按钮 -->
<view class="bottom-actions">
<nut-button color="#f97316" size="large" block @click="onPublish">
<nut-button v-if="!isAuthMode" color="#f97316" size="large" block @click="onPublish">
{{ isEditMode ? '保存修改' : '确认发布' }}
</nut-button>
<nut-button v-else color="#f97316" size="large" block @click="onPublish">
确认发布
</nut-button>
</view>
<!-- 选择器弹窗 -->
......@@ -289,15 +292,11 @@ import './index.less'
// 获取页面参数
const instance = Taro.getCurrentInstance()
const { id, mode } = instance.router?.params || {}
const { id, mode, type } = instance.router?.params || {}
const isEditMode = ref(mode === 'edit' && id)
const isAuthMode = ref(type === 'auth' && id)
const carId = ref(id || '')
const themeVars = ref({
navbarBackground: '#fb923c',
navbarColor: '#ffffff',
})
// 文件上传相关
// const frontFileList = ref([]) // 正面照(保留用于兼容旧代码)
// const leftFileList = ref([]) // 左侧照(保留用于兼容旧代码)
......@@ -853,5 +852,10 @@ onMounted(() => {
wx.setNavigationBarTitle({
title: isEditMode.value ? '编辑车源' : '发布车源'
});
if (isAuthMode.value) {
wx.setNavigationBarTitle({
title: '发布车源'
});
}
})
</script>
......