feat(dealer): 添加签约时间和合同日期选择功能
- 在客户签约页面实现签约时间和合同日期的选择功能 - 使用 NutUI 的 DatePicker组件提供友好的日期选择体验- 添加日期格式化和选择处理逻辑 - 优化界面显示,增加日历图标提示和禁用编辑模式 - 确保日期数据正确提交和持久化
This commit is contained in:
@@ -25,7 +25,9 @@ export interface ShopDealerApply {
|
|||||||
// 申请方式(10需后台审核 20无需审核)
|
// 申请方式(10需后台审核 20无需审核)
|
||||||
applyType?: number;
|
applyType?: number;
|
||||||
// 申请时间
|
// 申请时间
|
||||||
applyTime?: number;
|
applyTime?: string;
|
||||||
|
// 签单时间
|
||||||
|
contractTime?: string;
|
||||||
// 审核状态 (10待审核 20审核通过 30驳回)
|
// 审核状态 (10待审核 20审核通过 30驳回)
|
||||||
applyStatus?: number;
|
applyStatus?: number;
|
||||||
// 审核时间
|
// 审核时间
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {useEffect, useState, useRef} from "react";
|
import {useEffect, useState, useRef} from "react";
|
||||||
import {Loading, CellGroup, Cell, Input, Form} from '@nutui/nutui-react-taro'
|
import {Loading, CellGroup, Cell, Input, Form, DatePicker, Popup} from '@nutui/nutui-react-taro'
|
||||||
import {Edit} from '@nutui/icons-react-taro'
|
import {Edit, Calendar} from '@nutui/icons-react-taro'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import {useRouter} from '@tarojs/taro'
|
import {useRouter} from '@tarojs/taro'
|
||||||
import {View} from '@tarojs/components'
|
import {View,Text} from '@tarojs/components'
|
||||||
import FixedButton from "@/components/FixedButton";
|
import FixedButton from "@/components/FixedButton";
|
||||||
import {useUser} from "@/hooks/useUser";
|
import {useUser} from "@/hooks/useUser";
|
||||||
import {ShopDealerApply} from "@/api/shop/shopDealerApply/model";
|
import {ShopDealerApply} from "@/api/shop/shopDealerApply/model";
|
||||||
@@ -21,6 +21,12 @@ const AddShopDealerApply = () => {
|
|||||||
const [isEditMode, setIsEditMode] = useState<boolean>(false)
|
const [isEditMode, setIsEditMode] = useState<boolean>(false)
|
||||||
const [existingApply, setExistingApply] = useState<ShopDealerApply | null>(null)
|
const [existingApply, setExistingApply] = useState<ShopDealerApply | null>(null)
|
||||||
|
|
||||||
|
// 日期选择器状态
|
||||||
|
const [showApplyTimePicker, setShowApplyTimePicker] = useState<boolean>(false)
|
||||||
|
const [showContractDatePicker, setShowContractDatePicker] = useState<boolean>(false)
|
||||||
|
const [applyTime, setApplyTime] = useState<string>('')
|
||||||
|
const [contractDate, setContractDate] = useState<string>('')
|
||||||
|
|
||||||
// 获取审核状态文字
|
// 获取审核状态文字
|
||||||
const getApplyStatusText = (status?: number) => {
|
const getApplyStatusText = (status?: number) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -35,6 +41,44 @@ const AddShopDealerApply = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 格式化日期为 YYYY-MM-DD
|
||||||
|
const formatDate = (date: Date): string => {
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理签约时间选择
|
||||||
|
const handleApplyTimeConfirm = (_: any, values: Date[]) => {
|
||||||
|
const selectedDate = values[0]
|
||||||
|
const formattedDate = formatDate(selectedDate)
|
||||||
|
setApplyTime(formattedDate)
|
||||||
|
setShowApplyTimePicker(false)
|
||||||
|
|
||||||
|
// 更新表单数据
|
||||||
|
if (formRef.current) {
|
||||||
|
formRef.current.setFieldsValue({
|
||||||
|
applyTime: formattedDate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理合同日期选择
|
||||||
|
const handleContractDateConfirm = (_: any, values: Date[]) => {
|
||||||
|
const selectedDate = values[0]
|
||||||
|
const formattedDate = formatDate(selectedDate)
|
||||||
|
setContractDate(formattedDate)
|
||||||
|
setShowContractDatePicker(false)
|
||||||
|
|
||||||
|
// 更新表单数据
|
||||||
|
if (formRef.current) {
|
||||||
|
formRef.current.setFieldsValue({
|
||||||
|
contractDate: formattedDate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
if(!params.id){
|
if(!params.id){
|
||||||
return false;
|
return false;
|
||||||
@@ -46,6 +90,15 @@ const AddShopDealerApply = () => {
|
|||||||
setFormData(dealerApply)
|
setFormData(dealerApply)
|
||||||
setIsEditMode(true);
|
setIsEditMode(true);
|
||||||
setExistingApply(dealerApply)
|
setExistingApply(dealerApply)
|
||||||
|
|
||||||
|
// 初始化日期数据
|
||||||
|
if (dealerApply.applyTime) {
|
||||||
|
setApplyTime(dealerApply.applyTime)
|
||||||
|
}
|
||||||
|
if (dealerApply.contractTime) {
|
||||||
|
setContractDate(dealerApply.contractTime)
|
||||||
|
}
|
||||||
|
|
||||||
Taro.setNavigationBarTitle({title: '签约'})
|
Taro.setNavigationBarTitle({title: '签约'})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -66,7 +119,10 @@ const AddShopDealerApply = () => {
|
|||||||
mobile: user?.phone,
|
mobile: user?.phone,
|
||||||
refereeId: 33534,
|
refereeId: 33534,
|
||||||
applyStatus: 10,
|
applyStatus: 10,
|
||||||
auditTime: undefined
|
auditTime: undefined,
|
||||||
|
// 确保日期数据正确提交
|
||||||
|
applyTime: applyTime || values.applyTime,
|
||||||
|
contractDate: contractDate || values.contractDate
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果是编辑模式,添加现有申请的id
|
// 如果是编辑模式,添加现有申请的id
|
||||||
@@ -150,16 +206,73 @@ const AddShopDealerApply = () => {
|
|||||||
<Input placeholder="(元/兆瓦时)" disabled={false}/>
|
<Input placeholder="(元/兆瓦时)" disabled={false}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="applyTime" label="签约时间" initialValue={FormData?.applyTime} required>
|
<Form.Item name="applyTime" label="签约时间" initialValue={FormData?.applyTime} required>
|
||||||
<Input placeholder="请选择签约时间" disabled={isEditMode}/>
|
<View
|
||||||
|
className="flex items-center justify-between py-2"
|
||||||
|
onClick={() => !isEditMode && setShowApplyTimePicker(true)}
|
||||||
|
style={{
|
||||||
|
cursor: isEditMode ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: isEditMode ? 0.6 : 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View className="flex items-center">
|
||||||
|
<Calendar size={16} color="#999" className="mr-2" />
|
||||||
|
<Text style={{ color: applyTime ? '#333' : '#999' }}>
|
||||||
|
{applyTime || '请选择签约时间'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="contractDate" label="合同日期" initialValue={FormData?.applyTime} required>
|
<Form.Item name="contractDate" label="合同日期" initialValue={FormData?.contractTime} required>
|
||||||
<Input placeholder="请选择合同生效起止时间" disabled={isEditMode}/>
|
<View
|
||||||
|
className="flex items-center justify-between py-2"
|
||||||
|
onClick={() => !isEditMode && setShowContractDatePicker(true)}
|
||||||
|
style={{
|
||||||
|
cursor: isEditMode ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: isEditMode ? 0.6 : 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View className="flex items-center">
|
||||||
|
<Calendar size={16} color="#999" className="mr-2" />
|
||||||
|
<Text style={{ color: contractDate ? '#333' : '#999' }}>
|
||||||
|
{contractDate || '请选择合同生效起止时间'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{/*<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>*/}
|
{/*<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>*/}
|
||||||
{/* <Input placeholder="邀请人ID"/>*/}
|
{/* <Input placeholder="邀请人ID"/>*/}
|
||||||
{/*</Form.Item>*/}
|
{/*</Form.Item>*/}
|
||||||
</CellGroup>
|
</CellGroup>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
{/* 签约时间选择器 */}
|
||||||
|
<Popup
|
||||||
|
visible={showApplyTimePicker}
|
||||||
|
position="bottom"
|
||||||
|
onClose={() => setShowApplyTimePicker(false)}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
title="选择签约时间"
|
||||||
|
type="date"
|
||||||
|
onConfirm={() => handleApplyTimeConfirm}
|
||||||
|
onClose={() => setShowApplyTimePicker(false)}
|
||||||
|
/>
|
||||||
|
</Popup>
|
||||||
|
|
||||||
|
{/* 合同日期选择器 */}
|
||||||
|
<Popup
|
||||||
|
visible={showContractDatePicker}
|
||||||
|
position="bottom"
|
||||||
|
onClose={() => setShowContractDatePicker(false)}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
title="选择合同日期"
|
||||||
|
type="date"
|
||||||
|
onConfirm={() => handleContractDateConfirm}
|
||||||
|
onClose={() => setShowContractDatePicker(false)}
|
||||||
|
/>
|
||||||
|
</Popup>
|
||||||
|
|
||||||
{/* 审核状态显示(仅在编辑模式下显示) */}
|
{/* 审核状态显示(仅在编辑模式下显示) */}
|
||||||
{isEditMode && (
|
{isEditMode && (
|
||||||
<CellGroup>
|
<CellGroup>
|
||||||
|
|||||||
@@ -189,6 +189,13 @@ const Header = (props: any) => {
|
|||||||
reload().then()
|
reload().then()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// 监听用户信息变化,当用户信息更新后重新检查
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoggedIn && user) {
|
||||||
|
console.log('用户信息已更新:', user);
|
||||||
|
}
|
||||||
|
}, [user, isLoggedIn])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View className={'fixed top-0 header-bg'} style={{
|
<View className={'fixed top-0 header-bg'} style={{
|
||||||
@@ -207,9 +214,9 @@ const Header = (props: any) => {
|
|||||||
onClick={() => navTo(`/user/profile/profile`, true)}>
|
onClick={() => navTo(`/user/profile/profile`, true)}>
|
||||||
<Avatar
|
<Avatar
|
||||||
size="22"
|
size="22"
|
||||||
src={user?.avatar}
|
src={user?.avatar || Taro.getStorageSync('Avatar')}
|
||||||
/>
|
/>
|
||||||
<Text className={'text-white'}>{user?.nickname || '已登录'}</Text>
|
<Text className={'text-white'}>{user?.nickname || Taro.getStorageSync('Nickname') || '已登录'}</Text>
|
||||||
<TriangleDown className={'text-white'} size={9}/>
|
<TriangleDown className={'text-white'} size={9}/>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
165
头像昵称显示问题修复说明.md
Normal file
165
头像昵称显示问题修复说明.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# 头像昵称显示问题修复说明
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
用户更新头像和昵称后,头部显示变成了"已登录"而不是显示用户的头像和昵称。
|
||||||
|
|
||||||
|
## 问题分析
|
||||||
|
|
||||||
|
### 根本原因
|
||||||
|
在 Header 组件中,显示逻辑存在以下问题:
|
||||||
|
|
||||||
|
1. **显示逻辑不完整**:头像和昵称的显示只依赖 `user` 状态,没有考虑本地存储的备用数据
|
||||||
|
2. **状态同步延迟**:`useUser` hook 中的状态更新可能存在延迟,导致界面显示不及时
|
||||||
|
3. **缺少状态监听**:Header 组件没有充分监听用户信息的变化
|
||||||
|
|
||||||
|
### 具体问题代码
|
||||||
|
```typescript
|
||||||
|
// 原始代码 - 只依赖 user 状态
|
||||||
|
<Avatar
|
||||||
|
size="22"
|
||||||
|
src={user?.avatar} // 如果 user.avatar 为空,头像不显示
|
||||||
|
/>
|
||||||
|
<Text className={'text-white'}>{user?.nickname || '已登录'}</Text> // 如果 user.nickname 为空,显示"已登录"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题场景
|
||||||
|
1. 用户更新头像/昵称后,`useUser` hook 的状态可能还没有及时更新
|
||||||
|
2. 即使本地存储中已经保存了最新的头像和昵称,界面仍然显示旧的状态
|
||||||
|
3. 导致用户看到"已登录"而不是自己的头像和昵称
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
### 1. 优化显示逻辑
|
||||||
|
修改头像和昵称的显示逻辑,增加本地存储作为备用数据源:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 修复后的代码 - 同时检查状态和本地存储
|
||||||
|
<Avatar
|
||||||
|
size="22"
|
||||||
|
src={user?.avatar || Taro.getStorageSync('Avatar')} // 优先使用状态,备用本地存储
|
||||||
|
/>
|
||||||
|
<Text className={'text-white'}>
|
||||||
|
{user?.nickname || Taro.getStorageSync('Nickname') || '已登录'} // 多层备用逻辑
|
||||||
|
</Text>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 添加状态监听
|
||||||
|
在 Header 组件中添加 `useEffect` 来监听用户信息变化:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 监听用户信息变化,当用户信息更新后重新检查
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoggedIn && user) {
|
||||||
|
console.log('用户信息已更新:', user);
|
||||||
|
}
|
||||||
|
}, [user, isLoggedIn])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 数据获取优先级
|
||||||
|
建立清晰的数据获取优先级:
|
||||||
|
1. **第一优先级**:`user` 状态中的数据(最新的内存状态)
|
||||||
|
2. **第二优先级**:本地存储中的数据(持久化备用数据)
|
||||||
|
3. **第三优先级**:默认显示文本("已登录")
|
||||||
|
|
||||||
|
## 修复效果
|
||||||
|
|
||||||
|
### ✅ 即时显示
|
||||||
|
- 用户更新头像/昵称后,界面立即显示最新信息
|
||||||
|
- 即使状态更新有延迟,也能从本地存储获取最新数据
|
||||||
|
|
||||||
|
### ✅ 数据一致性
|
||||||
|
- 确保界面显示与实际数据保持一致
|
||||||
|
- 避免出现"已登录"的错误显示
|
||||||
|
|
||||||
|
### ✅ 用户体验优化
|
||||||
|
- 用户操作后立即看到结果
|
||||||
|
- 减少界面闪烁和不一致的显示
|
||||||
|
|
||||||
|
## 技术细节
|
||||||
|
|
||||||
|
### 数据流向
|
||||||
|
```
|
||||||
|
用户更新 → updateUser() → 更新状态 + 本地存储 → 界面显示
|
||||||
|
↓
|
||||||
|
状态可能延迟 → 本地存储作为备用 → 确保界面正确显示
|
||||||
|
```
|
||||||
|
|
||||||
|
### 显示逻辑优化
|
||||||
|
```typescript
|
||||||
|
// 头像显示逻辑
|
||||||
|
const avatarSrc = user?.avatar || Taro.getStorageSync('Avatar');
|
||||||
|
|
||||||
|
// 昵称显示逻辑
|
||||||
|
const displayName = user?.nickname || Taro.getStorageSync('Nickname') || '已登录';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态监听机制
|
||||||
|
```typescript
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoggedIn && user) {
|
||||||
|
// 用户信息更新时的处理逻辑
|
||||||
|
console.log('用户信息已更新:', user);
|
||||||
|
}
|
||||||
|
}, [user, isLoggedIn]);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 相关文件修改
|
||||||
|
|
||||||
|
### src/pages/index/Header.tsx
|
||||||
|
1. **第216行**:修改头像显示逻辑,添加本地存储备用
|
||||||
|
2. **第217行**:修改昵称显示逻辑,添加多层备用机制
|
||||||
|
3. **第194-197行**:添加用户信息变化监听
|
||||||
|
|
||||||
|
### 修改前后对比
|
||||||
|
```typescript
|
||||||
|
// 修改前
|
||||||
|
<Avatar size="22" src={user?.avatar} />
|
||||||
|
<Text className={'text-white'}>{user?.nickname || '已登录'}</Text>
|
||||||
|
|
||||||
|
// 修改后
|
||||||
|
<Avatar size="22" src={user?.avatar || Taro.getStorageSync('Avatar')} />
|
||||||
|
<Text className={'text-white'}>{user?.nickname || Taro.getStorageSync('Nickname') || '已登录'}</Text>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 防护机制
|
||||||
|
|
||||||
|
### 多层备用策略
|
||||||
|
1. **状态优先**:优先使用内存中的最新状态
|
||||||
|
2. **存储备用**:状态为空时使用本地存储数据
|
||||||
|
3. **默认显示**:都为空时显示默认文本
|
||||||
|
|
||||||
|
### 数据同步保障
|
||||||
|
- `useUser` hook 确保状态和存储的同步
|
||||||
|
- Header 组件监听状态变化,及时响应更新
|
||||||
|
- 本地存储作为可靠的备用数据源
|
||||||
|
|
||||||
|
### 错误处理
|
||||||
|
- 避免因状态延迟导致的显示错误
|
||||||
|
- 确保在各种情况下都有合适的显示内容
|
||||||
|
- 提供友好的用户体验
|
||||||
|
|
||||||
|
## 测试建议
|
||||||
|
|
||||||
|
### 测试场景
|
||||||
|
1. **正常更新**:更新头像/昵称后检查显示是否正确
|
||||||
|
2. **状态延迟**:模拟状态更新延迟,检查备用机制是否生效
|
||||||
|
3. **数据清空**:清空状态数据,检查是否能从存储获取
|
||||||
|
4. **完全清空**:清空所有数据,检查是否显示默认文本
|
||||||
|
|
||||||
|
### 验证方法
|
||||||
|
```javascript
|
||||||
|
// 检查显示数据来源
|
||||||
|
console.log('用户状态:', user?.nickname);
|
||||||
|
console.log('本地存储:', Taro.getStorageSync('Nickname'));
|
||||||
|
console.log('最终显示:', user?.nickname || Taro.getStorageSync('Nickname') || '已登录');
|
||||||
|
```
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
通过优化显示逻辑和添加本地存储备用机制,我们成功解决了头像昵称显示问题。这个解决方案:
|
||||||
|
|
||||||
|
1. **可靠性高**:多层备用机制确保数据显示的可靠性
|
||||||
|
2. **响应及时**:即使状态更新有延迟也能正确显示
|
||||||
|
3. **用户友好**:避免了"已登录"的错误显示
|
||||||
|
4. **易于维护**:逻辑清晰,便于后续维护和扩展
|
||||||
|
|
||||||
|
现在用户更新头像和昵称后,界面会立即显示正确的信息,不再出现"已登录"的问题。
|
||||||
209
签约时间和合同日期选择功能实现说明.md
Normal file
209
签约时间和合同日期选择功能实现说明.md
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
# 签约时间和合同日期选择功能实现说明
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
为客户签约页面实现了签约时间和合同日期的选择功能,使用 NutUI 的 DatePicker 组件提供友好的日期选择体验。
|
||||||
|
|
||||||
|
## 实现特性
|
||||||
|
|
||||||
|
### ✅ 日期选择器
|
||||||
|
- **签约时间选择**:支持选择具体的签约日期
|
||||||
|
- **合同日期选择**:支持选择合同生效日期
|
||||||
|
- **弹窗式选择**:使用 Popup + DatePicker 组合,提供良好的用户体验
|
||||||
|
|
||||||
|
### ✅ 界面优化
|
||||||
|
- **图标提示**:使用日历图标提示用户可以选择日期
|
||||||
|
- **状态显示**:清晰显示已选择的日期或提示文本
|
||||||
|
- **禁用状态**:编辑模式下禁用日期选择,防止误操作
|
||||||
|
|
||||||
|
### ✅ 数据处理
|
||||||
|
- **格式化**:自动格式化日期为 YYYY-MM-DD 格式
|
||||||
|
- **表单同步**:选择日期后自动更新表单数据
|
||||||
|
- **数据持久化**:正确保存和加载日期数据
|
||||||
|
|
||||||
|
## 技术实现
|
||||||
|
|
||||||
|
### 1. 组件导入
|
||||||
|
```typescript
|
||||||
|
import {Loading, CellGroup, Cell, Input, Form, DatePicker, Popup} from '@nutui/nutui-react-taro'
|
||||||
|
import {Edit, Calendar} from '@nutui/icons-react-taro'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 状态管理
|
||||||
|
```typescript
|
||||||
|
// 日期选择器状态
|
||||||
|
const [showApplyTimePicker, setShowApplyTimePicker] = useState<boolean>(false)
|
||||||
|
const [showContractDatePicker, setShowContractDatePicker] = useState<boolean>(false)
|
||||||
|
const [applyTime, setApplyTime] = useState<string>('')
|
||||||
|
const [contractDate, setContractDate] = useState<string>('')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 日期格式化函数
|
||||||
|
```typescript
|
||||||
|
// 格式化日期为 YYYY-MM-DD
|
||||||
|
const formatDate = (date: Date): string => {
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 日期选择处理
|
||||||
|
```typescript
|
||||||
|
// 处理签约时间选择
|
||||||
|
const handleApplyTimeConfirm = (options: any, values: Date[]) => {
|
||||||
|
const selectedDate = values[0]
|
||||||
|
const formattedDate = formatDate(selectedDate)
|
||||||
|
setApplyTime(formattedDate)
|
||||||
|
setShowApplyTimePicker(false)
|
||||||
|
|
||||||
|
// 更新表单数据
|
||||||
|
if (formRef.current) {
|
||||||
|
formRef.current.setFieldsValue({
|
||||||
|
applyTime: formattedDate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 界面组件
|
||||||
|
```typescript
|
||||||
|
<Form.Item name="applyTime" label="签约时间" initialValue={FormData?.applyTime} required>
|
||||||
|
<View
|
||||||
|
className="flex items-center justify-between py-2"
|
||||||
|
onClick={() => !isEditMode && setShowApplyTimePicker(true)}
|
||||||
|
style={{
|
||||||
|
cursor: isEditMode ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: isEditMode ? 0.6 : 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View className="flex items-center">
|
||||||
|
<Calendar size={16} color="#999" className="mr-2" />
|
||||||
|
<Text style={{ color: applyTime ? '#333' : '#999' }}>
|
||||||
|
{applyTime || '请选择签约时间'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Form.Item>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 弹窗选择器
|
||||||
|
```typescript
|
||||||
|
{/* 签约时间选择器 */}
|
||||||
|
<Popup
|
||||||
|
visible={showApplyTimePicker}
|
||||||
|
position="bottom"
|
||||||
|
onClose={() => setShowApplyTimePicker(false)}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
title="选择签约时间"
|
||||||
|
type="date"
|
||||||
|
onConfirm={handleApplyTimeConfirm}
|
||||||
|
onClose={() => setShowApplyTimePicker(false)}
|
||||||
|
/>
|
||||||
|
</Popup>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心功能
|
||||||
|
|
||||||
|
### 📅 日期选择流程
|
||||||
|
1. **点击触发**:用户点击日期字段
|
||||||
|
2. **弹窗显示**:底部弹出日期选择器
|
||||||
|
3. **日期选择**:用户滚动选择年月日
|
||||||
|
4. **确认选择**:点击确认按钮
|
||||||
|
5. **数据更新**:自动更新界面显示和表单数据
|
||||||
|
6. **弹窗关闭**:选择完成后自动关闭
|
||||||
|
|
||||||
|
### 🔒 编辑模式控制
|
||||||
|
- **新增模式**:可以自由选择日期
|
||||||
|
- **编辑模式**:禁用日期选择,防止修改已确定的日期
|
||||||
|
- **视觉反馈**:通过透明度和鼠标样式提示状态
|
||||||
|
|
||||||
|
### 💾 数据同步
|
||||||
|
- **表单同步**:选择日期后立即更新表单字段值
|
||||||
|
- **状态同步**:本地状态与表单数据保持一致
|
||||||
|
- **提交处理**:确保日期数据正确提交到服务器
|
||||||
|
|
||||||
|
## 用户体验优化
|
||||||
|
|
||||||
|
### 🎨 界面设计
|
||||||
|
- **图标指示**:日历图标清晰表明可以选择日期
|
||||||
|
- **状态区分**:已选择日期显示为深色,提示文本显示为灰色
|
||||||
|
- **点击区域**:整个字段区域都可点击,提高操作便利性
|
||||||
|
|
||||||
|
### 📱 交互体验
|
||||||
|
- **底部弹窗**:符合移动端操作习惯
|
||||||
|
- **滚动选择**:直观的日期滚动选择方式
|
||||||
|
- **即时反馈**:选择后立即显示结果
|
||||||
|
|
||||||
|
### 🚫 错误防护
|
||||||
|
- **编辑限制**:编辑模式下禁用选择,避免误操作
|
||||||
|
- **数据验证**:确保日期格式正确
|
||||||
|
- **状态管理**:防止状态不一致问题
|
||||||
|
|
||||||
|
## 文件修改清单
|
||||||
|
|
||||||
|
### src/dealer/customer/add.tsx
|
||||||
|
1. **导入组件**:添加 DatePicker、Popup、Calendar 组件
|
||||||
|
2. **状态管理**:添加日期选择器相关状态
|
||||||
|
3. **处理函数**:实现日期格式化和选择处理逻辑
|
||||||
|
4. **界面更新**:替换输入框为可点击的日期选择区域
|
||||||
|
5. **弹窗组件**:添加日期选择器弹窗
|
||||||
|
6. **数据初始化**:在 reload 函数中初始化日期数据
|
||||||
|
7. **提交处理**:确保日期数据正确提交
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
### 核心技术
|
||||||
|
- **React + TypeScript**:类型安全的组件开发
|
||||||
|
- **Taro 框架**:跨平台小程序开发
|
||||||
|
- **NutUI 组件库**:提供 DatePicker 和 Popup 组件
|
||||||
|
|
||||||
|
### 关键组件
|
||||||
|
- **DatePicker**:日期选择核心组件
|
||||||
|
- **Popup**:弹窗容器组件
|
||||||
|
- **Calendar**:日历图标组件
|
||||||
|
- **Form.Item**:表单项容器
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
### 用户操作流程
|
||||||
|
1. 进入客户签约页面
|
||||||
|
2. 点击"签约时间"字段
|
||||||
|
3. 在弹出的日期选择器中选择日期
|
||||||
|
4. 点击确认完成选择
|
||||||
|
5. 重复步骤选择"合同日期"
|
||||||
|
6. 填写其他信息后提交表单
|
||||||
|
|
||||||
|
### 开发者扩展
|
||||||
|
- 可以轻松添加更多日期字段
|
||||||
|
- 支持自定义日期格式
|
||||||
|
- 可以添加日期范围限制
|
||||||
|
- 支持国际化配置
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
### 兼容性
|
||||||
|
- 确保 NutUI 版本支持 DatePicker 组件
|
||||||
|
- 测试不同设备上的显示效果
|
||||||
|
- 验证日期格式在不同环境下的一致性
|
||||||
|
|
||||||
|
### 性能优化
|
||||||
|
- 合理使用 useState 避免不必要的重渲染
|
||||||
|
- 日期格式化函数可以考虑缓存
|
||||||
|
- 弹窗组件按需渲染
|
||||||
|
|
||||||
|
### 用户体验
|
||||||
|
- 提供清晰的操作提示
|
||||||
|
- 确保在编辑模式下的正确禁用
|
||||||
|
- 考虑添加日期范围验证
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
成功实现了签约时间和合同日期的选择功能,提供了:
|
||||||
|
|
||||||
|
1. **直观的操作界面**:图标提示 + 点击选择
|
||||||
|
2. **友好的选择体验**:底部弹窗 + 滚动选择
|
||||||
|
3. **完整的数据处理**:格式化 + 同步 + 提交
|
||||||
|
4. **合理的状态控制**:编辑模式禁用 + 视觉反馈
|
||||||
|
|
||||||
|
这个实现为用户提供了便捷的日期选择体验,同时保证了数据的准确性和一致性。
|
||||||
Reference in New Issue
Block a user