修改发布

This commit is contained in:
筱野
2025-08-23 21:39:46 +08:00
parent 8bc2fa8d97
commit c6f4f11259
29 changed files with 384 additions and 241 deletions

View File

@@ -53,6 +53,7 @@
"@tarojs/runtime": "4.1.5",
"@tarojs/shared": "4.1.5",
"@tarojs/taro": "4.1.5",
"dayjs": "^1.11.13",
"qqmap-wx-jssdk": "^1.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",

View File

@@ -1,2 +0,0 @@
export { default } from './FormBasicInfo'
export type { FormBasicInfoProps } from './FormBasicInfo'

View File

@@ -24,6 +24,7 @@ const TextareaTag: React.FC<TextareaTagProps> = ({
}) => {
// 处理输入框变化
const [tags, setTags] = useState<string[]>([])
console.log(value, 'options')
const handleInputChange = useCallback((e: any) => {
onChange(e.detail.value)
}, [onChange])
@@ -43,6 +44,7 @@ const TextareaTag: React.FC<TextareaTagProps> = ({
onChange(newValue)
}, [value, onChange])
console.log(options, 'options')
return (
<View className='textarea-tag'>
{/* 选择选项 */}

View File

@@ -1,11 +1,11 @@
import React from 'react'
import { View, Text, Picker } from '@tarojs/components'
import { getDate, getTime } from '@/utils/timeUtils'
import './TimeSelector.scss'
export interface TimeRange {
startDate: string
startTime: string
endTime: string
start_time: string
end_time: string
}
interface TimeSelectorProps {
@@ -15,40 +15,13 @@ interface TimeSelectorProps {
const TimeSelector: React.FC<TimeSelectorProps> = ({
value = {
startDate: '',
startTime: '',
endTime: ''
start_time: '',
end_time: ''
},
onChange
}) => {
// 格式化日期显示
const formatDate = (dateStr: string) => {
return dateStr.replace(/-/g, '年').replace(/-/g, '月') + '日'
}
// 处理开始日期变化
const handleStartDateChange = (e: any) => {
onChange({
...value,
startDate: e.detail.value
})
}
// 处理开始时间变化
const handleStartTimeChange = (e: any) => {
onChange({
...value,
startTime: e.detail.value
})
}
// 处理结束时间变化
const handleEndTimeChange = (e: any) => {
onChange({
...value,
endTime: e.detail.value
})
}
return (
<View className='time-selector'>
@@ -61,8 +34,8 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
<View className='time-content'>
<Text className='time-label'></Text>
<view className='time-text-wrapper'>
<Text className='time-text'>20251123</Text>
<Text className='time-text time-am'>8:00AM</Text>
<Text className='time-text'>{getDate(value.start_time)}</Text>
<Text className='time-text time-am'>{getTime(value.start_time)}</Text>
</view>
</View>
</View>
@@ -75,8 +48,7 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
<View className='time-content'>
<Text className='time-label'></Text>
<view className='time-text-wrapper'>
<Text className='time-text'>20251123</Text>
<Text className='time-text time-am'>8:00AM</Text>
<Text className='time-text time-am'>{getTime(value.end_time)}</Text>
</view>
</View>
</View>

View File

@@ -1 +0,0 @@
export { default } from './TitleInput'

View File

@@ -3,14 +3,14 @@ import { View } from '@tarojs/components'
import { TextArea } from '@nutui/nutui-react-taro'
import './index.scss'
interface TitleInputProps {
interface TitleTextareaProps {
value: string
onChange: (value: string) => void
maxLength?: number
placeholder?: string
}
const TitleInput: React.FC<TitleInputProps> = ({
const TitleTextarea: React.FC<TitleTextareaProps> = ({
value,
onChange,
maxLength = 20,
@@ -32,4 +32,4 @@ const TitleInput: React.FC<TitleInputProps> = ({
)
}
export default TitleInput
export default TitleTextarea

View File

@@ -0,0 +1 @@
export { default } from './TitleTextarea'

View File

@@ -2,12 +2,11 @@ import ActivityTypeSwitch from './ActivityTypeSwitch'
import TextareaTag from './TextareaTag'
import FormSwitch from './FormSwitch'
import ImageUpload from './ImageUpload'
import FormBasicInfo from './FormBasicInfo'
import Range from './Range'
import NumberInterval from './NumberInterval'
import { SelectStadium, StadiumDetail } from './SelectStadium'
import TimeSelector from './TimeSelector'
import TitleInput from './TitleInput'
import TitleTextarea from './TitleTextarea'
import CommonPopup from './CommonPopup'
export {
@@ -15,13 +14,10 @@ export {
TextareaTag,
FormSwitch,
ImageUpload,
FormBasicInfo,
Range,
NumberInterval,
SelectStadium,
TimeSelector,
TitleInput,
StadiumDetail,
TitleTextarea,
CommonPopup
}

View File

@@ -1,5 +1,4 @@
import { type TimeRange } from './TimeSelector'
import { type Stadium } from './SelectStadium'
import { type ActivityType } from './ActivityTypeSwitch'
import { type CoverImage } from './ImageUpload'
export type { TimeRange, Stadium, ActivityType, CoverImage }
export type { TimeRange, ActivityType, CoverImage }

View File

@@ -43,7 +43,7 @@ export interface FormFieldConfig {
// 发布球局表单配置
export const publishBallFormSchema: FormFieldConfig[] = [
{
key: 'coverImages',
key: 'image_list',
label: '活动封页',
type: FieldType.UPLOADIMAGE,
placeholder: '请选择活动类型',
@@ -59,115 +59,101 @@ export const publishBallFormSchema: FormFieldConfig[] = [
placeholder: '好的标题更吸引人哦',
required: true,
props: {
maxLength: 80
},
rules: [
{ required: true, message: '请输入活动标题' },
{ max: 20, message: '标题不能超过20个字符' }
]
},
{
key: 'date',
label: '',
type: FieldType.TIMEINTERVAL,
placeholder: '请选择活动日期',
required: true,
rules: [
{ required: true, message: '请选择活动日期' }
]
maxLength: 20
}
},
{
key: 'timeRange',
label: '活动信息',
type: FieldType.ACTIVITYINFO,
placeholder: '请选择活动时间',
required: true,
rules: [
{ required: true, message: '请选择活动时间' }
],
children: [
{
key: 'fee',
label: '费用',
iconType: 'ICON_COST',
type: FieldType.NUMBER,
placeholder: '请输入活动费用(元)',
defaultValue: 0,
rules: [
{ min: 0, message: '费用不能为负数' },
{ max: 1000, message: '费用不能超过1000元' }
],
},
{
key: 'location',
label: '地点',
iconType: 'ICON_LOCATION',
type: FieldType.LOCATION,
placeholder: '请选择活动地点',
required: true,
},
{
key: 'sport',
label: '玩法',
iconType: 'ICON_GAMEPLAY',
type: FieldType.SELECT,
placeholder: '请选择玩法',
required: true,
options: [
{ label: '篮球', value: 'basketball' },
{ label: '足球', value: 'football' },
{ label: '羽毛球', value: 'badminton' },
{ label: '网球', value: 'tennis' },
{ label: '乒乓球', value: 'pingpong' },
{ label: '排球', value: 'volleyball' }
],
}
]
},
{
key: 'minParticipants',
label: '人数要求',
type: FieldType.NUMBERINTERVAL,
placeholder: '请输入最少参与人数',
defaultValue: 1,
props: {
showSummary: true,
summary: '最少1人最多4人',
},
rules: [
{ min: 1, message: '最少参与人数不能为0' },
{ max: 4, message: '最少参与人数不能超过100人' }
],
label: '',
type: FieldType.TIMEINTERVAL,
placeholder: '请选择活动日期',
required: true
},
// {
// key: 'activityInfo',
// label: '活动信息',
// type: FieldType.ACTIVITYINFO,
// placeholder: '请选择活动时间',
// required: true,
// rules: [
// { required: true, message: '请选择活动时间' }
// ],
// children: [
// {
// key: 'price',
// label: '费用',
// iconType: 'ICON_COST',
// type: FieldType.NUMBER,
// placeholder: '请输入活动费用(元)',
// defaultValue: 0,
// rules: [
// { min: 0, message: '费用不能为负数' },
// { max: 1000, message: '费用不能超过1000元' }
// ],
// },
// {
// key: 'location',
// label: '地点',
// iconType: 'ICON_LOCATION',
// type: FieldType.LOCATION,
// placeholder: '请选择活动地点',
// required: true,
// },
// {
// key: 'play_type',
// label: '玩法',
// iconType: 'ICON_GAMEPLAY',
// type: FieldType.SELECT,
// placeholder: '请选择玩法',
// required: true,
// options: [
// { label: '篮球', value: 'basketball' },
// { label: '足球', value: 'football' },
// { label: '羽毛球', value: 'badminton' },
// { label: '网球', value: 'tennis' },
// { label: '乒乓球', value: 'pingpong' },
// { label: '排球', value: 'volleyball' }
// ],
// }
// ]
// },
// {
// key: 'players',
// label: '人数要求',
// type: FieldType.NUMBERINTERVAL,
// placeholder: '请输入最少参与人数',
// defaultValue: 1,
// props: {
// showSummary: true,
// summary: '最少1人最多4人',
// }
// },
// {
// key: 'skill_level',
// label: 'NTRP 水平要求',
// type: FieldType.RANGE,
// placeholder: '请选择开始时间',
// required: true,
// props: {
// showTitle: false,
// showSummary: true,
// className: 'ntrp-range',
// step: 0.5,
// min: 1.0,
// max: 5.0,
// }
// },
{
key: 'ntpLevel',
label: 'NTRP 水平要求',
type: FieldType.RANGE,
placeholder: '请选择开始时间',
required: true,
props: {
showTitle: false,
showSummary: true,
className: 'ntrp-range',
step: 0.5,
min: 1.0,
max: 5.0,
}
},
{
key: 'additionalRequirements',
key: 'descriptionInfo',
label: '补充要求(选填)',
type: FieldType.TEXTAREATAG,
placeholder: '补充性别偏好、特殊要求和注意事项等信息',
required: true,
options:[
{ label: '仅限男生', value: 'beginner' },
{ label: '仅限女生', value: 'intermediate' },
{ label: '性别不限', value: 'advanced' }
],
rules: [
{ max: 100, message: '补充要求不能超过100个字符' }
{ label: '仅限男生', value: '仅限男生' },
{ label: '仅限女生', value: '仅限女生' },
{ label: '性别不限', value: '性别不限' }
]
},
{
@@ -180,9 +166,6 @@ export const publishBallFormSchema: FormFieldConfig[] = [
subTitle: '开启自动候补逻辑',
showToast: true,
description: '开启后,当活动人数不足时,系统会自动将活动状态改为“候补”,并通知用户。',
},
rules: [
{ required: true, message: '请选择开启自动候补逻辑' }
]
}
}
]

View File

@@ -75,6 +75,9 @@
overflow: hidden;
white-space: nowrap;
text-align: right;
&.selected{
color: #000;
}
}
.arrow{
width: 16px;

View File

@@ -1,33 +1,36 @@
import React from 'react'
import React, { useState } from 'react'
import { View, Text, Input, Image, Picker } from '@tarojs/components'
import { Stadium } from '../SelectStadium'
import PopupGameplay from '../PopupGameplay'
import img from '@/config/images';
import './FormBasicInfo.scss'
import { FormFieldConfig } from '@/config/formSchema/publishBallFormSchema';
interface FormBasicInfoProps {
fee: string
location: string
gameplay: string
selectedStadium: Stadium | null
onFeeChange: (value: string) => void
onLocationChange: (value: string) => void
onGameplayChange: (value: string) => void
onStadiumSelect: () => void
value: any
onChange: (value: any) => void
children: FormFieldConfig[]
}
const FormBasicInfo: React.FC<FormBasicInfoProps> = ({
fee,
location,
gameplay,
selectedStadium,
onFeeChange,
onLocationChange,
onGameplayChange,
onStadiumSelect,
value,
onChange,
children
}) => {
const [gameplayVisible, setGameplayVisible] = useState(false)
const handleGameplaySelect = () => {
setGameplayVisible(true)
}
const handleGameplayConfirm = (selectedGameplay: string) => {
onGameplayChange(selectedGameplay)
setGameplayVisible(false)
}
const handleGameplayClose = () => {
setGameplayVisible(false)
}
const renderChildren = () => {
return children.map((child: any, index: number) => {
return <View className='form-item'>
@@ -43,8 +46,8 @@ const FormBasicInfo: React.FC<FormBasicInfoProps> = ({
placeholder='请输入'
placeholderClass='title-placeholder'
type='digit'
value={fee}
onInput={(e) => onFeeChange(e.detail.value)}
value={value[child.key]}
onInput={(e) => onChange(child.key, e.detail.value)}
/>
<Text className='unit'>/</Text>
</View>
@@ -53,9 +56,9 @@ const FormBasicInfo: React.FC<FormBasicInfoProps> = ({
{
index === 1 && (<View className='form-wrapper'>
<Text className='form-item-label'>{child.label}</Text>
<View className='form-right-wrapper' onClick={onStadiumSelect}>
<Text className={`right-text ${selectedStadium ? 'selected' : ''}`}>
{selectedStadium ? selectedStadium.name : '请选择'}
<View className='form-right-wrapper' onClick={() => {}}>
<Text className={`right-text ${value[child.key] ? 'selected' : ''}`}>
{value[child.key] ? value[child.key] : '请选择'}
</Text>
<Image src={img.ICON_ARROW_RIGHT} className='arrow'/>
</View>
@@ -64,9 +67,9 @@ const FormBasicInfo: React.FC<FormBasicInfoProps> = ({
{
index === 2 && ( <View className='form-wrapper'>
<Text className='form-item-label'>{child.label}</Text>
<View className='form-right-wrapper'>
<Text className={`right-text ${gameplay ? 'selected' : ''}`}>
{gameplay ? gameplay : '请选择'}
<View className='form-right-wrapper' onClick={handleGameplaySelect}>
<Text className={`right-text ${value[child.key] ? 'selected' : ''}`}>
{value[child.key] ? value[child.key] : '请选择'}
</Text>
<Image src={img.ICON_ARROW_RIGHT} className='arrow'/>
</View>
@@ -78,7 +81,15 @@ const FormBasicInfo: React.FC<FormBasicInfoProps> = ({
return (
<View className='form-basic-info'>
{/* 费用 */}
{renderChildren()}
{/* {renderChildren()} */}
{/* 玩法选择弹窗 */}
{/* <PopupGameplay
visible={gameplayVisible}
onClose={handleGameplayClose}
onConfirm={handleGameplayConfirm}
selectedGameplay={value[children[2].key]}
/> */}
</View>
)
}

View File

@@ -0,0 +1 @@
export { default } from './FormBasicInfo'

View File

@@ -0,0 +1,34 @@
.optionsList {
padding: 26px 15px 16px 15px;
display: flex;
flex-direction: column;
gap: 6px;
background-color: #fff;
}
.optionItem {
display: flex;
min-height: 40px;
justify-content: center;
align-items: center;
flex: 1 0 0;
border-radius: 999px;
border: 0.5px solid rgba(0, 0, 0, 0.12);
background: #FFF;
}
.optionItem.selected {
border: 0.5px solid rgba(0, 0, 0, 0.06);
color: #fff;
font-size: 14px;
background: #000;
.optionText {
color: #fff;
}
}
.optionText {
font-size: 14px;
color: #333;
}

View File

@@ -0,0 +1,58 @@
import React, { useState, useEffect } from 'react'
import { View, Text } from '@tarojs/components'
import { CommonPopup } from '../../../../components'
import styles from './PopupGameplay.module.scss'
interface PopupGameplayProps {
visible: boolean
onClose: () => void
onConfirm: (selectedGameplay: string) => void
selectedGameplay?: string
}
export default function PopupGameplay({ visible, onClose, onConfirm, selectedGameplay = '不限' }: PopupGameplayProps) {
const [selectedOption, setSelectedOption] = useState(selectedGameplay)
const options = ['不限', '单打', '双打', '拉球']
useEffect(() => {
if (visible && selectedGameplay) {
setSelectedOption(selectedGameplay)
}
}, [visible, selectedGameplay])
const handleOptionSelect = (option: string) => {
setSelectedOption(option)
}
const handleClose = () => {
onClose()
}
const handleConfirm = () => {
onConfirm(selectedOption)
}
return (
<CommonPopup
visible={visible}
onClose={handleClose}
showHeader={false}
onConfirm={handleConfirm}
confirmText='确定'
cancelText='取消'
>
<View className={styles.optionsList}>
{options.map((option) => (
<View
key={option}
className={`${styles.optionItem} ${selectedOption === option ? styles.selected : ''}`}
onClick={() => handleOptionSelect(option)}
>
<Text className={styles.optionText}>{option}</Text>
</View>
))}
</View>
</CommonPopup>
)
}

View File

@@ -0,0 +1 @@
export { default } from './PopupGameplay'

View File

@@ -1,9 +1,8 @@
import React, { useState } from 'react'
import { View, Text, Input, ScrollView, Image } from '@tarojs/components'
import { Popup } from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
import StadiumDetail from './StadiumDetail'
import CommonPopup from '../CommonPopup'
import { CommonPopup } from '../../../../components'
import './SelectStadium.scss'
import images from '@/config/images'

View File

@@ -6,28 +6,52 @@ import PublishForm from './publishForm'
import { publishBallFormSchema } from '../../config/formSchema/publishBallFormSchema';
import { PublishBallFormData } from '../../../types/publishBall';
import PublishService from '@/services/publishService';
import { getNextHourTime, getEndTime } from '@/utils/timeUtils';
import styles from './index.module.scss'
import images from '@/config/images'
const defaultFormData: PublishBallFormData = {
title: '',
image_list: [],
timeRange: {
start_time: getNextHourTime(),
end_time: getEndTime(getNextHourTime())
},
activityInfo: {
play_type: '不限',
price: '',
venue_id: null,
location_name: '',
location: '',
latitude: '',
longitude: '',
court_type: '',
court_surface: '',
venue_description_tag: [],
venue_description: '',
venue_image_list: [],
},
players: {
current_players: 0,
max_players: 0,
},
skill_level: {
skill_level_max: 5.0,
skill_level_min: 1.0,
},
descriptionInfo: {
description: '',
description_tag: [],
},
is_substitute_supported: false,
is_wechat_contact: false,
wechat_contact: ''
}
const PublishBall: React.FC = () => {
const [activityType, setActivityType] = useState<ActivityType>('individual')
const [formData, setFormData] = useState<PublishBallFormData[]>([
{
title: '',
timeRange: {
startDate: '2025-11-23',
startTime: '08:00',
endTime: '10:00'
},
fee: '',
location: '',
gameplay: '',
minParticipants: 1,
maxParticipants: 4,
ntpLevel: [2.0, 4.0],
additionalRequirements: '',
autoDegrade: false
}
defaultFormData
])
// 删除确认弹窗状态
@@ -56,21 +80,12 @@ const PublishBall: React.FC = () => {
}
const handleAdd = () => {
const newStartTime = getNextHourTime()
setFormData(prev => [...prev, {
...defaultFormData,
title: '',
timeRange: {
startDate: '2025-11-23',
startTime: '08:00',
endTime: '10:00'
},
fee: '',
location: '',
gameplay: '',
minParticipants: 1,
maxParticipants: 4,
ntpLevel: [2.0, 4.0],
additionalRequirements: '',
autoDegrade: false
start_time: newStartTime,
end_time: getEndTime(newStartTime)
}])
}

View File

@@ -2,8 +2,11 @@ import React, { useState } from 'react'
import { View, Text } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { ImageUpload, Range, TimeSelector, TextareaTag, SelectStadium, NumberInterval, TitleInput, FormBasicInfo, FormSwitch } from '../../components'
import { type Stadium, type CoverImage } from '../../components/index.types'
import { ImageUpload, Range, TimeSelector, TextareaTag, NumberInterval, TitleTextarea, FormSwitch } from '../../components'
import { SelectStadium } from './components/SelectStadium'
import FormBasicInfo from './components/FormBasicInfo'
import { type CoverImage } from '../../components/index.types'
import { Stadium } from './components/SelectStadium'
import { FormFieldConfig, FieldType } from '../../config/formSchema/publishBallFormSchema'
import { PublishBallFormData } from '../../../types/publishBall';
@@ -11,7 +14,7 @@ import styles from './index.module.scss'
// 组件映射器
const componentMap = {
[FieldType.TEXT]: TitleInput,
[FieldType.TEXT]: TitleTextarea,
[FieldType.TIMEINTERVAL]: TimeSelector,
[FieldType.RANGE]: Range,
[FieldType.TEXTAREATAG]: TextareaTag,
@@ -95,7 +98,7 @@ const PublishForm: React.FC<{
const Component = componentMap[item.type]
const optionProps = {
...item.props,
...(item.key === 'additionalRequirements' ? { options: item.options } : {}),
...(item.type === FieldType.TEXTAREATAG ? { options: item.options } : {}),
...(item.props?.className ? { className: styles[item.props.className] } : {})
}
console.log(item.props?.className)
@@ -120,15 +123,10 @@ const PublishForm: React.FC<{
{/* 费用地点玩法区域 - 合并白色块 */}
<View className={styles['bg-section']}>
<FormBasicInfo
fee={formData.fee}
location={formData.location}
gameplay={formData.gameplay}
selectedStadium={selectedStadium}
children={item.children || []}
onFeeChange={(value) => updateFormData('fee', value)}
onLocationChange={(value) => updateFormData('location', value)}
onGameplayChange={(value) => updateFormData('gameplay', value)}
onStadiumSelect={() => setShowStadiumSelector(true)}
value={formData[item.key]}
onChange={(value) => updateFormData(item.key as keyof PublishBallFormData, value)}
{...optionProps}
/>
</View>
</>

44
src/utils/timeUtils.ts Normal file
View File

@@ -0,0 +1,44 @@
import dayjs from 'dayjs'
/**
* 获取下一个整点时间
* @returns 格式为 YYYY-MM-DD HH:mm 的字符串
*/
export const getNextHourTime = (): string => {
const now = dayjs()
const nextHour = now.add(1, 'hour').startOf('hour')
return nextHour.format('YYYY-MM-DD HH:mm')
}
/**
* 根据开始时间计算结束时间2小时后
* @param startTime 开始时间,格式为 YYYY-MM-DD HH:mm
* @returns 格式为 YYYY-MM-DD HH:mm 的字符串
*/
export const getEndTime = (startTime: string): string => {
const startDateTime = dayjs(startTime)
const endDateTime = startDateTime.add(2, 'hour')
return endDateTime.format('YYYY-MM-DD HH:mm')
}
export const getDate = (date: string): string => {
return dayjs(date).format('YYYY年MM月DD日')
}
export const getTime = (time: string): string => {
const timeObj = dayjs(time)
const hour = timeObj.hour()
const minute = timeObj.minute()
// 判断是上午还是下午
const period = hour < 12 ? 'AM' : 'PM'
// 转换为12小时制
const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour
// 格式化分钟,保证两位数
const minuteStr = minute.toString().padStart(2, '0')
return `${hour12}:${minuteStr} ${period}`
}

View File

@@ -1,15 +1,38 @@
import { TimeRange } from "@/components/index.types"
export interface PublishBallFormData {
title: string
timeRange: TimeRange
fee: string
location: string
gameplay: string
minParticipants: number
maxParticipants: number
ntpLevel: number[]
additionalRequirements: string
autoDegrade: boolean
title: string // 球局标题
image_list: Array<string>[] // 球局封面
timeRange: {
start_time: string,
end_time: string
}
activityInfo: {
play_type: string // 玩法类型
price: number | string // 价格
venue_id?: number | null // 场地id
location_name?: string // 场地名称
location?: string // 场地地址
latitude?: string // 纬度
longitude?: string // 经度
court_type?: string // 场地类型 1: 室内 2: 室外
court_surface?: string // 场地表面 1: 硬地 2: 红土 3: 草地
venue_description_tag?: Array<string>[] // 场地描述标签
venue_description?: string // 场地描述
venue_image_list?: Array<string>[] // 场地图片
}
players:{
current_players: number // 当前人数
max_players: number // 最大人数
}
skill_level: {
skill_level_max: number // 最高水平要求(NTRP)
skill_level_min: number //最低水平要求(NTRP)
}
descriptionInfo: {
description: string // 备注
description_tag: Array<string>[] // 备注标签
}
is_substitute_supported: boolean // 是否支持替补
is_wechat_contact: boolean // 是否需要微信联系
wechat_contact: string // 微信联系
}

View File

@@ -4099,6 +4099,11 @@ data-view-byte-offset@^1.0.1:
es-errors "^1.3.0"
is-data-view "^1.0.1"
dayjs@^1.11.13:
version "1.11.13"
resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
debug@2.6.9:
version "2.6.9"
resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"