You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
464 lines
11 KiB
464 lines
11 KiB
<template>
|
|
<view class="container">
|
|
<!-- 自定义导航栏 -->
|
|
<view class="custom-navbar">
|
|
<view class="navbar-content">
|
|
<view class="back-btn" @click="goBack">
|
|
<uv-icon name="arrow-left" size="20" color="#333"></uv-icon>
|
|
</view>
|
|
<view class="navbar-title">Bug反馈</view>
|
|
<view class="placeholder"></view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 表单内容 -->
|
|
<view class="form-container">
|
|
<view class="form-item">
|
|
<view class="form-label">
|
|
选择模板
|
|
<text class="required">*</text>
|
|
</view>
|
|
<uv-input
|
|
v-model="selectedTemplateName"
|
|
placeholder="请选择模板"
|
|
:readonly="true"
|
|
@click="openTemplatePicker"
|
|
suffixIcon="arrow-down"
|
|
></uv-input>
|
|
<view class="error-text" v-if="errors.template_id">{{ errors.template_id }}</view>
|
|
</view>
|
|
|
|
<view class="form-item">
|
|
<view class="form-label">
|
|
问题名称
|
|
<text class="required">*</text>
|
|
</view>
|
|
<uv-input
|
|
v-model="formData.name"
|
|
placeholder="请输入问题名称"
|
|
:clearable="true"
|
|
:maxlength="50"
|
|
@input="onNameInput"
|
|
></uv-input>
|
|
<view class="error-text" v-if="errors.name">{{ errors.name }}</view>
|
|
</view>
|
|
|
|
<view class="form-item">
|
|
<view class="form-label">
|
|
问题描述
|
|
<text class="required">*</text>
|
|
</view>
|
|
<uv-textarea
|
|
v-model="formData.description"
|
|
placeholder="请详细描述遇到的问题..."
|
|
:maxlength="500"
|
|
:autoHeight="true"
|
|
:minHeight="120"
|
|
@input="onDescriptionInput"
|
|
></uv-textarea>
|
|
<view class="char-count">{{ formData.description.length }}/500</view>
|
|
<view class="error-text" v-if="errors.description">{{ errors.description }}</view>
|
|
</view>
|
|
|
|
<view class="form-item">
|
|
<view class="form-label">
|
|
邮箱
|
|
<text class="required">*</text>
|
|
</view>
|
|
<uv-input
|
|
v-model="formData.email"
|
|
placeholder="请输入邮箱地址"
|
|
:clearable="true"
|
|
type="email"
|
|
@input="onEmailInput"
|
|
></uv-input>
|
|
<view class="error-text" v-if="errors.email">{{ errors.email }}</view>
|
|
</view>
|
|
|
|
<view class="form-item">
|
|
<view class="form-label">
|
|
手机
|
|
<text class="required">*</text>
|
|
</view>
|
|
<uv-input
|
|
v-model="formData.phone"
|
|
placeholder="请输入手机号"
|
|
:clearable="true"
|
|
type="number"
|
|
:maxlength="11"
|
|
@input="onPhoneInput"
|
|
></uv-input>
|
|
<view class="error-text" v-if="errors.phone">{{ errors.phone }}</view>
|
|
</view>
|
|
|
|
<view class="form-item">
|
|
<view class="form-label">联系厂家</view>
|
|
<uv-input
|
|
v-model="formData.vendor_contact"
|
|
placeholder="请输入联系厂家(选填)"
|
|
:clearable="true"
|
|
:maxlength="100"
|
|
@input="onVendorContactInput"
|
|
></uv-input>
|
|
<view class="error-text" v-if="errors.vendor_contact">{{ errors.vendor_contact }}</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 提交按钮 -->
|
|
<view class="submit-section">
|
|
<uv-button
|
|
type="primary"
|
|
:loading="submitLoading"
|
|
@click="submitBugFeedback"
|
|
:disabled="!isFormValid"
|
|
>
|
|
提交反馈
|
|
</uv-button>
|
|
</view>
|
|
|
|
<!-- 模板选择器 -->
|
|
<uv-picker
|
|
ref="templatePicker"
|
|
:columns="templateColumns"
|
|
@confirm="onTemplateConfirm"
|
|
></uv-picker>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useUserStore } from '@/store'
|
|
import RPC from '@/utils/request'
|
|
|
|
const userStore = useUserStore()
|
|
|
|
// 表单数据
|
|
const formData = ref({
|
|
name: '',
|
|
description: '',
|
|
email: '',
|
|
phone: '',
|
|
vendor_contact: '',
|
|
template_id: '',
|
|
template_name: ''
|
|
})
|
|
|
|
// 错误信息
|
|
const errors = ref({
|
|
name: '',
|
|
description: '',
|
|
email: '',
|
|
phone: '',
|
|
vendor_contact: '',
|
|
template_id: ''
|
|
})
|
|
|
|
// 提交状态
|
|
const submitLoading = ref(false)
|
|
|
|
// 模板相关
|
|
const templateList = ref([])
|
|
const templatePicker = ref(null)
|
|
const templateColumns = ref([[]])
|
|
const selectedTemplateName = ref('')
|
|
|
|
// 表单验证
|
|
const isFormValid = computed(() => {
|
|
return formData.value.name.trim() &&
|
|
formData.value.description.trim() &&
|
|
formData.value.email.trim() &&
|
|
formData.value.phone.trim() &&
|
|
formData.value.template_id
|
|
})
|
|
|
|
// 邮箱验证
|
|
const validateEmail = (email) => {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
return emailRegex.test(email)
|
|
}
|
|
|
|
// 手机号验证
|
|
const validatePhone = (phone) => {
|
|
const phoneRegex = /^1[3-9]\d{9}$/
|
|
return phoneRegex.test(phone)
|
|
}
|
|
|
|
// 输入事件处理
|
|
const onNameInput = () => {
|
|
if (errors.value.name) {
|
|
errors.value.name = ''
|
|
}
|
|
}
|
|
|
|
const onDescriptionInput = () => {
|
|
if (errors.value.description) {
|
|
errors.value.description = ''
|
|
}
|
|
}
|
|
|
|
const onEmailInput = () => {
|
|
if (errors.value.email) {
|
|
errors.value.email = ''
|
|
}
|
|
}
|
|
|
|
const onPhoneInput = () => {
|
|
if (errors.value.phone) {
|
|
errors.value.phone = ''
|
|
}
|
|
}
|
|
|
|
const onVendorContactInput = () => {
|
|
if (errors.value.vendor_contact) {
|
|
errors.value.vendor_contact = ''
|
|
}
|
|
}
|
|
|
|
// 获取模板列表
|
|
const getTemplateList = async () => {
|
|
try {
|
|
const res = await RPC.post("/template/list_with_access", {
|
|
page: 1,
|
|
limit: -1
|
|
})
|
|
|
|
templateList.value = res.rows || []
|
|
templateColumns.value = [templateList.value.map(item => item.name)]
|
|
} catch (error) {
|
|
console.error('获取模板列表失败:', error)
|
|
uni.showToast({
|
|
title: '获取模板列表失败',
|
|
icon: 'error'
|
|
})
|
|
}
|
|
}
|
|
|
|
// 打开模板选择器
|
|
const openTemplatePicker = () => {
|
|
if (templateList.value.length === 0) {
|
|
uni.showToast({
|
|
title: '暂无可用模板',
|
|
icon: 'none'
|
|
})
|
|
return
|
|
}
|
|
templatePicker.value.open()
|
|
}
|
|
|
|
// 模板选择确认
|
|
const onTemplateConfirm = (val) => {
|
|
const selectedIndex = templateColumns.value[0].indexOf(val.value[0])
|
|
if (selectedIndex !== -1) {
|
|
const selectedTemplate = templateList.value[selectedIndex]
|
|
formData.value.template_id = selectedTemplate.id
|
|
formData.value.template_name = selectedTemplate.name
|
|
selectedTemplateName.value = selectedTemplate.name
|
|
|
|
// 清除模板选择错误
|
|
if (errors.value.template_id) {
|
|
errors.value.template_id = ''
|
|
}
|
|
}
|
|
}
|
|
|
|
// 表单验证函数
|
|
const validateForm = () => {
|
|
let isValid = true
|
|
|
|
// 验证模板选择
|
|
if (!formData.value.template_id) {
|
|
errors.value.template_id = '请选择模板'
|
|
isValid = false
|
|
}
|
|
|
|
// 验证问题名称
|
|
if (!formData.value.name.trim()) {
|
|
errors.value.name = '请输入问题名称'
|
|
isValid = false
|
|
}
|
|
|
|
// 验证问题描述
|
|
if (!formData.value.description.trim()) {
|
|
errors.value.description = '请输入问题描述'
|
|
isValid = false
|
|
}
|
|
|
|
// 验证邮箱
|
|
if (!formData.value.email.trim()) {
|
|
errors.value.email = '请输入邮箱地址'
|
|
isValid = false
|
|
} else if (!validateEmail(formData.value.email.trim())) {
|
|
errors.value.email = '请输入正确的邮箱格式'
|
|
isValid = false
|
|
}
|
|
|
|
// 验证手机号
|
|
if (!formData.value.phone.trim()) {
|
|
errors.value.phone = '请输入手机号'
|
|
isValid = false
|
|
} else if (!validatePhone(formData.value.phone.trim())) {
|
|
errors.value.phone = '请输入正确的手机号格式'
|
|
isValid = false
|
|
}
|
|
|
|
return isValid
|
|
}
|
|
|
|
// 提交Bug反馈
|
|
const submitBugFeedback = async () => {
|
|
if (!validateForm()) {
|
|
return
|
|
}
|
|
|
|
submitLoading.value = true
|
|
|
|
try {
|
|
const response = await RPC.post('/requirement/create', {
|
|
proposer_id: userStore.userInfo.id,
|
|
proposer: userStore.userInfo.username,
|
|
name: formData.value.name.trim(),
|
|
description: formData.value.description.trim(),
|
|
email: formData.value.email.trim(),
|
|
phone: formData.value.phone.trim(),
|
|
vendor_contact: formData.value.vendor_contact.trim(),
|
|
template_id: formData.value.template_id,
|
|
template_name: formData.value.template_name,
|
|
category: 'bug反馈'
|
|
})
|
|
|
|
uni.showToast({
|
|
title: 'Bug反馈提交成功',
|
|
icon: 'success'
|
|
})
|
|
|
|
// 延迟返回上一页
|
|
setTimeout(() => {
|
|
uni.navigateBack()
|
|
}, 1500)
|
|
|
|
} catch (error) {
|
|
console.error('提交Bug反馈失败:', error)
|
|
uni.showToast({
|
|
title: error.message || '提交失败,请重试',
|
|
icon: 'none'
|
|
})
|
|
} finally {
|
|
submitLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 返回上一页
|
|
const goBack = () => {
|
|
const hasContent = formData.value.name.trim() ||
|
|
formData.value.description.trim() ||
|
|
formData.value.email.trim() ||
|
|
formData.value.phone.trim() ||
|
|
formData.value.vendor_contact.trim() ||
|
|
formData.value.template_id
|
|
|
|
if (hasContent) {
|
|
uni.showModal({
|
|
title: '提示',
|
|
content: '您有未保存的内容,确定要离开吗?',
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
uni.navigateBack()
|
|
}
|
|
}
|
|
})
|
|
} else {
|
|
uni.navigateBack()
|
|
}
|
|
}
|
|
|
|
// 页面加载时获取模板列表
|
|
onMounted(() => {
|
|
getTemplateList()
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.container {
|
|
min-height: 100vh;
|
|
background-color: #f8f8f8;
|
|
}
|
|
|
|
.custom-navbar {
|
|
background: white;
|
|
padding-top: var(--status-bar-height);
|
|
border-bottom: 1px solid #e5e5e5;
|
|
|
|
.navbar-content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
height: 44px;
|
|
padding: 0 16px;
|
|
|
|
.back-btn {
|
|
width: 40px;
|
|
height: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.navbar-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.placeholder {
|
|
width: 40px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.form-container {
|
|
padding: 20px 16px;
|
|
|
|
.form-item {
|
|
margin-bottom: 24px;
|
|
|
|
.form-label {
|
|
font-size: 16px;
|
|
color: #333;
|
|
margin-bottom: 8px;
|
|
font-weight: 500;
|
|
|
|
.required {
|
|
color: #ff3333;
|
|
margin-left: 2px;
|
|
}
|
|
}
|
|
|
|
.char-count {
|
|
text-align: right;
|
|
font-size: 12px;
|
|
color: #999;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.error-text {
|
|
color: #ff3333;
|
|
font-size: 12px;
|
|
margin-top: 4px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.submit-section {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background: white;
|
|
padding: 16px;
|
|
border-top: 1px solid #e5e5e5;
|
|
|
|
/* #ifdef H5 */
|
|
bottom: 50px;
|
|
/* #endif */
|
|
}
|
|
</style> |