This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
<template>
|
||||
<view class="card user-info-card interactive">
|
||||
<view class="flex-row-g20">
|
||||
<!-- <view class="user-avatar">
|
||||
<image :src="avatarUrl" mode="aspectFill" alt="用户头像" />
|
||||
</view> -->
|
||||
<view class="user-details flex-col-g8">
|
||||
<view class="flex-row-g20">
|
||||
<view class="title">{{ currentCardNo || '-' }}</view>
|
||||
</view>
|
||||
<view class="caption">ICCID: {{ deviceInfo.iccid || '-' }}</view>
|
||||
<view class="caption">套餐名称:{{ deviceInfo.packageName || '-' }}</view>
|
||||
<view class="caption">套餐到期时间:{{ deviceInfo.expireDate || '-' }}</view>
|
||||
</view>
|
||||
@@ -22,11 +20,6 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useUserStore } from '@/store/index.js';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
defineProps({
|
||||
currentCardNo: { type: String, default: '' },
|
||||
deviceInfo: { type: Object, default: () => ({}) },
|
||||
@@ -34,32 +27,12 @@
|
||||
networkStatus: { type: [String, Number], default: '离线' },
|
||||
isDevice: { type: Boolean, default: true }
|
||||
});
|
||||
|
||||
const defaultAvatar = 'https://img1.baidu.com/it/u=2462918877,1866131262&fm=253&fmt=auto&app=138&f=JPEG?w=506&h=500';
|
||||
|
||||
const avatarUrl = computed(() => {
|
||||
return userStore.state.userInfo.avatar || defaultAvatar;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.user-info-card {
|
||||
color: var(--text-primary);
|
||||
|
||||
.user-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: var(--radius-medium);
|
||||
overflow: hidden;
|
||||
border: 2rpx solid var(--border-light);
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.user-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
## Context
|
||||
|
||||
The current device exchange popup stores recipient name, phone, and address in three flat fields, with `recipient_address` being a single string. The shipping submit API currently accepts only `recipient_name`, `recipient_phone`, and `recipient_address`, so the frontend must remain compatible unless the backend contract changes later.
|
||||
|
||||
The homepage already loads `/api/c/v1/asset/info` through `assetApi.getInfo(identifier)`. The UI shows package name and expiry date in the top summary card, but the request now also returns `data.iccid`, which should be exposed without adding a second asset query.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
- Goals:
|
||||
- Add a structured province/city/district selector using `element-china-area-data`
|
||||
- Keep a separate detailed-address field for device exchange shipping info
|
||||
- Allow users to paste one shipping-info string and prefill recipient name, phone, and address fields
|
||||
- Preserve compatibility with the existing exchange shipping submit endpoint
|
||||
- Show `ICCID` above the package name in the homepage top summary card
|
||||
|
||||
- Non-Goals:
|
||||
- Redesign the backend exchange shipping API
|
||||
- Add OCR or image-based recognition for shipping labels
|
||||
- Normalize all historical address data outside the device exchange popup
|
||||
|
||||
## Decisions
|
||||
|
||||
- Decision: Use `element-china-area-data` as the source for province/city/district options.
|
||||
Why: The request explicitly requires that dataset, and a static local area dataset avoids introducing extra network dependencies.
|
||||
|
||||
- Decision: Keep `/api/c/v1/exchange/{id}/shipping-info` unchanged and compose `recipient_address` on the client.
|
||||
Why: The existing API already accepts one address string, so the frontend can implement the new UX without blocking on backend changes.
|
||||
|
||||
- Decision: Recognize pasted shipping text with best-effort client-side parsing for mainland China phone numbers and common copied-address formats.
|
||||
Why: Users often copy a single line or multi-line string from chat or e-commerce apps, and partial automation reduces manual entry while still allowing correction.
|
||||
|
||||
- Decision: When reopening existing saved shipping info, prefill split fields with best-effort parsing and preserve the original address content if a full region split cannot be derived.
|
||||
Why: Existing records are already stored as a combined string, so the UI needs a no-data-loss compatibility path during the transition.
|
||||
|
||||
- Decision: Use `data.iccid` from the existing asset info response as the homepage summary source of truth.
|
||||
Why: The user explicitly named that field and endpoint, and it avoids mixing multiple ICCID sources in the header card.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- Pasted shipping strings vary in format, so recognition must remain best-effort and allow manual correction before submission.
|
||||
- Some previously saved combined addresses may not map cleanly back to province/city/district selections, so fallback behavior must preserve the original text.
|
||||
- Adding a new area dataset dependency increases bundle size slightly, but it keeps address selection deterministic and local.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Add the `element-china-area-data` dependency and wire it into the device exchange popup.
|
||||
2. Introduce split address state, pasted-text parsing, and composed submit payload handling.
|
||||
3. Update homepage asset state to read `data.iccid` and render it above package name.
|
||||
4. Verify that existing exchange records remain editable without losing previously saved address content.
|
||||
@@ -0,0 +1,28 @@
|
||||
## Why
|
||||
|
||||
The device exchange popup currently collects the shipping address as a single free-text field. This makes Chinese address entry error-prone, does not support selecting province/city/district from a standard area dataset, and forces users to manually rewrite recipient details even when they already copied a full shipping string from another app.
|
||||
|
||||
The homepage top summary card already loads asset information from `/api/c/v1/asset/info`, but it does not surface the `ICCID` value even though the response now provides `data.iccid`. Users need that value visible above the package name on the homepage.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Replace the single shipping-address input in `pages/device-exchange/device-exchange.vue` with province/city/district selection backed by `element-china-area-data` plus a separate detailed-address input
|
||||
- Add a pasteable shipping-info input so the page can recognize and prefill recipient name, phone number, and address from one copied string
|
||||
- Keep the existing exchange shipping submission API contract and assemble the selected region text together with the detailed address into `recipient_address`
|
||||
- Add an `ICCID` line above the package name in the homepage top summary card
|
||||
- Use `data.iccid` from `/api/c/v1/asset/info?identifier=...` as the homepage `ICCID` source of truth
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `device-exchange-shipping-entry`: Define structured shipping entry, pasted shipping-text recognition, and payload composition for the device exchange popup
|
||||
- `index-asset-summary`: Define homepage summary display rules for `ICCID`
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected code: `pages/device-exchange/device-exchange.vue`, `pages/index/index.vue`, `components/UserInfoCard.vue`, related address parsing helpers if introduced
|
||||
- Affected dependencies: `element-china-area-data`
|
||||
- Affected API usage: `/api/c/v1/exchange/{id}/shipping-info`, `/api/c/v1/asset/info`
|
||||
- No backend API contract change is required if the frontend continues submitting a composed `recipient_address` string
|
||||
@@ -0,0 +1,50 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Device exchange shipping entry SHALL collect a structured recipient address
|
||||
The system SHALL collect recipient name, recipient phone number, a province/city/district selection sourced from `element-china-area-data`, and a separate detailed address when the user fills shipping information for a pending device exchange.
|
||||
|
||||
#### Scenario: User opens the shipping popup for a pending exchange
|
||||
- **WHEN** the device exchange record is in the state that allows filling shipping information
|
||||
- **AND** the user opens the shipping popup
|
||||
- **THEN** the popup SHALL show editable fields for recipient name, recipient phone number, province/city/district, and detailed address
|
||||
|
||||
#### Scenario: User attempts to submit without a complete address
|
||||
- **WHEN** the user submits the shipping popup without selecting province/city/district or without entering a detailed address
|
||||
- **THEN** the system SHALL block submission
|
||||
- **AND** the system SHALL tell the user that the shipping address is incomplete
|
||||
|
||||
### Requirement: Device exchange shipping entry SHALL support pasted shipping-text recognition
|
||||
The system SHALL provide a way for the user to paste one shipping-info string and SHALL attempt to recognize recipient name, mainland China phone number, and address content from that string into editable form fields.
|
||||
|
||||
#### Scenario: Pasted shipping text contains recognizable recipient information
|
||||
- **WHEN** the user pastes a shipping-info string containing a recipient name, an 11-digit mainland China phone number, and address content
|
||||
- **THEN** the system SHALL prefill the recognized recipient name and phone number
|
||||
- **AND** the system SHALL prefill the recognized address content into the structured address form as far as it can infer
|
||||
|
||||
#### Scenario: Pasted shipping text is only partially recognizable
|
||||
- **WHEN** the user pastes a shipping-info string but the system cannot confidently extract every required field
|
||||
- **THEN** the system SHALL preserve the fields it can recognize
|
||||
- **AND** the user SHALL still be able to manually complete or correct the remaining fields before submission
|
||||
|
||||
### Requirement: Device exchange shipping submission SHALL preserve the existing API contract
|
||||
The system SHALL continue submitting `recipient_name`, `recipient_phone`, and `recipient_address` to `/api/c/v1/exchange/{id}/shipping-info`, and SHALL compose `recipient_address` from the selected province/city/district text together with the detailed address.
|
||||
|
||||
#### Scenario: User submits valid shipping information
|
||||
- **WHEN** the user submits the popup with a valid recipient name, a valid mainland China phone number, a selected province/city/district, and a detailed address
|
||||
- **THEN** the system SHALL call `/api/c/v1/exchange/{id}/shipping-info`
|
||||
- **AND** the payload SHALL keep `recipient_name` and `recipient_phone`
|
||||
- **AND** the payload SHALL send a composed `recipient_address` string containing the selected region text and the detailed address
|
||||
|
||||
### Requirement: Existing exchange shipping information SHALL remain editable without data loss
|
||||
The system SHALL preserve previously saved shipping information when reopening the popup and SHALL support best-effort conversion from a previously saved combined address string into the new structured address fields.
|
||||
|
||||
#### Scenario: Existing saved address can be split into region and detail
|
||||
- **WHEN** the popup opens for an exchange record that already has a saved `recipient_address`
|
||||
- **AND** the saved address can be matched to province/city/district data
|
||||
- **THEN** the system SHALL prefill the region selection and the detailed address
|
||||
|
||||
#### Scenario: Existing saved address cannot be fully split
|
||||
- **WHEN** the popup opens for an exchange record that already has a saved `recipient_address`
|
||||
- **AND** the saved address cannot be confidently split into province/city/district plus detailed address
|
||||
- **THEN** the system SHALL preserve the original saved address content for manual correction
|
||||
- **AND** the user SHALL still be able to complete a valid structured address before resubmitting
|
||||
@@ -0,0 +1,23 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Homepage top summary SHALL display ICCID above the package name
|
||||
The system SHALL display an `ICCID` line above the package name in the homepage top summary card after asset information is loaded.
|
||||
|
||||
#### Scenario: Asset info returns an ICCID value
|
||||
- **WHEN** the homepage loads asset information successfully
|
||||
- **AND** the response contains `data.iccid`
|
||||
- **THEN** the top summary card SHALL display `ICCID: <value>` above the package name
|
||||
|
||||
#### Scenario: Asset info does not return an ICCID value
|
||||
- **WHEN** the homepage loads asset information successfully
|
||||
- **AND** `data.iccid` is empty or missing
|
||||
- **THEN** the top summary card SHALL still display the `ICCID` field
|
||||
- **AND** the field SHALL show a stable fallback value
|
||||
|
||||
### Requirement: Homepage ICCID SHALL use the asset info response as the source of truth
|
||||
The system SHALL source the homepage `ICCID` value from `data.iccid` returned by `/api/c/v1/asset/info?identifier=...`.
|
||||
|
||||
#### Scenario: Homepage refreshes asset information
|
||||
- **WHEN** the homepage reads or refreshes asset information from `/api/c/v1/asset/info`
|
||||
- **THEN** the `ICCID` shown in the top summary card SHALL be updated from `data.iccid`
|
||||
- **AND** the header SHALL NOT depend on deriving the display value from a different ICCID field for this summary position
|
||||
@@ -0,0 +1,20 @@
|
||||
## 1. Device Exchange Shipping Entry
|
||||
|
||||
- [x] 1.1 Add `element-china-area-data` and integrate province/city/district selection into `pages/device-exchange/device-exchange.vue`
|
||||
- [x] 1.2 Replace the single shipping-address input with structured region selection plus a separate detailed-address input
|
||||
- [x] 1.3 Add a pasted shipping-info input and parse recipient name, recipient phone, and address content into editable form fields
|
||||
- [x] 1.4 Compose the selected region text and detailed address into `recipient_address` when submitting `/api/c/v1/exchange/{id}/shipping-info`
|
||||
- [x] 1.5 Preserve edit compatibility for previously saved combined addresses
|
||||
|
||||
## 2. Homepage ICCID Summary
|
||||
|
||||
- [x] 2.1 Read `data.iccid` from `assetApi.getInfo(identifier)` into homepage state
|
||||
- [x] 2.2 Render `ICCID` above the package name in `components/UserInfoCard.vue`
|
||||
- [x] 2.3 Show a stable fallback when `data.iccid` is absent
|
||||
|
||||
## 3. Regression Verification
|
||||
|
||||
- [x] 3.1 Verify the device exchange popup blocks submission when region selection or detailed address is incomplete
|
||||
- [x] 3.2 Verify a pasted shipping string can prefill recipient name, phone number, and address with manual correction still available
|
||||
- [x] 3.3 Verify the exchange shipping submit payload remains compatible with the existing backend contract
|
||||
- [x] 3.4 Verify the homepage top summary shows `ICCID` from `/api/c/v1/asset/info` above the package name
|
||||
@@ -19,6 +19,7 @@
|
||||
"@dcloudio/uni-h5": "3.0.0-4080720251210001",
|
||||
"clipboard": "^2.0.11",
|
||||
"dayjs": "^1.11.19",
|
||||
"element-china-area-data": "^6.1.0",
|
||||
"jweixin-module": "^1.6.0",
|
||||
"uview-plus": "^3.6.29",
|
||||
"vue": "^3.4.0"
|
||||
|
||||
@@ -1,83 +1,158 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view v-if="!exchangeData && !loading" class="empty-state">
|
||||
<view class="empty-icon">🔄</view>
|
||||
<view class="empty-icon">📧</view>
|
||||
<view class="empty-title">暂无换货记录</view>
|
||||
<view class="empty-desc">当前账号下暂无设备换货记录</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="exchangeData" class="card exchange-card">
|
||||
<view class="exchange-header flex-row-sb">
|
||||
<view class="exchange-no">{{ exchangeData?.exchange_no }}</view>
|
||||
<view class="tag-apple" :class="getStatusClass(exchangeData?.status)">{{ exchangeData?.status_text }}</view>
|
||||
<view class="exchange-no">{{ exchangeData.exchange_no }}</view>
|
||||
<view class="tag-apple" :class="getStatusClass(exchangeData.status)">
|
||||
{{ exchangeData.status_text }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="exchange-info">
|
||||
<view class="info-row flex-row-sb">
|
||||
<view class="info-label">换货原因</view>
|
||||
<view class="info-value">{{ exchangeData?.exchange_reason }}</view>
|
||||
<view class="info-value">{{ exchangeData.exchange_reason }}</view>
|
||||
</view>
|
||||
<view class="info-row flex-row-sb">
|
||||
<view class="info-label">申请时间</view>
|
||||
<view class="info-value">{{ formatTime(exchangeData?.created_at) }}</view>
|
||||
<view class="info-value">{{ formatTime(exchangeData.created_at) }}</view>
|
||||
</view>
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData?.recipient_name">
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData.recipient_name">
|
||||
<view class="info-label">收件人</view>
|
||||
<view class="info-value">{{ exchangeData?.recipient_name }}</view>
|
||||
<view class="info-value">{{ exchangeData.recipient_name }}</view>
|
||||
</view>
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData?.recipient_phone">
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData.recipient_phone">
|
||||
<view class="info-label">联系电话</view>
|
||||
<view class="info-value">{{ exchangeData?.recipient_phone }}</view>
|
||||
<view class="info-value">{{ exchangeData.recipient_phone }}</view>
|
||||
</view>
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData?.recipient_address">
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData.recipient_address">
|
||||
<view class="info-label">收货地址</view>
|
||||
<view class="info-value address-value">{{ exchangeData?.recipient_address }}</view>
|
||||
<view class="info-value address-value">{{ exchangeData.recipient_address }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="exchange-actions" v-if="exchangeData?.status === 1">
|
||||
<view class="exchange-actions" v-if="exchangeData.status === 1">
|
||||
<button class="btn-apple btn-primary" @tap="openPopup">填写信息</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<up-popup :show="showPopup" mode="center" @close="showPopup=false">
|
||||
<up-popup :show="showPopup" mode="center" @close="handlePopupClose">
|
||||
<view class="popup-content">
|
||||
<view class="popup-title">填写换货信息</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">粘贴收货信息</view>
|
||||
<up-textarea
|
||||
v-model="form.raw_shipping_text"
|
||||
placeholder="可粘贴包含收件人、电话、地址的一整段文字"
|
||||
autoHeight
|
||||
:maxlength="-1"
|
||||
border="surround"
|
||||
/>
|
||||
<view class="form-helper">识别结果可继续手动修改</view>
|
||||
<button class="btn-apple btn-secondary parse-btn" @tap="fillFromPastedText">
|
||||
识别并填充
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">收件人姓名</view>
|
||||
<up-input placeholder="请输入收件人姓名" border="surround" v-model="form.recipient_name" />
|
||||
<up-input
|
||||
v-model="form.recipient_name"
|
||||
placeholder="请输入收件人姓名"
|
||||
border="surround"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">收件人电话</view>
|
||||
<up-input placeholder="请输入收件人电话" border="surround" v-model="form.recipient_phone" />
|
||||
<up-input
|
||||
v-model="form.recipient_phone"
|
||||
placeholder="请输入收件人电话"
|
||||
border="surround"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">收货地址</view>
|
||||
<up-input placeholder="请输入详细收货地址" border="surround" v-model="form.recipient_address" />
|
||||
<view class="form-label">所在地区</view>
|
||||
<view class="region-trigger" @tap="openAreaPicker">
|
||||
<text class="region-trigger-text" :class="{ placeholder: !regionDisplayText }">
|
||||
{{ regionDisplayText || '请选择省 / 市 / 区' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">详细地址</view>
|
||||
<up-textarea
|
||||
v-model="form.recipient_address_detail"
|
||||
placeholder="请输入详细收货地址"
|
||||
autoHeight
|
||||
:maxlength="-1"
|
||||
border="surround"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="popup-btn-group">
|
||||
<button class="btn-apple btn-secondary" @tap="showPopup=false">取消</button>
|
||||
<button class="btn-apple btn-secondary" @tap="handlePopupClose">取消</button>
|
||||
<button class="btn-apple btn-primary" @tap="submitExchange">提交</button>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
|
||||
<up-picker
|
||||
:show="showAreaPicker"
|
||||
:columns="areaPickerColumns"
|
||||
keyName="label"
|
||||
title="选择所在地区"
|
||||
:defaultIndex="areaPickerDefaultIndex"
|
||||
closeOnClickOverlay
|
||||
@confirm="confirmAreaPicker"
|
||||
@change="handleAreaPickerChange"
|
||||
@cancel="showAreaPicker = false"
|
||||
@close="showAreaPicker = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { exchangeApi } from '@/api/index.js';
|
||||
import { useUserStore } from '@/store/index.js';
|
||||
import {
|
||||
buildRegionColumns,
|
||||
composeRecipientAddress,
|
||||
getRegionDisplayText,
|
||||
parseSavedRecipientAddress,
|
||||
parseShippingText
|
||||
} from '@/utils/shippingAddress.js';
|
||||
|
||||
const PHONE_REG = /^1[3-9]\d{9}$/;
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
let exchangeData = ref(null);
|
||||
let loading = ref(false);
|
||||
let showPopup = ref(false);
|
||||
let form = reactive({
|
||||
const exchangeData = ref(null);
|
||||
const loading = ref(false);
|
||||
const showPopup = ref(false);
|
||||
const showAreaPicker = ref(false);
|
||||
const areaPickerColumns = ref([]);
|
||||
const areaPickerDefaultIndex = ref([0, 0, 0]);
|
||||
const form = reactive({
|
||||
raw_shipping_text: '',
|
||||
recipient_name: '',
|
||||
recipient_phone: '',
|
||||
recipient_address: ''
|
||||
recipient_region_codes: [],
|
||||
recipient_address_detail: ''
|
||||
});
|
||||
|
||||
const regionDisplayText = computed(() => {
|
||||
return getRegionDisplayText(form.recipient_region_codes);
|
||||
});
|
||||
|
||||
const getStatusClass = (status) => {
|
||||
@@ -93,67 +168,166 @@
|
||||
|
||||
const formatTime = (time) => {
|
||||
if (!time) return '-';
|
||||
return time.split('T').join(' ').slice(0, 19);
|
||||
return String(time).split('T').join(' ').slice(0, 19);
|
||||
};
|
||||
|
||||
const syncAreaPickerState = (regionCodes = []) => {
|
||||
const { columns, defaultIndex } = buildRegionColumns(regionCodes);
|
||||
areaPickerColumns.value = columns;
|
||||
areaPickerDefaultIndex.value = defaultIndex.map((index) => (index < 0 ? 0 : index));
|
||||
};
|
||||
|
||||
const loadExchange = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await exchangeApi.getPending(userStore.state.identifier);
|
||||
// 接口返回的是单个对象,不是数组
|
||||
exchangeData.value = data || null;
|
||||
} catch (e) {
|
||||
console.error('加载换货记录失败', e);
|
||||
} catch (error) {
|
||||
console.error('加载换货记录失败', error);
|
||||
exchangeData.value = null;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handlePopupClose = () => {
|
||||
showPopup.value = false;
|
||||
showAreaPicker.value = false;
|
||||
};
|
||||
|
||||
const openAreaPicker = () => {
|
||||
syncAreaPickerState(form.recipient_region_codes);
|
||||
showAreaPicker.value = true;
|
||||
};
|
||||
|
||||
const openPopup = () => {
|
||||
form.recipient_name = exchangeData?.recipient_name || '';
|
||||
form.recipient_phone = exchangeData?.recipient_phone || '';
|
||||
form.recipient_address = exchangeData?.recipient_address || '';
|
||||
const currentExchange = exchangeData.value || {};
|
||||
const { regionCodes, detailAddress } = parseSavedRecipientAddress(
|
||||
currentExchange.recipient_address || ''
|
||||
);
|
||||
|
||||
form.raw_shipping_text = '';
|
||||
form.recipient_name = currentExchange.recipient_name || '';
|
||||
form.recipient_phone = currentExchange.recipient_phone || '';
|
||||
form.recipient_region_codes = regionCodes;
|
||||
form.recipient_address_detail = detailAddress;
|
||||
|
||||
syncAreaPickerState(regionCodes);
|
||||
showPopup.value = true;
|
||||
};
|
||||
|
||||
const handleAreaPickerChange = (event) => {
|
||||
const selectedValues = event.value || [];
|
||||
const selectedProvinceCode = selectedValues[0]?.value || '';
|
||||
const selectedCityCode =
|
||||
event.columnIndex === 0 ? '' : selectedValues[1]?.value || '';
|
||||
const selectedDistrictCode =
|
||||
event.columnIndex === 2 ? selectedValues[2]?.value || '' : '';
|
||||
|
||||
const { columns, defaultIndex } = buildRegionColumns([
|
||||
selectedProvinceCode,
|
||||
selectedCityCode,
|
||||
selectedDistrictCode
|
||||
]);
|
||||
areaPickerColumns.value = columns;
|
||||
areaPickerDefaultIndex.value = defaultIndex.map((index) => (index < 0 ? 0 : index));
|
||||
};
|
||||
|
||||
const confirmAreaPicker = ({ value }) => {
|
||||
const selectedCodes = (value || []).map((item) => item?.value).filter(Boolean);
|
||||
if (selectedCodes.length === 3) {
|
||||
form.recipient_region_codes = selectedCodes;
|
||||
syncAreaPickerState(selectedCodes);
|
||||
}
|
||||
showAreaPicker.value = false;
|
||||
};
|
||||
|
||||
const fillFromPastedText = () => {
|
||||
if (!form.raw_shipping_text.trim()) {
|
||||
uni.showToast({ title: '请先粘贴收货信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = parseShippingText(form.raw_shipping_text);
|
||||
let updatedCount = 0;
|
||||
|
||||
if (parsed.recipient_name) {
|
||||
form.recipient_name = parsed.recipient_name;
|
||||
updatedCount += 1;
|
||||
}
|
||||
|
||||
if (parsed.recipient_phone) {
|
||||
form.recipient_phone = parsed.recipient_phone;
|
||||
updatedCount += 1;
|
||||
}
|
||||
|
||||
if (parsed.regionCodes.length === 3) {
|
||||
form.recipient_region_codes = parsed.regionCodes;
|
||||
syncAreaPickerState(parsed.regionCodes);
|
||||
updatedCount += 1;
|
||||
}
|
||||
|
||||
if (parsed.detailAddress) {
|
||||
form.recipient_address_detail = parsed.detailAddress;
|
||||
updatedCount += 1;
|
||||
}
|
||||
|
||||
if (!updatedCount) {
|
||||
uni.showToast({ title: '未识别到有效收货信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showToast({ title: '已识别并填充', icon: 'success' });
|
||||
};
|
||||
|
||||
const submitExchange = async () => {
|
||||
if (!form.recipient_name) {
|
||||
if (!form.recipient_name.trim()) {
|
||||
uni.showToast({ title: '请输入收件人姓名', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!form.recipient_phone) {
|
||||
|
||||
if (!form.recipient_phone.trim()) {
|
||||
uni.showToast({ title: '请输入收件人电话', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 手机号格式校验
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(form.recipient_phone)) {
|
||||
if (!PHONE_REG.test(form.recipient_phone.trim())) {
|
||||
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!form.recipient_address) {
|
||||
uni.showToast({ title: '请输入收货地址', icon: 'none' });
|
||||
if (form.recipient_region_codes.length !== 3) {
|
||||
uni.showToast({ title: '请选择省市区', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!form.recipient_address_detail.trim()) {
|
||||
uni.showToast({ title: '请输入详细收货地址', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
const recipientAddress = composeRecipientAddress(
|
||||
form.recipient_region_codes,
|
||||
form.recipient_address_detail
|
||||
);
|
||||
|
||||
try {
|
||||
await exchangeApi.submitShippingInfo(
|
||||
exchangeData.value.id,
|
||||
form.recipient_name,
|
||||
form.recipient_phone,
|
||||
form.recipient_address
|
||||
form.recipient_name.trim(),
|
||||
form.recipient_phone.trim(),
|
||||
recipientAddress
|
||||
);
|
||||
uni.showToast({ title: '提交成功', icon: 'success' });
|
||||
showPopup.value = false;
|
||||
loadExchange();
|
||||
} catch (e) {
|
||||
console.error('提交换货信息失败', e);
|
||||
handlePopupClose();
|
||||
await loadExchange();
|
||||
} catch (error) {
|
||||
console.error('提交换货信息失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
syncAreaPickerState();
|
||||
loadExchange();
|
||||
});
|
||||
</script>
|
||||
@@ -167,24 +341,57 @@
|
||||
justify-content: center;
|
||||
padding: 120rpx 40rpx;
|
||||
min-height: 60vh;
|
||||
.empty-icon { font-size: 120rpx; margin-bottom: 30rpx; opacity: 0.6; }
|
||||
.empty-title { font-size: 32rpx; font-weight: 600; color: var(--text-primary); margin-bottom: 16rpx; }
|
||||
.empty-desc { font-size: 26rpx; color: var(--text-tertiary); text-align: center; margin-bottom: 40rpx; }
|
||||
.empty-btn { width: 100%; max-width: 400rpx; }
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-tertiary);
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.exchange-card {
|
||||
.exchange-header {
|
||||
margin-bottom: var(--space-md);
|
||||
.exchange-no { font-size: 28rpx; font-weight: 600; color: var(--text-primary); }
|
||||
|
||||
.exchange-no {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.exchange-info {
|
||||
.info-row {
|
||||
padding: var(--space-xs) 0;
|
||||
.info-label { font-size: 24rpx; color: var(--text-tertiary); }
|
||||
.info-value { font-size: 24rpx; color: var(--text-primary); }
|
||||
.address-value { max-width: 400rpx; text-align: right; }
|
||||
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.address-value {
|
||||
max-width: 400rpx;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,14 +399,21 @@
|
||||
margin-top: var(--space-lg);
|
||||
padding-top: var(--space-md);
|
||||
border-top: 1rpx solid var(--gray-200);
|
||||
.btn-apple { width: 100%; }
|
||||
|
||||
.btn-apple {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
width: 600rpx;
|
||||
width: 660rpx;
|
||||
max-height: 80vh;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
@@ -207,19 +421,55 @@
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: var(--space-md);
|
||||
|
||||
.form-label {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.form-helper {
|
||||
margin-top: var(--space-xs);
|
||||
font-size: 22rpx;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.parse-btn {
|
||||
width: 100%;
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.region-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 84rpx;
|
||||
padding: 0 24rpx;
|
||||
border: 2rpx solid var(--border-light);
|
||||
border-radius: 12rpx;
|
||||
background: #fff;
|
||||
|
||||
.region-trigger-text {
|
||||
font-size: 28rpx;
|
||||
color: var(--text-primary);
|
||||
|
||||
&.placeholder {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-btn-group {
|
||||
display: flex;
|
||||
gap: var(--space-md);
|
||||
margin-top: var(--space-lg);
|
||||
.btn-apple { flex: 1; }
|
||||
|
||||
.btn-apple {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -151,6 +151,7 @@
|
||||
packageName: '-',
|
||||
expireDate: '-',
|
||||
walletBalance: 0,
|
||||
iccid: '-',
|
||||
currentIccid: '-',
|
||||
category: '-',
|
||||
phone: '-',
|
||||
@@ -233,6 +234,7 @@
|
||||
deviceInfo.bound_phone = data.bound_phone || '';
|
||||
deviceInfo.packageName = data.current_package || '-';
|
||||
deviceInfo.expireDate = formatDate(data.current_package_expires_at);
|
||||
deviceInfo.iccid = data.iccid || '-';
|
||||
deviceInfo.walletBalance = data.wallet_balance ?? 0;
|
||||
isRealName.value = data.real_name_status === 1;
|
||||
realNameStatus.value = data.real_name_status === 1 ? '已实名' : '未实名';
|
||||
|
||||
4862
pnpm-lock.yaml
generated
Normal file
4862
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
309
utils/shippingAddress.js
Normal file
309
utils/shippingAddress.js
Normal file
@@ -0,0 +1,309 @@
|
||||
import { regionData } from 'element-china-area-data';
|
||||
|
||||
const GENERIC_CITY_LABELS = new Set([
|
||||
'市辖区',
|
||||
'县',
|
||||
'自治区直辖县级行政区划',
|
||||
'省直辖县级行政区划'
|
||||
]);
|
||||
|
||||
const NAME_LABEL_REG = /(收货人|收件人|联系人|姓名)/g;
|
||||
const PHONE_LABEL_REG = /(手机号码|手机号|联系电话|联系方式|电话|手机)/g;
|
||||
const ADDRESS_LABEL_REG = /(收货地址|详细地址|地址|所在地区|地区)/g;
|
||||
const ADDRESS_SIGNAL_REG = /(省|市|区|县|旗|乡|镇|街道|大道|路|街|巷|弄|村|社区|小区|号|栋|单元|室|楼)/;
|
||||
const PHONE_REG = /(?<!\d)(?:\+?86[-\s]?)?(1[3-9](?:[\s-]?\d){9})(?!\d)/;
|
||||
|
||||
const provinceOptions = regionData.map(({ label, value }) => ({ label, value }));
|
||||
|
||||
const districtRecords = regionData.flatMap((province) =>
|
||||
(province.children || []).flatMap((city) =>
|
||||
(city.children || []).map((district) => ({
|
||||
province,
|
||||
city,
|
||||
district,
|
||||
provinceSearchLabel: normalizeForSearch(province.label),
|
||||
citySearchLabel: normalizeForSearch(getDisplayCityLabel(city.label)),
|
||||
districtSearchLabel: normalizeForSearch(district.label),
|
||||
fullSearchLabel: normalizeForSearch(
|
||||
`${province.label}${getDisplayCityLabel(city.label)}${district.label}`
|
||||
)
|
||||
}))
|
||||
)
|
||||
);
|
||||
|
||||
function normalizeText(value = '') {
|
||||
return String(value)
|
||||
.replace(/\r/g, '\n')
|
||||
.replace(/\u00a0/g, ' ')
|
||||
.replace(/\t/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function normalizeForSearch(value = '') {
|
||||
return normalizeText(value).replace(/[\s,,.。;;::、\-_/]/g, '');
|
||||
}
|
||||
|
||||
function getDisplayCityLabel(label = '') {
|
||||
return GENERIC_CITY_LABELS.has(label) ? '' : label;
|
||||
}
|
||||
|
||||
function getProvinceByCode(provinceCode) {
|
||||
return regionData.find((item) => item.value === provinceCode) || null;
|
||||
}
|
||||
|
||||
function getCityByCode(provinceCode, cityCode) {
|
||||
const province = getProvinceByCode(provinceCode);
|
||||
return province?.children?.find((item) => item.value === cityCode) || null;
|
||||
}
|
||||
|
||||
function getDistrictByCode(provinceCode, cityCode, districtCode) {
|
||||
const city = getCityByCode(provinceCode, cityCode);
|
||||
return city?.children?.find((item) => item.value === districtCode) || null;
|
||||
}
|
||||
|
||||
function getCityOptions(provinceCode) {
|
||||
const province = getProvinceByCode(provinceCode) || regionData[0];
|
||||
return (province?.children || []).map(({ label, value }) => ({ label, value }));
|
||||
}
|
||||
|
||||
function getDistrictOptions(provinceCode, cityCode) {
|
||||
const province = getProvinceByCode(provinceCode) || regionData[0];
|
||||
const city =
|
||||
province?.children?.find((item) => item.value === cityCode) ||
|
||||
province?.children?.[0] ||
|
||||
null;
|
||||
|
||||
return (city?.children || []).map(({ label, value }) => ({ label, value }));
|
||||
}
|
||||
|
||||
function getLabelParts(regionCodes = []) {
|
||||
const [provinceCode, cityCode, districtCode] = regionCodes;
|
||||
const province = getProvinceByCode(provinceCode);
|
||||
const city = getCityByCode(provinceCode, cityCode);
|
||||
const district = getDistrictByCode(provinceCode, cityCode, districtCode);
|
||||
|
||||
if (!province || !city || !district) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const displayCityLabel = getDisplayCityLabel(city.label);
|
||||
return [province.label, displayCityLabel, district.label].filter(Boolean);
|
||||
}
|
||||
|
||||
function cleanupAddressText(value = '') {
|
||||
return normalizeText(value)
|
||||
.replace(NAME_LABEL_REG, ' ')
|
||||
.replace(PHONE_LABEL_REG, ' ')
|
||||
.replace(ADDRESS_LABEL_REG, ' ')
|
||||
.replace(/[::]/g, ' ')
|
||||
.replace(/[,,;;]/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function extractPhone(rawText = '') {
|
||||
const text = normalizeText(rawText);
|
||||
const match = text.match(PHONE_REG);
|
||||
if (!match) {
|
||||
return {
|
||||
phone: '',
|
||||
textWithoutPhone: text
|
||||
};
|
||||
}
|
||||
|
||||
const digits = match[1].replace(/\D/g, '');
|
||||
const phone = digits.length > 11 ? digits.slice(-11) : digits;
|
||||
|
||||
return {
|
||||
phone,
|
||||
textWithoutPhone: text.replace(match[0], ' ').replace(/\s+/g, ' ').trim()
|
||||
};
|
||||
}
|
||||
|
||||
function pickBestMatch(records, matcher) {
|
||||
return records
|
||||
.filter(matcher)
|
||||
.sort((left, right) => {
|
||||
return (
|
||||
right.fullSearchLabel.length - left.fullSearchLabel.length ||
|
||||
right.districtSearchLabel.length - left.districtSearchLabel.length
|
||||
);
|
||||
})[0] || null;
|
||||
}
|
||||
|
||||
function findRegionMatch(rawText = '') {
|
||||
const searchText = normalizeForSearch(rawText);
|
||||
if (!searchText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let match = pickBestMatch(districtRecords, (record) => {
|
||||
return record.fullSearchLabel && searchText.includes(record.fullSearchLabel);
|
||||
});
|
||||
|
||||
if (!match) {
|
||||
match = pickBestMatch(districtRecords, (record) => {
|
||||
return (
|
||||
record.provinceSearchLabel &&
|
||||
record.districtSearchLabel &&
|
||||
searchText.includes(record.provinceSearchLabel) &&
|
||||
searchText.includes(record.districtSearchLabel)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
function getRegionStartIndex(text, match) {
|
||||
const cityLabel = getDisplayCityLabel(match.city.label);
|
||||
const candidates = [match.province.label, cityLabel, match.district.label]
|
||||
.filter(Boolean)
|
||||
.map((label) => text.indexOf(label))
|
||||
.filter((index) => index >= 0);
|
||||
|
||||
return candidates.length ? Math.min(...candidates) : -1;
|
||||
}
|
||||
|
||||
function stripMatchedRegion(addressText, match) {
|
||||
const cityLabel = getDisplayCityLabel(match.city.label);
|
||||
let detailAddress = normalizeText(addressText);
|
||||
|
||||
[match.province.label, cityLabel, match.district.label]
|
||||
.filter(Boolean)
|
||||
.forEach((label) => {
|
||||
detailAddress = detailAddress.replace(label, '');
|
||||
});
|
||||
|
||||
return detailAddress.replace(/^[,,;;::\s]+/, '').trim();
|
||||
}
|
||||
|
||||
function extractNameFromText(rawText = '', textWithoutPhone = '', regionStartIndex = -1) {
|
||||
const sourceText = regionStartIndex > 0
|
||||
? textWithoutPhone.slice(0, regionStartIndex)
|
||||
: textWithoutPhone || rawText;
|
||||
|
||||
const directNameMatch = normalizeText(rawText).match(
|
||||
/(?:收货人|收件人|联系人|姓名)\s*[::]?\s*([A-Za-z\u4e00-\u9fa5·]{2,20})/
|
||||
);
|
||||
if (directNameMatch?.[1]) {
|
||||
return directNameMatch[1].trim();
|
||||
}
|
||||
|
||||
const cleaned = cleanupAddressText(sourceText)
|
||||
.replace(PHONE_REG, ' ')
|
||||
.replace(/\d+/g, ' ')
|
||||
.trim();
|
||||
const tokens = cleaned.split(/\s+/).filter(Boolean);
|
||||
const nameToken = tokens.find((token) => {
|
||||
return !ADDRESS_SIGNAL_REG.test(token) && /^[A-Za-z\u4e00-\u9fa5·]{2,20}$/.test(token);
|
||||
});
|
||||
|
||||
return nameToken || '';
|
||||
}
|
||||
|
||||
function extractAddressSource(textWithoutPhone = '', name = '', match = null) {
|
||||
let addressSource = normalizeText(textWithoutPhone);
|
||||
|
||||
if (name) {
|
||||
addressSource = addressSource.replace(name, ' ').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
if (match) {
|
||||
const regionStartIndex = getRegionStartIndex(addressSource, match);
|
||||
if (regionStartIndex >= 0) {
|
||||
return addressSource.slice(regionStartIndex).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return cleanupAddressText(addressSource);
|
||||
}
|
||||
|
||||
function formatRegionCodes(match) {
|
||||
return [match.province.value, match.city.value, match.district.value];
|
||||
}
|
||||
|
||||
export function buildRegionColumns(regionCodes = []) {
|
||||
const provinceCode =
|
||||
getProvinceByCode(regionCodes[0])?.value || provinceOptions[0]?.value || '';
|
||||
const cityOptions = getCityOptions(provinceCode);
|
||||
const cityCode =
|
||||
cityOptions.find((item) => item.value === regionCodes[1])?.value ||
|
||||
cityOptions[0]?.value ||
|
||||
'';
|
||||
const districtOptions = getDistrictOptions(provinceCode, cityCode);
|
||||
const districtCode =
|
||||
districtOptions.find((item) => item.value === regionCodes[2])?.value ||
|
||||
districtOptions[0]?.value ||
|
||||
'';
|
||||
|
||||
return {
|
||||
columns: [provinceOptions, cityOptions, districtOptions],
|
||||
defaultIndex: [
|
||||
provinceOptions.findIndex((item) => item.value === provinceCode),
|
||||
cityOptions.findIndex((item) => item.value === cityCode),
|
||||
districtOptions.findIndex((item) => item.value === districtCode)
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
export function getRegionDisplayText(regionCodes = []) {
|
||||
return getLabelParts(regionCodes).join(' / ');
|
||||
}
|
||||
|
||||
export function composeRecipientAddress(regionCodes = [], detailAddress = '') {
|
||||
const regionText = getLabelParts(regionCodes).join('');
|
||||
return `${regionText}${normalizeText(detailAddress)}`.trim();
|
||||
}
|
||||
|
||||
export function parseSavedRecipientAddress(rawAddress = '') {
|
||||
const addressText = normalizeText(rawAddress);
|
||||
if (!addressText) {
|
||||
return {
|
||||
regionCodes: [],
|
||||
detailAddress: ''
|
||||
};
|
||||
}
|
||||
|
||||
const match = findRegionMatch(addressText);
|
||||
if (!match) {
|
||||
return {
|
||||
regionCodes: [],
|
||||
detailAddress: addressText
|
||||
};
|
||||
}
|
||||
|
||||
const detailAddress = stripMatchedRegion(addressText, match);
|
||||
return {
|
||||
regionCodes: formatRegionCodes(match),
|
||||
detailAddress: detailAddress || addressText
|
||||
};
|
||||
}
|
||||
|
||||
export function parseShippingText(rawText = '') {
|
||||
const text = normalizeText(rawText);
|
||||
if (!text) {
|
||||
return {
|
||||
recipient_name: '',
|
||||
recipient_phone: '',
|
||||
regionCodes: [],
|
||||
detailAddress: ''
|
||||
};
|
||||
}
|
||||
|
||||
const { phone, textWithoutPhone } = extractPhone(text);
|
||||
const regionMatch = findRegionMatch(textWithoutPhone);
|
||||
const regionStartIndex = regionMatch ? getRegionStartIndex(textWithoutPhone, regionMatch) : -1;
|
||||
const recipientName = extractNameFromText(text, textWithoutPhone, regionStartIndex);
|
||||
const addressSource = extractAddressSource(textWithoutPhone, recipientName, regionMatch);
|
||||
const detailAddress = regionMatch
|
||||
? stripMatchedRegion(addressSource, regionMatch)
|
||||
: cleanupAddressText(addressSource);
|
||||
|
||||
return {
|
||||
recipient_name: recipientName,
|
||||
recipient_phone: phone,
|
||||
regionCodes: regionMatch ? formatRegionCodes(regionMatch) : [],
|
||||
detailAddress
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user