| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- <template>
- <view class="app-container pb-24">
- <TopBar title="新增项目" show-back />
- <view class="px-4 py-4 gap-y-4">
- <!-- 基本信息 -->
- <view class="card p-4">
- <view class="detail-section-title mb-4">
- <view class="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center mr-3">
- <text class="uni-icons uniui-folder-add-filled text-primary"></text>
- </view>
- <text>基本信息</text>
- </view>
- <view class="gap-y-4">
- <view>
- <text class="form-label">
- 项目名称
- <text class="text-red-500">*</text>
- </text>
- <uni-easyinput class="form-input" placeholder="请输入项目名称" v-model="form.name" @blur="validateField('name')" />
- <text v-if="errors.name" class="text-red-500 text-xs mt-1">{{ errors.name }}</text>
- </view>
- <view>
- <text class="form-label">
- 项目类型
- <text class="text-red-500">*</text>
- </text>
- <picker mode="selector" :range="types" :value="typeIndex" @change="onTypeChange">
- <view class="form-select flex items-center" :class="{ 'text-gray-400': !form.type }">
- <text class="uni-icons uniui-arrowdown text-gray-400 mr-2"></text>
- {{ form.type || '请选择项目类型' }}
- </view>
- </picker>
- <text v-if="errors.type" class="text-red-500 text-xs mt-1">{{ errors.type }}</text>
- </view>
- <view class="grid grid-cols-3 gap-3">
- <view>
- <text class="form-label">省</text>
- <uni-easyinput class="form-input" placeholder="省" v-model="form.province" />
- </view>
- <view>
- <text class="form-label">市</text>
- <uni-easyinput class="form-input" placeholder="市" v-model="form.city" />
- </view>
- <view>
- <text class="form-label">区/县</text>
- <uni-easyinput class="form-input" placeholder="区/县" v-model="form.district" />
- </view>
- </view>
- <view>
- <text class="form-label">
- 详细地址
- <text class="text-red-500">*</text>
- </text>
- <uni-easyinput class="form-input" placeholder="请输入详细地址" v-model="form.address" @blur="validateField('address')" />
- <text v-if="errors.address" class="text-red-500 text-xs mt-1">{{ errors.address }}</text>
- </view>
- </view>
- </view>
- <!-- 客户信息 -->
- <view class="card p-4">
- <view class="detail-section-title mb-4">
- <view class="w-8 h-8 rounded-lg bg-purple-100 flex items-center justify-center mr-3">
- <text class="uni-icons uniui-person-filled text-purple-500"></text>
- </view>
- <text>客户信息</text>
- </view>
- <view class="gap-y-4">
- <view>
- <text class="form-label">
- 客户名称
- <text class="text-red-500">*</text>
- </text>
- <uni-easyinput class="form-input" placeholder="请输入客户名称" v-model="form.customerName" @blur="validateField('customerName')" />
- <text v-if="errors.customerName" class="text-red-500 text-xs mt-1">{{ errors.customerName }}</text>
- </view>
- <view>
- <text class="form-label">
- 联系电话
- <text class="text-red-500">*</text>
- </text>
- <input
- class="form-input"
- placeholder="请输入联系电话"
- v-model="form.customerPhone"
- type="number"
- maxlength="11"
- @blur="validateField('customerPhone')"
- />
- <text v-if="errors.customerPhone" class="text-red-500 text-xs mt-1">{{ errors.customerPhone }}</text>
- </view>
- </view>
- </view>
- <!-- 合同信息 -->
- <view class="card p-4">
- <view class="detail-section-title mb-4">
- <view class="w-8 h-8 rounded-lg bg-green-100 flex items-center justify-center mr-3">
- <text class="uni-icons uniui-wallet-filled text-green-500"></text>
- </view>
- <text>合同信息</text>
- </view>
- <view class="gap-y-4">
- <view>
- <text class="form-label">合同金额(元)</text>
- <uni-easyinput class="form-input" placeholder="请输入合同金额" v-model="form.contractAmount" type="digit" />
- </view>
- <view class="grid grid-cols-2 gap-3">
- <view>
- <text class="form-label">合同开始日期</text>
- <picker mode="date" :value="form.contractStartDate" @change="onStartDateChange">
- <view class="form-select flex items-center text-gray-700">
- <text class="uni-icons uniui-calendar text-gray-400 mr-2"></text>
- {{ form.contractStartDate || '选择日期' }}
- </view>
- </picker>
- </view>
- <view>
- <text class="form-label">合同结束日期</text>
- <picker mode="date" :value="form.contractEndDate" @change="onEndDateChange">
- <view class="form-select flex items-center text-gray-700">
- <text class="uni-icons uniui-calendar text-gray-400 mr-2"></text>
- {{ form.contractEndDate || '选择日期' }}
- </view>
- </picker>
- </view>
- </view>
- <view>
- <text class="form-label">服务周期</text>
- <uni-easyinput class="form-input" placeholder="如:每周2次" v-model="form.serviceCycle" />
- </view>
- </view>
- </view>
- <!-- 项目联系人 -->
- <view class="card p-4">
- <view class="detail-section-title mb-4">
- <view class="w-8 h-8 rounded-lg bg-orange-100 flex items-center justify-center mr-3">
- <text class="uni-icons uniui-personadd-filled text-orange-500"></text>
- </view>
- <text>项目联系人</text>
- </view>
- <view class="gap-y-4">
- <view
- v-for="(contact, index) in form.contacts"
- :key="index"
- class="border border-gray-200 rounded-xl p-4"
- >
- <view class="flex justify-between items-center mb-3">
- <text class="text-sm font-semibold text-gray-700">联系人 {{ index + 1 }}</text>
- <text class="text-red-500 text-sm flex items-center" @click="removeContact(index)">
- <text class="uni-icons uniui-trash text-sm mr-1"></text>
- 删除
- </text>
- </view>
- <view class="gap-y-3">
- <uni-easyinput class="form-input mb-2" placeholder="姓名" v-model="contact.name" />
- <uni-easyinput class="form-input mb-2" placeholder="角色/职位" v-model="contact.role" />
- <uni-easyinput class="form-input" placeholder="联系电话" v-model="contact.phone" type="number" maxlength="11" />
- </view>
- </view>
- <button
- class="w-full py-3 border border-dashed border-gray-300 rounded-xl text-sm text-gray-500 flex items-center justify-center bg-transparent"
- @click="addContact"
- >
- <text class="uni-icons uniui-plusempty mr-1"></text>
- 添加联系人
- </button>
- </view>
- </view>
- <!-- 服务点信息 -->
- <view class="card p-4">
- <view class="detail-section-title mb-4">
- <view class="w-8 h-8 rounded-lg bg-cyan-100 flex items-center justify-center mr-3">
- <text class="uni-icons uniui-location-filled text-cyan-500"></text>
- </view>
- <text>服务点信息</text>
- </view>
- <view class="gap-y-4">
- <view
- v-for="(point, index) in form.servicePoints"
- :key="index"
- class="border border-gray-200 rounded-xl p-4"
- >
- <view class="flex justify-between items-center mb-3">
- <text class="text-sm font-semibold text-gray-700">服务点 {{ index + 1 }}</text>
- <text class="text-red-500 text-sm flex items-center" @click="removeServicePoint(index)">
- <text class="uni-icons uniui-trash text-sm mr-1"></text>
- 删除
- </text>
- </view>
- <view class="gap-y-3">
- <uni-easyinput class="form-input mb-2" placeholder="服务点名称" v-model="point.name" />
- <uni-easyinput class="form-input mb-2" placeholder="服务点地址" v-model="point.address" />
- <uni-easyinput class="form-input mb-2" placeholder="服务类型" v-model="point.serviceType" />
- <uni-easyinput class="form-input mb-2" placeholder="联系人" v-model="point.contactName" />
- <uni-easyinput class="form-input" placeholder="联系电话" v-model="point.contactPhone" type="number" maxlength="11" />
- </view>
- </view>
- <button
- class="w-full py-3 border border-dashed border-gray-300 rounded-xl text-sm text-gray-500 flex items-center justify-center bg-transparent"
- @click="addServicePoint"
- >
- <text class="uni-icons uniui-plusempty mr-1"></text>
- 添加服务点
- </button>
- </view>
- </view>
- <!-- 备注 -->
- <view class="card p-4">
- <view class="detail-section-title mb-4">
- <view class="w-8 h-8 rounded-lg bg-gray-100 flex items-center justify-center mr-3">
- <text class="uni-icons uniui-compose text-gray-500"></text>
- </view>
- <text>备注</text>
- </view>
- <textarea
- v-model="form.remark"
- class="w-full p-3 bg-surface rounded-xl text-sm text-gray-700"
- placeholder="请输入项目备注..."
- :maxlength="200"
- />
- </view>
- <!-- 提交按钮 -->
- <view class="px-4 pt-2 pb-6">
- <button
- class="w-full py-4 btn-primary rounded-2xl text-base font-semibold flex items-center justify-center"
- @click="submitForm"
- :disabled="submitting"
- >
- <text v-if="submitting" class="uni-icons uniui-spinner-cycle mr-2 animate-spin"></text>
- <text class="uni-icons uniui-checkmarkempty mr-2" v-else></text>
- {{ submitting ? '提交中...' : '提交项目' }}
- </button>
- </view>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { ref, reactive } from 'vue'
- import TopBar from '../../components/common/TopBar.vue'
- import { createProject } from '../../api/project'
- const types = ['包年', '单次', '季度', '月付']
- const typeIndex = ref(0)
- const form = ref({
- name: '',
- type: '',
- province: '',
- city: '',
- district: '',
- address: '',
- customerName: '',
- customerPhone: '',
- contractAmount: '',
- contractStartDate: '',
- contractEndDate: '',
- serviceCycle: '',
- remark: '',
- contacts: [] as { name: string; role: string; phone: string }[],
- servicePoints: [] as { name: string; address: string; serviceType: string; contactName: string; contactPhone: string }[],
- })
- const errors = reactive<Record<string, string>>({
- name: '',
- type: '',
- address: '',
- customerName: '',
- customerPhone: '',
- })
- const submitting = ref(false)
- function onTypeChange(e: any) {
- typeIndex.value = e.detail.value
- form.value.type = types[typeIndex.value]
- errors.type = ''
- }
- function onStartDateChange(e: any) {
- form.value.contractStartDate = e.detail.value
- }
- function onEndDateChange(e: any) {
- form.value.contractEndDate = e.detail.value
- }
- function validateField(field: string) {
- const value = form.value[field as keyof typeof form.value] as string
- errors[field] = ''
- if (!value || !value.toString().trim()) {
- const fieldNames: Record<string, string> = {
- name: '项目名称',
- address: '详细地址',
- customerName: '客户名称',
- customerPhone: '联系电话',
- }
- errors[field] = `请输入${fieldNames[field] || field}`
- return false
- }
- if (field === 'customerPhone') {
- const phoneRegex = /^1[3-9]\d{9}$/
- if (!phoneRegex.test(value)) {
- errors[field] = '请输入正确的手机号'
- return false
- }
- }
- return true
- }
- function validateForm() {
- let isValid = true
- const requiredFields = ['name', 'type', 'address', 'customerName', 'customerPhone']
- for (const field of requiredFields) {
- if (!validateField(field)) {
- isValid = false
- }
- }
- return isValid
- }
- function addContact() {
- form.value.contacts.push({ name: '', role: '', phone: '' })
- }
- function removeContact(index: number) {
- form.value.contacts.splice(index, 1)
- }
- function addServicePoint() {
- form.value.servicePoints.push({ name: '', address: '', serviceType: '', contactName: '', contactPhone: '' })
- }
- function removeServicePoint(index: number) {
- form.value.servicePoints.splice(index, 1)
- }
- async function submitForm() {
- if (!validateForm()) {
- uni.showToast({ title: '请完善表单信息', icon: 'none' })
- return
- }
- submitting.value = true
- try {
- const payload = {
- projectName: form.value.name,
- projectType: form.value.type,
- address: [form.value.province, form.value.city, form.value.district, form.value.address].filter(Boolean).join(''),
- customerName: form.value.customerName,
- customerPhone: form.value.customerPhone,
- contractAmount: form.value.contractAmount ? Number(form.value.contractAmount) : undefined,
- contractStartDate: form.value.contractStartDate || undefined,
- contractEndDate: form.value.contractEndDate || undefined,
- serviceCycle: form.value.serviceCycle || undefined,
- remark: form.value.remark || undefined,
- contacts: form.value.contacts.filter((c) => c.name || c.phone),
- servicePoints: form.value.servicePoints.filter((p) => p.name || p.address),
- }
- await createProject(payload)
- uni.showToast({ title: '创建成功', icon: 'success' })
- setTimeout(() => {
- uni.navigateBack()
- }, 1500)
- } catch (error) {
- uni.showToast({ title: '创建失败', icon: 'none' })
- } finally {
- submitting.value = false
- }
- }
- </script>
|