diff --git a/src/app.config.ts b/src/app.config.ts index 29bdd35..6ea5cc9 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -3,7 +3,7 @@ export default defineAppConfig({ 'pages/login/index/index', 'pages/login/verification/index', 'pages/login/terms/index', - // 'pages/publishBall/index', + 'pages/publishBall/index', // 'pages/mapDisplay/index', // 'pages/list/index', 'pages/index/index', diff --git a/src/components/UploadCover/index.scss b/src/components/UploadCover/index.scss index e69de29..ff76d06 100644 --- a/src/components/UploadCover/index.scss +++ b/src/components/UploadCover/index.scss @@ -0,0 +1,127 @@ +@use '~@/scss/images.scss' as img; +@use '~@/scss/themeColor.scss' as theme; + +.upload-cover-root { + display: flex; + width: 100%; + height: 112px; + margin-bottom: 8px; + position: relative; + align-items: flex-end; + + &.upload-cover-act-center { + justify-content: center; + } + + .upload-cover-act { + display: flex; + width: 108px; + height: 108px; + padding: 16px 12px 10px 12px; + margin-top: 4px; + box-sizing: border-box; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 8px; + border-radius: 20px; + border: 1px dashed rgba(0, 0, 0, 0.12); + background: theme.$page-background-color; + box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06); + z-index: 1; + + .upload-cover-act-icon { + width: 20px; + height: 20px; + } + + .upload-cover-text { + color: var(--Labels-Secondary, var(--Labels-Secondary, rgba(60, 60, 67, 0.60))); + font-feature-settings: 'liga' off, 'clig' off; + font-family: "PingFang SC"; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 166.667% */ + } + } + + .cover-image-list-container { + position: absolute; + left: 114px; + top: 0; + width: calc(100% - 114px); + overflow-x: scroll; + height: 112px; + + &.full { + left: 0; + width: 100%; + } + + .cover-image-list { + width: auto; + height: 112px; + display: flex; + gap: 6px; + justify-content: flex-start; + align-items: flex-end; + flex-wrap: nowrap; + flex-shrink: 0; + flex-grow: 0; + + .cover-image-item { + display: flex; + width: 108px; + height: 108px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 8px; + border-radius: 20px; + position: relative; + border: 1px solid rgba(0, 0, 0, 0.12); + box-sizing: border-box; + + .cover-image-item-image { + width: 100%; + height: 100%; + aspect-ratio: 1/1; + border-radius: 20px; + } + + .cover-image-item-delete { + position: absolute; + top: -4px; + right: -4px; + width: 16px; + height: 16px; + } + } + } + } +} + +.upload-source-popup-container { + width: 100%; + height: auto; + display: flex; + flex-direction: column; + padding: 26px 0; + box-sizing: border-box; +} + +.upload-source-popup-item { + display: flex; + width: 100%; + height: 56px; + padding: 16px 24px; + box-sizing: border-box; + justify-content: center; + align-items: center; + border-bottom: 1px solid rgba(0, 0, 0, 0.12); + + &:last-child { + border-bottom: none; + } +} \ No newline at end of file diff --git a/src/components/UploadCover/index.tsx b/src/components/UploadCover/index.tsx index 426b037..5698f0a 100644 --- a/src/components/UploadCover/index.tsx +++ b/src/components/UploadCover/index.tsx @@ -1,22 +1,128 @@ -import React, { useState } from 'react' -import { Popup } from "@nutui/nutui-react-taro"; +import React, { useCallback, useState } from 'react' +import { Image, View, Text } from '@tarojs/components' +import img from '../../config/images' +import UploadSourcePopup from './upload-source-popup' +import UploadFromWx from './upload-from-wx' +import { CommonPopup } from '../' import './index.scss' +import { uploadFileResponseData } from '@/services/uploadFiles' -export default function UploadCover(props) { - const { value = [], onChange = () => {} } = props +export type sourceType = 'album' | 'history' | 'preset' + +export type source = sourceType[] + +export type CoverImageValue = { + id: string + url: string + tempFilePath?: string +} + +export interface UploadCoverProps { + value: CoverImageValue[] + onChange: (value: CoverImageValue[] | ((prev: CoverImageValue[]) => CoverImageValue[]) +) => void + source: source + maxCount: number +} + +// const values = [ +// 'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/1a35ebbf-2361-44da-b338-7608561d0b31.png', +// 'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/cf5a82ba-90af-4138-a1b3-9119adcde9e0.png', +// 'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/49d7cdf0-b03c-4a0f-91c6-e7778080cfcd.png' +// ] + +const mergeCoverImages = (value: CoverImageValue[], images: CoverImageValue[]) => { + console.log(value, images, 11111) + // 根据id来更新url, 如果id不存在,则添加到value中 + const newImages = images + const updatedValue = value.map(item => { + const index = images.findIndex(image => image.id === item.id) + if (index !== -1) { + newImages.splice(index, 1) + return { ...item, url: images[index].url } + } + return item + }) + return [...updatedValue, ...newImages] +} + +export default function UploadCover(props: UploadCoverProps) { + const { + value = [], + onChange = () => void 0, + source = ['album', 'history', 'preset'], + maxCount = 9, + } = props const [visible, setVisible] = useState(false) + const onAdd = useCallback((images: CoverImageValue[]) => { + onChange(prev => mergeCoverImages(prev, images)) + setVisible(false) + }, [value]) + + const onWxAdd = useCallback((images: CoverImageValue[], onFileUploaded: Promise<{ id: string, data: uploadFileResponseData }[]>) => { + onAdd(images) + onFileUploaded.then(res => { + console.log(res, 11111) + onAdd(res.map(item => ({ + id: item.id, + url: item.data.file_path, + }))) + }) + }, [onAdd]) + const onDelete = (image: CoverImageValue) => { + onChange(value.filter(item => item.id !== image.id)) + } + return ( <> - setVisible(false)} round - closeable + position="bottom" + hideFooter > -
-
上传封面
+ + { + source.map((item) => { + return ( + + { + item === 'album' ? ( + + ) : ( + + ) + } + + ) + }) + } + + +
+ {value.length < maxCount && ( +
setVisible(true)}> + +
添加活动封面
+
+ )} +
+
+ { + value.map((item) => { + return ( + + + onDelete(item)} /> + + ) + }) + } +
+
); diff --git a/src/components/UploadCover/upload-from-wx.scss b/src/components/UploadCover/upload-from-wx.scss new file mode 100644 index 0000000..195c5b6 --- /dev/null +++ b/src/components/UploadCover/upload-from-wx.scss @@ -0,0 +1,11 @@ +.upload-from-wx-text { + width: 100%; + height: 56px; + padding: 16px 24px; + box-sizing: border-box; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 24px; + text-align: center; +} \ No newline at end of file diff --git a/src/components/UploadCover/upload-from-wx.tsx b/src/components/UploadCover/upload-from-wx.tsx new file mode 100644 index 0000000..05c0351 --- /dev/null +++ b/src/components/UploadCover/upload-from-wx.tsx @@ -0,0 +1,47 @@ +import React from 'react' +import { View, Text } from '@tarojs/components' +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 + maxCount: number +} + +export default function UploadFromWx(props: UploadFromWxProps) { + const { + onAdd = () => void 0, + maxCount = 9, // calc from parent + } = props + const handleImportFromWx = () => { + Taro.chooseImage({ + count: maxCount, + 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, + description: 'test', + tags: 'test', + is_public: 1 as unknown as 0 | 1, + id: (Date.now() + count++).toString(), + })) + const onFileUploaded = uploadApi.batchUpload(files) + onAdd(res.tempFiles.map(item => ({ + id: Date.now().toString(), + url: item.path, + })), onFileUploaded) // TODO: add loading state + }) + } + return ( + + 从相册添加 + + ) +} \ No newline at end of file diff --git a/src/components/UploadCover/upload-source-popup.scss b/src/components/UploadCover/upload-source-popup.scss new file mode 100644 index 0000000..999c934 --- /dev/null +++ b/src/components/UploadCover/upload-source-popup.scss @@ -0,0 +1,156 @@ +@use '~@/scss/themeColor.scss' as theme; + +.upload-source-popup-text { + width: 100%; + height: 56px; + padding: 16px 24px; + box-sizing: border-box; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 24px; + text-align: center; +} + +.upload-popup { + width: 100%; + padding: 26px 0; + box-sizing: border-box; + + .upload-popup-title { + display: flex; + padding: 18px 20px 10px 20px; + align-items: center; + align-self: stretch; + font-family: "PingFang SC"; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 24px; /* 150% */ + letter-spacing: -0.23px; + } + + .upload-popup-scroll-view { + max-height: calc(100vh - 260px); + overflow-y: auto; + + .upload-popup-image-list { + width: 100%; + padding: 0 16px; + box-sizing: border-box; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px 10px; + + .upload-popup-image-item { + aspect-ratio: 1/1; + border-radius: 9px; + border: 1px solid rgba(0, 0, 0, 0.12); + box-sizing: border-box; + background: rgba(0, 0, 0, 0.06); + margin: 0; + position: relative; + + &:not(.selected) { + &.disabled { + opacity: 0.5; + pointer-events: none; + cursor: not-allowed; + } + } + + .upload-popup-image-item-image { + width: 100%; + height: 100%; + border-radius: 9px; + margin: 0; + } + + .upload-popup-image-item-select { + position: absolute; + top: 5px; + right: 5px; + width: 14px; + height: 14px; + color: #fff; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + + &.selected { + background: rgba(0, 0, 0, 0.5); + + .select-image-icon { + width: 7px; + height: 7px; + } + } + } + + .select-image-icon { + width: 14px; + height: 14px; + } + } + } + + .upload-popup-image-list-empty { + width: 100%; + height: 60vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + } + + .upload-popup-image-list-empty-image { + width: 80%; + aspect-ratio: 4/3; + height: auto; + } + + .upload-popup-image-list-empty-text { + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 24px; /* 150% */ + letter-spacing: -0.23px; + } + } + + .upload-popup-footer { + display: flex; + width: 100%; + height: 62px; + padding: 8px 10px 10px 10px; + box-sizing: border-box; + justify-content: center; + align-items: flex-start; + gap: 8px; + + .upload-popup-footer-cancel, .upload-popup-footer-confirm { + font-feature-settings: 'liga' off, 'clig' off; + font-family: "PingFang SC"; + box-sizing: border-box; + height: 44px; + border-radius: 12px; + border: 1px solid rgba(0, 0, 0, 0.12); + flex: 1; + } + + .upload-popup-footer-cancel { + background: theme.$page-background-color; + } + + .upload-popup-footer-confirm { + background: theme.$primary-color; + color: rgba(255, 255, 255, 0.5); + + &.active { + color: #fff; + } + } + } +} \ No newline at end of file diff --git a/src/components/UploadCover/upload-source-popup.tsx b/src/components/UploadCover/upload-source-popup.tsx new file mode 100644 index 0000000..ccb156f --- /dev/null +++ b/src/components/UploadCover/upload-source-popup.tsx @@ -0,0 +1,161 @@ +import React, { useState, useEffect } from 'react' +import { Image, View, Text, ScrollView, Button } from '@tarojs/components' +import Taro from '@tarojs/taro' +import img from '../../config/images' +import publishService from '../../services/publishService' +import { CommonPopup } from '../' +import emptyStatus from '../../static/emptyStatus/publish-empty.png' + +import './upload-source-popup.scss' + +type SourceType = 'history' | 'preset' + +type ImageItem = { + id: string + url: string + tempFilePath?: string +} + +interface UploadImageProps { + sourceType: SourceType + onAdd: (images: ImageItem[]) => void + maxCount: number +} + +const sourceMap = new Map([ + ['history', '历史图库'], + ['preset', '预设图库'] +]) + +const checkImageSelected = (images: ImageItem[], image: ImageItem) => { + return images.some(item => item.id === image.id) +} + +export default function UploadImage(props: UploadImageProps) { + const { + sourceType = 'history', + onAdd = () => void 0, + maxCount = 9, + } = props + const [visible, setVisible] = useState(false) + const [images, setImages] = useState([]) + const [selectedImages, setSelectedImages] = useState([]) + + const handleImageClick = (image: ImageItem) => { + if (checkImageSelected(selectedImages, image)) { + setSelectedImages(selectedImages.filter(item => item.id !== image.id)) + } else if (!outOfMax) { + setSelectedImages([...selectedImages, image]) + } else { + Taro.showToast({ + title: `最多选择${maxCount}张图片`, + icon: 'none' + }) + } + } + + useEffect(() => { + if (visible) { + publishService.getPictures({ + pageOption: { + page: 1, + pageSize: 100, + }, + seachOption: { + tag: '', + resource_type: 'image', + dateRange: [], + }, + }).then(res => { + if (res.success) { + setImages(res.data.data.rows.map(item => ({ + id: Date.now().toString(), + url: item.thumbnail_url, + }))) + } else { + // TODO: 显示错误信息 + Taro.showToast({ + title: res.message, + icon: 'none' + }) + } + }) + } else { + setSelectedImages([]) + } + }, [visible]) + + const handleConfirm = () => { + if (selectedImages.length > 0) { + onAdd(selectedImages) + setVisible(false) + } else { + Taro.showToast({ + title: '请选择图片', + icon: 'none' + }) + } + } + + const outOfMax = selectedImages.length >= maxCount + + return ( + <> + setVisible(false)} + round + hideFooter + position='bottom' + > + + {sourceMap.get(sourceType)} + {/* TODO: 分页 加载更多 */} + {/* TODO: 图片加载失败 */} + {/* TODO: 图片加载中 */} + + {images.length > 0 ? ( + + {images.map(item => { + const isSelected = checkImageSelected(selectedImages, item) + return ( + handleImageClick(item)}> + + + {isSelected ? ( + + ) : ( + + )} + + + ) + })} + + ) : ( + + + 暂无内容 + + )} + + {images.length > 0 ? ( + + + + + ) : ( + + + + )} + + + setVisible(true)}>{sourceMap.get(sourceType)}选取 + + ); +}; + diff --git a/src/components/index.ts b/src/components/index.ts index cc5f816..6adf6c5 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -9,19 +9,21 @@ import { SelectStadium, StadiumDetail } from './SelectStadium' import TimeSelector from './TimeSelector' import TitleInput from './TitleInput' import CommonPopup from './CommonPopup' +import UploadCover from './UploadCover' -export { - ActivityTypeSwitch, - TextareaTag, +export { + ActivityTypeSwitch, + TextareaTag, FormSwitch, - ImageUpload, + ImageUpload, FormBasicInfo, - Range, - NumberInterval, - SelectStadium, - TimeSelector, + Range, + NumberInterval, + SelectStadium, + TimeSelector, TitleInput, StadiumDetail, - CommonPopup + CommonPopup, + UploadCover } diff --git a/src/config/images.js b/src/config/images.js index 2c7d7b0..7d8921a 100644 --- a/src/config/images.js +++ b/src/config/images.js @@ -8,8 +8,8 @@ export default { ICON_COST: require('@/static/publishBall/icon-cost.svg'), ICON_TIPS: require('@/static/publishBall/icon-tips.svg'), ICON_ARROW_RIGHT: require('@/static/publishBall/icon-arrow-right.svg'), - ICON_ARROW_LEFT: require('@/static/publishBall/icon-arrow-left.svg'), - ICON_LOGO_GO: require('@/static/publishBall/icon-logo-go.svg'), + ICON_ARROW_LEFT: require('@/static/detail/icon-arrow-left.svg'), + ICON_LOGO_GO: require('@/static/detail/icon-logo-go.svg'), ICON_SEARCH: require('@/static/publishBall/icon-search.svg'), ICON_MAP: require('@/static/publishBall/icon-map.svg'), ICON_STADIUM: require('@/static/publishBall/icon-stadium.svg'), @@ -18,5 +18,8 @@ export default { ICON_HEART_CIRCLE: require('@/static/publishBall/icon-heartcircle.png'), ICON_ADD: require('@/static/publishBall/icon-add.svg'), ICON_COPY: require('@/static/publishBall/icon-arrow-right.svg'), - ICON_DELETE: require('@/static/publishBall/icon-delete.svg') + ICON_DELETE: require('@/static/publishBall/icon-delete.svg'), + ICON_CIRCLE_UNSELECT: require('@/static/publishBall/icon-circle-unselect.svg'), + ICON_CIRCLE_SELECT: require('@/static/publishBall/icon-circle-select-ring.svg'), + ICON_CIRCLE_SELECT_ARROW: require('@/static/publishBall/icon-circle-select-arrow.svg'), } \ No newline at end of file diff --git a/src/pages/detail/index.scss b/src/pages/detail/index.scss index ce17fc4..267fa4a 100644 --- a/src/pages/detail/index.scss +++ b/src/pages/detail/index.scss @@ -1,3 +1,5 @@ +@use '~@/scss/images.scss' as img; + .detail-page { width: 100%; height: 100%; @@ -24,6 +26,10 @@ display: flex; align-items: center; + .detail-navigator-back { + border-right: 1px solid #444; + } + .detail-navigator-back, .detail-navigator-icon { height: 20px; width: 50%; @@ -31,7 +37,13 @@ display: flex; justify-content: center; - & > svg { + & > .detail-navigator-back-icon { + width: 20px; + height: 20px; + color: #fff; + } + + & > .detail-navigator-logo-icon { width: 20px; height: 20px; color: #fff; diff --git a/src/pages/detail/index.tsx b/src/pages/detail/index.tsx index 7c94d32..7693a67 100644 --- a/src/pages/detail/index.tsx +++ b/src/pages/detail/index.tsx @@ -9,6 +9,7 @@ import { useUserStats, useUserActions } from '../../store/userStore' +import img from '../../config/images' import { getTextColorOnImage } from '../../utils/processImage' import './index.scss' @@ -66,18 +67,10 @@ function Index() { - - - - + - - - - - - + {/* 我的自定义标题 */} diff --git a/src/pages/publishBall/publishForm.tsx b/src/pages/publishBall/publishForm.tsx index cc7e1cc..eaae5bb 100644 --- a/src/pages/publishBall/publishForm.tsx +++ b/src/pages/publishBall/publishForm.tsx @@ -2,7 +2,7 @@ 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 { ImageUpload, Range, TimeSelector, TextareaTag, SelectStadium, NumberInterval, TitleInput, FormBasicInfo, FormSwitch, UploadCover } from '../../components' import { type Stadium, type CoverImage } from '../../components/index.types' import { FormFieldConfig, FieldType } from '../../config/formSchema/publishBallFormSchema' import { PublishBallFormData } from '../../../types/publishBall'; @@ -21,17 +21,21 @@ const componentMap = { [FieldType.CHECKBOX]: FormSwitch, } -const PublishForm: React.FC<{ - formData: PublishBallFormData, - onChange: (key: keyof PublishBallFormData, value: any, index?: number) => void, +const PublishForm: React.FC<{ + formData: PublishBallFormData, + onChange: (key: keyof PublishBallFormData, value: any, index?: number) => void, optionsConfig: FormFieldConfig[] }> = ({ formData, onChange, optionsConfig }) => { const [coverImages, setCoverImages] = useState([]) const [showStadiumSelector, setShowStadiumSelector] = useState(false) const [selectedStadium, setSelectedStadium] = useState(null) // 处理封面图片变化 - const handleCoverImagesChange = (images: CoverImage[]) => { - setCoverImages(images) + const handleCoverImagesChange = (fn: (images: CoverImage[]) => CoverImage[]) => { + if (fn instanceof Function) { + setCoverImages(fn(coverImages)) + } else { + setCoverImages(fn) + } } // 更新表单数据 @@ -80,7 +84,7 @@ const PublishForm: React.FC<{ // TODO: 实现提交逻辑 console.log('提交数据:', { coverImages, formData }) - + Taro.showToast({ title: '发布成功', icon: 'success' @@ -102,8 +106,8 @@ const PublishForm: React.FC<{ console.log(optionProps, item.label, formData[item.key]); if (item.type === FieldType.UPLOADIMAGE) { /* 活动封面 */ - return @@ -149,7 +153,7 @@ const PublishForm: React.FC<{ value={formData[item.key]} onChange={(value) => updateFormData(item.key as keyof PublishBallFormData, value)} {...optionProps} - placeholder={item.placeholder} + placeholder={item.placeholder} /> diff --git a/src/scss/images.scss b/src/scss/images.scss index 32d40b4..a863e16 100644 --- a/src/scss/images.scss +++ b/src/scss/images.scss @@ -16,7 +16,20 @@ $-images: ( 'icon-personal': '/publishBall/icon-personal.svg', 'icon-changda': '/publishBall/icon-changda.svg', 'icon-cost': '/publishBall/icon-cost.svg', - 'icon-remove': '/publishBall/icon-remove.svg' + 'icon-remove': '/publishBall/icon-remove.svg', + 'icon-arrow-left': '/detail/icon-arrow-left.svg', + 'icon-logo-go': '/detail/icon-logo-go.svg', + 'icon-search': '/publishBall/icon-search.svg', + 'icon-map': '/publishBall/icon-map.svg', + 'icon-stadium': '/publishBall/icon-stadium.svg', + 'icon-arrow-small': '/publishBall/icon-arrow-small.svg', + 'icon-map-search': '/publishBall/icon-map-search.svg', + 'icon-heartcircle': '/publishBall/icon-heartcircle.png', + 'icon-copy': '/publishBall/icon-arrow-right.svg', + 'icon-delete': '/publishBall/icon-delete.svg', + 'icon-circle-unselect': '/publishBall/icon-circle-unselect.svg', + 'icon-circle-select-ring': '/publishBall/icon-circle-select-ring.svg', + 'icon-circle-select-arrow': '/publishBall/icon-circle-select-arrow.svg', ) !default; // 图片获取函数 diff --git a/src/services/detailApi.ts b/src/services/detailApi.ts new file mode 100644 index 0000000..e971bf0 --- /dev/null +++ b/src/services/detailApi.ts @@ -0,0 +1,41 @@ +import httpService from './httpService' +import type { ApiResponse } from './httpService' + +// 用户接口 +export interface GameDetail { + id: number, + title: string, + venue_id: number, + creator_id: number, + game_date: string, + start_time: string, + end_time: string, + max_participants: number, + current_participants: number, + ntrp_level: string, + play_style: string, + description: string, + status: string, + created_at: string, + updated_at: string, +} + +// 响应接口 +export interface Response { + code: string + message: string + data: GameDetail +} + +// 发布球局类 +class GameDetailService { + // 用户登录 + async getDetail(id: number): Promise> { + return httpService.post('/games/detail', { id }, { + showLoading: true, + }) + } +} + +// 导出认证服务实例 +export default new GameDetailService() \ No newline at end of file diff --git a/src/services/httpService.ts b/src/services/httpService.ts index 9b7a5fd..fcee8f3 100644 --- a/src/services/httpService.ts +++ b/src/services/httpService.ts @@ -15,6 +15,7 @@ export interface RequestConfig { needAuth?: boolean // 是否需要token认证 showLoading?: boolean // 是否显示加载提示 loadingText?: string // 加载提示文本 + showToast?: boolean // 是否显示toast } // 响应数据接口 @@ -58,7 +59,7 @@ class HttpService { // 构建完整URL private buildUrl(url: string, params?: Record): string { const fullUrl = url.startsWith('http') ? url : `${this.baseURL}${url}` - + if (params) { const searchParams = new URLSearchParams() Object.entries(params).forEach(([key, value]) => { @@ -69,7 +70,7 @@ class HttpService { const queryString = searchParams.toString() return queryString ? `${fullUrl}?${queryString}` : fullUrl } - + return fullUrl } @@ -95,7 +96,7 @@ class HttpService { const logMethod = console[level] || console.log const timestamp = new Date().toLocaleTimeString() - + if (data) { logMethod(`[${timestamp}] HTTP ${level.toUpperCase()}: ${message}`, data) } else { @@ -165,9 +166,9 @@ class HttpService { // 处理业务错误 private handleBusinessError(data: any): void { const message = data.message || '操作失败' - + this.log('error', `业务错误: ${message}`, data) - + Taro.showToast({ title: message, icon: 'none', @@ -187,7 +188,7 @@ class HttpService { } = config const fullUrl = this.buildUrl(url, method === 'GET' ? params : undefined) - + this.log('info', `发起请求: ${method} ${fullUrl}`, { data: method !== 'GET' ? data : undefined, params: method === 'GET' ? params : undefined @@ -223,18 +224,18 @@ class HttpService { return this.handleResponse(response) } catch (error) { this.log('error', '请求失败', error) - + // 在模拟模式下返回模拟数据 if (envConfig.enableMock && isDevelopment()) { this.log('info', '使用模拟数据') return this.getMockResponse(url, method) } - + Taro.showToast({ title: '网络连接失败', icon: 'none' }) - + throw error } finally { // 隐藏加载提示 @@ -247,7 +248,7 @@ class HttpService { // 获取模拟数据 private getMockResponse(url: string, method: string): ApiResponse { this.log('info', `返回模拟数据: ${method} ${url}`) - + return { code: 200, success: true, @@ -323,4 +324,4 @@ class HttpService { } // 导出HTTP服务实例 -export default new HttpService() \ No newline at end of file +export default new HttpService() \ No newline at end of file diff --git a/src/services/publishService.ts b/src/services/publishService.ts index 9e73c87..db87e42 100644 --- a/src/services/publishService.ts +++ b/src/services/publishService.ts @@ -16,6 +16,12 @@ export interface PublishBallData { description: string, } +export interface createGameData extends PublishBallData { + status: string, + created_at: string, + updated_at: string, +} + // 响应接口 export interface Response { code: string @@ -23,16 +29,74 @@ export interface Response { data: any } +// export type SourceType = 'history' | 'preset' + +export interface getPicturesReq { + pageOption: { + page: number, + pageSize: number, + }, + seachOption: { + tag: string, + resource_type: string, + dateRange: string[], + }, +} + +export interface getPicturesRes { + code: number, + message: string, + data: { + rows: [ + { + user_id: string, + resource_type: string, + file_name: string, + original_name: string, + file_path: string, + file_url: string, + file_size: number, + mime_type: string, + width: number, + height: number, + duration: number, + thumbnail_url: string, + is_public: string, + tags: string[], + description: string, + view_count: number, + download_count: number, + status: string, + last_modify_time: string, + } + ], + count: number, + page: number, + pageSize: number, + totalPages: number, + } +} + +function delay(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)) +} // 发布球局类 class PublishService { // 用户登录 - async createPersonal(data: PublishBallData): Promise> { + async createPersonal(data: PublishBallData): Promise> { return httpService.post('/games/create', data, { showLoading: true, loadingText: '发布中...' }) } + + async getPictures(req: getPicturesReq): Promise> { + return httpService.post('/gallery/sys_img_list', req, { + showLoading: true, + showToast: false, + }) + } } // 导出认证服务实例 -export default new PublishService() \ No newline at end of file +export default new PublishService() \ No newline at end of file diff --git a/src/services/uploadFiles.ts b/src/services/uploadFiles.ts new file mode 100644 index 0000000..ba5a2eb --- /dev/null +++ b/src/services/uploadFiles.ts @@ -0,0 +1,75 @@ +import httpService from './httpService' +import type { ApiResponse } from './httpService' +import Taro from '@tarojs/taro' +import envConfig from '@/config/env' + +// 用户接口 +export interface UploadFilesData { + id: string, + filePath: string, + description?: string, + tags?: string, + is_public?: 0 | 1, +} + +// {"code":0,"message":"请求成功!","data":{"tags":["test"],"create_time":"2025-08-24 22:51:03","last_modify_time":"2025-08-24 22:51:03","duration":"0","thumbnail_url":"","view_count":"0","download_count":"0","id":16,"user_id":1,"resource_type":"image","file_name":"front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","original_name":"QyoUvEsLG6ci57c7e25cca0845dafed3ee1fde07876d.png","file_path":"http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","file_url":"http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","file_size":17756,"mime_type":"image/png","description":"test","is_public":"1","status":"active","width":0,"height":0,"uploadInfo":{"success":true,"name":"front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","path":"http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","ossPath":"http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","fileType":"image/png","fileSize":17756,"originalName":"QyoUvEsLG6ci57c7e25cca0845dafed3ee1fde07876d.png","suffix":"png","storagePath":"front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png"}}} + +export interface uploadFileResponse { + code: number, + message: string, + data: uploadFileResponseData, +} + +export interface uploadFileResponseData { + id: number, + user_id: number, + file_name: string, + original_name: string, + file_path: string, + file_url: string, + file_size: number, + resource_type: string, + mime_type: string, + description: string, + tags: string[], + is_public: string, + view_count: number, + download_count: number, + created_at: string, + updated_at: string, +} + +function delay(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)) +} +// 发布球局类 +class UploadApi { + async upload(req: UploadFilesData): Promise<{ id: string, data: uploadFileResponseData }> { + // return httpService.post('/files/upload', req, { + // showLoading: true, + // }) + const { id, ...rest } = req + return Taro.uploadFile({ + url: `${envConfig.apiBaseURL}/api/gallery/upload`, + filePath: rest.filePath, + name: 'file', + formData: { + description: rest.description, + tags: rest.tags, + is_public: rest.is_public, + } + }).then(res => { + return { + id, + data: JSON.parse(res.data).data, + } + }) + } + + async batchUpload(req: UploadFilesData[]): Promise<{ id: string, data: uploadFileResponseData }[]> { + return Promise.all(req.map(item => this.upload(item))) + } +} + +// 导出认证服务实例 +export default new UploadApi() \ No newline at end of file diff --git a/src/static/publishBall/icon-arrow-left.svg b/src/static/detail/icon-arrow-left.svg similarity index 100% rename from src/static/publishBall/icon-arrow-left.svg rename to src/static/detail/icon-arrow-left.svg diff --git a/src/static/publishBall/icon-logo-go!.svg b/src/static/detail/icon-logo-go.svg similarity index 100% rename from src/static/publishBall/icon-logo-go!.svg rename to src/static/detail/icon-logo-go.svg diff --git a/src/static/emptyStatus/comment-empty.png b/src/static/emptyStatus/comment-empty.png new file mode 100644 index 0000000..ebe1e82 Binary files /dev/null and b/src/static/emptyStatus/comment-empty.png differ diff --git a/src/static/emptyStatus/comment-failed.png b/src/static/emptyStatus/comment-failed.png new file mode 100644 index 0000000..4c6ff52 Binary files /dev/null and b/src/static/emptyStatus/comment-failed.png differ diff --git a/src/static/emptyStatus/publish-empty.png b/src/static/emptyStatus/publish-empty.png new file mode 100644 index 0000000..8b41a26 Binary files /dev/null and b/src/static/emptyStatus/publish-empty.png differ diff --git a/src/static/emptyStatus/publish-failed.png b/src/static/emptyStatus/publish-failed.png new file mode 100644 index 0000000..09b8777 Binary files /dev/null and b/src/static/emptyStatus/publish-failed.png differ diff --git a/src/static/publishBall/icon-circle-select-arrow.svg b/src/static/publishBall/icon-circle-select-arrow.svg new file mode 100644 index 0000000..196e4ed --- /dev/null +++ b/src/static/publishBall/icon-circle-select-arrow.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/static/publishBall/icon-circle-select-ring.svg b/src/static/publishBall/icon-circle-select-ring.svg new file mode 100644 index 0000000..61d19c5 --- /dev/null +++ b/src/static/publishBall/icon-circle-select-ring.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/static/publishBall/icon-circle-unselect.svg b/src/static/publishBall/icon-circle-unselect.svg new file mode 100644 index 0000000..aae86ec --- /dev/null +++ b/src/static/publishBall/icon-circle-unselect.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file