From bb6ec8c18376de3d7fc53f7eeb8927c9eedf90b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AD=B1=E9=87=8E?= Date: Sun, 24 Aug 2025 16:04:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=8E=B7=E5=8F=96=E5=9C=BA?= =?UTF-8?q?=E9=A6=86=E3=80=81=E5=AD=97=E5=85=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/index.ts | 16 +- src/app.ts | 16 +- .../DateTimePicker/DateTimePicker.tsx | 115 ++++++++++ src/components/DateTimePicker/README.md | 67 ++++++ src/components/DateTimePicker/example.tsx | 45 ++++ .../DateTimePicker/index.module.scss | 102 +++++++++ src/components/DateTimePicker/index.ts | 2 + .../NumberInterval/NumberInterval.tsx | 30 +-- src/components/TextareaTag/TextareaTag.tsx | 38 ++-- src/components/TimeSelector/TimeSelector.tsx | 22 +- src/components/index.ts | 4 +- .../formSchema/publishBallFormSchema.ts | 169 ++++++++------- .../FormBasicInfo/FormBasicInfo.tsx | 85 ++++++-- .../PopupGameplay/PopupGameplay.tsx | 24 +-- .../SelectStadium/SelectStadium.scss | 24 ++- .../SelectStadium/SelectStadium.tsx | 203 ++++++++++-------- .../SelectStadium/StadiumDetail.scss | 1 - .../SelectStadium/StadiumDetail.tsx | 129 ++++++----- .../components/WechatSwitch/WechatSwitch.tsx | 41 ++++ .../components/WechatSwitch/index.module.scss | 91 ++++++++ .../components/WechatSwitch/index.ts | 1 + src/pages/publishBall/index.module.scss | 3 - src/pages/publishBall/index.tsx | 110 +++++++--- src/pages/publishBall/publishForm.tsx | 114 +++++----- src/services/commonApi.ts | 7 + src/services/publishService.ts | 58 +++-- src/store/dictionaryStore.ts | 79 +++++++ src/utils/locationUtils.ts | 25 +++ types/publishBall.ts | 10 +- 29 files changed, 1217 insertions(+), 414 deletions(-) create mode 100644 src/components/DateTimePicker/DateTimePicker.tsx create mode 100644 src/components/DateTimePicker/README.md create mode 100644 src/components/DateTimePicker/example.tsx create mode 100644 src/components/DateTimePicker/index.module.scss create mode 100644 src/components/DateTimePicker/index.ts create mode 100644 src/pages/publishBall/components/WechatSwitch/WechatSwitch.tsx create mode 100644 src/pages/publishBall/components/WechatSwitch/index.module.scss create mode 100644 src/pages/publishBall/components/WechatSwitch/index.ts create mode 100644 src/store/dictionaryStore.ts diff --git a/config/index.ts b/config/index.ts index 7c4734e..c43eeef 100644 --- a/config/index.ts +++ b/config/index.ts @@ -10,12 +10,13 @@ export default defineConfig<'webpack5'>(async (merge, { command, mode }) => { const baseConfig: UserConfigExport<'webpack5'> = { projectName: 'playBallTogether', date: '2025-8-9', - designWidth: 375, + designWidth: 390, // 改为 390 deviceRatio: { - 640: 2.34 / 2, - 750: 1, - 375: 2, - 828: 1.81 / 2 + 640: 2.34 / 2 * (390 / 640), // 原值重新计算 + 750: 1 * (390 / 750), // 原值重新计算 + 375: 2 * (390 / 375), // 原值重新计算 + 828: 1.81 / 2 * (390 / 828), // 原值重新计算 + 390: 2 // 新增基准设备 }, sourceRoot: 'src', outputRoot: 'dist', @@ -54,6 +55,11 @@ export default defineConfig<'webpack5'>(async (merge, { command, mode }) => { pxtransform: { enable: true, config: { + platform: 'weapp', + designWidth: 390, // 这里也要同步修改 + deviceRatio: { + 390: 2 // 这里只需要基准比例 + }, selectorBlackList: ['nut-'] } }, diff --git a/src/app.ts b/src/app.ts index b9f6294..6ef979c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,18 +1,32 @@ import { Component, ReactNode } from 'react' import './app.scss' import './nutui-theme.scss' +import { useDictionaryStore } from './store/dictionaryStore' interface AppProps { children: ReactNode } class App extends Component { - componentDidMount() {} + componentDidMount() { + // 初始化字典数据 + this.initDictionaryData() + } componentDidShow() {} componentDidHide() {} + // 初始化字典数据 + private async initDictionaryData() { + try { + const { fetchDictionary } = useDictionaryStore.getState() + await fetchDictionary() + } catch (error) { + console.error('初始化字典数据失败:', error) + } + } + render() { // this.props.children 是将要会渲染的页面 return this.props.children diff --git a/src/components/DateTimePicker/DateTimePicker.tsx b/src/components/DateTimePicker/DateTimePicker.tsx new file mode 100644 index 0000000..9975fff --- /dev/null +++ b/src/components/DateTimePicker/DateTimePicker.tsx @@ -0,0 +1,115 @@ +import React, { useState, useEffect } from 'react' +import { View, Text } from '@tarojs/components' +import { Picker, Popup } from '@nutui/nutui-react-taro' +import styles from './index.module.scss' + +export interface DateTimePickerProps { + visible: boolean + onClose: () => void + onConfirm: (year: number, month: number) => void + defaultYear?: number + defaultMonth?: number + minYear?: number + maxYear?: number +} + +const DateTimePicker: React.FC = ({ + visible, + onClose, + onConfirm, + defaultYear = new Date().getFullYear(), + defaultMonth = new Date().getMonth() + 1, + minYear = 2020, + maxYear = 2030 +}) => { + const [selectedYear, setSelectedYear] = useState(defaultYear) + const [selectedMonth, setSelectedMonth] = useState(defaultMonth) + + // 生成年份选项 + const yearOptions = Array.from({ length: maxYear - minYear + 1 }, (_, index) => ({ + text: `${minYear + index}年`, + value: minYear + index + })) + + // 生成月份选项 + const monthOptions = Array.from({ length: 12 }, (_, index) => ({ + text: `${index + 1}月`, + value: index + 1 + })) + + useEffect(() => { + if (visible) { + setSelectedYear(defaultYear) + setSelectedMonth(defaultMonth) + } + }, [visible, defaultYear, defaultMonth]) + + const handleYearChange = (value: any) => { + setSelectedYear(value[0]) + } + + const handleMonthChange = (value: any) => { + setSelectedMonth(value[0]) + } + + const handleConfirm = () => { + onConfirm(selectedYear, selectedMonth) + onClose() + } + + const handleCancel = () => { + onClose() + } + + return ( + + {/* 拖拽手柄 */} + + + {/* 时间选择器 */} + + + {/* 年份选择 */} + + + + + + {/* 月份选择 */} + + + + + + + + {/* 操作按钮 */} + + + 取消 + + + 完成 + + + + ) +} + +export default DateTimePicker diff --git a/src/components/DateTimePicker/README.md b/src/components/DateTimePicker/README.md new file mode 100644 index 0000000..61754dc --- /dev/null +++ b/src/components/DateTimePicker/README.md @@ -0,0 +1,67 @@ +# DateTimePicker 年月选择器 + +一个基于 NutUI 的年月切换弹窗组件,支持自定义年份范围和默认值。 + +## 功能特性 + +- 🎯 年月分别选择,操作简单直观 +- 🎨 遵循设计稿样式,美观易用 +- 📱 支持移动端手势操作 +- ⚙️ 可自定义年份范围 +- �� 基于 NutUI 组件库,稳定可靠 + +## 使用方法 + +```tsx +import { DateTimePicker } from '@/components' + +const MyComponent = () => { + const [visible, setVisible] = useState(false) + + const handleConfirm = (year: number, month: number) => { + console.log('选择的年月:', year, month) + setVisible(false) + } + + return ( + setVisible(false)} + onConfirm={handleConfirm} + defaultYear={2025} + defaultMonth={11} + minYear={2020} + maxYear={2030} + /> + ) +} +``` + +## API 参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| visible | boolean | - | 是否显示弹窗 | +| onClose | () => void | - | 关闭弹窗的回调 | +| onConfirm | (year: number, month: number) => void | - | 确认选择的回调 | +| defaultYear | number | 当前年份 | 默认选中的年份 | +| defaultMonth | number | 当前月份 | 默认选中的月份 | +| minYear | number | 2020 | 可选择的最小年份 | +| maxYear | number | 2030 | 可选择的最大年份 | + +## 样式定制 + +组件使用 CSS Modules,可以通过修改 `index.module.scss` 文件来自定义样式。 + +主要样式类: +- `.date-time-picker-popup` - 弹窗容器 +- `.picker-columns` - 选择器列容器 +- `.picker-column` - 单列选择器 +- `.action-buttons` - 操作按钮区域 + +## 注意事项 + +1. 组件基于 NutUI 的 Picker 和 Popup 组件 +2. 年份范围建议不要设置过大,以免影响性能 +3. 月份固定为 1-12 月 +4. 组件会自动处理边界情况 diff --git a/src/components/DateTimePicker/example.tsx b/src/components/DateTimePicker/example.tsx new file mode 100644 index 0000000..b9c44d8 --- /dev/null +++ b/src/components/DateTimePicker/example.tsx @@ -0,0 +1,45 @@ +import React, { useState } from 'react' +import { View, Button } from '@tarojs/components' +import DateTimePicker from './DateTimePicker' + +const DateTimePickerExample: React.FC = () => { + const [visible, setVisible] = useState(false) + const [selectedDate, setSelectedDate] = useState('') + + const handleOpen = () => { + setVisible(true) + } + + const handleClose = () => { + setVisible(false) + } + + const handleConfirm = (year: number, month: number) => { + setSelectedDate(`${year}年${month}月`) + console.log('选择的日期:', year, month) + } + + return ( + + + + {selectedDate && ( + + 已选择: {selectedDate} + + )} + + + + ) +} + +export default DateTimePickerExample diff --git a/src/components/DateTimePicker/index.module.scss b/src/components/DateTimePicker/index.module.scss new file mode 100644 index 0000000..ea892ee --- /dev/null +++ b/src/components/DateTimePicker/index.module.scss @@ -0,0 +1,102 @@ +.date-time-picker-popup { + :global(.nut-popup) { + border-radius: 16px 16px 0 0; + background: #fff; + } +} + +.popup-handle { + width: 40px; + height: 4px; + background: #e5e5e5; + border-radius: 2px; + margin: 12px auto 0; +} + +.picker-container { + padding: 20px 0; +} + +.picker-columns { + display: flex; + justify-content: center; + align-items: center; + gap: 60px; +} + +.picker-column { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; +} + +.picker-label { + font-size: 14px; + color: #999; + font-weight: 400; +} + +.year-picker, +.month-picker { + :global(.nut-picker) { + width: 80px; + } + + :global(.nut-picker__content) { + height: 200px; + } + + :global(.nut-picker-item) { + height: 40px; + line-height: 40px; + font-size: 16px; + color: #333; + } + + :global(.nut-picker-item--selected) { + color: #000; + font-weight: 500; + } + + :global(.nut-picker-item--disabled) { + color: #ccc; + } +} + +.action-buttons { + display: flex; + padding: 0 20px 20px; + gap: 12px; +} + +.cancel-btn, +.confirm-btn { + flex: 1; + height: 44px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +.cancel-btn { + background: #fff; + border: 1px solid #e5e5e5; +} + +.cancel-text { + color: #666; + font-size: 16px; +} + +.confirm-btn { + background: #000; + border: 1px solid #000; +} + +.confirm-text { + color: #fff; + font-size: 16px; +} diff --git a/src/components/DateTimePicker/index.ts b/src/components/DateTimePicker/index.ts new file mode 100644 index 0000000..4817df4 --- /dev/null +++ b/src/components/DateTimePicker/index.ts @@ -0,0 +1,2 @@ +import DateTimePicker from './DateTimePicker' +export default DateTimePicker diff --git a/src/components/NumberInterval/NumberInterval.tsx b/src/components/NumberInterval/NumberInterval.tsx index 2a8f829..0a4ef3b 100644 --- a/src/components/NumberInterval/NumberInterval.tsx +++ b/src/components/NumberInterval/NumberInterval.tsx @@ -4,18 +4,18 @@ import './NumberInterval.scss' import { InputNumber } from '@nutui/nutui-react-taro' interface NumberIntervalProps { - minParticipants: number - maxParticipants: number - onMinParticipantsChange: (value: number) => void - onMaxParticipantsChange: (value: number) => void + value: [number, number] + onChange: (value: [number, number]) => void } const NumberInterval: React.FC = ({ - minParticipants, - maxParticipants, - onMinParticipantsChange, - onMaxParticipantsChange + value, + onChange }) => { + const [minParticipants, maxParticipants] = value || [1, 4] + const handleChange = (value: [number | string, number | string]) => { + onChange([Number(value[0]), Number(value[1])]) + } return ( @@ -23,9 +23,10 @@ const NumberInterval: React.FC = ({ handleChange([value, maxParticipants])} formatter={(value) => `${value}人`} /> @@ -35,9 +36,10 @@ const NumberInterval: React.FC = ({ handleChange([value, maxParticipants])} + min={minParticipants} + max={maxParticipants} formatter={(value) => `${value}人`} /> diff --git a/src/components/TextareaTag/TextareaTag.tsx b/src/components/TextareaTag/TextareaTag.tsx index 83152c7..4aa5e91 100644 --- a/src/components/TextareaTag/TextareaTag.tsx +++ b/src/components/TextareaTag/TextareaTag.tsx @@ -6,8 +6,8 @@ import { Checkbox } from '@nutui/nutui-react-taro' import './TextareaTag.scss' interface TextareaTagProps { - value: string - onChange: (value: string) => void + value: { description: string, description_tag: string[] } + onChange: (value: { description: string, description_tag: string[] }) => void title?: string showTitle?: boolean placeholder?: string @@ -22,27 +22,15 @@ const TextareaTag: React.FC = ({ maxLength = 500, options = [] }) => { - // 处理输入框变化 - const [tags, setTags] = useState([]) - console.log(value, 'options') - const handleInputChange = useCallback((e: any) => { - onChange(e.detail.value) + // 处理文本输入变化 + const handleTextChange = useCallback((e: any) => { + onChange({...value, description: e.detail.value}) }, [onChange]) - // 选择预设选项 - const handleSelectOption = useCallback((option: string) => { - let newValue = '' - - if (value) { - // 如果已有内容,用分号分隔添加 - newValue = value + ';' + option - } else { - // 如果没有内容,直接添加 - newValue = option - } - - onChange(newValue) - }, [value, onChange]) + // 处理标签选择变化 + const handleTagChange = useCallback((selectedTags: string[]) => { + onChange({...value, description_tag: selectedTags}) + }, [onChange]) console.log(options, 'options') return ( @@ -54,8 +42,8 @@ const TextareaTag: React.FC = ({ setTags(value)} + value={value.description_tag} + onChange={handleTagChange} > { options?.map((option, index) => ( @@ -76,9 +64,9 @@ const TextareaTag: React.FC = ({