Add custom request management

This commit is contained in:
王鹏
2026-09-16 16:53:24 +08:00
parent 05e04ce021
commit e7ab83c564
9 changed files with 920 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
import request from '@/utils/request'
export function listCustomRequest(query) {
return request({
url: '/app/customRequest/list',
method: 'get',
params: query
})
}
export function getCustomRequest(id) {
return request({
url: '/app/customRequest/' + id,
method: 'get'
})
}
export function updateCustomRequestStatus(data) {
return request({
url: '/app/customRequest/status',
method: 'put',
data: data
})
}

View File

@@ -0,0 +1,262 @@
<template>
<div class="app-container">
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true" label-width="82px">
<el-form-item label="需求单号" prop="requestNo">
<el-input v-model="queryParams.requestNo" placeholder="请输入需求单号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="技术栈" prop="techStackName">
<el-input v-model="queryParams.techStackName" placeholder="请输入技术栈" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="用户" prop="userName">
<el-input v-model="queryParams.userName" placeholder="账号或昵称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="需求状态" prop="status">
<el-select v-model="queryParams.status" placeholder="全部状态" clearable>
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="积分状态" prop="pointStatus">
<el-select v-model="queryParams.pointStatus" placeholder="全部状态" clearable>
<el-option label="已扣除" :value="0" />
<el-option label="已退回" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="提交时间">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="requestList">
<el-table-column label="需求单号" prop="requestNo" min-width="205" show-overflow-tooltip />
<el-table-column label="用户" min-width="150">
<template slot-scope="scope">
<div>{{ scope.row.nickName || scope.row.userName || '-' }}</div>
<div class="secondary-text">{{ scope.row.userName || '-' }} / ID: {{ scope.row.userId }}</div>
</template>
</el-table-column>
<el-table-column label="项目名称" prop="projectName" min-width="180" show-overflow-tooltip />
<el-table-column label="技术栈" prop="techStackName" min-width="185" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.techStackName || '-' }}</template>
</el-table-column>
<el-table-column label="项目需求" prop="requirementContent" min-width="250" show-overflow-tooltip />
<el-table-column label="状态" align="center" width="120">
<template slot-scope="scope">
<el-tag :type="statusType(scope.row.status)" size="small">{{ statusLabel(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="积分" align="center" width="105">
<template slot-scope="scope">
<span :class="scope.row.pointStatus === 1 ? 'point-refunded' : ''">
{{ scope.row.pointAmount }} / {{ scope.row.pointStatus === 1 ? '已退' : '已扣' }}
</span>
</template>
</el-table-column>
<el-table-column label="当前进度" prop="progressNote" min-width="210" show-overflow-tooltip />
<el-table-column label="更新时间" prop="updateTime" width="165" />
<el-table-column label="操作" align="center" width="145" fixed="right">
<template slot-scope="scope">
<el-button v-hasPermi="['app:customRequest:query']" type="text" icon="el-icon-view" size="mini" @click="handleView(scope.row)">详情</el-button>
<el-button v-if="scope.row.status !== 5" v-hasPermi="['app:customRequest:edit']" type="text" icon="el-icon-edit" size="mini" @click="handleUpdate(scope.row)">更新</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<el-dialog title="定制需求详情" :visible.sync="detailOpen" width="760px" append-to-body>
<el-descriptions v-if="detail.id" :column="2" border>
<el-descriptions-item label="需求单号" :span="2">{{ detail.requestNo }}</el-descriptions-item>
<el-descriptions-item label="用户昵称">{{ detail.nickName || '-' }}</el-descriptions-item>
<el-descriptions-item label="用户账号">{{ detail.userName || '-' }}</el-descriptions-item>
<el-descriptions-item label="项目名称" :span="2">{{ detail.projectName }}</el-descriptions-item>
<el-descriptions-item label="技术栈" :span="2">{{ detail.techStackName || '-' }}</el-descriptions-item>
<el-descriptions-item label="项目需求" :span="2">
<div class="requirement-detail">{{ detail.requirementContent }}</div>
</el-descriptions-item>
<el-descriptions-item label="当前状态">
<el-tag :type="statusType(detail.status)" size="small">{{ statusLabel(detail.status) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="积分状态">{{ detail.pointAmount }}积分 / {{ detail.pointStatus === 1 ? '已退回' : '已扣除' }}</el-descriptions-item>
<el-descriptions-item label="当前进度" :span="2">{{ detail.progressNote || '-' }}</el-descriptions-item>
<el-descriptions-item v-if="detail.rejectReason" label="未通过原因" :span="2">{{ detail.rejectReason }}</el-descriptions-item>
<el-descriptions-item label="提交时间">{{ detail.createTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ detail.updateTime || '-' }}</el-descriptions-item>
</el-descriptions>
<div slot="footer" class="dialog-footer"><el-button @click="detailOpen = false">关闭</el-button></div>
</el-dialog>
<el-dialog title="更新需求状态" :visible.sync="updateOpen" width="560px" append-to-body>
<el-form ref="updateForm" :model="form" :rules="rules" label-width="100px">
<el-form-item label="项目名称"><span>{{ form.projectName }}</span></el-form-item>
<el-form-item label="技术栈"><span>{{ form.techStackName || '-' }}</span></el-form-item>
<el-form-item label="新状态" prop="status">
<el-select v-model="form.status" placeholder="请选择状态" style="width: 100%" @change="handleStatusChange">
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" :disabled="form.pointStatus === 1 && item.value !== 5" />
</el-select>
</el-form-item>
<el-form-item v-if="form.status !== 5" label="进度说明" prop="progressNote">
<el-input v-model="form.progressNote" type="textarea" :rows="4" maxlength="300" show-word-limit placeholder="这段内容会展示给用户" />
</el-form-item>
<el-form-item v-if="form.status === 5" label="未通过原因" prop="rejectReason">
<el-input v-model="form.rejectReason" type="textarea" :rows="3" maxlength="300" show-word-limit placeholder="必填,保存后自动退回99积分" />
</el-form-item>
<el-alert v-if="form.status === 5 && form.pointStatus === 0" title="保存后将自动退回99积分,此操作不能撤销。" type="warning" :closable="false" show-icon />
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="updateOpen = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitUpdate">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getCustomRequest, listCustomRequest, updateCustomRequestStatus } from '@/api/app/customRequest'
export default {
name: 'CustomRequest',
data() {
return {
loading: false,
submitting: false,
showSearch: true,
total: 0,
requestList: [],
dateRange: [],
detailOpen: false,
updateOpen: false,
detail: {},
form: {},
statusOptions: [
{ value: 0, label: '待审核' },
{ value: 1, label: '审核中' },
{ value: 2, label: '已接单' },
{ value: 3, label: '开发中' },
{ value: 4, label: '已完成' },
{ value: 5, label: '审核未通过' }
],
statusProgressDefaults: {
0: '需求已提交,等待平台审核',
1: '平台正在审核您的项目需求',
2: '项目需求已通过审核,平台已接单',
3: '项目正在开发中,请耐心等待',
4: '项目已开发完成,请联系南音查看演示成果',
5: '需求审核未通过,99积分已退回'
},
queryParams: {
pageNum: 1,
pageSize: 10,
requestNo: undefined,
projectName: undefined,
techStackName: undefined,
userName: undefined,
status: undefined,
pointStatus: undefined
},
rules: {
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
progressNote: [{ required: true, message: '请填写进度说明', trigger: 'blur' }],
rejectReason: [{ required: true, message: '请填写审核未通过原因', trigger: 'blur' }]
}
}
},
created() { this.getList() },
methods: {
getList() {
this.loading = true
listCustomRequest(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
this.requestList = response.rows || []
this.total = response.total || 0
}).finally(() => { this.loading = false })
},
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
resetQuery() { this.dateRange = []; this.resetForm('queryForm'); this.handleQuery() },
handleView(row) {
getCustomRequest(row.id).then(response => { this.detail = response.data; this.detailOpen = true })
},
handleUpdate(row) {
getCustomRequest(row.id).then(response => {
const data = response.data
this.form = {
id: data.id,
projectName: data.projectName,
techStackName: data.techStackName,
status: data.status,
pointStatus: data.pointStatus,
progressNote: data.progressNote || '',
rejectReason: data.rejectReason || ''
}
this.updateOpen = true
this.$nextTick(() => { if (this.$refs.updateForm) this.$refs.updateForm.clearValidate() })
})
},
handleStatusChange(status) {
this.form.progressNote = this.statusProgressDefaults[status] || ''
if (status !== 5) this.form.rejectReason = ''
this.$nextTick(() => {
if (this.$refs.updateForm) this.$refs.updateForm.clearValidate(['progressNote', 'rejectReason'])
})
},
submitUpdate() {
this.$refs.updateForm.validate(valid => {
if (!valid) return
const doUpdate = () => {
this.submitting = true
const updateData = Object.assign({}, this.form, {
progressNote: String(this.form.progressNote || '').trim(),
rejectReason: this.form.status === 5 ? String(this.form.rejectReason || '').trim() : ''
})
updateCustomRequestStatus(updateData).then(() => {
this.$modal.msgSuccess(this.form.status === 5 && this.form.pointStatus === 0 ? '已拒绝并退回99积分' : '状态更新成功')
this.updateOpen = false
this.getList()
}).finally(() => { this.submitting = false })
}
if (this.form.status === 5 && this.form.pointStatus === 0) {
this.$modal.confirm('确认审核不通过并退回99积分吗?退款后不能恢复为其他状态。').then(doUpdate).catch(() => {})
} else {
doUpdate()
}
})
},
statusLabel(status) {
const item = this.statusOptions.find(option => option.value === status)
return item ? item.label : '未知状态'
},
statusType(status) {
return { 0: 'info', 1: 'warning', 2: 'success', 3: 'primary', 4: 'success', 5: 'danger' }[status] || 'info'
}
}
}
</script>
<style scoped>
.secondary-text { margin-top: 2px; color: #909399; font-size: 12px; }
.point-refunded { color: #2b9660; }
.requirement-detail { white-space: pre-wrap; line-height: 1.7; }
</style>