diff --git a/project.private.config.json b/project.private.config.json
new file mode 100644
index 0000000..2c19aa8
--- /dev/null
+++ b/project.private.config.json
@@ -0,0 +1,23 @@
+{
+ "libVersion": "3.9.0",
+ "projectname": "playBallTogether",
+ "condition": {},
+ "setting": {
+ "urlCheck": true,
+ "coverView": true,
+ "lazyloadPlaceholderEnable": false,
+ "skylineRenderEnable": false,
+ "preloadBackgroundData": false,
+ "autoAudits": false,
+ "useApiHook": true,
+ "useApiHostProcess": true,
+ "showShadowRootInWxmlPanel": false,
+ "useStaticServer": false,
+ "useLanDebug": false,
+ "showES6CompileOption": false,
+ "compileHotReLoad": true,
+ "checkInvalidKey": true,
+ "ignoreDevUnusedFiles": true,
+ "bigPackageSizeSupport": false
+ }
+}
\ No newline at end of file
diff --git a/src/app.config.ts b/src/app.config.ts
index 7520adf..901708f 100644
--- a/src/app.config.ts
+++ b/src/app.config.ts
@@ -1,15 +1,14 @@
export default defineAppConfig({
pages: [
'pages/list/index',
- 'pages/publishBall/index',
// 'pages/userInfo/myself/index',
- // 'pages/login/index/index',
- // 'pages/login/verification/index',
- // 'pages/login/terms/index',
-
-
+ 'pages/login/index/index',
+ 'pages/login/verification/index',
+ 'pages/login/terms/index',
+ 'pages/publishBall/index',
// 'pages/mapDisplay/index',
- // 'pages/index/index'
+ 'pages/index/index',
+ 'pages/detail/index',
],
window: {
backgroundTextStyle: 'light',
diff --git a/src/components/UploadCover/index.scss b/src/components/UploadCover/index.scss
new file mode 100644
index 0000000..ff76d06
--- /dev/null
+++ 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
new file mode 100644
index 0000000..5698f0a
--- /dev/null
+++ b/src/components/UploadCover/index.tsx
@@ -0,0 +1,130 @@
+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 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
+ 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 a61fa13..16b7a31 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -9,17 +9,19 @@ import TimeSelector from './TimeSelector'
import TitleTextarea from './TitleTextarea'
import CommonPopup from './CommonPopup'
import DateTimePicker from './DateTimePicker/DateTimePicker'
+import UploadCover from './UploadCover'
-export {
- ActivityTypeSwitch,
- TextareaTag,
+export {
+ ActivityTypeSwitch,
+ TextareaTag,
FormSwitch,
- ImageUpload,
- Range,
- NumberInterval,
- TimeSelector,
+ ImageUpload,
+ Range,
+ NumberInterval,
+ TimeSelector,
TitleTextarea,
CommonPopup,
- DateTimePicker
+ DateTimePicker,
+ UploadCover,
}
diff --git a/src/config/images.js b/src/config/images.js
index f647a3d..5372a86 100644
--- a/src/config/images.js
+++ b/src/config/images.js
@@ -17,6 +17,8 @@ export default {
ICON_MENU_ITEM_SELECTED: require('@/static/list/icon-menu-item-selected.svg'),
ICON_ARROW_DOWN_WHITE: require('@/static/list/icon-arrow-down-white.svg'),
ICON_LIST_RIGHT_ARROW: require('@/static/list/icon-list-right-arrow.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'),
@@ -25,5 +27,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.config.ts b/src/pages/detail/index.config.ts
new file mode 100644
index 0000000..eaeff9d
--- /dev/null
+++ b/src/pages/detail/index.config.ts
@@ -0,0 +1,4 @@
+export default definePageConfig({
+ navigationBarTitleText: '球局详情',
+ navigationStyle: 'custom',
+})
diff --git a/src/pages/detail/index.scss b/src/pages/detail/index.scss
new file mode 100644
index 0000000..267fa4a
--- /dev/null
+++ b/src/pages/detail/index.scss
@@ -0,0 +1,102 @@
+@use '~@/scss/images.scss' as img;
+
+.detail-page {
+ width: 100%;
+ height: 100%;
+
+ .custom-navbar {
+ height: 56px; /* 通常与原生导航栏高度一致 */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ // background-color: #fff;
+ color: #000;
+ padding-top: 36px; /* 适配状态栏 */
+ }
+
+ .detail-navigator {
+ height: 30px;
+ width: 80px;
+ border-radius: 15px;
+ position: absolute;
+ left: 12px;
+ border: 1px solid #888;
+ box-sizing: border-box;
+ color: #fff;
+ display: flex;
+ align-items: center;
+
+ .detail-navigator-back {
+ border-right: 1px solid #444;
+ }
+
+ .detail-navigator-back, .detail-navigator-icon {
+ height: 20px;
+ width: 50%;
+
+ display: flex;
+ justify-content: center;
+
+ & > .detail-navigator-back-icon {
+ width: 20px;
+ height: 20px;
+ color: #fff;
+ }
+
+ & > .detail-navigator-logo-icon {
+ width: 20px;
+ height: 20px;
+ color: #fff;
+ }
+ }
+ }
+
+ .detail-page-bg {
+ width: 100%;
+ height: 100%;
+ position: fixed;
+ left: 0;
+ top: 0;
+ background-size: cover;
+ filter: blur(40px);
+ transform: scale(1.5);
+ z-index: -2;
+ width: calc(100% + 20px);
+ height: calc(100% + 20px);
+ margin: -10px;
+ }
+
+ .detail-page-bg-text {
+ width: 100%;
+ height: 100%;
+ position: fixed;
+ left: 0;
+ top: 0;
+ z-index: -1;
+ background-color: rgba(0, 0, 0, 0.3);
+ }
+
+ .detail-swiper {
+ height: 240px;
+ margin-top: 15px;
+ margin-left: 15px;
+ }
+
+ .detail-swiper-item {
+ overflow: visible;
+ height: 100%;
+
+ .detail-swiper-item-image {
+ width: 100%;
+ height: 100%;
+ border-radius: 12px;
+ transition: transform 0.5s;
+ }
+ }
+
+ .detail-text {
+ font-size: 40rpx;
+ margin-top: 20px;
+ transition: color 0.3s ease-in;
+ }
+}
diff --git a/src/pages/detail/index.tsx b/src/pages/detail/index.tsx
new file mode 100644
index 0000000..7693a67
--- /dev/null
+++ b/src/pages/detail/index.tsx
@@ -0,0 +1,110 @@
+import React, { useState, useEffect } from 'react'
+import { View, Text, Button, Swiper, SwiperItem, Image } from '@tarojs/components'
+import { Cell, Avatar, Progress } from '@nutui/nutui-react-taro'
+import Taro from '@tarojs/taro'
+// 导入API服务
+import demoApi from '../../services/demoApi'
+import commonApi from '../../services/commonApi'
+import {
+ useUserStats,
+ useUserActions
+} from '../../store/userStore'
+import img from '../../config/images'
+import { getTextColorOnImage } from '../../utils/processImage'
+import './index.scss'
+
+const images = [
+ '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'
+]
+
+
+
+function Index() {
+ // 使用Zustand store
+ // const userStats = useUserStats()
+ // const { incrementRequestCount, resetUserStats } = useUserActions()
+
+ const [current, setCurrent] = useState(0)
+ const [colors, setColors] = useState([])
+
+ // 本地状态管理
+ const [loading, setLoading] = useState(false)
+ const [userProfile, setUserProfile] = useState(null)
+ const [interests, setInterests] = useState([])
+
+ // 页面加载时获取数据
+ useEffect(() => {
+ initializeData()
+ calcBgMainColors()
+ }, [])
+
+ // 初始化数据
+ const initializeData = async () => {
+ try {
+ // 获取推荐的兴趣爱好
+ const interestsRes = await demoApi.getRecommendedInterests()
+ if (interestsRes.success) {
+ setInterests(interestsRes.data || [])
+ }
+ } catch (error) {
+ console.log('获取初始数据失败:', error)
+ }
+ }
+
+ const calcBgMainColors = async () => {
+ const textcolors: string[] = []
+ for (const index in images) {
+ const { textColor } = await getTextColorOnImage(images[index])
+ textcolors[index] = textColor
+ }
+ setColors(textcolors)
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {/* 我的自定义标题 */}
+
+
+
+ { setCurrent(e.detail.current) }}
+ >
+ {images.map((imageUrl, index) => (
+
+
+
+ ))}
+
+
+ Five feet from the bed was an earthen wall that had suffered from numerous cracks due to the passage of time. From the other side of the wall came the nagging voice of his mother and the occasional deep breathing of his father who was smoking his pipe.
+
+
+ )
+}
+
+export default Index
diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx
index bf32306..7c4e67b 100644
--- a/src/pages/index/index.tsx
+++ b/src/pages/index/index.tsx
@@ -5,8 +5,8 @@ import Taro from '@tarojs/taro'
// 导入API服务
import demoApi from '../../services/demoApi'
import commonApi from '../../services/commonApi'
-import {
- useUserStats,
+import {
+ useUserStats,
useUserActions
} from '../../store/userStore'
import './index.scss'
@@ -15,7 +15,7 @@ function Index() {
// 使用Zustand store
const userStats = useUserStats()
const { incrementRequestCount, resetUserStats } = useUserActions()
-
+
// 本地状态管理
const [loading, setLoading] = useState(false)
const [userProfile, setUserProfile] = useState(null)
@@ -43,19 +43,19 @@ function Index() {
const handleGetUserProfile = async () => {
console.log('获取用户信息...');
setLoading(true)
-
+
try {
const response = await demoApi.getUserProfile()
-
+
if (response.success) {
setUserProfile(response.data)
incrementRequestCount()
-
+
Taro.showToast({
title: '获取用户信息成功',
icon: 'success'
})
-
+
console.log('用户信息:', response.data)
}
} catch (error) {
@@ -64,7 +64,7 @@ function Index() {
title: '获取失败,使用模拟数据',
icon: 'none'
})
-
+
// 模拟数据
setUserProfile({
id: '123',
@@ -83,7 +83,7 @@ function Index() {
const handleSubmitStats = async () => {
console.log('提交统计数据...');
setLoading(true)
-
+
try {
const response = await commonApi.submitForm('userStats', [
{
@@ -97,21 +97,21 @@ function Index() {
}
}
])
-
+
if (response.success) {
incrementRequestCount()
-
+
Taro.showToast({
title: '统计数据提交成功',
icon: 'success'
})
-
+
console.log('提交结果:', response.data)
}
} catch (error) {
console.error('提交统计数据失败:', error)
incrementRequestCount() // 即使失败也计数,用于演示
-
+
Taro.showToast({
title: '网络模拟提交成功',
icon: 'success'
@@ -125,7 +125,7 @@ function Index() {
const handleSubmitFeedback = async () => {
console.log('提交用户反馈...');
setLoading(true)
-
+
try {
const response = await demoApi.submitFeedback({
matchId: 'demo_match_' + Date.now(),
@@ -134,21 +134,21 @@ function Index() {
aspects: ['场地环境', '服务质量', '价格合理'],
comments: `用户反馈 - 请求次数: ${userStats.requestCount + 1},体验良好!`
})
-
+
if (response.success) {
incrementRequestCount()
-
+
Taro.showToast({
title: '反馈提交成功',
icon: 'success'
})
-
+
console.log('反馈结果:', response.data)
}
} catch (error) {
console.error('提交反馈失败:', error)
incrementRequestCount() // 即使失败也计数,用于演示
-
+
Taro.showToast({
title: '网络模拟提交成功',
icon: 'success'
@@ -163,7 +163,7 @@ function Index() {
console.log('重置所有数据...');
resetUserStats()
setUserProfile(null)
-
+
Taro.showToast({
title: '数据已重置',
icon: 'success'
@@ -181,9 +181,9 @@ function Index() {
{/* 用户信息卡片 */}
-
{userProfile?.nickname?.charAt(0) || 'U'}
@@ -205,17 +205,17 @@ function Index() {
{/* 统计数据 */}
📊 API 请求统计
-
+
|
|
|
- |
{interests.length > 0 && (
- |
)}
@@ -224,10 +224,10 @@ function Index() {
{/* API 请求按钮区域 */}
🚀 API 请求功能
-
+
-
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 b9ec637..63ea809 100644
--- a/src/services/publishService.ts
+++ b/src/services/publishService.ts
@@ -30,6 +30,12 @@ export interface PublishBallData {
wechat_contact?: string // 微信联系
}
+export interface createGameData extends PublishBallData {
+ status: string,
+ created_at: string,
+ updated_at: string,
+}
+
// 响应接口
export interface Response {
code: string
@@ -37,7 +43,6 @@ export interface Response {
data: any
}
-// 响应接口
export interface StadiumListResponse {
rows: Stadium[]
}
@@ -51,10 +56,61 @@ export interface Stadium {
latitude?: number
}
+// 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: '发布中...'
@@ -66,7 +122,13 @@ class PublishService {
return httpService.post('/venues/list', data, {
showLoading: false })
}
+ 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/detail/icon-arrow-left.svg b/src/static/detail/icon-arrow-left.svg
new file mode 100644
index 0000000..fca111b
--- /dev/null
+++ b/src/static/detail/icon-arrow-left.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/static/detail/icon-logo-go.svg b/src/static/detail/icon-logo-go.svg
new file mode 100644
index 0000000..468b5ff
--- /dev/null
+++ b/src/static/detail/icon-logo-go.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
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
diff --git a/src/utils/processImage.ts b/src/utils/processImage.ts
new file mode 100644
index 0000000..1143085
--- /dev/null
+++ b/src/utils/processImage.ts
@@ -0,0 +1,33 @@
+export async function getTextColorOnImage(url) {
+ let canvas
+ const width = 100
+ const height = 50
+ try {
+ // 1. 创建离屏Canvas
+ canvas = wx.createOffscreenCanvas({ type: '2d', width, height })
+ const ctx = canvas.getContext('2d')
+
+ // 2. 加载图片
+ const img = canvas.createImage()
+ await new Promise((resolve) => {
+ img.onload = resolve
+ img.src = url
+ })
+
+ // 3. 绘制并分析图像
+ ctx.drawImage(img, 0, 0, width, height)
+
+ // TODO: 增加取样,提高精确性
+ const pixelData = ctx.getImageData(10, 10, 1, 1).data
+
+ // 4. 计算文字颜色
+ const brightness = (pixelData[0] * 2126 + pixelData[1] * 7152 + pixelData[2] * 722) / 10000
+ return { textColor: brightness > 128 ? 'black' : 'white' }
+ } finally {
+ // 释放资源
+ if (canvas) {
+ canvas.width = 0;
+ canvas.height = 0;
+ }
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index b4fcd75..4dfd709 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9242,7 +9242,16 @@ strict-uri-encode@^1.0.0:
resolved "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -9338,7 +9347,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -10334,7 +10350,7 @@ word-wrap@^1.2.5:
resolved "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -10352,6 +10368,15 @@ wrap-ansi@^6.0.1:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"