You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

453 lines
10 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view>
<uni-popup ref="ticketFormPopup" type="center" :is-mask-click="false">
<view class="ticket-form-popup">
<view class="popup-head">
<view class="popup-head-bar"></view>
<text class="popup-head-title">取号</text>
<view class="popup-head-close" @click="closeTicketFormPopup">×</view>
</view>
<view class="section">
<view class="section-title">
<view class="section-bar"></view>
<text>纳税人或缴费人</text>
</view>
<view class="form-grid">
<view class="form-row">
<text class="form-label">姓名</text>
<input class="form-input" :value="form.name" disabled placeholder="" />
</view>
<view class="form-row">
<text class="form-label">证件类型</text>
<input class="form-input" :value="form.idType" disabled placeholder="" />
</view>
<view class="form-row">
<text class="form-label">证件号码</text>
<input class="form-input" :value="form.idCard" disabled placeholder="" />
</view>
<view class="form-row">
<text class="form-label">
手机号码<span class="required">*</span>
</text>
<input
v-model="form.phone"
class="form-input form-input-required"
type="number"
maxlength="11"
placeholder="请输入手机号码"
/>
</view>
</view>
</view>
<view class="section">
<view class="section-title">
<view class="section-bar"></view>
<text>请选择办理业务</text>
</view>
<view class="form-row form-row-top">
<text class="form-label">
窗口业务<span class="required">*</span>
</text>
<view class="radio-group">
<view
v-for="(item, index) in businessList"
:key="index"
class="radio-item"
:class="{ active: selectedBizUid === item.value }"
@click="selectedBizUid = item.value"
>
<view class="radio-dot">
<view v-if="selectedBizUid === item.value" class="radio-dot-inner"></view>
</view>
<text class="radio-text">{{ item.name }}</text>
<text class="radio-waiting">等候{{ item.waitingCount ?? 0 }}</text>
</view>
</view>
</view>
</view>
<view class="popup-actions">
<button class="action-btn action-ghost" :disabled="ticketLoading" @click="closeTicketFormPopup">
取消
</button>
<button class="action-btn action-primary" :disabled="ticketLoading" @click="confirmTakeTicket">
取号
</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { getBizList } from "@/api/index.js";
import { takeTicket } from "@/api/ticket.js";
import { reactive, ref, watch } from "vue";
import { validatePhoneNumber } from "@/utils/validator";
const emit = defineEmits(["success", "loading", "visibleChange"]);
const ticketFormPopup = ref(null);
const businessList = ref([]);
const ticketLoading = ref(false);
const currentRow = ref(null);
const tktType = ref("normal");
const selectedBizUid = ref("");
const form = reactive({
name: "",
idType: "",
idCard: "",
phone: "",
});
watch(ticketLoading, (value) => {
emit("loading", value);
});
const resetForm = () => {
form.name = "";
form.idType = "";
form.idCard = "";
form.phone = "";
selectedBizUid.value = "";
currentRow.value = null;
};
const loadBusinessList = async () => {
try {
const res = await getBizList({ withWaiting: true });
const list = Array.isArray(res) ? res : [];
businessList.value = list.map((item, index) => ({
name: item.name || item.bizName || "",
value: item.value || item.uid || item.id || String(index),
waitingCount: Number(item.waitingCount ?? 0),
}));
if (businessList.value.length === 1) {
selectedBizUid.value = businessList.value[0].value;
}
} catch (error) {
console.log("获取业务列表失败:", error);
}
};
const openTicketForm = async () => {
emit("visibleChange", true);
await loadBusinessList();
ticketFormPopup.value?.open();
};
/** 普通取号:无实名信息,姓名/证件为空,手机号必填 */
const open = async () => {
resetForm();
tktType.value = "normal";
await openTicketForm();
};
/** 预检取号:回填人员信息,手机号必填 */
const openWithRow = async (row) => {
resetForm();
currentRow.value = row;
tktType.value = "realname";
form.name = String(row?.name || "").trim();
form.idType = "身份证";
form.idCard = String(row?.idCard || "").trim();
form.phone = String(row?.phone || "").trim();
await openTicketForm();
};
const closeTicketFormPopup = () => {
ticketFormPopup.value?.close();
emit("visibleChange", false);
};
const buildTakeTicketParams = (bizItem) => {
const params = {
bizUid: bizItem.value,
tktType: tktType.value,
rankUserPhone: String(form.phone || "").trim(),
};
if (currentRow.value) {
params.uid = currentRow.value.uid;
params.idCard = currentRow.value.idCard || form.idCard;
params.rankUserName = currentRow.value.name || form.name;
}
return params;
};
const confirmTakeTicket = async () => {
if (ticketLoading.value) return;
const phoneResult = validatePhoneNumber(form.phone);
if (!phoneResult.valid) {
uni.showToast({
title: phoneResult.message || "请输入正确手机号",
icon: "none",
});
return;
}
const bizItem = businessList.value.find((item) => item.value === selectedBizUid.value);
if (!bizItem) {
uni.showToast({
title: "请选择窗口业务",
icon: "none",
});
return;
}
if (currentRow.value) {
currentRow.value = {
...currentRow.value,
phone: form.phone.trim(),
};
}
ticketLoading.value = true;
try {
const result = await takeTicket(buildTakeTicketParams(bizItem));
const tktId = result?.tktId || result?.ticketNumber || "";
uni.showToast({
title: tktId ? `取号成功:${tktId}` : "取号成功",
icon: "success",
});
closeTicketFormPopup();
emit("success", result);
} catch (error) {
console.log("取号失败", error);
} finally {
ticketLoading.value = false;
}
};
defineExpose({
open,
openWithRow,
ticketLoading,
});
</script>
<style scoped lang="scss">
.ticket-form-popup {
width: calc(75 * var(--pad-vw));
max-height: calc(86 * var(--pad-vh));
background: #fff;
border-radius: 16px;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 18px 48px rgba(30, 58, 138, 0.16);
}
.popup-head {
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding: 18px 52px 12px;
background: linear-gradient(180deg, #f7f9fd 0%, #fff 100%);
}
.popup-head-bar {
position: absolute;
left: 20px;
top: 50%;
width: 4px;
height: 18px;
margin-top: -3px;
border-radius: 2px;
background: #1d4ed8;
}
.popup-head-title {
font-size: 20px;
font-weight: 700;
color: #1e3a8a;
}
.popup-head-close {
position: absolute;
right: 16px;
top: 14px;
width: 32px;
height: 32px;
line-height: 30px;
text-align: center;
border-radius: 16px;
background: #eef3f9;
color: #64748b;
font-size: 20px;
}
.section {
padding: 8px 24px 4px;
}
.section-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 700;
color: #1e3a8a;
margin-bottom: 14px;
}
.section-bar {
width: 4px;
height: 14px;
border-radius: 2px;
background: #2563eb;
}
.form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px 24px;
}
.form-row {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.form-row-top {
align-items: flex-start;
}
.form-label {
width: 88px;
flex-shrink: 0;
font-size: 14px;
color: #475569;
text-align: right;
}
.required {
color: #ef4444;
margin-left: 2px;
}
.form-input {
flex: 1;
min-width: 0;
height: 40px;
border: 1px solid #d7e2ee;
border-radius: 10px;
padding: 0 12px;
font-size: 14px;
color: #1e293b;
background: #f4f7fb;
box-sizing: border-box;
}
.form-input-required {
background: #fff;
border-color: #c7d7ea;
}
.form-input[disabled] {
background: #f4f7fb;
color: #64748b;
}
.radio-group {
flex: 1;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px 12px;
}
.radio-item {
min-width: 0;
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border: 1px solid #d7e2ee;
border-radius: 10px;
background: #f8fafc;
}
.radio-item.active {
border-color: #2563eb;
background: #eff6ff;
}
.radio-dot {
width: 16px;
height: 16px;
border-radius: 50%;
border: 1px solid #94a3b8;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.radio-item.active .radio-dot {
border-color: #2563eb;
}
.radio-dot-inner {
width: 8px;
height: 8px;
border-radius: 50%;
background: #2563eb;
}
.radio-text {
font-size: 14px;
color: #1e293b;
font-weight: 600;
}
.radio-waiting {
margin-left: auto;
font-size: 12px;
color: #94a3b8;
flex-shrink: 0;
}
.popup-actions {
padding: 16px 24px 20px;
display: flex;
justify-content: flex-end;
gap: 12px;
}
.action-btn {
min-width: 112px;
height: 42px;
padding: 0 22px;
border-radius: 21px;
font-size: 15px;
font-weight: 600;
border: 1px solid transparent;
display: flex;
align-items: center;
justify-content: center;
}
.action-ghost {
background: #fff;
color: #1e3a8a;
border-color: #c7d7ea;
}
.action-primary {
background: #2563eb;
color: #fff;
box-shadow: 0 6px 14px rgba(37, 99, 235, 0.22);
}
.action-btn[disabled] {
opacity: 0.45;
box-shadow: none;
}
</style>