diff --git a/.gitignore b/.gitignore
index b7dd2d1..fdc3298 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ deploy_versions/
node_modules/
.DS_Store
.swc
+src/config/env.ts
diff --git a/src/app.config.ts b/src/app.config.ts
index b58ead8..8f41f6d 100644
--- a/src/app.config.ts
+++ b/src/app.config.ts
@@ -1,6 +1,8 @@
export default defineAppConfig({
pages: [
+ 'pages/home/index', //中转页
+
'pages/login/index/index',
'pages/login/verification/index',
'pages/login/terms/index',
@@ -12,15 +14,25 @@ export default defineAppConfig({
'pages/detail/index',
'pages/message/index',
'pages/orderCheck/index',
-
- 'pages/userInfo/myself/index', // 个人中心
- 'pages/userInfo/edit/index', // 个人中心
- 'pages/userInfo/favorites/index', // 个人中心
- 'pages/userInfo/orders/index', // 个人中心
+
+
// 'pages/mapDisplay/index',
],
-
+ subPackages: [
+ {
+ root: "mod_user",
+ pages: [
+ 'pages/myself/index', // 个人中心
+ 'pages/edit/index', // 个人中心
+ 'pages/favorites/index', // 个人中心
+ 'pages/orders/index', // 个人中心
+
+ ]
+
+ }
+ ],
+
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
diff --git a/src/components/Auth/index.tsx b/src/components/Auth/index.tsx
index 137e358..2ec4f42 100644
--- a/src/components/Auth/index.tsx
+++ b/src/components/Auth/index.tsx
@@ -32,17 +32,17 @@ export default function withAuth
(WrappedComponent: React.Compo
if (!is_login) {
const currentPage = getCurrentFullPath()
- Taro.redirectTo({
- url: `/pages/login/index/index${
- currentPage ? `?redirect=${encodeURIComponent(currentPage)}` : ''
- }`,
- })
+ // Taro.redirectTo({
+ // url: `/pages/login/index/index${
+ // currentPage ? `?redirect=${encodeURIComponent(currentPage)}` : ''
+ // }`,
+ // })
}
}, [])
- if (!authed) {
- return // 空壳,避免 children 渲染出错
- }
+ // if (!authed) {
+ // return // 空壳,避免 children 渲染出错
+ // }
return
}
diff --git a/src/components/CalendarCard/CalendarCard.tsx b/src/components/CalendarCard/CalendarCard.tsx
deleted file mode 100644
index eb1e23c..0000000
--- a/src/components/CalendarCard/CalendarCard.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import React, { useMemo, useState } from 'react'
-import { View, Text, Image } from '@tarojs/components'
-import styles from './index.module.scss'
-import images from '@/config/images'
-interface CalendarCardProps {
- value?: Date
- minDate?: Date
- maxDate?: Date
- onChange?: (date: Date) => void
- onNext?: (date: Date) => void
- onHeaderClick?: (date: Date) => void
-}
-
-const startOfMonth = (date: Date) => new Date(date.getFullYear(), date.getMonth(), 1)
-const endOfMonth = (date: Date) => new Date(date.getFullYear(), date.getMonth() + 1, 0)
-const addMonths = (date: Date, delta: number) => new Date(date.getFullYear(), date.getMonth() + delta, 1)
-
-const formatHeader = (date: Date) => `${date.getMonth() + 1}月 ${date.getFullYear()}`
-
-const CalendarCard: React.FC = ({
- value,
- minDate,
- maxDate,
- onChange,
- onHeaderClick
-}) => {
- const today = new Date()
- const [current, setCurrent] = useState(value || startOfMonth(today))
- const [selected, setSelected] = useState(value || today)
-
-
- const firstDay = useMemo(() => startOfMonth(current), [current])
- const lastDay = useMemo(() => endOfMonth(current), [current])
-
- const days = useMemo(() => {
- const startWeekday = firstDay.getDay() // 0 周日
- const prevPadding = startWeekday // 周日为第一列
- const total = prevPadding + lastDay.getDate()
- const rows = Math.ceil(total / 7)
- const grid: (Date | null)[] = []
- for (let i = 0; i < rows * 7; i++) {
- const day = i - prevPadding + 1
- if (day < 1 || day > lastDay.getDate()) {
- grid.push(null)
- } else {
- grid.push(new Date(current.getFullYear(), current.getMonth(), day))
- }
- }
- return grid
- }, [firstDay, lastDay, current])
-
- const isDisabled = (d: Date) => {
- if (minDate && d < minDate) return true
- if (maxDate && d > maxDate) return true
- return false
- }
-
- const gotoMonth = (delta: number) => setCurrent(prev => addMonths(prev, delta))
-
- const handleHeaderClick = () => {
- onHeaderClick && onHeaderClick(current)
- }
-
- const handleSelectDay = (d: Date | null) => {
- if (!d || isDisabled(d)) return
- setSelected(d)
- onChange && onChange(d)
- }
-
-
-
-
-
-
-
- return (
-
-
-
- {formatHeader(current)}
- gotoMonth(1)} />
-
-
- gotoMonth(-1)} />
- gotoMonth(1)} />
-
-
-
-
- {['周日','周一','周二','周三','周四','周五','周六'].map((w) => (
- {w}
- ))}
-
-
-
- {days.map((d, idx) => {
- const isSelected = !!(d && selected && d.toDateString() === new Date(selected.getFullYear(), selected.getMonth(), selected.getDate()).toDateString())
- return (
- handleSelectDay(d)}
- >
- {d ? {d.getDate()} : null}
-
- )
- })}
-
-
-
-
-
-
- )
-}
-
-export default CalendarCard
diff --git a/src/components/CalendarCard/DialogCalendarCard.tsx b/src/components/CalendarCard/DialogCalendarCard.tsx
deleted file mode 100644
index 92bfec4..0000000
--- a/src/components/CalendarCard/DialogCalendarCard.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import React, { useState, useEffect } from 'react'
-import CommonPopup from '@/components/CommonPopup'
-import CalendarCard from './CalendarCard'
-import DateTimePicker from '@/components/DateTimePicker'
-import HourMinutePicker from '@/components/HourMinutePicker'
-export interface DialogCalendarCardProps {
- value?: Date
- minDate?: Date
- maxDate?: Date
- onChange?: (date: Date) => void
- onNext?: (date: Date) => void
- visible: boolean
- onClose: () => void
- title?: React.ReactNode
-}
-
-const DialogCalendarCard: React.FC = ({
- visible,
- onClose,
- title,
- value,
- minDate,
- maxDate,
- onChange,
- onNext
-}) => {
- const [selected, setSelected] = useState(value || new Date())
- const [type, setType] = useState<'year' | 'month' | 'time'>('year');
- const [selectedHour, setSelectedHour] = useState(8)
- const [selectedMinute, setSelectedMinute] = useState(0)
-
- const handleConfirm = () => {
- if (type === 'year') {
- // 年份选择完成后,进入月份选择
- setType('time')
- } else if (type === 'month') {
- // 月份选择完成后,进入时间选择
- setType('year')
- } else if (type === 'time') {
- // 时间选择完成后,调用onNext回调
- const finalDate = new Date(selected.getFullYear(), selected.getMonth(), selected.getDate(), selectedHour, selectedMinute)
- console.log('finalDate', finalDate)
- if (onChange) onChange(finalDate)
- onClose()
- }
- }
-
- const handleChange = (d: Date) => {
- console.log('handleChange', d)
- setSelected(d)
- // if (onChange) onChange(d)
- }
- const onHeaderClick = (date: Date) => {
- console.log('onHeaderClick', date)
- setSelected(date)
- setType('month')
- }
- const getConfirmText = () => {
- if (type === 'time' || type === 'month') return '完成'
- return '下一步'
- }
- const handleDateTimePickerChange = (year: number, month: number) => {
- console.log('year', year)
- console.log('month', month)
- setSelected(new Date(year, month - 1, 1))
- }
- const dialogClose = () => {
- if (type === 'month') {
- setType('year')
- } else if (type === 'time') {
- setType('year')
- } else {
- onClose()
- }
- }
- useEffect(() => {
- setSelected(value || new Date())
- if (visible) {
- setType('year')
- setSelectedHour(8)
- setSelectedMinute(0)
- }
- }, [value, visible])
-
-
- return (
-
- {
- type === 'year' &&
- }
- {
- type === 'month' &&
- }
- {
- type === 'time' && {
- setSelectedHour(hour)
- setSelectedMinute(minute)
- }}
- defaultHour={selectedHour}
- defaultMinute={selectedMinute}
- />
- }
-
- )
-}
-
-export default DialogCalendarCard
diff --git a/src/components/CalendarCard/index.ts b/src/components/CalendarCard/index.ts
deleted file mode 100644
index 15ceb7e..0000000
--- a/src/components/CalendarCard/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from './CalendarCard'
-export { default as DialogCalendarCard } from './DialogCalendarCard'
diff --git a/src/components/CommonPopup/index.module.scss b/src/components/CommonPopup/index.module.scss
index 1fb1e10..7210f10 100644
--- a/src/components/CommonPopup/index.module.scss
+++ b/src/components/CommonPopup/index.module.scss
@@ -75,7 +75,7 @@
height: 44px;
border: 0.5px solid rgba(0, 0, 0, 0.06);
background: #000;
- border-radius: 12px;
+ border-radius: 12px!important;
padding: 4px 10px;
}
}
\ No newline at end of file
diff --git a/src/components/DateTimePicker/DateTimePicker.tsx b/src/components/DateTimePicker/DateTimePicker.tsx
deleted file mode 100644
index c5714e9..0000000
--- a/src/components/DateTimePicker/DateTimePicker.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import React, { useState, useEffect } from 'react'
-import { View, Text, PickerView, PickerViewColumn } from '@tarojs/components'
-import styles from './index.module.scss'
-
-
-
-export interface DateTimePickerProps {
- onChange: (year: number, month: number) => void
- defaultYear?: number
- defaultMonth?: number
- minYear?: number
- maxYear?: number
-}
-
-const DateTimePicker: React.FC = ({
-
- onChange,
- defaultYear = new Date().getFullYear(),
- defaultMonth = new Date().getMonth() + 1,
- minYear = 2020,
- maxYear = 2030
-}) => {
- console.log('defaultYear', defaultYear)
- console.log('defaultMonth', defaultMonth)
- const [selectedYear, setSelectedYear] = useState(defaultYear)
- const [selectedMonth, setSelectedMonth] = useState(defaultMonth)
-
- // 计算当前选项在数组中的索引
- const getYearIndex = (year: number) => year - minYear
- const getMonthIndex = (month: number) => month - 1
-
- // 生成多列选择器的选项数据
- const pickerOptions = [
- // 年份列
- Array.from({ length: maxYear - minYear + 1 }, (_, index) => ({
- text: `${minYear + index}年`,
- value: minYear + index
- })),
- // 月份列
- Array.from({ length: 12 }, (_, index) => ({
- text: `${index + 1}月`,
- value: index + 1
- }))
- ]
-
-
-
- useEffect(() => {
- setSelectedYear(defaultYear)
- setSelectedMonth(defaultMonth)
- }, [ defaultYear, defaultMonth])
-
- const handlePickerChange = (event: any) => {
- const values = event.detail.value
- if (values && values.length >= 2) {
- // 根据索引获取实际值
- const yearIndex = values[0]
- const monthIndex = values[1]
- const year = minYear + yearIndex
- const month = monthIndex + 1
- setSelectedYear(year)
- setSelectedMonth(month)
- onChange(year, month)
- }
- }
-
- return (
-
- e.stopPropagation()}>
- {/* 拖拽手柄 */}
-
-
- {/* 时间选择器 */}
-
- {/* 多列选择器 */}
-
-
-
- {pickerOptions[0].map((option, index) => (
-
- {option.text}
-
- ))}
-
-
- {pickerOptions[1].map((option, index) => (
-
- {option.text}
-
- ))}
-
-
-
-
-
-
- )
-}
-
-export default DateTimePicker
diff --git a/src/components/DateTimePicker/index.module.scss b/src/components/DateTimePicker/index.module.scss
deleted file mode 100644
index 67c6587..0000000
--- a/src/components/DateTimePicker/index.module.scss
+++ /dev/null
@@ -1,89 +0,0 @@
-/* 日期选择器弹出层样式 */
-.date-time-picker-popup {
- .common-popup-content {
- padding: 0;
- }
-}
-
-.popup-handle {
- width: 32px;
- height: 4px;
- background: #e0e0e0;
- border-radius: 2px;
- margin: 12px auto;
-}
-
-.picker-container {
- padding: 26px 16px 0 16px;
- background: #fff;
-}
-
-.picker-header {
- text-align: center;
- margin-bottom: 20px;
-}
-
-.picker-title {
- font-size: 18px;
- font-weight: 500;
- color: #333;
-}
-
-.picker-wrapper {
- position: relative;
-}
-
-.multi-column-picker {
- width: 100%;
- height: 216px;
- background: #fff;
- border-radius: 8px;
- overflow: hidden;
- box-shadow: none;
- /* 自定义选择器样式 */
- ::-webkit-scrollbar {
- width: 0;
- background: transparent;
- }
-
- /* 选中项指示器 */
- &::before {
- content: '';
- position: absolute;
- top: 50%;
- left: 0;
- right: 0;
- height: 48px;
- background: rgba(22, 24, 35, 0.05);
- transform: translateY(-50%);
- pointer-events: none;
- z-index: 1;
- border-radius: 4px;
- }
-}
-
-.picker-item {
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 20px;
- color: #161823;
- transition: all 0.3s ease;
- &.picker-item-active {
- color: rgba(22, 24, 35, 0.05);
- font-weight: 600;
- transform: scale(1.05);
- }
-}
-
-.picker-column {
- border: none;
- outline: none;
-}
-
-.picker-item-text {
- font-size: 16px;
- color: inherit;
- text-align: center;
-}
-
diff --git a/src/components/DateTimePicker/index.ts b/src/components/DateTimePicker/index.ts
deleted file mode 100644
index 4817df4..0000000
--- a/src/components/DateTimePicker/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-import DateTimePicker from './DateTimePicker'
-export default DateTimePicker
diff --git a/src/components/EditModal/EditModal.scss b/src/components/EditModal/EditModal.scss
index aae757b..a5f11fd 100644
--- a/src/components/EditModal/EditModal.scss
+++ b/src/components/EditModal/EditModal.scss
@@ -82,10 +82,11 @@
// 内容区域
.modal_content {
- padding: 0px 16px 20px;
+ padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 20px;
+ box-sizing: border-box;
.input_container {
display: flex;
@@ -96,7 +97,13 @@
border-radius: 12px;
padding: 10px 16px;
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
- min-height: 120px;
+
+
+ // 名字输入时的容器样式
+ &:has(.nickname_input) {
+ min-height: 40px;
+ padding: 10px 16px;
+ }
.text_input {
flex: 1;
@@ -109,11 +116,21 @@
background: transparent;
outline: none;
resize: none;
- min-height: 80px;
+
+ min-height: 120px;
&::placeholder {
color: rgba(60, 60, 67, 0.3);
}
+
+ // 名字输入特殊样式
+ &.nickname_input {
+ min-height: 80px;
+ min-height: 20px;
+ height: 20px;
+ line-height: 20px;
+ padding: 0;
+ }
}
.char_count {
diff --git a/src/components/EditModal/index.tsx b/src/components/EditModal/index.tsx
index 0f97ce9..3fb59b6 100644
--- a/src/components/EditModal/index.tsx
+++ b/src/components/EditModal/index.tsx
@@ -1,11 +1,12 @@
import React, { useState, useEffect } from 'react';
-import { View, Text, Textarea, Button } from '@tarojs/components';
+import { View, Text, Textarea, Input, Picker } from '@tarojs/components';
import Taro from '@tarojs/taro';
import './EditModal.scss';
interface EditModalProps {
visible: boolean;
title: string;
+ type: string;
placeholder: string;
initialValue: string;
maxLength: number;
@@ -17,6 +18,7 @@ interface EditModalProps {
const EditModal: React.FC = ({
visible,
title,
+ type,
placeholder,
initialValue,
maxLength,
@@ -82,17 +84,34 @@ const EditModal: React.FC = ({
{/* 文本输入区域 */}
-
-
- {value.length}/{maxLength}
-
+ ) : (
+ <>
+
+
+ {value.length}/{maxLength}
+
+ >
+ )}
{/* 验证提示 */}
diff --git a/src/components/FilterPopup/index.tsx b/src/components/FilterPopup/index.tsx
index a83a5bb..1aff3d9 100644
--- a/src/components/FilterPopup/index.tsx
+++ b/src/components/FilterPopup/index.tsx
@@ -1,3 +1,4 @@
+import { useMemo, useState } from "react";
import { Popup } from "@nutui/nutui-react-taro";
import Range from "../../components/Range";
import Bubble from "../../components/Bubble";
@@ -5,15 +6,14 @@ import styles from "./index.module.scss";
import { Button } from "@nutui/nutui-react-taro";
import { useListStore } from "src/store/listStore";
import { BubbleOption, FilterPopupProps } from "../../../types/list/types";
-import CalendarCard from "@/components/CalendarCard/index";
import dateRangeUtils from '@/utils/dateRange'
-
+import dayjs from "dayjs";
+import { CalendarUI } from "@/components";
// 场地
import CourtType from "@/components/CourtType";
// 玩法
import GamePlayType from "@/components/GamePlayType";
import { useDictionaryActions } from "@/store/dictionaryStore";
-import { useMemo } from "react";
import { View } from "@tarojs/components";
@@ -44,6 +44,14 @@ const FilterPopup = (props: FilterPopupProps) => {
* @param dictionaryValue 字典选项
* @returns 选项列表
*/
+ const [selectedDates, setSelectedDates] = useState([])
+
+ const handleDateChange = (dates: Date[]) => {
+ const dateArray = dates.map(date => dayjs(date).format('YYYY-MM-DD'))
+ setSelectedDates(dateArray)
+ console.log('选中的日期:', dateArray)
+ }
+
const handleOptions = (dictionaryValue: []) => {
return dictionaryValue?.map((item) => ({ label: item, value: item })) || [];
};
@@ -131,13 +139,15 @@ const FilterPopup = (props: FilterPopupProps) => {
name="dateRangeQuick"
/>
-
+
{/* 时间气泡选项 */}
+
{
let url = `/pages/${code}/index`
if (code === 'personal') {
- url = '/pages/userInfo/myself/index'
+ url = '/mod_user/pages/myself/index'
}
Taro.redirectTo({
url: url,
diff --git a/src/components/Picker/CalendarDialog/DialogCalendarCard.tsx b/src/components/Picker/CalendarDialog/DialogCalendarCard.tsx
new file mode 100644
index 0000000..d9695b5
--- /dev/null
+++ b/src/components/Picker/CalendarDialog/DialogCalendarCard.tsx
@@ -0,0 +1,151 @@
+import React, { useState, useEffect, useRef } from 'react'
+import CommonPopup from '@/components/CommonPopup'
+import { View } from '@tarojs/components'
+import CalendarUI, { CalendarUIRef } from '@/components/Picker/CalendarUI/CalendarUI'
+import { PickerCommon, PickerCommonRef } from '@/components/Picker'
+import dayjs from 'dayjs'
+import styles from './index.module.scss'
+export interface DialogCalendarCardProps {
+ value?: Date
+ onChange?: (date: Date) => void
+ visible: boolean
+ onClose: () => void
+ title?: React.ReactNode
+}
+
+const DialogCalendarCard: React.FC = ({
+ visible,
+ onClose,
+ title,
+ value,
+ onChange,
+}) => {
+ const [selected, setSelected] = useState(value || new Date())
+ const calendarRef = useRef(null);
+ const [type, setType] = useState<'year' | 'month' | 'time'>('year');
+ const [selectedHour, setSelectedHour] = useState(8)
+ const [selectedMinute, setSelectedMinute] = useState(0)
+ const pickerRef = useRef(null);
+ const hourMinutePickerRef = useRef(null);
+ const [pendingJump, setPendingJump] = useState<{ year: number; month: number } | null>(null)
+ const handleConfirm = () => {
+ if (type === 'year') {
+ // 年份选择完成后,进入月份选择
+ setType('time')
+ } else if (type === 'month') {
+ // 月份选择完成后,进入时间选择
+ const value = pickerRef.current?.getValue()
+ if (value) {
+ const year = value[0] as number
+ const month = value[1] as number
+ setSelected(new Date(year, month - 1, 1))
+ setPendingJump({ year, month })
+ }
+ setType('year')
+ } else if (type === 'time') {
+ // 时间选择完成后,调用onNext回调
+ const value = hourMinutePickerRef.current?.getValue()
+ if (value) {
+ const hour = value[0] as number
+ const minute = value[1] as number
+ setSelectedHour(hour)
+ setSelectedMinute(minute)
+ const finalDate = new Date(dayjs(selected).format('YYYY-MM-DD') + ' ' + hour + ':' + minute)
+ if (onChange) onChange(finalDate)
+ }
+ onClose()
+ }
+ }
+
+ const handleChange = (d: Date | Date[]) => {
+ if (Array.isArray(d)) {
+ setSelected(d[0])
+ } else {
+ setSelected(d)
+ }
+ }
+ const onHeaderClick = (date: Date) => {
+ setSelected(date)
+ setType('month')
+ }
+ const getConfirmText = () => {
+ if (type === 'time' || type === 'month') return '完成'
+ return '下一步'
+ }
+ const handleDateTimePickerChange = (value: (string | number)[]) => {
+ const year = value[0] as number
+ const month = value[1] as number
+ setSelected(new Date(year, month - 1, 1))
+ }
+ const dialogClose = () => {
+ if (type === 'month') {
+ setType('year')
+ } else if (type === 'time') {
+ setType('year')
+ } else {
+ onClose()
+ }
+ }
+ useEffect(() => {
+ if (visible && value) {
+ setSelected(value || new Date())
+ setSelectedHour(value ? dayjs(value).hour() : 8)
+ setSelectedMinute(value ? dayjs(value).minute() : 0)
+ }
+ }, [value, visible])
+
+ useEffect(() => {
+ if (type === 'year' && pendingJump && calendarRef.current) {
+ calendarRef.current.jumpTo(pendingJump.year, pendingJump.month)
+ setPendingJump(null)
+ }
+ }, [type, pendingJump])
+
+
+ return (
+
+ {
+ type === 'year' &&
+
+
+ }
+ {
+ type === 'month' &&
+
+ }
+ {
+ type === 'time' &&
+
+ }
+
+ )
+}
+
+export default DialogCalendarCard
diff --git a/src/components/Picker/CalendarDialog/index.module.scss b/src/components/Picker/CalendarDialog/index.module.scss
new file mode 100644
index 0000000..95e8153
--- /dev/null
+++ b/src/components/Picker/CalendarDialog/index.module.scss
@@ -0,0 +1,3 @@
+.calendar-container{
+ padding: 26px 12px 8px;
+}
\ No newline at end of file
diff --git a/src/components/Picker/CalendarUI/CalendarUI.tsx b/src/components/Picker/CalendarUI/CalendarUI.tsx
new file mode 100644
index 0000000..16fa745
--- /dev/null
+++ b/src/components/Picker/CalendarUI/CalendarUI.tsx
@@ -0,0 +1,211 @@
+import React, { useState, useEffect, useRef, useImperativeHandle } from 'react'
+import { CalendarCard } from '@nutui/nutui-react-taro'
+import { View, Text, Image } from '@tarojs/components'
+import images from '@/config/images'
+import styles from './index.module.scss'
+import { getMonth, getWeekend, getWeekendOfCurrentWeek } from '@/utils/timeUtils'
+import { PopupPicker } from '@/components/Picker/index'
+interface NutUICalendarProps {
+ type?: 'single' | 'range' | 'multiple'
+ value?: string | Date | String[] | Date[]
+ defaultValue?: string | string[]
+ onChange?: (value: Date | Date[]) => void,
+ isBorder?: boolean
+ showQuickActions?: boolean,
+ onHeaderClick?: (date: Date) => void
+}
+
+export interface CalendarUIRef {
+ jumpTo: (year: number, month: number) => void
+}
+
+const NutUICalendar = React.forwardRef(({
+ type = 'single',
+ value,
+ onChange,
+ isBorder = false,
+ showQuickActions = true,
+ onHeaderClick
+}, ref) => {
+ // 根据类型初始化选中值
+// const getInitialValue = (): Date | Date[] => {
+// console.log(value,defaultValue,'today')
+
+// if (typeof value === 'string' && value) {
+// return new Date(value)
+// }
+// if (Array.isArray(value) && value.length > 0) {
+// return value.map(item => new Date(item))
+// }
+// if (typeof defaultValue === 'string' && defaultValue) {
+// return new Date(defaultValue)
+// }
+// if (Array.isArray(defaultValue) && defaultValue.length > 0) {
+// return defaultValue.map(item => new Date(item))
+// }
+// const today = new Date();
+// if (type === 'multiple') {
+// return [today]
+// }
+// return today
+// }
+ const startOfMonth = (date: Date) => new Date(date.getFullYear(), date.getMonth(), 1)
+
+ const [selectedValue, setSelectedValue] = useState()
+ const [current, setCurrent] = useState(startOfMonth(new Date()))
+ const calendarRef = useRef(null)
+ const [visible, setvisible] = useState(false)
+ console.log('current', current)
+ // 当外部 value 变化时更新内部状态
+ useEffect(() => {
+ if (Array.isArray(value) && value.length > 0) {
+ setSelectedValue(value.map(item => new Date(item)))
+ setCurrent(new Date(value[0]))
+ }
+ if ((typeof value === 'string' || value instanceof Date) && value) {
+ setSelectedValue(new Date(value))
+ setCurrent(new Date(value))
+ }
+ }, [value])
+
+ useImperativeHandle(ref, () => ({
+ jumpTo: (year: number, month: number) => {
+ calendarRef.current?.jumpTo(year, month)
+ }
+ }))
+
+ const handleDateChange = (newValue: any) => {
+ setSelectedValue(newValue)
+ onChange?.(newValue as any)
+ }
+ const formatHeader = (date: Date) => `${getMonth(date)}`
+
+ const handlePageChange = (data: { year: number; month: number }) => {
+ // 月份切换时的处理逻辑,如果需要的话
+ console.log('月份切换:', data)
+ }
+
+ const gotoMonth = (delta: number) => {
+ const base = current instanceof Date ? new Date(current) : new Date()
+ base.setMonth(base.getMonth() + delta)
+ const next = startOfMonth(base)
+ setCurrent(next)
+ // 同步底部 CalendarCard 的月份
+ try {
+ calendarRef.current?.jump?.(delta)
+ } catch (e) {
+ console.warn('CalendarCardRef jump 调用失败', e)
+ }
+ handlePageChange({ year: next.getFullYear(), month: next.getMonth() + 1 })
+ }
+
+ const handleHeaderClick = () => {
+ onHeaderClick && onHeaderClick(current)
+ setvisible(true)
+ }
+
+
+
+ const syncMonthTo = (anchor: Date) => {
+ // 计算从 current 到目标 anchor 所在月份的偏移,调用 jump(delta)
+ const monthsDelta = (anchor.getFullYear() - current.getFullYear()) * 12 + (anchor.getMonth() - current.getMonth())
+ if (monthsDelta !== 0) {
+ gotoMonth(monthsDelta)
+ }
+ }
+ const renderDay = (day: any) => {
+ const { date, month, year} = day;
+ const today = new Date()
+ if (date === today.getDate() && month === today.getMonth() + 1 && year === today.getFullYear()) {
+ return (
+
+ {date}
+
+ )
+ }
+ return date
+ }
+
+ const selectWeekend = () => {
+ const [start, end] = getWeekend()
+ setSelectedValue([start, end])
+ syncMonthTo(start)
+ onChange?.([start, end])
+ }
+ const selectWeek = () => {
+ const dayList = getWeekendOfCurrentWeek(7)
+ setSelectedValue(dayList)
+ syncMonthTo(dayList[0])
+ onChange?.(dayList)
+ }
+ const selectMonth = () => {
+ const dayList = getWeekendOfCurrentWeek(30)
+ setSelectedValue(dayList)
+ syncMonthTo(dayList[0])
+ onChange?.(dayList)
+ }
+
+ const handleMonthChange = (value: any) => {
+ const [year, month] = value;
+ const newDate = new Date(year, month - 1, 1);
+ setCurrent(newDate);
+ calendarRef.current?.jumpTo(year, month)
+ }
+
+
+ return (
+
+ {/* 快速操作行 */}
+ {
+ showQuickActions &&
+
+ 本周末
+ 一周内
+ 一个月
+
+ }
+
+ {/* 自定义头部显示周一到周日 */}
+
+
+ {formatHeader(current as Date)}
+ gotoMonth(1)} />
+
+
+ gotoMonth(-1)}>
+
+
+ gotoMonth(1)}>
+
+
+
+
+
+ {[ '周日', '周一', '周二', '周三', '周四', '周五', '周六'].map((day) => (
+
+ {day}
+
+ ))}
+
+
+ {/* NutUI CalendarCard 组件 */}
+
+
+ { visible && handleMonthChange(value)}/> }
+
+ )
+})
+
+export default NutUICalendar
\ No newline at end of file
diff --git a/src/components/Picker/CalendarUI/index.module.scss b/src/components/Picker/CalendarUI/index.module.scss
new file mode 100644
index 0000000..ce79dc7
--- /dev/null
+++ b/src/components/Picker/CalendarUI/index.module.scss
@@ -0,0 +1,292 @@
+.calendar-card {
+ background: #fff;
+ border-radius: 16px;
+ &.border{
+ border-radius: 12px;
+ border: 0.5px solid rgba(0, 0, 0, 0.12);
+ margin-bottom: 6px;
+ padding: 12px 12px 8px;
+
+ }
+ }
+
+ .header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 9px 4px 11px 4px;
+ height: 24px;
+ }
+ .header-left {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+ .header-text {
+ font-size: 17px;
+ font-weight: 600;
+ color: #000;
+ }
+ .header-actions {
+ display: flex;
+ width: 60px;
+ .arrow-left-container {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ width: 50%;
+ flex: 1;
+ }
+ .arrow-right-container {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ width: 50%;
+ flex: 1;
+ }
+ }
+ .month-arrow{
+ width: 8px;
+ height: 24px;
+ }
+ .arrow {
+ width: 10px;
+ height: 24px;
+ position: relative;
+ }
+ .arrow.left {
+ left: 9px;
+ transform: rotate(-180deg);
+ }
+
+ .week-row {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ padding: 0 0 4px 0;
+ }
+ .week-item {
+ text-align: center;
+ color: rgba(60, 60, 67, 0.30);
+ font-size: 13px;
+ }
+
+ // 新增的周一到周日头部样式
+ .week-header {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ padding: 8px 0;
+ }
+ .week-day {
+ text-align: center;
+ color: rgba(60, 60, 67, 0.30);
+ font-size: 14px;
+ font-weight: 500;
+ }
+
+ .grid {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 8px 0;
+ padding: 4px 0 16px;
+ }
+ .cell {
+ height: 44px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 20px;
+ position: relative;
+ }
+ .cell.empty {
+ opacity: 0;
+ }
+ .cell.disabled {
+ color: rgba(0,0,0,0.2);
+ }
+ .cell-text.selected {
+ width: 44px;
+ height: 44px;
+ border-radius: 22px;
+ background: rgba(0,0,0,0.9);
+ color: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ // 时间段选择样式
+ .cell-text.range-start {
+ width: 44px;
+ height: 44px;
+ border-radius: 22px;
+ background: rgba(0,0,0,0.9);
+ color: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .cell-text.range-end {
+ width: 44px;
+ height: 44px;
+ border-radius: 22px;
+ background: rgba(0,0,0,0.9);
+ color: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .cell-text.in-range {
+ width: 44px;
+ height: 44px;
+ border-radius: 22px;
+ background: rgba(0,0,0,0.1);
+ color: #000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .footer {
+ display: flex;
+ gap: 12px;
+ }
+ .btn {
+ flex: 1;
+ height: 44px;
+ border-radius: 22px;
+ background: rgba(0,0,0,0.06);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ .btn.primary {
+ background: #000;
+ color: #fff;
+ }
+
+ .hm-placeholder {
+ height: 240px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+// CalendarRange 组件样式
+.calendar-range {
+ background: #fff;
+ border-radius: 16px;
+ overflow: hidden;
+}
+
+.quick-select {
+ display: flex;
+ padding: 16px 12px 12px;
+ gap: 8px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+}
+
+.quick-btn {
+ flex: 1;
+ height: 36px;
+ border-radius: 18px;
+ background: rgba(0, 0, 0, 0.06);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+}
+
+.quick-btn.active {
+ background: rgba(0, 0, 0, 0.9);
+}
+
+.quick-btn-text {
+ font-size: 14px;
+ color: #000;
+ font-weight: 500;
+}
+
+.quick-btn.active .quick-btn-text {
+ color: #fff;
+}
+
+.quick-actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 6px;
+ margin-bottom: 8px;
+}
+
+.quick-action {
+ border-radius: 999px;
+ border: 0.5px solid rgba(0, 0, 0, 0.12);
+ background: #FFF;
+ display: flex;
+ height: 28px;
+ padding: 4px 10px;
+ justify-content: center;
+ align-items: center;
+ font-size: 14px;
+ color: #000;
+ flex: 1;
+}
+
+
+// 隐藏 CalendarCard 默认头部
+:global {
+ .nut-calendarcard {
+ .nut-calendarcard-header {
+ display: none !important;
+ }
+ .nut-calendarcard-content{
+ .nut-calendarcard-days{
+ &:first-child{
+ display: none !important;
+ }
+ }
+ }
+
+ }
+ .nut-calendarcard-day{
+ margin-bottom:0px!important;
+ height: 44px;
+ width: 44px!important;
+ &.active{
+ background-color: #000!important;
+ color: #fff!important;
+ height: 44px;
+ border-radius: 22px!important;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 44px!important;
+ font-size: 24px!important;
+ .day-container{
+ background-color: transparent!important;
+ }
+ }
+ &.weekend{
+ color: rgb(0,0,0)!important;
+ &.active{
+ color: #fff!important;
+ }
+ }
+ }
+ .nut-calendarcard-day-inner{
+ font-size: 20px;
+ .day-container{
+ background-color: #f5f5f5;
+ border-radius: 22px;
+ width: 44px;
+ height: 44px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 24px;
+ }
+ }
+}
+
diff --git a/src/components/Picker/Picker.tsx b/src/components/Picker/Picker.tsx
new file mode 100644
index 0000000..e0a1f31
--- /dev/null
+++ b/src/components/Picker/Picker.tsx
@@ -0,0 +1,86 @@
+import React, { useState, useCallback, useEffect } from 'react'
+import { Picker, ConfigProvider } from '@nutui/nutui-react-taro'
+import { View } from '@tarojs/components'
+import styles from './index.module.scss'
+
+interface PickerOption {
+ text: string | number
+ value: string | number
+}
+
+interface PickerProps {
+ visible: boolean
+ options?: PickerOption[][]
+ defaultValue?: (string | number)[]
+ onConfirm?: (options: PickerOption[], values: (string | number)[]) => void
+ onChange?: (options: PickerOption[], values: (string | number)[], columnIndex: number) => void
+}
+
+const CustomPicker = ({
+ visible,
+ options = [],
+ defaultValue = [],
+ onConfirm,
+ onChange
+}: PickerProps) => {
+ // 使用内部状态管理当前选中的值
+ const [currentValue, setCurrentValue] = useState<(string | number)[]>(defaultValue)
+
+ // 当外部 defaultValue 变化时,同步更新内部状态
+ useEffect(() => {
+ setCurrentValue(defaultValue)
+ }, [defaultValue])
+
+ const confirmPicker = (
+ options: PickerOption[],
+ values: (string | number)[]
+ ) => {
+ let description = ''
+ options.forEach((option: any) => {
+ description += ` ${option.text}`
+ })
+
+ if (onConfirm) {
+ onConfirm(options, values)
+ }
+ }
+
+ const changePicker = useCallback((options: any[], values: any, columnIndex: number) => {
+ // 更新内部状态
+ setCurrentValue(values)
+
+ if (onChange) {
+ onChange(options, values, columnIndex)
+ }
+ }, [onChange])
+
+ return (
+ <>
+
+
+ confirmPicker(list, values)}
+ />
+
+
+ >
+ )
+}
+
+export default CustomPicker
\ No newline at end of file
diff --git a/src/components/Picker/PickerCommon.tsx b/src/components/Picker/PickerCommon.tsx
new file mode 100644
index 0000000..4581e4b
--- /dev/null
+++ b/src/components/Picker/PickerCommon.tsx
@@ -0,0 +1,70 @@
+import React, { useState, useEffect, useCallback } from 'react'
+import Picker from './Picker'
+import { renderYearMonth, renderHourMinute } from './PickerData'
+interface PickerOption {
+ text: string | number
+ value: string | number
+}
+
+interface PickerProps {
+ options?: PickerOption[][]
+ value?: (string | number)[]
+ type?: 'month' | 'hour' | null
+ onConfirm?: (options: PickerOption[], values: (string | number)[]) => void
+ onChange?: ( value: (string | number)[] ) => void
+}
+
+export interface PickerCommonRef {
+ getValue: () => (string | number)[]
+ setValue: (v: (string | number)[]) => void
+}
+
+const PopupPicker = ({
+ value = [],
+ onChange,
+ options = [],
+ type = null
+}: PickerProps, ref: React.Ref) => {
+ const [defaultValue, setDefaultValue] = useState<(string | number)[]>([])
+
+ const [defaultOptions, setDefaultOptions] = useState([])
+ const changePicker = (options: any[], values: any, columnIndex: number) => {
+ console.log('picker onChange', columnIndex, values, options)
+ setDefaultValue(values)
+ }
+
+ useEffect(() => {
+ if (type === 'month') {
+ setDefaultOptions(renderYearMonth())
+ } else if (type === 'hour') {
+ setDefaultOptions(renderHourMinute())
+ } else {
+ setDefaultOptions(options)
+ }
+ }, [type])
+
+ useEffect(() => {
+ // 同步初始值到内部状态,供 getValue 使用
+ setDefaultValue(value)
+ }, [value])
+
+ React.useImperativeHandle(ref, () => ({
+ getValue: () => (defaultValue && defaultValue.length ? defaultValue : value),
+ setValue: (v: (string | number)[]) => {
+ setDefaultValue(v)
+ },
+ }), [defaultValue, value])
+
+ return (
+ <>
+
+ >
+ )
+}
+
+export default React.forwardRef(PopupPicker)
diff --git a/src/components/Picker/PickerData.js b/src/components/Picker/PickerData.js
new file mode 100644
index 0000000..c145e8d
--- /dev/null
+++ b/src/components/Picker/PickerData.js
@@ -0,0 +1,30 @@
+export const renderYearMonth = (minYear = 2020, maxYear = 2099) => {
+ return [
+ // 年份列
+ Array.from({ length: maxYear - minYear + 1 }, (_, index) => ({
+ text: `${minYear + index}年`,
+ value: minYear + index
+ })),
+ // 月份列
+ Array.from({ length: 12 }, (_, index) => ({
+ text: `${index + 1}月`,
+ value: index + 1
+ }))
+ ]
+}
+
+export const renderHourMinute = (minHour = 0, maxHour = 23) => {
+ // 生成小时和分钟的选项数据
+ return [
+ // 小时列
+ Array.from({ length: maxHour - minHour + 1 }, (_, index) => ({
+ text: `${minHour + index}时`,
+ value: minHour + index
+ })),
+ // 分钟列 (5分钟间隔)
+ Array.from({ length: 12 }, (_, index) => ({
+ text: `${index * 5 < 10 ? '0' + index * 5 : index * 5}分`,
+ value: index * 5
+ }))
+ ]
+}
\ No newline at end of file
diff --git a/src/components/Picker/PopupPicker.tsx b/src/components/Picker/PopupPicker.tsx
new file mode 100644
index 0000000..2f2292d
--- /dev/null
+++ b/src/components/Picker/PopupPicker.tsx
@@ -0,0 +1,90 @@
+import React, { useState, useEffect, useCallback } from 'react'
+import CommonPopup from '@/components/CommonPopup'
+import Picker from './Picker'
+import { renderYearMonth, renderHourMinute } from './PickerData'
+interface PickerOption {
+ text: string | number
+ value: string | number
+}
+
+interface PickerProps {
+ visible: boolean
+ setvisible: (visible: boolean) => void
+ options?: PickerOption[][]
+ value?: (string | number)[]
+ type?: 'month' | 'hour' | null
+ onConfirm?: (options: PickerOption[], values: (string | number)[]) => void
+ onChange?: ( value: (string | number)[] ) => void
+}
+
+const PopupPicker = ({
+ visible,
+ setvisible,
+ value = [],
+ onConfirm,
+ onChange,
+ options = [],
+ type = null
+}: PickerProps) => {
+
+ const [defaultValue, setDefaultValue] = useState<(string | number)[]>([])
+ const [defaultOptions, setDefaultOptions] = useState([])
+ const changePicker = (options: any[], values: any, columnIndex: number) => {
+ if (onChange) {
+ console.log('picker onChange', columnIndex, values, options)
+
+ setDefaultValue(values)
+ }
+ }
+
+ const handleConfirm = () => {
+ console.log(defaultValue,'defaultValue');
+ onChange(defaultValue)
+ setvisible(false)
+ }
+
+ const dialogClose = () => {
+ setvisible(false)
+ }
+ useEffect(() => {
+ if (type === 'month') {
+ setDefaultOptions(renderYearMonth())
+ } else if (type === 'hour') {
+ setDefaultOptions(renderHourMinute())
+ } else {
+ setDefaultOptions(options)
+ }
+ }, [type])
+
+// useEffect(() => {
+// if (value.length > 0 && defaultOptions.length > 0) {
+// setDefaultValue([...value])
+// }
+// }, [value, defaultOptions])
+ return (
+ <>
+
+
+
+ >
+ )
+}
+
+export default PopupPicker
diff --git a/src/components/Picker/index.module.scss b/src/components/Picker/index.module.scss
new file mode 100644
index 0000000..841cdee
--- /dev/null
+++ b/src/components/Picker/index.module.scss
@@ -0,0 +1,25 @@
+.picker-container {
+ :global{
+ .nut-popup-round{
+ position: relative!important;
+ .nut-picker-control {
+ display: none!important;
+ }
+ .nut-picker{
+ &::after{
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 16px;
+ right: 16px!important;
+ width: calc(100% - 32px);
+ height: 48px;
+ background: rgba(22, 24, 35, 0.05);
+ transform: translateY(-50%);
+ border-radius: 4px;
+ pointer-events: none;
+ }
+ }
+ }
+ }
+}
diff --git a/src/components/Picker/index.ts b/src/components/Picker/index.ts
new file mode 100644
index 0000000..a8915a1
--- /dev/null
+++ b/src/components/Picker/index.ts
@@ -0,0 +1,6 @@
+export { default as CustomPicker } from './Picker'
+export { default as PopupPicker } from './PopupPicker'
+export { default as PickerCommon } from './PickerCommon'
+export type { PickerCommonRef } from './PickerCommon'
+export { default as CalendarUI } from './CalendarUI/CalendarUI'
+export { default as DialogCalendarCard } from './CalendarDialog/DialogCalendarCard'
\ No newline at end of file
diff --git a/src/components/Range/index.tsx b/src/components/Range/index.tsx
index 01c7b8c..53ae228 100644
--- a/src/components/Range/index.tsx
+++ b/src/components/Range/index.tsx
@@ -10,7 +10,10 @@ interface RangeProps {
max?: number;
step?: number;
value?: [number, number];
- onChange?: (name: string, value: [number, number]) => void;
+ onChange?: {
+ (value: [number, number]): void
+ (name: string, value: [number, number]): void
+ };
disabled?: boolean;
className?: string;
name: string;
@@ -41,7 +44,11 @@ const NtrpRange: React.FC = ({
const handleEndChange = (val: [number, number]) => {
setCurrentValue(val);
- onChange?.(name, val);
+ if (name) {
+ onChange?.(name, val);
+ } else {
+ onChange?.(val)
+ }
};
const marks = useMemo(() => {
diff --git a/src/components/TimeSelector/TimeSelector.tsx b/src/components/TimeSelector/TimeSelector.tsx
index 1390d66..ef4e934 100644
--- a/src/components/TimeSelector/TimeSelector.tsx
+++ b/src/components/TimeSelector/TimeSelector.tsx
@@ -1,8 +1,9 @@
-import React, { useState } from 'react'
+import React, { useState, useEffect } from 'react'
import { View, Text, } from '@tarojs/components'
import { getDate, getTime, getDateStr, getEndTime } from '@/utils/timeUtils'
-import DialogCalendarCard from '@/components/CalendarCard/DialogCalendarCard'
+import { DialogCalendarCard } from '@/components/index'
import './TimeSelector.scss'
+import dayjs from 'dayjs'
export interface TimeRange {
start_time: string
@@ -23,12 +24,38 @@ const TimeSelector: React.FC = ({
}) => {
// 格式化日期显示
const [visible, setVisible] = useState(false)
+ const [currentTimeValue, setCurrentTimeValue] = useState(new Date())
+ const [currentTimeType, setCurrentTimeType] = useState<'start' | 'end'>('start')
+ const [showEndTime, setShowEndTime] = useState(false)
const handleConfirm = (date: Date) => {
console.log('选择的日期:', date)
- const start_time = getDateStr(date)
- const end_time = getEndTime(start_time)
+ const start_time = currentTimeType === 'start' ? getDateStr(date) : value.start_time;
+ const isLater = dayjs(value.start_time).isAfter(dayjs(value.end_time));
+ if (isLater) {
+ if (onChange) onChange({start_time, end_time: getEndTime(start_time)})
+ return
+ }
+ const initEndTime = value.end_time ? value.end_time : getEndTime(start_time)
+ const end_time = currentTimeType === 'end' ? getDateStr(date) : initEndTime;
if (onChange) onChange({start_time, end_time})
}
+ const openPicker = (type: 'start' | 'end') => {
+ setCurrentTimeValue(type === 'start' ? new Date(value.start_time) : new Date(value.end_time))
+ setCurrentTimeType(type)
+ setVisible(true)
+ }
+
+ useEffect(() => {
+ if (value.start_time && value.end_time) {
+ const start_time = dayjs(value.start_time).format('YYYY-MM-DD')
+ const end_time = dayjs(value.end_time).format('YYYY-MM-DD')
+ if (start_time === end_time) {
+ setShowEndTime(false)
+ } else {
+ setShowEndTime(true)
+ }
+ }
+ }, [value])
return (
@@ -37,7 +64,7 @@ const TimeSelector: React.FC = ({
- setVisible(true)}>
+ openPicker('start')}>
开始时间
{getDate(value.start_time)}
@@ -51,9 +78,10 @@ const TimeSelector: React.FC = ({
-
+ openPicker('end')}>
结束时间
+ {showEndTime && ({getDate(value.end_time)})}
{getTime(value.end_time)}
@@ -61,6 +89,7 @@ const TimeSelector: React.FC = ({
setVisible(false)}
/>
diff --git a/src/components/UploadCover/index.tsx b/src/components/UploadCover/index.tsx
index 6da8f37..eaf9d51 100644
--- a/src/components/UploadCover/index.tsx
+++ b/src/components/UploadCover/index.tsx
@@ -25,6 +25,7 @@ export interface UploadCoverProps {
source?: source
maxCount?: number
align?: 'center' | 'left'
+ tag?: 'cover' | 'screenshot'
}
// const values = [
@@ -34,7 +35,6 @@ export interface UploadCoverProps {
// ]
const mergeCoverImages = (value: CoverImageValue[], images: CoverImageValue[]) => {
- console.log(value, images, 11111)
// 根据id来更新url, 如果id不存在,则添加到value中
const newImages = images
const updatedValue = value.map(item => {
@@ -55,6 +55,7 @@ export default function UploadCover(props: UploadCoverProps) {
source = ['album', 'history', 'preset'] as source,
maxCount = 9,
align = 'center',
+ tag = 'cover',
} = props
const [visible, setVisible] = useState(false)
@@ -68,12 +69,12 @@ export default function UploadCover(props: UploadCoverProps) {
setVisible(false)
}, [value])
- const onWxAdd = useCallback((images: CoverImageValue[], onFileUploaded: Promise<{ id: string, data: uploadFileResponseData }[]>) => {
+ const onWxAdd = useCallback((images: CoverImageValue[], onFileUpdate: Promise<{ id: string, url: string }[]>) => {
onAdd(images)
- onFileUploaded.then(res => {
+ onFileUpdate.then(res => {
onAdd(res.map(item => ({
id: item.id,
- url: item.data.file_path,
+ url: item.url,
})))
})
}, [onAdd])
@@ -111,7 +112,7 @@ export default function UploadCover(props: UploadCoverProps) {
}
-
+
{value.length < maxCount && (
setVisible(true)}>
diff --git a/src/components/UploadCover/upload-from-wx.tsx b/src/components/UploadCover/upload-from-wx.tsx
index 33e23fe..35aabda 100644
--- a/src/components/UploadCover/upload-from-wx.tsx
+++ b/src/components/UploadCover/upload-from-wx.tsx
@@ -4,13 +4,81 @@ import Taro from '@tarojs/taro'
import uploadApi from '@/services/uploadFiles'
import './upload-from-wx.scss'
import { CoverImageValue } from '.'
-import { uploadFileResponseData } from '@/services/uploadFiles'
export interface UploadFromWxProps {
- onAdd: (images: CoverImageValue[], onFileUploaded: Promise<{ id: string, data: uploadFileResponseData }[]>) => void
+ onAdd: (images: CoverImageValue[], onFileUploaded: Promise<{ id: string, url: string }[]>) => void
maxCount: number
}
+async function convert_to_jpg_and_compress (src: string, { width, height }): Promise
{
+ const canvas = Taro.createOffscreenCanvas({ type: '2d', width, height })
+ const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
+
+ const image = canvas.createImage()
+ await new Promise(resolve => {
+ image.onload = resolve
+ image.src = src
+ })
+ ctx.clearRect(0, 0, width, height)
+ ctx.drawImage(image as unknown as CanvasImageSource, 0, 0, width, height)
+
+ // const imageData = ctx.getImageData(0, 0, width, height)
+
+ return new Promise((resolve, reject) => {
+ Taro.canvasToTempFilePath({
+ canvas: canvas as unknown as Taro.Canvas,
+ fileType: 'jpg',
+ quality: 0.7,
+ success: res => resolve(res.tempFilePath),
+ fail: reject
+ })
+ })
+}
+
+
+async function compressImage(files) {
+ const res: string[] = []
+ for (const file of files) {
+ const compressed_image = await convert_to_jpg_and_compress(file.path, { width: file.width, height: file.height })
+ res.push(compressed_image)
+ }
+ return res
+}
+// 图片标准容器为 360 * 240 3:2
+// 压缩后图片最大宽高
+const IMAGE_MAX_SIZE = {
+ width: 1080,
+ height: 720,
+}
+
+// 标准长宽比,判断标准
+const STANDARD_ASPECT_RATIO = IMAGE_MAX_SIZE.width / IMAGE_MAX_SIZE.height
+
+type ChoosenImageRes = { path: string, size: number, width: number, height: number }
+// 根据图片标准重新设置图片尺寸
+async function onChooseImageSuccess(tempFiles) {
+ const result: ChoosenImageRes[] = []
+ for (const tempFile of tempFiles) {
+ const { width, height } = await Taro.getImageInfo({ src: tempFile.path })
+ const image_aspect_ratio = width / height
+ let fileRes: ChoosenImageRes = { path: tempFile.path, size: tempFile.size, width: 0, height: 0 }
+ // 如果图片长宽比小于标准长宽比,则依照图片高度以及图片最大高度来重新设置图片尺寸
+ if (image_aspect_ratio < STANDARD_ASPECT_RATIO) {
+ fileRes = {
+ ...fileRes,
+ ...(height > IMAGE_MAX_SIZE.height ? { width: Math.floor(IMAGE_MAX_SIZE.height * image_aspect_ratio), height: IMAGE_MAX_SIZE.height } : { width: Math.floor(height * image_aspect_ratio), height }),
+ }
+ } else {
+ fileRes = {
+ ...fileRes,
+ ...(width > IMAGE_MAX_SIZE.width ? { width: IMAGE_MAX_SIZE.width, height: Math.floor(IMAGE_MAX_SIZE.width / image_aspect_ratio) } : { width, height: Math.floor(width / image_aspect_ratio) }),
+ }
+ }
+ result.push(fileRes)
+ }
+ return result
+}
+
export default function UploadFromWx(props: UploadFromWxProps) {
const {
onAdd = () => void 0,
@@ -22,21 +90,29 @@ export default function UploadFromWx(props: UploadFromWxProps) {
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
}).then(async (res) => {
- // TODO: compress image
- // TODO: cropping image to const size
- let count = 0
- const files = res.tempFiles.map(item => ({
- filePath: item.path,
+ const analyzedFiles = await onChooseImageSuccess(res.tempFiles)
+ // compress image
+ // cropping image to standard size
+ const compressedTempFiles = await compressImage(analyzedFiles)
+
+ let start = Date.now()
+ const files = compressedTempFiles.map(path => ({
+ filePath: path,
description: '封面图',
tags: 'cover',
is_public: 1 as unknown as 0 | 1,
- id: (Date.now() + count++).toString(),
+ id: (start++).toString(),
}))
- const onFileUploaded = uploadApi.batchUpload(files)
+ const onFileUpdate = uploadApi.batchUpload(files).then(res => {
+ return res.map(item => ({
+ id: item.id,
+ url: item.data.file_url
+ }))
+ })
onAdd(files.map(item => ({
id: item.id,
url: item.filePath,
- })), onFileUploaded) // TODO: add loading state
+ })), onFileUpdate)
})
}
return (
diff --git a/src/components/UploadCover/upload-source-popup.tsx b/src/components/UploadCover/upload-source-popup.tsx
index 5b02ad0..6858b44 100644
--- a/src/components/UploadCover/upload-source-popup.tsx
+++ b/src/components/UploadCover/upload-source-popup.tsx
@@ -18,6 +18,7 @@ type ImageItem = {
interface UploadImageProps {
onAdd: (images: ImageItem[]) => void
+ tag: 'cover' | 'screenshot'
}
export const sourceMap = new Map([
@@ -32,6 +33,7 @@ const checkImageSelected = (images: ImageItem[], image: ImageItem) => {
export default forwardRef(function UploadImage(props: UploadImageProps, ref) {
const {
onAdd = () => void 0,
+ tag = 'cover',
} = props
const [visible, setVisible] = useState(false)
const [sourceType, setSourceType] = useState('history')
@@ -62,7 +64,7 @@ export default forwardRef(function UploadImage(props: UploadImageProps, ref) {
}))
function fetchImages(st: SourceType) {
- publishService.getPictures({ type: st }).then(res => {
+ publishService.getPictures({ type: st, tag }).then(res => {
if (res.code === 0) {
let start = 0
setImages(res.data.rows.map(item => ({
diff --git a/src/components/UserInfo/index.scss b/src/components/UserInfo/index.scss
index e4bb1df..dfe55a1 100644
--- a/src/components/UserInfo/index.scss
+++ b/src/components/UserInfo/index.scss
@@ -5,7 +5,6 @@
display: flex;
flex-direction: column;
gap: 16px;
- margin-bottom: 16px;
// 基本信息
.basic_info {
@@ -220,6 +219,7 @@
background: #FFFFFF;
border: 0.5px solid rgba(0, 0, 0, 0.16);
border-radius: 999px;
+ box-sizing: border-box;
.tag_icon {
width: 12px;
diff --git a/src/components/UserInfo/index.tsx b/src/components/UserInfo/index.tsx
index e742fe4..1a9625e 100644
--- a/src/components/UserInfo/index.tsx
+++ b/src/components/UserInfo/index.tsx
@@ -15,15 +15,18 @@ export interface UserInfo {
hosted: number;
participated: number;
};
- tags: string[];
- bio: string;
+ personal_profile: string;
location: string;
occupation: string;
ntrp_level: string;
phone?: string;
gender?: string;
+
+ latitude?: string,
+ longitude?: string,
}
+
// 用户信息卡片组件属性
interface UserInfoCardProps {
user_info: UserInfo;
@@ -35,12 +38,12 @@ interface UserInfoCardProps {
}
- // 处理编辑用户信息
- const on_edit = () => {
- Taro.navigateTo({
- url: '/pages/userInfo/edit/index'
- });
- };
+// 处理编辑用户信息
+const on_edit = () => {
+ Taro.navigateTo({
+ url: '/mod_user/pages/edit/index'
+ });
+};
// 用户信息卡片组件
export const UserInfoCard: React.FC = ({
user_info,
@@ -61,11 +64,11 @@ export const UserInfoCard: React.FC = ({
{user_info.nickname}
{user_info.join_date}
-
+
+ className="tag_icon"
+ src={require('../../static/userInfo/edit.svg')}
+ />
{/* 统计数据 */}
@@ -113,7 +116,7 @@ export const UserInfoCard: React.FC = ({
/>
)}
-
+
{/* 只有当前用户才显示分享按钮 */}
{is_current_user && on_share && (