first commit
This commit is contained in:
parent
dfae1b91e4
commit
c372f5d893
159
package-lock.json
generated
159
package-lock.json
generated
@ -44,6 +44,7 @@
|
|||||||
"@reactuses/core": "^6.0.5",
|
"@reactuses/core": "^6.0.5",
|
||||||
"@tanstack/react-query": "^5.82.0",
|
"@tanstack/react-query": "^5.82.0",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@ -5657,6 +5658,42 @@
|
|||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/autoprefixer": {
|
||||||
|
"version": "10.4.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
||||||
|
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"browserslist": "^4.24.4",
|
||||||
|
"caniuse-lite": "^1.0.30001702",
|
||||||
|
"fraction.js": "^4.3.7",
|
||||||
|
"normalize-range": "^0.1.2",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"postcss-value-parser": "^4.2.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"autoprefixer": "bin/autoprefixer"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/available-typed-arrays": {
|
"node_modules/available-typed-arrays": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||||
@ -5750,6 +5787,14 @@
|
|||||||
"node": "^4.5.0 || >= 5.9"
|
"node": "^4.5.0 || >= 5.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/baseline-browser-mapping": {
|
||||||
|
"version": "2.8.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz",
|
||||||
|
"integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==",
|
||||||
|
"bin": {
|
||||||
|
"baseline-browser-mapping": "dist/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
@ -5787,6 +5832,38 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/browserslist": {
|
||||||
|
"version": "4.26.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
|
||||||
|
"integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"baseline-browser-mapping": "^2.8.9",
|
||||||
|
"caniuse-lite": "^1.0.30001746",
|
||||||
|
"electron-to-chromium": "^1.5.227",
|
||||||
|
"node-releases": "^2.0.21",
|
||||||
|
"update-browserslist-db": "^1.1.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"browserslist": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/buffer": {
|
"node_modules/buffer": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
@ -5938,9 +6015,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001739",
|
"version": "1.0.30001750",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz",
|
||||||
"integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==",
|
"integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@ -5954,8 +6031,7 @@
|
|||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"license": "CC-BY-4.0"
|
|
||||||
},
|
},
|
||||||
"node_modules/ccount": {
|
"node_modules/ccount": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@ -6756,6 +6832,11 @@
|
|||||||
"fast-check": "^3.23.1"
|
"fast-check": "^3.23.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/electron-to-chromium": {
|
||||||
|
"version": "1.5.234",
|
||||||
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz",
|
||||||
|
"integrity": "sha512-RXfEp2x+VRYn8jbKfQlRImzoJU01kyDvVPBmG39eU2iuRVhuS6vQNocB8J0/8GrIMLnPzgz4eW6WiRnJkTuNWg=="
|
||||||
|
},
|
||||||
"node_modules/embla-carousel": {
|
"node_modules/embla-carousel": {
|
||||||
"version": "8.6.0",
|
"version": "8.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
||||||
@ -7144,6 +7225,14 @@
|
|||||||
"@esbuild/win32-x64": "0.25.9"
|
"@esbuild/win32-x64": "0.25.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/escalade": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escape-carriage": {
|
"node_modules/escape-carriage": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz",
|
||||||
@ -7904,6 +7993,18 @@
|
|||||||
"node": ">=0.4.x"
|
"node": ">=0.4.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fraction.js": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://github.com/sponsors/rawify"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/framer-motion": {
|
"node_modules/framer-motion": {
|
||||||
"version": "12.23.24",
|
"version": "12.23.24",
|
||||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
|
||||||
@ -10917,6 +11018,11 @@
|
|||||||
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
|
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-releases": {
|
||||||
|
"version": "2.0.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz",
|
||||||
|
"integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg=="
|
||||||
|
},
|
||||||
"node_modules/nodemon": {
|
"node_modules/nodemon": {
|
||||||
"version": "3.1.10",
|
"version": "3.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
|
||||||
@ -10979,6 +11085,14 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/normalize-range": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nypm": {
|
"node_modules/nypm": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.1.tgz",
|
||||||
@ -11360,7 +11474,6 @@
|
|||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@ -11385,6 +11498,11 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-value-parser": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||||
|
},
|
||||||
"node_modules/preact": {
|
"node_modules/preact": {
|
||||||
"version": "10.27.1",
|
"version": "10.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.27.1.tgz",
|
||||||
@ -13397,6 +13515,35 @@
|
|||||||
"@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
|
"@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/update-browserslist-db": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"escalade": "^3.2.0",
|
||||||
|
"picocolors": "^1.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"update-browserslist-db": "cli.js"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"browserslist": ">= 4.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/uri-js": {
|
"node_modules/uri-js": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||||
|
|||||||
@ -49,6 +49,7 @@
|
|||||||
"@reactuses/core": "^6.0.5",
|
"@reactuses/core": "^6.0.5",
|
||||||
"@tanstack/react-query": "^5.82.0",
|
"@tanstack/react-query": "^5.82.0",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
137
src/app/create/page.tsx
Normal file
137
src/app/create/page.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import {
|
||||||
|
Plus,
|
||||||
|
ArrowRight,
|
||||||
|
Droplets,
|
||||||
|
Package,
|
||||||
|
Sparkles
|
||||||
|
} from 'lucide-react'
|
||||||
|
import BottomNavigation from '@/components/bottom-navigation'
|
||||||
|
import { FadeIn, SlideIn, StaggerContainer, StaggerItem } from '@/components/animations'
|
||||||
|
|
||||||
|
export default function CreatePage() {
|
||||||
|
const [selectedType, setSelectedType] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const createOptions = [
|
||||||
|
{
|
||||||
|
id: 'perfume-group',
|
||||||
|
title: '香水团购',
|
||||||
|
description: '发起香水团购活动,人数越多折扣越大',
|
||||||
|
icon: Droplets,
|
||||||
|
color: 'bg-pink-500',
|
||||||
|
gradient: 'from-pink-500 to-rose-600',
|
||||||
|
features: ['阶梯折扣', '正品保证', '快速成团']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'perfume-split',
|
||||||
|
title: '香水分装',
|
||||||
|
description: '发起香水分装服务,小容量更实惠',
|
||||||
|
icon: Package,
|
||||||
|
color: 'bg-indigo-500',
|
||||||
|
gradient: 'from-indigo-500 to-purple-600',
|
||||||
|
features: ['小容量', '超值价格', '多款选择']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleCreateClick = (type: string) => {
|
||||||
|
setSelectedType(type)
|
||||||
|
// 根据类型跳转到不同的创建页面
|
||||||
|
if (type === 'perfume-group') {
|
||||||
|
// 跳转到香水团购创建页面
|
||||||
|
window.location.href = '/create/perfume-group'
|
||||||
|
} else if (type === 'perfume-split') {
|
||||||
|
// 跳转到香水分装创建页面
|
||||||
|
window.location.href = '/create/perfume-split'
|
||||||
|
} else {
|
||||||
|
console.log(`创建 ${type}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-pink-50 pb-20">
|
||||||
|
{/* 优化后的顶部标题 */}
|
||||||
|
<FadeIn>
|
||||||
|
<div className="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-200/50 shadow-sm">
|
||||||
|
<div className="px-4 py-6">
|
||||||
|
<SlideIn direction="left">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Sparkles className="w-6 h-6 text-purple-600" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent">
|
||||||
|
创建内容
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-600 text-sm mt-1">选择您想要创建的内容类型</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SlideIn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FadeIn>
|
||||||
|
|
||||||
|
{/* 创建选项 */}
|
||||||
|
<div className="px-4 py-8">
|
||||||
|
<StaggerContainer className="space-y-6">
|
||||||
|
{createOptions.map((option, index) => {
|
||||||
|
const IconComponent = option.icon
|
||||||
|
return (
|
||||||
|
<StaggerItem key={option.id}>
|
||||||
|
<div
|
||||||
|
className={`bg-white/70 backdrop-blur-sm rounded-3xl p-6 shadow-lg border border-white/50 transition-all duration-300 hover:shadow-xl hover:scale-[1.02] ${
|
||||||
|
selectedType === option.id ? 'ring-2 ring-purple-400 ring-opacity-50 shadow-purple-200/50' : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => setSelectedType(option.id)}
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-5">
|
||||||
|
{/* 优化后的图标 */}
|
||||||
|
<div className={`w-16 h-16 rounded-2xl bg-gradient-to-r ${option.gradient} flex items-center justify-center flex-shrink-0 shadow-lg`}>
|
||||||
|
<IconComponent className="w-8 h-8 text-white" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 内容 */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-2">{option.title}</h3>
|
||||||
|
<p className="text-gray-600 text-sm mb-4 leading-relaxed">{option.description}</p>
|
||||||
|
|
||||||
|
{/* 特性标签 */}
|
||||||
|
<div className="flex flex-wrap gap-2 mb-6">
|
||||||
|
{option.features.map((feature, index) => (
|
||||||
|
<Badge
|
||||||
|
key={index}
|
||||||
|
variant="outline"
|
||||||
|
className="text-xs bg-white/50 border-gray-200 text-gray-700 hover:bg-purple-50 hover:border-purple-200 transition-colors"
|
||||||
|
>
|
||||||
|
{feature}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 创建按钮 */}
|
||||||
|
<Button
|
||||||
|
className={`w-full bg-gradient-to-r ${option.gradient} hover:opacity-90 text-white shadow-lg hover:shadow-xl transition-all duration-300 rounded-2xl py-3 text-base font-semibold`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleCreateClick(option.id)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className="w-5 h-5 mr-2" />
|
||||||
|
立即创建
|
||||||
|
<ArrowRight className="w-5 h-5 ml-2" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StaggerItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</StaggerContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 底部导航 */}
|
||||||
|
<BottomNavigation />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
190
src/app/create/perfume-group/page.tsx
Normal file
190
src/app/create/perfume-group/page.tsx
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { ProductSelector } from '@/components/product-selector'
|
||||||
|
import {
|
||||||
|
ArrowLeft,
|
||||||
|
Droplets,
|
||||||
|
Users,
|
||||||
|
Clock,
|
||||||
|
Calendar
|
||||||
|
} from 'lucide-react'
|
||||||
|
|
||||||
|
interface Product {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
brand: string
|
||||||
|
price: number
|
||||||
|
originalPrice: number
|
||||||
|
image: string
|
||||||
|
category: string
|
||||||
|
tags: string[]
|
||||||
|
rating: number
|
||||||
|
reviews: number
|
||||||
|
stock: number
|
||||||
|
description: string
|
||||||
|
volume?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CreatePerfumeGroupPage() {
|
||||||
|
const router = useRouter()
|
||||||
|
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null)
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
duration: '7', // 团购持续天数
|
||||||
|
minParticipants: '3',
|
||||||
|
maxParticipants: '50'
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleInputChange = (field: string, value: string) => {
|
||||||
|
setFormData(prev => ({ ...prev, [field]: value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
// 这里处理表单提交逻辑
|
||||||
|
console.log('创建香水团购:', { selectedProduct, formData })
|
||||||
|
// 可以跳转到团购详情页或返回列表页
|
||||||
|
router.push('/create')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50">
|
||||||
|
{/* 头部 */}
|
||||||
|
<div className="bg-gradient-to-br from-pink-500 via-pink-600 to-rose-600 relative overflow-hidden">
|
||||||
|
<div className="absolute inset-0 bg-black/10"></div>
|
||||||
|
<div className="relative z-10 px-4 py-6">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
className="text-white hover:bg-white/20 p-2"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl font-bold text-white">创建香水团购</h1>
|
||||||
|
<p className="text-pink-100 text-sm">选择香水,设置团购参数</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 表单内容 */}
|
||||||
|
<div className="px-4 py-6 space-y-6 max-w-2xl mx-auto">
|
||||||
|
{/* 商品选择 */}
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl overflow-hidden">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-lg font-semibold text-gray-900">
|
||||||
|
<Droplets className="w-5 h-5 text-pink-600" />
|
||||||
|
选择香水
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ProductSelector
|
||||||
|
selectedProduct={selectedProduct}
|
||||||
|
onProductSelect={setSelectedProduct}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 团购设置 */}
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl overflow-hidden">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-lg font-semibold text-gray-900">
|
||||||
|
<Users className="w-5 h-5 text-blue-600" />
|
||||||
|
团购设置
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="duration" className="text-sm font-medium text-gray-700">持续天数</Label>
|
||||||
|
<Input
|
||||||
|
id="duration"
|
||||||
|
type="number"
|
||||||
|
value={formData.duration}
|
||||||
|
onChange={(e) => handleInputChange('duration', e.target.value)}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="minParticipants" className="text-sm font-medium text-gray-700">最少人数</Label>
|
||||||
|
<Input
|
||||||
|
id="minParticipants"
|
||||||
|
type="number"
|
||||||
|
value={formData.minParticipants}
|
||||||
|
onChange={(e) => handleInputChange('minParticipants', e.target.value)}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="maxParticipants" className="text-sm font-medium text-gray-700">最多人数</Label>
|
||||||
|
<Input
|
||||||
|
id="maxParticipants"
|
||||||
|
type="number"
|
||||||
|
value={formData.maxParticipants}
|
||||||
|
onChange={(e) => handleInputChange('maxParticipants', e.target.value)}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 商品预览 */}
|
||||||
|
{selectedProduct && (
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl overflow-hidden">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-lg font-semibold text-gray-900">
|
||||||
|
<Calendar className="w-5 h-5 text-pink-600" />
|
||||||
|
团购预览
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="bg-gradient-to-br from-pink-50 via-white to-rose-50 rounded-xl p-4 border border-pink-100">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-16 h-16 bg-pink-100 rounded-lg flex items-center justify-center">
|
||||||
|
<Droplets className="w-8 h-8 text-pink-600" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-semibold text-gray-900">{selectedProduct.name}</h3>
|
||||||
|
<p className="text-sm text-gray-600">{selectedProduct.brand}</p>
|
||||||
|
<div className="mt-2 flex items-center gap-4">
|
||||||
|
<div className="text-lg font-bold text-pink-600">¥{selectedProduct.price}</div>
|
||||||
|
<div className="text-sm text-gray-500">
|
||||||
|
{formData.minParticipants}-{formData.maxParticipants}人团购
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 p-3 bg-white rounded-lg border border-pink-100">
|
||||||
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
<span className="text-gray-600">团购时长</span>
|
||||||
|
<span className="font-medium">{formData.duration}天</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 创建按钮 */}
|
||||||
|
<div className="pt-4">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
className="w-full bg-gradient-to-r from-pink-500 to-rose-600 hover:opacity-90 text-white py-3 text-lg font-semibold"
|
||||||
|
disabled={!selectedProduct}
|
||||||
|
>
|
||||||
|
创建香水团购
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
289
src/app/create/perfume-split/page.tsx
Normal file
289
src/app/create/perfume-split/page.tsx
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { ProductSelector } from '@/components/product-selector'
|
||||||
|
import {
|
||||||
|
ArrowLeft,
|
||||||
|
Package,
|
||||||
|
Droplets,
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Beaker,
|
||||||
|
Clock
|
||||||
|
} from 'lucide-react'
|
||||||
|
|
||||||
|
interface SplitOption {
|
||||||
|
volume: number // ml
|
||||||
|
price: number
|
||||||
|
quantity: number // 可分装数量
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Product {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
brand: string
|
||||||
|
price: number
|
||||||
|
originalPrice: number
|
||||||
|
image: string
|
||||||
|
category: string
|
||||||
|
tags: string[]
|
||||||
|
rating: number
|
||||||
|
reviews: number
|
||||||
|
stock: number
|
||||||
|
description: string
|
||||||
|
volume?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CreatePerfumeSplitPage() {
|
||||||
|
const router = useRouter()
|
||||||
|
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null)
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
originalVolume: '100', // 原装容量 ml
|
||||||
|
duration: '14', // 分装活动持续天数
|
||||||
|
})
|
||||||
|
|
||||||
|
const [splitOptions, setSplitOptions] = useState<SplitOption[]>([
|
||||||
|
{ volume: 5, price: 0, quantity: 10 },
|
||||||
|
{ volume: 10, price: 0, quantity: 8 },
|
||||||
|
{ volume: 20, price: 0, quantity: 4 }
|
||||||
|
])
|
||||||
|
|
||||||
|
const handleInputChange = (field: string, value: string) => {
|
||||||
|
setFormData(prev => ({ ...prev, [field]: value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const addSplitOption = () => {
|
||||||
|
const lastOption = splitOptions[splitOptions.length - 1]
|
||||||
|
setSplitOptions(prev => [...prev, {
|
||||||
|
volume: lastOption.volume + 5,
|
||||||
|
price: 0,
|
||||||
|
quantity: 1
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeSplitOption = (index: number) => {
|
||||||
|
if (splitOptions.length > 1) {
|
||||||
|
setSplitOptions(prev => prev.filter((_, i) => i !== index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSplitOption = (index: number, field: keyof SplitOption, value: number) => {
|
||||||
|
setSplitOptions(prev => prev.map((option, i) =>
|
||||||
|
i === index ? { ...option, [field]: value } : option
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
console.log('创建香水分装:', { selectedProduct, formData, splitOptions })
|
||||||
|
router.push('/create')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50">
|
||||||
|
{/* 头部 */}
|
||||||
|
<div className="bg-gradient-to-br from-indigo-500 via-purple-600 to-pink-600 relative overflow-hidden">
|
||||||
|
<div className="absolute inset-0 bg-black/10"></div>
|
||||||
|
<div className="relative z-10 px-4 py-6">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
className="text-white hover:bg-white/20 p-2"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl font-bold text-white">创建香水分装</h1>
|
||||||
|
<p className="text-purple-100 text-sm">选择香水,设置分装规格</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 表单内容 */}
|
||||||
|
<div className="px-4 py-6 space-y-6 max-w-2xl mx-auto">
|
||||||
|
{/* 商品选择 */}
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl overflow-hidden">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-lg font-semibold text-gray-900">
|
||||||
|
<Droplets className="w-5 h-5 text-purple-600" />
|
||||||
|
选择香水
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ProductSelector
|
||||||
|
selectedProduct={selectedProduct}
|
||||||
|
onProductSelect={setSelectedProduct}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 分装设置 */}
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl overflow-hidden">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-lg font-semibold text-gray-900">
|
||||||
|
<Beaker className="w-5 h-5 text-indigo-600" />
|
||||||
|
分装设置
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="originalVolume" className="text-sm font-medium text-gray-700">原装容量 (ml)</Label>
|
||||||
|
<Input
|
||||||
|
id="originalVolume"
|
||||||
|
type="number"
|
||||||
|
value={formData.originalVolume}
|
||||||
|
onChange={(e) => handleInputChange('originalVolume', e.target.value)}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="duration" className="text-sm font-medium text-gray-700">持续天数</Label>
|
||||||
|
<Input
|
||||||
|
id="duration"
|
||||||
|
type="number"
|
||||||
|
value={formData.duration}
|
||||||
|
onChange={(e) => handleInputChange('duration', e.target.value)}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 分装规格 */}
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl overflow-hidden">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-lg font-semibold text-gray-900">
|
||||||
|
<Package className="w-5 h-5 text-purple-600" />
|
||||||
|
分装规格
|
||||||
|
</CardTitle>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={addSplitOption}
|
||||||
|
className="text-purple-600 border-purple-200 hover:bg-purple-50"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4 mr-1" />
|
||||||
|
添加规格
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
{splitOptions.map((option, index) => (
|
||||||
|
<div key={index} className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
||||||
|
<div className="flex-1 grid grid-cols-3 gap-3">
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs text-gray-600">容量 (ml)</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={option.volume}
|
||||||
|
onChange={(e) => updateSplitOption(index, 'volume', parseInt(e.target.value) || 0)}
|
||||||
|
className="mt-1 h-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs text-gray-600">价格 (¥)</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={option.price}
|
||||||
|
onChange={(e) => updateSplitOption(index, 'price', parseInt(e.target.value) || 0)}
|
||||||
|
className="mt-1 h-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs text-gray-600">数量</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={option.quantity}
|
||||||
|
onChange={(e) => updateSplitOption(index, 'quantity', parseInt(e.target.value) || 0)}
|
||||||
|
className="mt-1 h-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{splitOptions.length > 1 && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => removeSplitOption(index)}
|
||||||
|
className="p-1 text-red-500 hover:bg-red-50"
|
||||||
|
>
|
||||||
|
<Minus className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 分装预览 */}
|
||||||
|
{selectedProduct && splitOptions.some(opt => opt.price > 0) && (
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl overflow-hidden">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-lg font-semibold text-gray-900">
|
||||||
|
<Package className="w-5 h-5 text-purple-600" />
|
||||||
|
分装预览
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="bg-gradient-to-br from-purple-50 via-white to-indigo-50 rounded-xl p-4 border border-purple-100">
|
||||||
|
<div className="flex items-center gap-4 mb-4">
|
||||||
|
<div className="w-16 h-16 bg-purple-100 rounded-lg flex items-center justify-center">
|
||||||
|
<Droplets className="w-8 h-8 text-purple-600" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-semibold text-gray-900">{selectedProduct.name}</h3>
|
||||||
|
<p className="text-sm text-gray-600">{selectedProduct.brand}</p>
|
||||||
|
<div className="mt-1 text-sm text-gray-500">
|
||||||
|
原装 {formData.originalVolume}ml · 分装 {formData.duration}天
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
{splitOptions.filter(opt => opt.price > 0).map((option, index) => (
|
||||||
|
<div key={index} className="flex items-center justify-between p-3 bg-white rounded-lg border border-purple-100">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 bg-indigo-100 rounded-full flex items-center justify-center">
|
||||||
|
<Droplets className="w-4 h-4 text-indigo-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-medium text-gray-900">{option.volume}ml</div>
|
||||||
|
<div className="text-xs text-gray-500">限量 {option.quantity} 份</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<div className="font-bold text-purple-600">¥{option.price}</div>
|
||||||
|
<div className="text-xs text-gray-500">¥{(option.price / option.volume).toFixed(1)}/ml</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 创建按钮 */}
|
||||||
|
<div className="pt-4">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
className="w-full bg-gradient-to-r from-indigo-500 to-purple-600 hover:opacity-90 text-white py-3 text-lg font-semibold"
|
||||||
|
disabled={!selectedProduct}
|
||||||
|
>
|
||||||
|
创建香水分装
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
221
src/app/distribution/orders/page.tsx
Normal file
221
src/app/distribution/orders/page.tsx
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation'
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||||
|
import { ArrowLeft, ShoppingBag, Calendar, DollarSign, TrendingUp, Package } from 'lucide-react'
|
||||||
|
|
||||||
|
export default function MemberOrdersPage() {
|
||||||
|
const router = useRouter()
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
const memberId = searchParams.get('memberId')
|
||||||
|
const memberName = searchParams.get('memberName')
|
||||||
|
|
||||||
|
// 模拟团队成员订单数据
|
||||||
|
const [memberOrders, setMemberOrders] = useState<any[]>([])
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 模拟获取订单数据
|
||||||
|
const mockOrders = [
|
||||||
|
{
|
||||||
|
id: 'ORD001',
|
||||||
|
productName: '高端护肤套装',
|
||||||
|
amount: 299,
|
||||||
|
commission: 29.9,
|
||||||
|
time: '2024-01-15 14:30',
|
||||||
|
status: 'completed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ORD002',
|
||||||
|
productName: '智能手表',
|
||||||
|
amount: 1299,
|
||||||
|
commission: 129.9,
|
||||||
|
time: '2024-01-14 10:20',
|
||||||
|
status: 'completed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ORD003',
|
||||||
|
productName: '运动鞋',
|
||||||
|
amount: 599,
|
||||||
|
commission: 59.9,
|
||||||
|
time: '2024-01-13 16:45',
|
||||||
|
status: 'pending'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ORD004',
|
||||||
|
productName: '蓝牙耳机',
|
||||||
|
amount: 199,
|
||||||
|
commission: 19.9,
|
||||||
|
time: '2024-01-12 09:15',
|
||||||
|
status: 'completed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ORD005',
|
||||||
|
productName: '咖啡机',
|
||||||
|
amount: 899,
|
||||||
|
commission: 89.9,
|
||||||
|
time: '2024-01-11 20:30',
|
||||||
|
status: 'completed'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setMemberOrders(mockOrders)
|
||||||
|
setLoading(false)
|
||||||
|
}, 500)
|
||||||
|
}, [memberId])
|
||||||
|
|
||||||
|
const totalCommission = memberOrders.reduce((sum, order) => sum + order.commission, 0)
|
||||||
|
const completedOrders = memberOrders.filter(order => order.status === 'completed').length
|
||||||
|
const totalAmount = memberOrders.reduce((sum, order) => sum + order.amount, 0)
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600 mx-auto mb-2"></div>
|
||||||
|
<p className="text-gray-600">加载中...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50">
|
||||||
|
{/* 优化后的头部区域 - 与主页面风格一致 */}
|
||||||
|
<div className="bg-gradient-to-br from-purple-600 via-purple-700 to-pink-600 relative overflow-hidden">
|
||||||
|
{/* 背景装饰 */}
|
||||||
|
<div className="absolute inset-0 bg-black/10"></div>
|
||||||
|
<div className="absolute top-0 right-0 w-32 h-32 bg-white/5 rounded-full -translate-y-16 translate-x-16"></div>
|
||||||
|
<div className="absolute bottom-0 left-0 w-24 h-24 bg-white/5 rounded-full translate-y-12 -translate-x-12"></div>
|
||||||
|
|
||||||
|
<div className="relative px-4 py-6">
|
||||||
|
{/* 导航和用户信息 */}
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
className="p-2 text-white hover:bg-white/20 rounded-full"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
<div className="flex items-center gap-3 flex-1">
|
||||||
|
<Avatar className="w-10 h-10 border-2 border-white/30">
|
||||||
|
<AvatarImage src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${memberName}`} />
|
||||||
|
<AvatarFallback className="bg-white/20 text-white font-semibold">
|
||||||
|
{memberName?.charAt(0)}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div>
|
||||||
|
<h1 className="font-bold text-white text-lg">{memberName}</h1>
|
||||||
|
<p className="text-white/80 text-sm">共 {memberOrders.length} 笔订单</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 统计概览卡片 */}
|
||||||
|
<div className="bg-white/10 backdrop-blur-md rounded-2xl p-4 border border-white/20">
|
||||||
|
<div className="grid grid-cols-3 gap-4 text-center">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-center gap-1 mb-1">
|
||||||
|
<DollarSign className="w-4 h-4 text-white" />
|
||||||
|
</div>
|
||||||
|
<p className="text-white/70 text-xs">总佣金</p>
|
||||||
|
<p className="text-white font-bold text-lg">¥{totalCommission.toFixed(1)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-center gap-1 mb-1">
|
||||||
|
<ShoppingBag className="w-4 h-4 text-white" />
|
||||||
|
</div>
|
||||||
|
<p className="text-white/70 text-xs">已完成</p>
|
||||||
|
<p className="text-white font-bold text-lg">{completedOrders}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-center gap-1 mb-1">
|
||||||
|
<TrendingUp className="w-4 h-4 text-white" />
|
||||||
|
</div>
|
||||||
|
<p className="text-white/70 text-xs">总金额</p>
|
||||||
|
<p className="text-white font-bold text-lg">¥{(totalAmount/1000).toFixed(1)}k</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-4 -mt-4 relative z-10 pb-6">
|
||||||
|
{/* 订单列表卡片 */}
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl overflow-hidden">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-lg font-semibold text-gray-900">
|
||||||
|
<Package className="w-5 h-5 text-purple-600" />
|
||||||
|
订单明细
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<div className="space-y-0">
|
||||||
|
{memberOrders.map((order, index) => (
|
||||||
|
<div
|
||||||
|
key={order.id}
|
||||||
|
className={`p-4 hover:bg-gray-50 transition-colors ${
|
||||||
|
index !== memberOrders.length - 1 ? 'border-b border-gray-100' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge
|
||||||
|
className={`text-xs font-medium ${
|
||||||
|
order.status === 'completed'
|
||||||
|
? 'bg-green-100 text-green-700 border-green-200'
|
||||||
|
: 'bg-orange-100 text-orange-700 border-orange-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{order.status === 'completed' ? '已完成' : '待确认'}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-xs text-gray-500 font-mono">#{order.id}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<span className="font-bold text-green-600 text-lg">+¥{order.commission}</span>
|
||||||
|
<p className="text-xs text-gray-500 mt-0.5">佣金</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 className="font-semibold text-gray-900 mb-2 text-base">{order.productName}</h4>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<span className="text-sm text-gray-600">
|
||||||
|
订单金额: <span className="font-semibold text-gray-900">¥{order.amount}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1 text-sm text-gray-500">
|
||||||
|
<Calendar className="w-3 h-3" />
|
||||||
|
<span>{order.time}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 空状态优化 */}
|
||||||
|
{memberOrders.length === 0 && (
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl overflow-hidden">
|
||||||
|
<CardContent className="text-center py-16">
|
||||||
|
<div className="w-16 h-16 mx-auto mb-4 bg-purple-100 rounded-full flex items-center justify-center">
|
||||||
|
<ShoppingBag className="w-8 h-8 text-purple-600" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 mb-2">暂无订单记录</h3>
|
||||||
|
<p className="text-gray-500">该成员还没有产生任何订单</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,13 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Progress } from '@/components/ui/progress'
|
import { Progress } from '@/components/ui/progress'
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import BottomNavigation from '@/components/bottom-navigation'
|
||||||
import {
|
import {
|
||||||
Users,
|
Users,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
@ -20,17 +21,22 @@ import {
|
|||||||
MessageCircle,
|
MessageCircle,
|
||||||
Download,
|
Download,
|
||||||
Star,
|
Star,
|
||||||
Zap,
|
|
||||||
ArrowUpRight,
|
ArrowUpRight,
|
||||||
Calendar,
|
Calendar,
|
||||||
ShoppingBag,
|
ShoppingBag,
|
||||||
Share2,
|
Eye,
|
||||||
ShoppingCart,
|
Wallet,
|
||||||
Home
|
UserPlus,
|
||||||
|
BarChart3,
|
||||||
|
Sparkles,
|
||||||
|
Info,
|
||||||
|
Clock,
|
||||||
|
CreditCard
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
export default function DistributionPage() {
|
export default function DistributionPage() {
|
||||||
const [activeTab, setActiveTab] = useState('overview')
|
const [activeTab, setActiveTab] = useState('overview')
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// 模拟数据
|
// 模拟数据
|
||||||
const stats = {
|
const stats = {
|
||||||
@ -41,7 +47,9 @@ export default function DistributionPage() {
|
|||||||
level: '品鉴分销员',
|
level: '品鉴分销员',
|
||||||
nextLevelProgress: 65,
|
nextLevelProgress: 65,
|
||||||
monthlyOrders: 45,
|
monthlyOrders: 45,
|
||||||
monthlyRevenue: 8950.00
|
monthlyRevenue: 8950.00,
|
||||||
|
weeklyGrowth: 12.5,
|
||||||
|
conversionRate: 8.3
|
||||||
}
|
}
|
||||||
|
|
||||||
const teamMembers = [
|
const teamMembers = [
|
||||||
@ -50,36 +58,45 @@ export default function DistributionPage() {
|
|||||||
name: '小美',
|
name: '小美',
|
||||||
avatar: '小',
|
avatar: '小',
|
||||||
level: '探索分销员',
|
level: '探索分销员',
|
||||||
joinTime: '2025-03-15',
|
|
||||||
totalOrders: 15,
|
totalOrders: 15,
|
||||||
commission: 234.50,
|
commission: 234.50,
|
||||||
status: 'active',
|
avatarUrl: 'https://picsum.photos/40/40?random=30',
|
||||||
monthlyOrders: 8,
|
growth: '+15%',
|
||||||
avatarUrl: '/api/placeholder/40/40'
|
orders: [
|
||||||
|
{ id: 'ORD001', productName: '经典玫瑰香水30ml', amount: 199.00, commission: 19.90, time: '2025-04-01 10:30', status: 'completed' },
|
||||||
|
{ id: 'ORD002', productName: '薰衣草精油礼盒', amount: 299.00, commission: 29.90, time: '2025-04-02 14:20', status: 'completed' },
|
||||||
|
{ id: 'ORD003', productName: '茉莉花香水50ml', amount: 399.00, commission: 39.90, time: '2025-04-03 16:45', status: 'pending' }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
name: '香香公主',
|
name: '香香公主',
|
||||||
avatar: '香',
|
avatar: '香',
|
||||||
level: '品鉴分销员',
|
level: '品鉴分销员',
|
||||||
joinTime: '2025-03-10',
|
|
||||||
totalOrders: 28,
|
totalOrders: 28,
|
||||||
commission: 456.80,
|
commission: 456.80,
|
||||||
status: 'active',
|
avatarUrl: 'https://picsum.photos/40/40?random=31',
|
||||||
monthlyOrders: 12,
|
growth: '+23%',
|
||||||
avatarUrl: '/api/placeholder/40/40'
|
orders: [
|
||||||
|
{ id: 'ORD004', productName: '白茶香水礼盒装', amount: 599.00, commission: 59.90, time: '2025-04-01 09:15', status: 'completed' },
|
||||||
|
{ id: 'ORD005', productName: '柑橘调香水30ml', amount: 259.00, commission: 25.90, time: '2025-04-02 11:30', status: 'completed' },
|
||||||
|
{ id: 'ORD006', productName: '木质调香水50ml', amount: 459.00, commission: 45.90, time: '2025-04-03 13:20', status: 'completed' }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
name: '花仙子',
|
name: '花仙子',
|
||||||
avatar: '花',
|
avatar: '花',
|
||||||
level: '调香分销员',
|
level: '调香分销员',
|
||||||
joinTime: '2025-03-01',
|
|
||||||
totalOrders: 42,
|
totalOrders: 42,
|
||||||
commission: 789.20,
|
commission: 789.20,
|
||||||
status: 'active',
|
avatarUrl: 'https://picsum.photos/40/40?random=32',
|
||||||
monthlyOrders: 18,
|
growth: '+8%',
|
||||||
avatarUrl: '/api/placeholder/40/40'
|
orders: [
|
||||||
|
{ id: 'ORD007', productName: '限量版香水套装', amount: 899.00, commission: 89.90, time: '2025-04-01 15:45', status: 'completed' },
|
||||||
|
{ id: 'ORD008', productName: '花香调香水30ml', amount: 329.00, commission: 32.90, time: '2025-04-02 17:10', status: 'completed' },
|
||||||
|
{ id: 'ORD009', productName: '东方调香水礼盒', amount: 699.00, commission: 69.90, time: '2025-04-03 19:30', status: 'pending' }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -91,7 +108,6 @@ export default function DistributionPage() {
|
|||||||
productName: '经典玫瑰香水30ml',
|
productName: '经典玫瑰香水30ml',
|
||||||
type: 'direct',
|
type: 'direct',
|
||||||
amount: 19.90,
|
amount: 19.90,
|
||||||
rate: 0.10,
|
|
||||||
status: 'settled',
|
status: 'settled',
|
||||||
time: '2025-04-02 14:30',
|
time: '2025-04-02 14:30',
|
||||||
orderAmount: 199.00
|
orderAmount: 199.00
|
||||||
@ -103,96 +119,31 @@ export default function DistributionPage() {
|
|||||||
productName: '薰衣草精油礼盒',
|
productName: '薰衣草精油礼盒',
|
||||||
type: 'indirect',
|
type: 'indirect',
|
||||||
amount: 14.95,
|
amount: 14.95,
|
||||||
rate: 0.05,
|
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
time: '2025-04-02 13:15',
|
time: '2025-04-02 13:15',
|
||||||
orderAmount: 299.00
|
orderAmount: 299.00
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
orderNo: 'ORD20250402003',
|
|
||||||
customerName: '王小姐',
|
|
||||||
productName: '小样体验包',
|
|
||||||
type: 'direct',
|
|
||||||
amount: 0.10,
|
|
||||||
rate: 0.10,
|
|
||||||
status: 'settled',
|
|
||||||
time: '2025-04-02 10:45',
|
|
||||||
orderAmount: 1.00
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const materials = [
|
const achievements = [
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
title: '宝妈话术模板',
|
|
||||||
content: '自用省50,分享赚100!香氛好物,自用省钱,分享赚钱',
|
|
||||||
category: '话术',
|
|
||||||
usage: 1250,
|
|
||||||
rating: 4.8,
|
|
||||||
image: '/api/placeholder/200/150'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
title: '产品种草文案',
|
|
||||||
content: '这款玫瑰香水太绝了!留香一整天,闺蜜都问我要链接',
|
|
||||||
category: '文案',
|
|
||||||
usage: 890,
|
|
||||||
rating: 4.6,
|
|
||||||
image: '/api/placeholder/200/150'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
title: '拼团分享海报',
|
|
||||||
content: '精美海报模板,一键生成分享图',
|
|
||||||
category: '海报',
|
|
||||||
usage: 2100,
|
|
||||||
rating: 4.9,
|
|
||||||
image: '/api/placeholder/200/150'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
title: '产品介绍视频',
|
|
||||||
content: '专业拍摄的产品展示视频,适合短视频平台',
|
|
||||||
category: '视频',
|
|
||||||
usage: 650,
|
|
||||||
rating: 4.7,
|
|
||||||
image: '/api/placeholder/200/150'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const tasks = [
|
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
title: '邀请3位好友',
|
title: '邀请3位好友',
|
||||||
description: '成功邀请3位好友注册并完成首单',
|
description: '成功邀请3位好友注册并完成首单',
|
||||||
reward: '解锁高佣金商品推广权',
|
|
||||||
progress: 2,
|
progress: 2,
|
||||||
target: 3,
|
target: 3,
|
||||||
status: 'in_progress',
|
reward: '解锁高佣金商品推广权',
|
||||||
icon: Users,
|
icon: UserPlus,
|
||||||
color: 'blue'
|
color: 'blue'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
title: '月销10单',
|
title: '月销售额达到5000元',
|
||||||
description: '本月完成10笔有效订单',
|
description: '单月团队销售额突破5000元',
|
||||||
reward: '获得限量香氛礼品',
|
progress: 4200,
|
||||||
progress: 7,
|
target: 5000,
|
||||||
target: 10,
|
|
||||||
status: 'in_progress',
|
|
||||||
icon: ShoppingBag,
|
|
||||||
color: 'green'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
title: '团队建设',
|
|
||||||
description: '团队人数达到20人',
|
|
||||||
reward: '晋升为调香分销员',
|
reward: '晋升为调香分销员',
|
||||||
progress: 23,
|
icon: Target,
|
||||||
target: 20,
|
|
||||||
status: 'completed',
|
|
||||||
icon: Crown,
|
|
||||||
color: 'purple'
|
color: 'purple'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -209,361 +160,335 @@ export default function DistributionPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyMaterial = async (content: string) => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(content)
|
|
||||||
alert('内容已复制到剪贴板')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('复制失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50 pb-20">
|
||||||
{/* Header */}
|
{/* 优化后的紧凑顶部区域 */}
|
||||||
<div className="bg-gradient-to-r from-purple-600 to-pink-600 text-white">
|
<div className="bg-gradient-to-br from-purple-600 via-purple-700 to-pink-600 relative overflow-hidden">
|
||||||
<div className="max-w-6xl mx-auto px-4 py-8">
|
{/* 背景装饰 */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="absolute inset-0 bg-black/10"></div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="absolute top-0 right-0 w-32 h-32 bg-white/5 rounded-full -translate-y-16 translate-x-16"></div>
|
||||||
<div className="w-16 h-16 bg-white/20 backdrop-blur rounded-full flex items-center justify-center">
|
<div className="absolute bottom-0 left-0 w-24 h-24 bg-white/5 rounded-full translate-y-12 -translate-x-12"></div>
|
||||||
<Crown className="w-8 h-8 text-white" />
|
|
||||||
|
<div className="relative px-4 py-6">
|
||||||
|
{/* 紧凑的用户信息区域 */}
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-12 h-12 bg-white/20 backdrop-blur rounded-xl flex items-center justify-center">
|
||||||
|
<Crown className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold">分销中心</h1>
|
<h1 className="text-xl font-bold text-white">分销中心</h1>
|
||||||
<p className="text-purple-100 mt-1">{stats.level} · 团队 {stats.teamSize} 人</p>
|
<div className="flex items-center gap-2 mt-1">
|
||||||
|
<Badge className="bg-white/20 text-white border-white/30 text-xs">
|
||||||
|
{stats.level}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-white/80 text-xs">团队 {stats.teamSize} 人</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
|
||||||
<p className="text-sm text-purple-100">累计佣金</p>
|
|
||||||
<p className="text-3xl font-bold">¥{stats.totalCommission.toFixed(2)}</p>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={copyInviteCode}
|
onClick={copyInviteCode}
|
||||||
className="mt-2 bg-white text-purple-600 hover:bg-purple-50"
|
size="sm"
|
||||||
|
className="bg-white text-purple-600 hover:bg-white/90 rounded-full px-4"
|
||||||
>
|
>
|
||||||
<Copy className="w-4 h-4 mr-2" />
|
<UserPlus className="w-4 h-4 mr-1" />
|
||||||
邀请好友
|
邀请
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 累计佣金展示 */}
|
||||||
|
<div className="text-center bg-white/10 backdrop-blur-md rounded-2xl p-4 border border-white/20">
|
||||||
|
<p className="text-white/80 text-sm mb-1">累计佣金</p>
|
||||||
|
<p className="text-3xl font-bold text-white">¥{stats.totalCommission.toFixed(2)}</p>
|
||||||
|
|
||||||
|
{/* 等级进度条 */}
|
||||||
|
<div className="mt-3">
|
||||||
|
<div className="flex items-center justify-between mb-1">
|
||||||
|
<span className="text-white/70 text-xs">升级进度</span>
|
||||||
|
<span className="text-white text-xs font-medium">{stats.nextLevelProgress}%</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={stats.nextLevelProgress} className="h-1.5 bg-white/20" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
<div className="px-4 -mt-4 relative z-10">
|
||||||
{/* 统计卡片 */}
|
{/* 核心数据概览卡片 - 合并设计 */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
<Card className="bg-white shadow-lg border-0 rounded-2xl overflow-hidden mb-4">
|
||||||
<Card className="bg-gradient-to-br from-green-50 to-green-100 border-green-200">
|
<CardHeader className="pb-3">
|
||||||
<CardContent className="p-6">
|
<CardTitle className="text-lg font-semibold text-gray-900 flex items-center gap-2">
|
||||||
<div className="flex items-center justify-between">
|
<BarChart3 className="w-5 h-5 text-purple-600" />
|
||||||
<div>
|
数据概览
|
||||||
<p className="text-sm text-green-600">今日佣金</p>
|
</CardTitle>
|
||||||
<p className="text-2xl font-bold text-green-700">¥{stats.todayCommission.toFixed(2)}</p>
|
|
||||||
<p className="text-xs text-green-600 mt-1">+12.5% 较昨日</p>
|
|
||||||
</div>
|
|
||||||
<div className="w-12 h-12 bg-green-500 rounded-full flex items-center justify-center">
|
|
||||||
<DollarSign className="w-6 h-6 text-white" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="bg-gradient-to-br from-blue-50 to-blue-100 border-blue-200">
|
|
||||||
<CardContent className="p-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-blue-600">团队人数</p>
|
|
||||||
<p className="text-2xl font-bold text-blue-700">{stats.teamSize}</p>
|
|
||||||
<p className="text-xs text-blue-600 mt-1">活跃 {stats.activeMembers} 人</p>
|
|
||||||
</div>
|
|
||||||
<div className="w-12 h-12 bg-blue-500 rounded-full flex items-center justify-center">
|
|
||||||
<Users className="w-6 h-6 text-white" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="bg-gradient-to-br from-purple-50 to-purple-100 border-purple-200">
|
|
||||||
<CardContent className="p-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-purple-600">本月订单</p>
|
|
||||||
<p className="text-2xl font-bold text-purple-700">{stats.monthlyOrders}</p>
|
|
||||||
<p className="text-xs text-purple-600 mt-1">¥{stats.monthlyRevenue.toFixed(0)}</p>
|
|
||||||
</div>
|
|
||||||
<div className="w-12 h-12 bg-purple-500 rounded-full flex items-center justify-center">
|
|
||||||
<ShoppingBag className="w-6 h-6 text-white" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="bg-gradient-to-br from-orange-50 to-orange-100 border-orange-200">
|
|
||||||
<CardContent className="p-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-orange-600">等级进度</p>
|
|
||||||
<p className="text-2xl font-bold text-orange-700">{stats.nextLevelProgress}%</p>
|
|
||||||
<Progress value={stats.nextLevelProgress} className="h-1 mt-2" />
|
|
||||||
</div>
|
|
||||||
<div className="w-12 h-12 bg-orange-500 rounded-full flex items-center justify-center">
|
|
||||||
<Award className="w-6 h-6 text-white" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
|
||||||
<TabsList className="grid w-full grid-cols-4">
|
|
||||||
<TabsTrigger value="overview">数据概览</TabsTrigger>
|
|
||||||
<TabsTrigger value="team">团队管理</TabsTrigger>
|
|
||||||
<TabsTrigger value="materials">素材库</TabsTrigger>
|
|
||||||
<TabsTrigger value="tasks">任务体系</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
{/* 数据概览 */}
|
|
||||||
<TabsContent value="overview" className="mt-6">
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
{/* 佣金明细 */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<CardTitle>佣金明细</CardTitle>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
<Calendar className="w-4 h-4 mr-2" />
|
|
||||||
本月
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="pt-0">
|
||||||
<div className="space-y-4">
|
{/* 主要指标网格 */}
|
||||||
{commissions.map((commission) => (
|
<div className="grid grid-cols-2 gap-3 mb-3">
|
||||||
<div key={commission.id} className="flex items-center justify-between p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
|
<div className="text-center p-3 bg-green-50 rounded-xl">
|
||||||
<div className="flex-1">
|
<div className="flex items-center justify-center gap-1 mb-2">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<Wallet className="w-4 h-4 text-green-600" />
|
||||||
<span className="font-medium">{commission.customerName}</span>
|
<span className="text-sm text-green-700 font-medium">今日佣金</span>
|
||||||
<Badge variant={commission.type === 'direct' ? 'default' : 'secondary'}>
|
</div>
|
||||||
{commission.type === 'direct' ? '直推' : '间推'}
|
<p className="text-2xl font-bold text-green-600">¥{stats.todayCommission.toFixed(0)}</p>
|
||||||
</Badge>
|
<Badge className="bg-green-100 text-green-700 text-xs mt-1">
|
||||||
<Badge variant={commission.status === 'settled' ? 'default' : 'outline'}>
|
+{stats.weeklyGrowth}%
|
||||||
{commission.status === 'settled' ? '已结算' : '待结算'}
|
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-600">{commission.productName}</p>
|
|
||||||
<p className="text-xs text-gray-500">{commission.orderNo} · {commission.time}</p>
|
<div className="text-center p-3 bg-blue-50 rounded-xl">
|
||||||
|
<div className="flex items-center justify-center gap-1 mb-2">
|
||||||
|
<Users className="w-4 h-4 text-blue-600" />
|
||||||
|
<span className="text-sm text-blue-700 font-medium">团队人数</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-2xl font-bold text-blue-600">{stats.teamSize}</p>
|
||||||
|
<Badge className="bg-blue-100 text-blue-700 text-xs mt-1">
|
||||||
|
{stats.activeMembers}活跃
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 次要指标 */}
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-xl">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-8 h-8 bg-purple-100 rounded-lg flex items-center justify-center">
|
||||||
|
<ShoppingBag className="w-4 h-4 text-purple-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-gray-600">本月订单</p>
|
||||||
|
<p className="text-lg font-bold text-gray-900">{stats.monthlyOrders}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-xl">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-8 h-8 bg-orange-100 rounded-lg flex items-center justify-center">
|
||||||
|
<TrendingUp className="w-4 h-4 text-orange-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-gray-600">月度营收</p>
|
||||||
|
<p className="text-lg font-bold text-gray-900">¥{(stats.monthlyRevenue/1000).toFixed(1)}K</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 转化率指标 */}
|
||||||
|
<div className="mt-3 p-3 bg-gradient-to-r from-purple-50 to-pink-50 rounded-xl">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-8 h-8 bg-purple-100 rounded-lg flex items-center justify-center">
|
||||||
|
<Target className="w-4 h-4 text-purple-600" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium text-gray-700">转化率</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-lg font-bold text-green-600">¥{commission.amount.toFixed(2)}</p>
|
<p className="text-xl font-bold text-purple-600">{stats.conversionRate}%</p>
|
||||||
<p className="text-xs text-gray-500">{(commission.rate * 100).toFixed(0)}%</p>
|
<p className="text-xs text-gray-500">行业领先</p>
|
||||||
<p className="text-xs text-gray-400">订单¥{commission.orderAmount}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 业绩趋势 */}
|
{/* 标签页内容 */}
|
||||||
<Card>
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||||
<CardHeader>
|
<TabsList className="grid w-full grid-cols-3 bg-white rounded-2xl p-1 shadow-sm mb-3">
|
||||||
<CardTitle>业绩趋势</CardTitle>
|
<TabsTrigger value="overview" className="rounded-xl data-[state=active]:bg-purple-100 data-[state=active]:text-purple-700">概览</TabsTrigger>
|
||||||
|
<TabsTrigger value="team" className="rounded-xl data-[state=active]:bg-purple-100 data-[state=active]:text-purple-700">团队</TabsTrigger>
|
||||||
|
<TabsTrigger value="earnings" className="rounded-xl data-[state=active]:bg-purple-100 data-[state=active]:text-purple-700">收益</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="overview" className="mt-0 space-y-3">
|
||||||
|
{/* 成就系统 - 优化布局 */}
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl overflow-hidden">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-lg">
|
||||||
|
<Sparkles className="w-5 h-5 text-purple-600" />
|
||||||
|
成就进度
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="space-y-3">
|
||||||
<div className="space-y-4">
|
{achievements.map((achievement) => {
|
||||||
<div className="grid grid-cols-2 gap-4">
|
const IconComponent = achievement.icon
|
||||||
<div className="text-center p-4 bg-blue-50 rounded-lg">
|
const progressPercentage = (achievement.progress / achievement.target) * 100
|
||||||
<p className="text-sm text-blue-600">本周佣金</p>
|
|
||||||
<p className="text-xl font-bold text-blue-700">¥568.00</p>
|
|
||||||
<p className="text-xs text-blue-600">+8.2%</p>
|
|
||||||
</div>
|
|
||||||
<div className="text-center p-4 bg-green-50 rounded-lg">
|
|
||||||
<p className="text-sm text-green-600">新增成员</p>
|
|
||||||
<p className="text-xl font-bold text-green-700">5人</p>
|
|
||||||
<p className="text-xs text-green-600">+25%</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="h-48 bg-gray-100 rounded-lg flex items-center justify-center">
|
return (
|
||||||
<p className="text-gray-500">业绩趋势图表</p>
|
<article key={achievement.id} className="p-3 bg-gray-50 rounded-xl">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className={`w-10 h-10 bg-${achievement.color}-100 rounded-xl flex items-center justify-center flex-shrink-0`}>
|
||||||
|
<IconComponent className={`w-5 h-5 text-${achievement.color}-600`} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h4 className="font-semibold text-gray-900 mb-1 text-sm">{achievement.title}</h4>
|
||||||
|
<p className="text-xs text-gray-600 mb-2">{achievement.description}</p>
|
||||||
|
<div className="mb-2">
|
||||||
|
<div className="flex items-center justify-between mb-1">
|
||||||
|
<span className="text-xs text-gray-500">
|
||||||
|
{achievement.progress} / {achievement.target}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs font-medium text-gray-900">
|
||||||
|
{Math.round(progressPercentage)}%
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
<Progress value={progressPercentage} className="h-1.5" />
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* 团队管理 */}
|
|
||||||
<TabsContent value="team" className="mt-6">
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<CardTitle>团队成员 ({teamMembers.length})</CardTitle>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
<Users className="w-4 h-4 mr-2" />
|
|
||||||
邀请成员
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{teamMembers.map((member) => (
|
|
||||||
<div key={member.id} className="flex items-center justify-between p-4 border rounded-lg hover:bg-gray-50 transition-colors">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Avatar className="w-10 h-10">
|
|
||||||
<AvatarImage src={member.avatarUrl} />
|
|
||||||
<AvatarFallback>{member.avatar}</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">{member.name}</p>
|
|
||||||
<p className="text-sm text-gray-600">{member.level}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-sm text-gray-600">总订单</p>
|
|
||||||
<p className="font-bold">{member.totalOrders}</p>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-sm text-gray-600">本月订单</p>
|
|
||||||
<p className="font-bold text-blue-600">{member.monthlyOrders}</p>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-sm text-gray-600">佣金</p>
|
|
||||||
<p className="font-bold text-green-600">¥{member.commission.toFixed(2)}</p>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-sm text-gray-600">加入时间</p>
|
|
||||||
<p className="text-sm">{member.joinTime}</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className={`w-2 h-2 rounded-full ${member.status === 'active' ? 'bg-green-500' : 'bg-gray-400'}`} />
|
|
||||||
<span className="text-sm">{member.status === 'active' ? '活跃' : '离线'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* 素材库 */}
|
|
||||||
<TabsContent value="materials" className="mt-6">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-6">
|
|
||||||
{materials.map((material) => (
|
|
||||||
<Card key={material.id} className="overflow-hidden hover:shadow-lg transition-shadow">
|
|
||||||
<div className="aspect-video bg-gray-100">
|
|
||||||
<img
|
|
||||||
src={material.image}
|
|
||||||
alt={material.title}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<CardContent className="p-4">
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
|
||||||
<h3 className="font-semibold">{material.title}</h3>
|
|
||||||
<Badge variant="outline">{material.category}</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-gray-600 mb-3 line-clamp-2">{material.content}</p>
|
|
||||||
<div className="flex items-center justify-between mb-3">
|
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Star className="w-4 h-4 text-yellow-500" />
|
<Gift className="w-3 h-3 text-orange-500" />
|
||||||
<span className="text-sm">{material.rating}</span>
|
<span className="text-xs text-orange-600 font-medium">{achievement.reward}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-gray-500">使用 {material.usage} 次</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
className="flex-1"
|
|
||||||
onClick={() => copyMaterial(material.content)}
|
|
||||||
>
|
|
||||||
<Copy className="w-4 h-4 mr-1" />
|
|
||||||
复制
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="outline" className="flex-1">
|
|
||||||
<Download className="w-4 h-4 mr-1" />
|
|
||||||
下载
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="outline">
|
|
||||||
<Share2 className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* 任务体系 */}
|
{/* 团队标签页 - 优化布局 */}
|
||||||
<TabsContent value="tasks" className="mt-6">
|
<TabsContent value="team" className="mt-0 space-y-3">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<Card className="bg-white shadow-lg border-0 rounded-2xl">
|
||||||
{tasks.map((task) => (
|
<CardHeader className="pb-3 flex flex-row items-center justify-between space-y-0">
|
||||||
<Card key={task.id} className={`relative ${task.status === 'completed' ? 'bg-green-50 border-green-200' : ''}`}>
|
<CardTitle className="text-lg font-semibold text-gray-800">团队成员</CardTitle>
|
||||||
<CardContent className="p-6">
|
<Badge className="bg-blue-100 text-blue-700 text-sm">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
{teamMembers.length} 人
|
||||||
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${
|
</Badge>
|
||||||
task.status === 'completed' ? 'bg-green-100' : `${task.color}-100`
|
</CardHeader>
|
||||||
}`}>
|
<CardContent className="pt-0">
|
||||||
{task.status === 'completed' ? (
|
<ul className="divide-y divide-gray-100" role="list">
|
||||||
<Award className="w-5 h-5 text-green-600" />
|
{teamMembers.map((member) => (
|
||||||
) : (
|
<li key={member.id} className="flex items-center gap-3 py-4">
|
||||||
<task.icon className={`w-5 h-5 text-${task.color}-600`} />
|
<Avatar className="w-12 h-12 flex-shrink-0">
|
||||||
)}
|
<AvatarImage src={member.avatarUrl} />
|
||||||
</div>
|
<AvatarFallback className="bg-purple-100 text-purple-600 font-semibold">
|
||||||
<div className="flex-1">
|
{member.avatar}
|
||||||
<h3 className="font-semibold">{task.title}</h3>
|
</AvatarFallback>
|
||||||
<p className="text-sm text-gray-600">{task.description}</p>
|
</Avatar>
|
||||||
</div>
|
<div className="flex-1 min-w-0">
|
||||||
<Badge variant={task.status === 'completed' ? 'default' : 'secondary'}>
|
<div className="flex items-center gap-2 mb-1">
|
||||||
{task.status === 'completed' ? '已完成' : '进行中'}
|
<h4 className="font-medium text-gray-900 truncate">{member.name}</h4>
|
||||||
|
<Badge variant="outline" className="text-xs flex-shrink-0">
|
||||||
|
{member.level}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-4 text-sm text-gray-600">
|
||||||
<div className="space-y-2 mb-4">
|
<span>订单 {member.totalOrders}</span>
|
||||||
<div className="flex items-center justify-between text-sm">
|
<span>佣金 ¥{member.commission}</span>
|
||||||
<span>进度</span>
|
<Badge className="bg-green-100 text-green-700 text-xs">
|
||||||
<span className="font-medium">{task.progress}/{task.target}</span>
|
{member.growth}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="flex-shrink-0 w-10 h-10 p-0 hover:bg-purple-50"
|
||||||
|
aria-label="查看订单明细"
|
||||||
|
onClick={() => {
|
||||||
|
router.push(`/distribution/orders?memberId=${member.id}&memberName=${encodeURIComponent(member.name)}`)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowUpRight className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
{/* 收益标签页 - 优化布局 */}
|
||||||
|
<TabsContent value="earnings" className="mt-0 space-y-3">
|
||||||
|
{/* 佣金结算说明 */}
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||||
|
<Info className="w-5 h-5 text-blue-600" />
|
||||||
|
佣金结算说明
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="pt-0 space-y-4">
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
{/* 结算周期 */}
|
||||||
|
<div className="flex items-start gap-3 p-3 bg-blue-50 rounded-xl">
|
||||||
|
<Clock className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-gray-900 mb-1">结算周期</h4>
|
||||||
|
<p className="text-sm text-gray-600">每月1日结算上月佣金,T+7个工作日到账</p>
|
||||||
</div>
|
</div>
|
||||||
<Progress
|
|
||||||
value={(task.progress / task.target) * 100}
|
|
||||||
className="h-2"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 bg-yellow-50 rounded-lg">
|
{/* 佣金比例 */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-start gap-3 p-3 bg-green-50 rounded-xl">
|
||||||
<Gift className="w-4 h-4 text-yellow-600" />
|
<DollarSign className="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" />
|
||||||
<span className="text-sm font-medium text-yellow-800">奖励:{task.reward}</span>
|
<div>
|
||||||
|
<h4 className="font-semibold text-gray-900 mb-1">佣金比例</h4>
|
||||||
|
<p className="text-sm text-gray-600">直推订单10%,团队订单5%,高级分销员享受更高比例</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 提现规则 */}
|
||||||
|
<div className="flex items-start gap-3 p-3 bg-purple-50 rounded-xl">
|
||||||
|
<CreditCard className="w-5 h-5 text-purple-600 mt-0.5 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-gray-900 mb-1">提现规则</h4>
|
||||||
|
<p className="text-sm text-gray-600">最低提现金额100元,每月可提现2次,手续费2元/笔</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 冻结说明 */}
|
||||||
|
<div className="flex items-start gap-3 p-3 bg-orange-50 rounded-xl">
|
||||||
|
<Eye className="w-5 h-5 text-orange-600 mt-0.5 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-gray-900 mb-1">冻结说明</h4>
|
||||||
|
<p className="text-sm text-gray-600">订单完成后佣金冻结15天,确保售后服务质量</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
|
||||||
|
<Card className="bg-white shadow-lg border-0 rounded-2xl">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="text-lg font-semibold text-gray-800">佣金明细</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="pt-0">
|
||||||
|
<ul className="divide-y divide-gray-100" role="list">
|
||||||
|
{commissions.map((commission) => (
|
||||||
|
<li key={commission.id} className="py-3">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
|
<Badge
|
||||||
|
className={`text-xs ${
|
||||||
|
commission.type === 'direct'
|
||||||
|
? 'bg-green-100 text-green-700'
|
||||||
|
: 'bg-blue-100 text-blue-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{commission.type === 'direct' ? '直推' : '团队'}
|
||||||
|
</Badge>
|
||||||
|
<span className="font-medium text-gray-900 truncate">{commission.productName}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<span className="font-semibold text-green-600 flex-shrink-0">+¥{commission.amount}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between text-sm text-gray-600">
|
||||||
|
<span>来自: {commission.customerName}</span>
|
||||||
|
<span>{commission.time}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 小程序风格底部导航栏 */}
|
{/* 底部导航 */}
|
||||||
<nav className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 safe-area-inset-bottom">
|
<BottomNavigation />
|
||||||
<div className="flex items-center justify-around py-2">
|
|
||||||
<a href="/" className="flex flex-col items-center gap-1 py-1 text-gray-600 active:bg-gray-100 rounded-lg px-3">
|
|
||||||
<Home className="w-5 h-5" />
|
|
||||||
<span className="text-xs">首页</span>
|
|
||||||
</a>
|
|
||||||
<a href="/group" className="flex flex-col items-center gap-1 py-1 text-gray-600 active:bg-gray-100 rounded-lg px-3">
|
|
||||||
<ShoppingCart className="w-5 h-5" />
|
|
||||||
<span className="text-xs">拼团</span>
|
|
||||||
</a>
|
|
||||||
<a href="/distribution" className="flex flex-col items-center gap-1 py-1 text-red-500 active:bg-red-50 rounded-lg px-3">
|
|
||||||
<TrendingUp className="w-5 h-5" />
|
|
||||||
<span className="text-xs">分销</span>
|
|
||||||
</a>
|
|
||||||
<a href="/profile" className="flex flex-col items-center gap-1 py-1 text-gray-600 active:bg-gray-100 rounded-lg px-3">
|
|
||||||
<Users className="w-5 h-5" />
|
|
||||||
<span className="text-xs">我的</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1,9 +1,65 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 222.2 84% 4.9%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
|
--primary: 222.2 47.4% 11.2%;
|
||||||
|
--primary-foreground: 210 40% 98%;
|
||||||
|
--secondary: 210 40% 96%;
|
||||||
|
--secondary-foreground: 222.2 84% 4.9%;
|
||||||
|
--muted: 210 40% 96%;
|
||||||
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
|
--accent: 210 40% 96%;
|
||||||
|
--accent-foreground: 222.2 84% 4.9%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
--border: 214.3 31.8% 91.4%;
|
||||||
|
--input: 214.3 31.8% 91.4%;
|
||||||
|
--ring: 222.2 84% 4.9%;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--chart-1: 12 76% 61%;
|
||||||
|
--chart-2: 173 58% 39%;
|
||||||
|
--chart-3: 197 37% 24%;
|
||||||
|
--chart-4: 43 74% 66%;
|
||||||
|
--chart-5: 27 87% 67%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 222.2 84% 4.9%;
|
||||||
|
--foreground: 210 40% 98%;
|
||||||
|
--card: 222.2 84% 4.9%;
|
||||||
|
--card-foreground: 210 40% 98%;
|
||||||
|
--popover: 222.2 84% 4.9%;
|
||||||
|
--popover-foreground: 210 40% 98%;
|
||||||
|
--primary: 210 40% 98%;
|
||||||
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
--muted: 217.2 32.6% 17.5%;
|
||||||
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
|
--accent: 217.2 32.6% 17.5%;
|
||||||
|
--accent-foreground: 210 40% 98%;
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
--border: 217.2 32.6% 17.5%;
|
||||||
|
--input: 217.2 32.6% 17.5%;
|
||||||
|
--ring: 212.7 26.8% 83.9%;
|
||||||
|
--chart-1: 220 70% 50%;
|
||||||
|
--chart-2: 160 60% 45%;
|
||||||
|
--chart-3: 30 80% 55%;
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
--chart-5: 340 75% 55%;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
border-color: hsl(var(--border));
|
border-color: hsl(var(--border));
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: hsl(var(--background));
|
background-color: hsl(var(--background));
|
||||||
color: hsl(var(--foreground));
|
color: hsl(var(--foreground));
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button'
|
|||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Progress } from '@/components/ui/progress'
|
import { Progress } from '@/components/ui/progress'
|
||||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
|
||||||
|
import BottomNavigation from '@/components/bottom-navigation'
|
||||||
import {
|
import {
|
||||||
Users,
|
Users,
|
||||||
Clock,
|
Clock,
|
||||||
@ -31,7 +32,7 @@ export default function GroupListPage() {
|
|||||||
id: '1',
|
id: '1',
|
||||||
title: '3人拼团 玫瑰香水盲盒',
|
title: '3人拼团 玫瑰香水盲盒',
|
||||||
description: '经典玫瑰香调,优雅女神范',
|
description: '经典玫瑰香调,优雅女神范',
|
||||||
image: '/api/placeholder/300/200',
|
image: 'https://picsum.photos/300/200?random=20',
|
||||||
originalPrice: 199,
|
originalPrice: 199,
|
||||||
groupPrice: 139,
|
groupPrice: 139,
|
||||||
groupSize: 3,
|
groupSize: 3,
|
||||||
@ -48,79 +49,79 @@ export default function GroupListPage() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
title: '5人拼团 薰衣草精油套装',
|
title: '5人拼团 薰衣草助眠套装',
|
||||||
description: '纯天然薰衣草精油,助眠神器',
|
description: '天然薰衣草精油,助眠好伴侣',
|
||||||
image: '/api/placeholder/300/200',
|
image: 'https://picsum.photos/300/200?random=21',
|
||||||
originalPrice: 299,
|
originalPrice: 299,
|
||||||
groupPrice: 199,
|
groupPrice: 199,
|
||||||
groupSize: 5,
|
groupSize: 5,
|
||||||
currentMembers: 3,
|
currentMembers: 4,
|
||||||
timeLeft: '1天5小时',
|
timeLeft: '5小时12分',
|
||||||
status: 'active',
|
status: 'active',
|
||||||
host: {
|
host: {
|
||||||
name: '精油达人',
|
name: '花仙子',
|
||||||
avatar: '精',
|
avatar: '花',
|
||||||
rating: 4.8
|
rating: 4.8
|
||||||
},
|
},
|
||||||
category: '精油',
|
category: '护理',
|
||||||
tags: ['新品']
|
tags: ['新品', '热销']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
title: '2人拼团 茉莉香氛蜡烛',
|
title: '2人拼团 柠檬清香车载香薰',
|
||||||
description: '手工制作,天然大豆蜡',
|
description: '清新柠檬香,净化车内空气',
|
||||||
image: '/api/placeholder/300/200',
|
image: 'https://picsum.photos/300/200?random=22',
|
||||||
originalPrice: 89,
|
originalPrice: 89,
|
||||||
groupPrice: 59,
|
groupPrice: 59,
|
||||||
groupSize: 2,
|
groupSize: 2,
|
||||||
currentMembers: 1,
|
currentMembers: 1,
|
||||||
timeLeft: '6小时12分',
|
timeLeft: '1天8小时',
|
||||||
status: 'active',
|
status: 'active',
|
||||||
host: {
|
host: {
|
||||||
name: '蜡烛工坊',
|
name: '清风徐来',
|
||||||
avatar: '蜡',
|
avatar: '清',
|
||||||
rating: 4.7
|
rating: 4.7
|
||||||
},
|
},
|
||||||
category: '香薰',
|
category: '车载',
|
||||||
tags: ['手工', '天然']
|
tags: ['清新']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '4',
|
id: '4',
|
||||||
title: '4人拼团 小样体验包',
|
title: '4人拼团 茉莉花茶香氛礼盒',
|
||||||
description: '10款热门香水小样,超值体验',
|
description: '优雅茉莉花香,送礼首选',
|
||||||
image: '/api/placeholder/300/200',
|
image: 'https://picsum.photos/300/200?random=23',
|
||||||
originalPrice: 99,
|
originalPrice: 399,
|
||||||
groupPrice: 29,
|
groupPrice: 299,
|
||||||
groupSize: 4,
|
groupSize: 4,
|
||||||
currentMembers: 4,
|
currentMembers: 3,
|
||||||
timeLeft: '已成团',
|
timeLeft: '3小时45分',
|
||||||
status: 'success',
|
status: 'active',
|
||||||
host: {
|
host: {
|
||||||
name: '小样控',
|
name: '茉莉小姐',
|
||||||
avatar: '样',
|
avatar: '茉',
|
||||||
rating: 4.9
|
rating: 5.0
|
||||||
},
|
},
|
||||||
category: '小样',
|
category: '礼盒',
|
||||||
tags: ['超值', '体验']
|
tags: ['礼品', '高端']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '5',
|
id: '5',
|
||||||
title: '3人拼团 沉香木手串',
|
title: '6人拼团 混合香调体验装',
|
||||||
description: '天然沉香木,淡雅香韵',
|
description: '多种香调小样,发现你的专属香味',
|
||||||
image: '/api/placeholder/300/200',
|
image: 'https://picsum.photos/300/200?random=24',
|
||||||
originalPrice: 399,
|
originalPrice: 159,
|
||||||
groupPrice: 299,
|
groupPrice: 99,
|
||||||
groupSize: 3,
|
groupSize: 6,
|
||||||
currentMembers: 1,
|
currentMembers: 5,
|
||||||
timeLeft: '3天2小时',
|
timeLeft: '30分钟',
|
||||||
status: 'active',
|
status: 'urgent',
|
||||||
host: {
|
host: {
|
||||||
name: '香木匠人',
|
name: '香调师',
|
||||||
avatar: '木',
|
avatar: '调',
|
||||||
rating: 4.6
|
rating: 4.9
|
||||||
},
|
},
|
||||||
category: '香木',
|
category: '体验',
|
||||||
tags: ['天然', '收藏']
|
tags: ['体验装', '紧急']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -132,228 +133,180 @@ export default function GroupListPage() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFilteredGroups = () => {
|
const getProgressPercentage = (current: number, total: number) => {
|
||||||
switch (activeTab) {
|
return (current / total) * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'urgent':
|
||||||
|
return 'text-red-600 bg-red-50'
|
||||||
case 'active':
|
case 'active':
|
||||||
return groupBuys.filter(group => group.status === 'active')
|
return 'text-green-600 bg-green-50'
|
||||||
case 'success':
|
|
||||||
return groupBuys.filter(group => group.status === 'success')
|
|
||||||
case 'hot':
|
|
||||||
return groupBuys.filter(group => group.tags.includes('热门'))
|
|
||||||
default:
|
default:
|
||||||
return groupBuys
|
return 'text-gray-600 bg-gray-50'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusBadge = (group: any) => {
|
const filteredGroups = groupBuys.filter(group => {
|
||||||
if (group.status === 'success') {
|
if (activeTab === 'all') return true
|
||||||
return <Badge className="bg-green-500 text-white text-xs px-2 py-1">已成团</Badge>
|
if (activeTab === 'urgent') return group.status === 'urgent'
|
||||||
}
|
if (activeTab === 'new') return group.tags.includes('新品')
|
||||||
if (group.currentMembers === group.groupSize - 1) {
|
return true
|
||||||
return <Badge className="bg-orange-500 text-white text-xs px-2 py-1">差1人</Badge>
|
})
|
||||||
}
|
|
||||||
return <Badge className="bg-red-500 text-white text-xs px-2 py-1">拼团中</Badge>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50 pb-20">
|
||||||
{/* 小程序风格顶部导航 */}
|
{/* 顶部导航 */}
|
||||||
<div className="sticky top-0 z-50 bg-white border-b border-gray-100">
|
<div className="bg-white border-b border-gray-100 sticky top-0 z-40">
|
||||||
<div className="px-4 py-3">
|
<div className="px-4 py-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h1 className="text-lg font-bold text-gray-800">拼团专区</h1>
|
<h1 className="text-xl font-bold text-gray-900">团队拼团</h1>
|
||||||
<div className="flex items-center gap-2">
|
<Button variant="ghost" size="sm">
|
||||||
<div className="relative">
|
<Search className="w-5 h-5" />
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
</Button>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="搜索商品"
|
|
||||||
className="w-48 pl-10 pr-4 py-2 text-sm bg-gray-100 rounded-full focus:outline-none focus:bg-white focus:ring-1 focus:ring-purple-300"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-4 pb-20">
|
{/* 标签切换 */}
|
||||||
{/* 小程序风格统计卡片 */}
|
<div className="flex gap-2">
|
||||||
<div className="grid grid-cols-3 gap-3 py-4">
|
<Button
|
||||||
<div className="bg-gradient-to-br from-red-400 to-red-500 text-white p-3 rounded-xl text-center shadow-sm">
|
variant={activeTab === 'all' ? 'default' : 'ghost'}
|
||||||
<div className="text-xl font-bold">156</div>
|
size="sm"
|
||||||
<div className="text-xs opacity-90">进行中</div>
|
onClick={() => setActiveTab('all')}
|
||||||
</div>
|
className="rounded-full"
|
||||||
<div className="bg-gradient-to-br from-green-400 to-green-500 text-white p-3 rounded-xl text-center shadow-sm">
|
|
||||||
<div className="text-xl font-bold">89</div>
|
|
||||||
<div className="text-xs opacity-90">今日成团</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-gradient-to-br from-orange-400 to-orange-500 text-white p-3 rounded-xl text-center shadow-sm">
|
|
||||||
<div className="text-xl font-bold">2.3k</div>
|
|
||||||
<div className="text-xs opacity-90">参团人数</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 小程序风格分类标签 */}
|
|
||||||
<div className="flex gap-2 mb-4 overflow-x-auto scrollbar-hide">
|
|
||||||
{[
|
|
||||||
{ key: 'all', label: '全部' },
|
|
||||||
{ key: 'active', label: '进行中' },
|
|
||||||
{ key: 'hot', label: '热门' },
|
|
||||||
{ key: 'success', label: '已成团' }
|
|
||||||
].map((tab) => (
|
|
||||||
<button
|
|
||||||
key={tab.key}
|
|
||||||
onClick={() => setActiveTab(tab.key)}
|
|
||||||
className={`px-4 py-2 rounded-full text-sm font-medium whitespace-nowrap transition-all ${
|
|
||||||
activeTab === tab.key
|
|
||||||
? 'bg-red-500 text-white shadow-md'
|
|
||||||
: 'bg-white text-gray-600 border border-gray-200'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{tab.label}
|
全部
|
||||||
</button>
|
</Button>
|
||||||
))}
|
<Button
|
||||||
|
variant={activeTab === 'urgent' ? 'default' : 'ghost'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setActiveTab('urgent')}
|
||||||
|
className="rounded-full"
|
||||||
|
>
|
||||||
|
<Zap className="w-4 h-4 mr-1" />
|
||||||
|
紧急
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={activeTab === 'new' ? 'default' : 'ghost'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setActiveTab('new')}
|
||||||
|
className="rounded-full"
|
||||||
|
>
|
||||||
|
<Star className="w-4 h-4 mr-1" />
|
||||||
|
新品
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 拼团商品列表 - 小程序风格 */}
|
{/* 统计信息 */}
|
||||||
<div className="space-y-3">
|
<div className="px-4 py-4">
|
||||||
{getFilteredGroups().map((group) => (
|
<div className="grid grid-cols-3 gap-4">
|
||||||
<Card key={group.id} className="overflow-hidden bg-white rounded-xl shadow-sm border-0">
|
<div className="bg-white rounded-xl p-4 text-center">
|
||||||
|
<div className="text-2xl font-bold text-blue-600 mb-1">{groupBuys.length}</div>
|
||||||
|
<div className="text-sm text-gray-600">进行中</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white rounded-xl p-4 text-center">
|
||||||
|
<div className="text-2xl font-bold text-green-600 mb-1">156</div>
|
||||||
|
<div className="text-sm text-gray-600">已完成</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white rounded-xl p-4 text-center">
|
||||||
|
<div className="text-2xl font-bold text-purple-600 mb-1">89</div>
|
||||||
|
<div className="text-sm text-gray-600">参与人数</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 拼团列表 */}
|
||||||
|
<div className="px-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{filteredGroups.map((group) => (
|
||||||
|
<Card key={group.id} className="overflow-hidden border-0 shadow-sm">
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<Link href={`/group/${group.id}`}>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{/* 商品图片 */}
|
{/* 商品图片 */}
|
||||||
<div className="relative w-28 h-28 flex-shrink-0">
|
<div className="w-24 h-24 flex-shrink-0">
|
||||||
<img
|
<img
|
||||||
src={group.image}
|
src={group.image}
|
||||||
alt={group.title}
|
alt={group.title}
|
||||||
className="w-full h-full object-cover rounded-l-xl"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
<div className="absolute top-2 left-2">
|
|
||||||
{getStatusBadge(group)}
|
|
||||||
</div>
|
|
||||||
{group.tags.map((tag, index) => (
|
|
||||||
<Badge
|
|
||||||
key={index}
|
|
||||||
className="absolute bottom-2 left-2 bg-black/60 text-white text-xs px-1.5 py-0.5"
|
|
||||||
>
|
|
||||||
{tag}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 商品信息 */}
|
{/* 内容区域 */}
|
||||||
<div className="flex-1 p-3">
|
<div className="flex-1 p-4">
|
||||||
<div className="flex justify-between items-start mb-2">
|
<div className="flex items-start justify-between mb-2">
|
||||||
<h3 className="font-medium text-sm text-gray-800 line-clamp-2 flex-1 mr-2">
|
<div className="flex-1">
|
||||||
|
<h3 className="font-semibold text-gray-900 text-sm line-clamp-1 mb-1">
|
||||||
{group.title}
|
{group.title}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<p className="text-xs text-gray-600 line-clamp-1 mb-2">
|
||||||
onClick={() => handleLike(group.id)}
|
{group.description}
|
||||||
className="p-1"
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="p-1 ml-2"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
handleLike(group.id)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Heart className={`w-4 h-4 ${likedGroups.includes(group.id) ? 'fill-red-500 text-red-500' : 'text-gray-400'}`} />
|
<Heart className={`w-4 h-4 ${likedGroups.includes(group.id) ? 'fill-red-500 text-red-500' : 'text-gray-400'}`} />
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 价格信息 */}
|
{/* 价格和进度 */}
|
||||||
<div className="flex items-baseline gap-2 mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-lg font-bold text-red-500">¥{group.groupPrice}</span>
|
<div className="flex items-baseline gap-2">
|
||||||
<span className="text-xs text-gray-400 line-through">¥{group.originalPrice}</span>
|
<span className="text-red-600 font-bold text-lg">¥{group.groupPrice}</span>
|
||||||
|
<span className="text-gray-400 line-through text-sm">¥{group.originalPrice}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-xs text-gray-500">
|
||||||
{/* 拼团进度 */}
|
|
||||||
<div className="mb-2">
|
|
||||||
<div className="flex items-center justify-between mb-1">
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Users className="w-3 h-3 text-red-500" />
|
|
||||||
<span className="text-xs text-gray-600">拼团进度</span>
|
|
||||||
</div>
|
|
||||||
<span className="text-xs font-medium text-red-500">
|
|
||||||
{group.currentMembers}/{group.groupSize}人
|
{group.currentMembers}/{group.groupSize}人
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 进度条 */}
|
||||||
|
<div className="mb-2">
|
||||||
<Progress
|
<Progress
|
||||||
value={(group.currentMembers / group.groupSize) * 100}
|
value={getProgressPercentage(group.currentMembers, group.groupSize)}
|
||||||
className="h-1.5"
|
className="h-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 底部信息 */}
|
{/* 底部信息 */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-2">
|
||||||
<div className="relative">
|
<Avatar className="w-5 h-5">
|
||||||
<Avatar className="w-4 h-4">
|
<AvatarFallback className="text-xs bg-purple-100 text-purple-600">
|
||||||
<AvatarFallback className="text-xs bg-gray-200">{group.host.avatar}</AvatarFallback>
|
{group.host.avatar}
|
||||||
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Crown className="w-2 h-2 text-yellow-500 absolute -top-0.5 -right-0.5" />
|
<span className="text-xs text-gray-600">{group.host.name}</span>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Star className="w-3 h-3 text-yellow-400 fill-current" />
|
||||||
|
<span className="text-xs text-gray-600">{group.host.rating}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-gray-500">{group.host.name}</span>
|
|
||||||
<Star className="w-2.5 h-2.5 text-yellow-500 fill-current" />
|
|
||||||
<span className="text-xs text-gray-500">{group.host.rating}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 text-orange-500">
|
<div className={`px-2 py-1 rounded-full text-xs ${getStatusColor(group.status)}`}>
|
||||||
<Clock className="w-3 h-3" />
|
<Clock className="w-3 h-3 inline mr-1" />
|
||||||
<span className="text-xs">{group.timeLeft}</span>
|
{group.timeLeft}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 操作按钮 */}
|
|
||||||
<div className="px-3 pb-3">
|
|
||||||
<Link href={`/group/${group.id}`} className="block">
|
|
||||||
<Button
|
|
||||||
className="w-full h-8 bg-gradient-to-r from-red-400 to-red-500 hover:from-red-500 hover:to-red-600 text-white text-sm rounded-full"
|
|
||||||
disabled={group.status === 'success'}
|
|
||||||
>
|
|
||||||
{group.status === 'success' ? (
|
|
||||||
<>
|
|
||||||
<Gift className="w-3 h-3 mr-1" />
|
|
||||||
查看详情
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Users className="w-3 h-3 mr-1" />
|
|
||||||
立即参团
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 空状态 */}
|
|
||||||
{getFilteredGroups().length === 0 && (
|
|
||||||
<div className="text-center py-12">
|
|
||||||
<Users className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
|
||||||
<h3 className="text-base font-medium text-gray-600 mb-2">暂无拼团活动</h3>
|
|
||||||
<p className="text-sm text-gray-500">敬请期待更多精彩拼团活动</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 小程序风格底部导航栏 */}
|
{/* 底部导航 */}
|
||||||
<nav className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 safe-area-inset-bottom">
|
<BottomNavigation />
|
||||||
<div className="flex items-center justify-around py-2">
|
|
||||||
<Link href="/" className="flex flex-col items-center gap-1 py-1 text-gray-600 active:bg-gray-100 rounded-lg px-3">
|
|
||||||
<Home className="h-5 w-5" />
|
|
||||||
<span className="text-xs">首页</span>
|
|
||||||
</Link>
|
|
||||||
<Link href="/group" className="flex flex-col items-center gap-1 py-1 text-red-500 active:bg-red-50 rounded-lg px-3">
|
|
||||||
<ShoppingCart className="h-5 w-5" />
|
|
||||||
<span className="text-xs">拼团</span>
|
|
||||||
</Link>
|
|
||||||
<Link href="/distribution" className="flex flex-col items-center gap-1 py-1 text-gray-600 active:bg-gray-100 rounded-lg px-3">
|
|
||||||
<TrendingUp className="h-5 w-5" />
|
|
||||||
<span className="text-xs">分销</span>
|
|
||||||
</Link>
|
|
||||||
<Link href="/profile" className="flex flex-col items-center gap-1 py-1 text-gray-600 active:bg-gray-100 rounded-lg px-3">
|
|
||||||
<Users className="h-5 w-5" />
|
|
||||||
<span className="text-xs">我的</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -64,7 +64,7 @@ export default function HomePage() {
|
|||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
name: '玫瑰香水盲盒',
|
name: '玫瑰香水盲盒',
|
||||||
image: '/api/placeholder/400/400',
|
image: 'https://picsum.photos/400/400?random=1',
|
||||||
price: 139,
|
price: 139,
|
||||||
originalPrice: 199,
|
originalPrice: 199,
|
||||||
discount: '7折',
|
discount: '7折',
|
||||||
@ -82,7 +82,7 @@ export default function HomePage() {
|
|||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
name: '薰衣草助眠香氛',
|
name: '薰衣草助眠香氛',
|
||||||
image: '/api/placeholder/400/400',
|
image: 'https://picsum.photos/400/400?random=2',
|
||||||
price: 89,
|
price: 89,
|
||||||
originalPrice: 129,
|
originalPrice: 129,
|
||||||
discount: '69折',
|
discount: '69折',
|
||||||
@ -104,7 +104,7 @@ export default function HomePage() {
|
|||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
title: '3人拼团 玫瑰香水',
|
title: '3人拼团 玫瑰香水',
|
||||||
image: '/api/placeholder/400/400',
|
image: 'https://picsum.photos/400/400?random=3',
|
||||||
originalPrice: 199,
|
originalPrice: 199,
|
||||||
groupPrice: 139,
|
groupPrice: 139,
|
||||||
groupSize: 3,
|
groupSize: 3,
|
||||||
@ -120,7 +120,7 @@ export default function HomePage() {
|
|||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
title: '5人拼团 薰衣草套装',
|
title: '5人拼团 薰衣草套装',
|
||||||
image: '/api/placeholder/400/400',
|
image: 'https://picsum.photos/400/400?random=4',
|
||||||
originalPrice: 299,
|
originalPrice: 299,
|
||||||
groupPrice: 199,
|
groupPrice: 199,
|
||||||
groupSize: 5,
|
groupSize: 5,
|
||||||
|
|||||||
562
src/app/product/[id]/page.tsx
Normal file
562
src/app/product/[id]/page.tsx
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect, useRef } from 'react'
|
||||||
|
import { useRouter, useParams } from 'next/navigation'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
|
import {
|
||||||
|
Heart,
|
||||||
|
Share2,
|
||||||
|
ShoppingCart,
|
||||||
|
Star,
|
||||||
|
ArrowLeft,
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Shield,
|
||||||
|
Truck,
|
||||||
|
RotateCcw,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight
|
||||||
|
} from 'lucide-react'
|
||||||
|
import { showSuccessToast, showLikeToast, showShareToast } from '@/lib/toast-utils'
|
||||||
|
|
||||||
|
interface Product {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
images: string[]
|
||||||
|
price: number
|
||||||
|
originalPrice: number
|
||||||
|
discount: string
|
||||||
|
rating: number
|
||||||
|
reviews: number
|
||||||
|
likes: number
|
||||||
|
comments: number
|
||||||
|
description: string
|
||||||
|
tags: string[]
|
||||||
|
stock: number
|
||||||
|
category: string
|
||||||
|
isHot: boolean
|
||||||
|
isNew: boolean
|
||||||
|
specifications: {
|
||||||
|
brand: string
|
||||||
|
capacity: string
|
||||||
|
fragrance: string
|
||||||
|
duration: string
|
||||||
|
origin: string
|
||||||
|
}
|
||||||
|
details: {
|
||||||
|
ingredients: string[]
|
||||||
|
usage: string
|
||||||
|
storage: string
|
||||||
|
notes: {
|
||||||
|
top: string[]
|
||||||
|
middle: string[]
|
||||||
|
base: string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ProductDetailPage() {
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams()
|
||||||
|
const productId = params.id as string
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const [product, setProduct] = useState<Product | null>(null)
|
||||||
|
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
||||||
|
const [quantity, setQuantity] = useState(1)
|
||||||
|
const [isLiked, setIsLiked] = useState(false)
|
||||||
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
|
||||||
|
// 模拟商品数据
|
||||||
|
useEffect(() => {
|
||||||
|
const mockProduct: Product = {
|
||||||
|
id: productId,
|
||||||
|
name: '玫瑰香水盲盒',
|
||||||
|
images: [
|
||||||
|
'https://picsum.photos/600/600?random=10',
|
||||||
|
'https://picsum.photos/600/600?random=11',
|
||||||
|
'https://picsum.photos/600/600?random=12',
|
||||||
|
'https://picsum.photos/600/600?random=13',
|
||||||
|
'https://picsum.photos/600/600?random=14',
|
||||||
|
'https://picsum.photos/600/600?random=15'
|
||||||
|
],
|
||||||
|
price: 139,
|
||||||
|
originalPrice: 199,
|
||||||
|
discount: '7折',
|
||||||
|
rating: 4.8,
|
||||||
|
reviews: 234,
|
||||||
|
likes: 567,
|
||||||
|
comments: 89,
|
||||||
|
description: '经典玫瑰香调,优雅女神范,持久留香8小时。采用法国进口玫瑰精油,层次丰富,前调清新,中调浓郁,后调温暖。适合日常使用,也是送礼的绝佳选择。',
|
||||||
|
tags: ['热销', '限量', '法国进口'],
|
||||||
|
stock: 15,
|
||||||
|
category: 'floral',
|
||||||
|
isHot: true,
|
||||||
|
isNew: false,
|
||||||
|
specifications: {
|
||||||
|
brand: 'Elegant Rose',
|
||||||
|
capacity: '50ml',
|
||||||
|
fragrance: '花香调',
|
||||||
|
duration: '6-8小时',
|
||||||
|
origin: '法国'
|
||||||
|
},
|
||||||
|
details: {
|
||||||
|
ingredients: ['玫瑰精油', '茉莉花精华', '白麝香', '雪松木', '香草'],
|
||||||
|
usage: '喷洒在脉搏处,如手腕、耳后、颈部等部位,避免直接接触衣物。',
|
||||||
|
storage: '请存放在阴凉干燥处,避免阳光直射和高温环境。',
|
||||||
|
notes: {
|
||||||
|
top: ['柠檬', '佛手柑', '粉红胡椒'],
|
||||||
|
middle: ['玫瑰', '茉莉', '牡丹'],
|
||||||
|
base: ['白麝香', '雪松', '香草']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setProduct(mockProduct)
|
||||||
|
setIsLoading(false)
|
||||||
|
}, 1000)
|
||||||
|
}, [productId])
|
||||||
|
|
||||||
|
const handleLike = () => {
|
||||||
|
setIsLiked(!isLiked)
|
||||||
|
showLikeToast()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleShare = () => {
|
||||||
|
if (navigator.share) {
|
||||||
|
navigator.share({
|
||||||
|
title: product?.name,
|
||||||
|
text: `来看看这个超棒的香氛产品!`,
|
||||||
|
url: window.location.href
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
navigator.clipboard.writeText(window.location.href)
|
||||||
|
showShareToast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddToCart = () => {
|
||||||
|
if (!product) return
|
||||||
|
|
||||||
|
// 检查库存
|
||||||
|
if (quantity > product.stock) {
|
||||||
|
showSuccessToast('库存不足,请减少购买数量')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟添加到购物车的逻辑
|
||||||
|
const cartItem = {
|
||||||
|
productId: product.id,
|
||||||
|
name: product.name,
|
||||||
|
price: product.price,
|
||||||
|
quantity: quantity,
|
||||||
|
image: product.images[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里可以调用实际的购物车API
|
||||||
|
console.log('添加到购物车:', cartItem)
|
||||||
|
|
||||||
|
showSuccessToast(`已添加 ${quantity} 件 "${product.name}" 到购物车`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBuyNow = () => {
|
||||||
|
if (!product) return
|
||||||
|
|
||||||
|
// 检查库存
|
||||||
|
if (quantity > product.stock) {
|
||||||
|
showSuccessToast('库存不足,请减少购买数量')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟立即购买的逻辑
|
||||||
|
const orderItem = {
|
||||||
|
productId: product.id,
|
||||||
|
name: product.name,
|
||||||
|
price: product.price,
|
||||||
|
quantity: quantity,
|
||||||
|
totalAmount: product.price * quantity,
|
||||||
|
image: product.images[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里可以跳转到结算页面或调用支付API
|
||||||
|
console.log('立即购买:', orderItem)
|
||||||
|
|
||||||
|
showSuccessToast(`正在为您处理 ${quantity} 件商品的订单,总金额 ¥${product.price * quantity}`)
|
||||||
|
|
||||||
|
// 模拟跳转到结算页面
|
||||||
|
setTimeout(() => {
|
||||||
|
showSuccessToast('订单创建成功!正在跳转到支付页面...')
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
|
||||||
|
const increaseQuantity = () => {
|
||||||
|
if (product && quantity < product.stock) {
|
||||||
|
setQuantity(quantity + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const decreaseQuantity = () => {
|
||||||
|
if (quantity > 1) {
|
||||||
|
setQuantity(quantity - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片滑动功能
|
||||||
|
const scrollToImage = (index: number) => {
|
||||||
|
setCurrentImageIndex(index)
|
||||||
|
if (scrollContainerRef.current) {
|
||||||
|
const container = scrollContainerRef.current
|
||||||
|
const imageWidth = container.clientWidth
|
||||||
|
container.scrollTo({
|
||||||
|
left: imageWidth * index,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePrevImage = () => {
|
||||||
|
if (!product) return
|
||||||
|
const prevIndex = currentImageIndex > 0 ? currentImageIndex - 1 : product.images.length - 1
|
||||||
|
scrollToImage(prevIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNextImage = () => {
|
||||||
|
if (!product) return
|
||||||
|
const nextIndex = currentImageIndex < product.images.length - 1 ? currentImageIndex + 1 : 0
|
||||||
|
scrollToImage(nextIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听滑动事件
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (scrollContainerRef.current && product) {
|
||||||
|
const container = scrollContainerRef.current
|
||||||
|
const imageWidth = container.clientWidth
|
||||||
|
const scrollLeft = container.scrollLeft
|
||||||
|
const newIndex = Math.round(scrollLeft / imageWidth)
|
||||||
|
if (newIndex !== currentImageIndex && newIndex >= 0 && newIndex < product.images.length) {
|
||||||
|
setCurrentImageIndex(newIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-pink-50 flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-16 h-16 border-4 border-purple-200 border-t-purple-600 rounded-full animate-spin mx-auto mb-4"></div>
|
||||||
|
<p className="text-gray-600">加载商品详情中...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-pink-50 flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-gray-600 mb-4">商品不存在</p>
|
||||||
|
<Button onClick={() => router.back()}>返回</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50">
|
||||||
|
{/* 顶部导航 */}
|
||||||
|
<div className="sticky top-0 z-50 bg-white/95 backdrop-blur-md border-b border-gray-100">
|
||||||
|
<div className="flex items-center justify-between px-4 py-3">
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => router.back()} className="p-2">
|
||||||
|
<ArrowLeft className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
<h1 className="font-medium text-gray-900">商品详情</h1>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button variant="ghost" size="sm" onClick={handleLike} className="p-2">
|
||||||
|
<Heart className={`w-5 h-5 ${isLiked ? 'fill-red-500 text-red-500' : 'text-gray-600'}`} />
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="sm" onClick={handleShare} className="p-2">
|
||||||
|
<Share2 className="w-5 h-5 text-gray-600" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pb-24">
|
||||||
|
{/* 商品图片轮播区域 */}
|
||||||
|
<div className="relative bg-white">
|
||||||
|
{/* 主图片滑动容器 */}
|
||||||
|
<div
|
||||||
|
ref={scrollContainerRef}
|
||||||
|
className="flex overflow-x-auto snap-x snap-mandatory scrollbar-hide"
|
||||||
|
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
|
||||||
|
onScroll={handleScroll}
|
||||||
|
>
|
||||||
|
{product.images.map((image, index) => (
|
||||||
|
<div key={index} className="w-full flex-shrink-0 snap-center">
|
||||||
|
<div className="aspect-square relative">
|
||||||
|
<img
|
||||||
|
src={image}
|
||||||
|
alt={`${product.name} - 图片 ${index + 1}`}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
{/* 标签 */}
|
||||||
|
{index === 0 && (
|
||||||
|
<div className="absolute top-4 left-4 flex flex-col gap-2">
|
||||||
|
{product.isHot && (
|
||||||
|
<Badge className="bg-red-500 text-white shadow-lg">热销</Badge>
|
||||||
|
)}
|
||||||
|
{product.isNew && (
|
||||||
|
<Badge className="bg-green-500 text-white shadow-lg">新品</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 左右切换按钮 */}
|
||||||
|
<button
|
||||||
|
onClick={handlePrevImage}
|
||||||
|
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/80 backdrop-blur-sm rounded-full shadow-lg flex items-center justify-center hover:bg-white transition-colors"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-5 h-5 text-gray-700" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleNextImage}
|
||||||
|
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/80 backdrop-blur-sm rounded-full shadow-lg flex items-center justify-center hover:bg-white transition-colors"
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-5 h-5 text-gray-700" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* 图片指示器 */}
|
||||||
|
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
|
||||||
|
{product.images.map((_, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => scrollToImage(index)}
|
||||||
|
className={`w-2 h-2 rounded-full transition-all ${
|
||||||
|
currentImageIndex === index
|
||||||
|
? 'bg-white w-6'
|
||||||
|
: 'bg-white/50'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 商品基本信息 */}
|
||||||
|
<div className="bg-white px-4 py-6 mb-2">
|
||||||
|
{/* 标签 */}
|
||||||
|
<div className="flex flex-wrap gap-2 mb-3">
|
||||||
|
{product.tags.map((tag, index) => (
|
||||||
|
<Badge key={index} variant="outline" className="text-purple-600 border-purple-200 bg-purple-50">
|
||||||
|
{tag}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 商品名称 */}
|
||||||
|
<h1 className="text-xl font-bold text-gray-900 mb-3 leading-tight">{product.name}</h1>
|
||||||
|
|
||||||
|
{/* 评分和评价 */}
|
||||||
|
<div className="flex items-center gap-4 mb-4">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Star className="w-4 h-4 fill-yellow-400 text-yellow-400" />
|
||||||
|
<span className="font-medium text-gray-900">{product.rating}</span>
|
||||||
|
<span className="text-gray-500 text-sm">({product.reviews}条评价)</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-gray-500 text-sm">
|
||||||
|
{product.likes} 人喜欢
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 价格 */}
|
||||||
|
<div className="flex items-baseline gap-3 mb-4">
|
||||||
|
<span className="text-2xl font-bold text-red-600">¥{product.price}</span>
|
||||||
|
<span className="text-base text-gray-400 line-through">¥{product.originalPrice}</span>
|
||||||
|
<Badge variant="destructive" className="text-xs">{product.discount}</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 商品描述 */}
|
||||||
|
<p className="text-gray-600 text-sm leading-relaxed">{product.description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 购买选项 */}
|
||||||
|
<div className="bg-white px-4 py-6 mb-2">
|
||||||
|
{/* 数量选择 */}
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<span className="font-medium text-gray-900">购买数量</span>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={decreaseQuantity}
|
||||||
|
disabled={quantity <= 1}
|
||||||
|
className="h-9 w-9 p-0 rounded-full"
|
||||||
|
>
|
||||||
|
<Minus className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<div className="flex items-center justify-center w-12 h-9 bg-gray-50 rounded-lg">
|
||||||
|
<span className="font-medium">{quantity}</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={increaseQuantity}
|
||||||
|
disabled={quantity >= product.stock}
|
||||||
|
className="h-9 w-9 p-0 rounded-full"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 库存和小计 */}
|
||||||
|
<div className="flex items-center justify-between text-sm text-gray-500 mb-6">
|
||||||
|
<span>库存 {product.stock} 件</span>
|
||||||
|
<span className="font-medium text-gray-900">小计 ¥{(product.price * quantity).toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 库存状态提示 */}
|
||||||
|
{product.stock === 0 && (
|
||||||
|
<div className="text-center py-3 bg-red-50 rounded-lg mb-4">
|
||||||
|
<span className="text-red-600 font-medium">商品已售罄</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{product.stock > 0 && product.stock <= 5 && (
|
||||||
|
<div className="text-center py-2 bg-orange-50 rounded-lg mb-4">
|
||||||
|
<span className="text-orange-600 text-sm">⚠️ 库存紧张,仅剩 {product.stock} 件</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 商品详情 */}
|
||||||
|
<div className="bg-white mb-2">
|
||||||
|
<Tabs defaultValue="details" className="w-full">
|
||||||
|
<TabsList className="grid w-full grid-cols-3 bg-gray-50 mx-4 rounded-lg">
|
||||||
|
<TabsTrigger value="details" className="text-sm">商品详情</TabsTrigger>
|
||||||
|
<TabsTrigger value="specs" className="text-sm">规格参数</TabsTrigger>
|
||||||
|
<TabsTrigger value="reviews" className="text-sm">用户评价</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="details" className="px-4 py-6">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-gray-900 mb-3">香调层次</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<span className="w-12 text-sm text-gray-500 flex-shrink-0">前调</span>
|
||||||
|
<span className="text-gray-900 text-sm">{product.details.notes.top.join('、')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<span className="w-12 text-sm text-gray-500 flex-shrink-0">中调</span>
|
||||||
|
<span className="text-gray-900 text-sm">{product.details.notes.middle.join('、')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<span className="w-12 text-sm text-gray-500 flex-shrink-0">后调</span>
|
||||||
|
<span className="text-gray-900 text-sm">{product.details.notes.base.join('、')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-gray-900 mb-3">主要成分</h3>
|
||||||
|
<p className="text-gray-600 text-sm">{product.details.ingredients.join('、')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-gray-900 mb-3">使用方法</h3>
|
||||||
|
<p className="text-gray-600 text-sm leading-relaxed">{product.details.usage}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-gray-900 mb-3">保存方法</h3>
|
||||||
|
<p className="text-gray-600 text-sm leading-relaxed">{product.details.storage}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="specs" className="px-4 py-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{Object.entries(product.specifications).map(([key, value]) => (
|
||||||
|
<div key={key} className="flex items-center justify-between py-3 border-b border-gray-100 last:border-0">
|
||||||
|
<span className="text-gray-500 text-sm">
|
||||||
|
{key === 'brand' ? '品牌' :
|
||||||
|
key === 'capacity' ? '容量' :
|
||||||
|
key === 'fragrance' ? '香调' :
|
||||||
|
key === 'duration' ? '持香时间' :
|
||||||
|
key === 'origin' ? '产地' : key}
|
||||||
|
</span>
|
||||||
|
<span className="font-medium text-gray-900 text-sm">{value}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="reviews" className="px-4 py-6">
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<Star className="w-8 h-8 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-500 mb-2">暂无用户评价</p>
|
||||||
|
<p className="text-sm text-gray-400">成为第一个评价此商品的用户</p>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 服务保障 */}
|
||||||
|
<div className="bg-white px-4 py-6">
|
||||||
|
<h3 className="font-semibold text-gray-900 mb-4">服务保障</h3>
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<div className="flex flex-col items-center text-center">
|
||||||
|
<div className="w-12 h-12 bg-green-50 rounded-full flex items-center justify-center mb-2">
|
||||||
|
<Shield className="w-6 h-6 text-green-500" />
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-gray-600">正品保证</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center text-center">
|
||||||
|
<div className="w-12 h-12 bg-blue-50 rounded-full flex items-center justify-center mb-2">
|
||||||
|
<Truck className="w-6 h-6 text-blue-500" />
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-gray-600">免费配送</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center text-center">
|
||||||
|
<div className="w-12 h-12 bg-purple-50 rounded-full flex items-center justify-center mb-2">
|
||||||
|
<RotateCcw className="w-6 h-6 text-purple-500" />
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-gray-600">7天退换</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 底部固定购买栏 */}
|
||||||
|
<div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-4 py-3 z-40">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="flex-1 h-12 border-gray-300"
|
||||||
|
onClick={handleAddToCart}
|
||||||
|
disabled={product.stock === 0}
|
||||||
|
>
|
||||||
|
<ShoppingCart className="w-5 h-5 mr-2" />
|
||||||
|
加入购物车
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="flex-1 h-12 bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white font-medium"
|
||||||
|
onClick={handleBuyNow}
|
||||||
|
disabled={product.stock === 0}
|
||||||
|
>
|
||||||
|
立即购买
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ import { Label } from '@/components/ui/label'
|
|||||||
import { Checkbox } from '@/components/ui/checkbox'
|
import { Checkbox } from '@/components/ui/checkbox'
|
||||||
import { Slider } from '@/components/ui/slider'
|
import { Slider } from '@/components/ui/slider'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
import BottomNavigation from '@/components/bottom-navigation'
|
||||||
import {
|
import {
|
||||||
Crown,
|
Crown,
|
||||||
Star,
|
Star,
|
||||||
@ -671,29 +672,8 @@ export default function SubscriptionPage() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom Navigation */}
|
{/* 底部导航 */}
|
||||||
<div className="fixed bottom-0 left-0 right-0 bg-white border-t">
|
<BottomNavigation />
|
||||||
<div className="max-w-6xl mx-auto px-4">
|
|
||||||
<div className="grid grid-cols-4 py-2">
|
|
||||||
<a href="/" className="flex flex-col items-center gap-1 py-2 text-gray-500 hover:text-purple-500 transition-colors">
|
|
||||||
<Home className="w-5 h-5" />
|
|
||||||
<span className="text-xs">首页</span>
|
|
||||||
</a>
|
|
||||||
<a href="/subscription" className="flex flex-col items-center gap-1 py-2 text-gray-500 hover:text-purple-500 transition-colors">
|
|
||||||
<ShoppingCart className="w-5 h-5" />
|
|
||||||
<span className="text-xs">拼团</span>
|
|
||||||
</a>
|
|
||||||
<a href="/distribution" className="flex flex-col items-center gap-1 py-2 text-gray-500 hover:text-purple-500 transition-colors">
|
|
||||||
<TrendingUp className="w-5 h-5" />
|
|
||||||
<span className="text-xs">分销</span>
|
|
||||||
</a>
|
|
||||||
<a href="/profile" className="flex flex-col items-center gap-1 py-2 text-gray-500 hover:text-purple-500 transition-colors">
|
|
||||||
<Users className="w-5 h-5" />
|
|
||||||
<span className="text-xs">我的</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -5,9 +5,9 @@ import { useRouter, usePathname } from 'next/navigation'
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
Home,
|
Home,
|
||||||
Search,
|
TrendingUp,
|
||||||
PlusSquare,
|
PlusSquare,
|
||||||
ShoppingBag,
|
Bell,
|
||||||
User
|
User
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
@ -18,9 +18,9 @@ export default function BottomNavigation() {
|
|||||||
// 根据当前路径确定活跃标签
|
// 根据当前路径确定活跃标签
|
||||||
const getActiveTab = () => {
|
const getActiveTab = () => {
|
||||||
if (pathname === '/') return 'home'
|
if (pathname === '/') return 'home'
|
||||||
if (pathname.startsWith('/discover')) return 'discover'
|
if (pathname.startsWith('/distribution')) return 'distribution'
|
||||||
if (pathname.startsWith('/create')) return 'create'
|
if (pathname.startsWith('/create')) return 'create'
|
||||||
if (pathname.startsWith('/orders')) return 'orders'
|
if (pathname.startsWith('/subscription')) return 'subscription'
|
||||||
if (pathname.startsWith('/profile')) return 'profile'
|
if (pathname.startsWith('/profile')) return 'profile'
|
||||||
return 'home'
|
return 'home'
|
||||||
}
|
}
|
||||||
@ -48,11 +48,11 @@ export default function BottomNavigation() {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className={`flex flex-col items-center gap-1 p-2 h-auto ${activeTab === 'discover' ? 'text-purple-600' : 'text-gray-500'}`}
|
className={`flex flex-col items-center gap-1 p-2 h-auto ${activeTab === 'distribution' ? 'text-purple-600' : 'text-gray-500'}`}
|
||||||
onClick={() => handleNavigation('discover', '/discover')}
|
onClick={() => handleNavigation('distribution', '/distribution')}
|
||||||
>
|
>
|
||||||
<Search className="w-5 h-5" />
|
<TrendingUp className="w-5 h-5" />
|
||||||
<span className="text-xs">发现</span>
|
<span className="text-xs">分销</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@ -66,11 +66,11 @@ export default function BottomNavigation() {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className={`flex flex-col items-center gap-1 p-2 h-auto ${activeTab === 'orders' ? 'text-purple-600' : 'text-gray-500'}`}
|
className={`flex flex-col items-center gap-1 p-2 h-auto ${activeTab === 'subscription' ? 'text-purple-600' : 'text-gray-500'}`}
|
||||||
onClick={() => handleNavigation('orders', '/orders')}
|
onClick={() => handleNavigation('subscription', '/subscription')}
|
||||||
>
|
>
|
||||||
<ShoppingBag className="w-5 h-5" />
|
<Bell className="w-5 h-5" />
|
||||||
<span className="text-xs">订单</span>
|
<span className="text-xs">订阅</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
import { Card, CardContent } from '@/components/ui/card'
|
import { Card, CardContent } from '@/components/ui/card'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
@ -52,17 +53,23 @@ export function ProductCard({
|
|||||||
isBookmarked,
|
isBookmarked,
|
||||||
variant = 'default'
|
variant = 'default'
|
||||||
}: ProductCardProps) {
|
}: ProductCardProps) {
|
||||||
|
const router = useRouter()
|
||||||
const [isHovered, setIsHovered] = useState(false)
|
const [isHovered, setIsHovered] = useState(false)
|
||||||
const [imageLoaded, setImageLoaded] = useState(false)
|
const [imageLoaded, setImageLoaded] = useState(false)
|
||||||
|
|
||||||
const discountPercentage = Math.round(((product.originalPrice - product.price) / product.originalPrice) * 100)
|
const discountPercentage = Math.round(((product.originalPrice - product.price) / product.originalPrice) * 100)
|
||||||
|
|
||||||
|
const handleCardClick = () => {
|
||||||
|
router.push(`/product/${product.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
if (variant === 'compact') {
|
if (variant === 'compact') {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className="group border-0 shadow-md hover:shadow-xl transition-all duration-300 rounded-2xl overflow-hidden cursor-pointer transform hover:scale-[1.02]"
|
className="group border-0 shadow-md hover:shadow-xl transition-all duration-300 rounded-2xl overflow-hidden cursor-pointer transform hover:scale-[1.02]"
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
onClick={handleCardClick}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="aspect-square overflow-hidden bg-gray-100">
|
<div className="aspect-square overflow-hidden bg-gray-100">
|
||||||
@ -170,6 +177,7 @@ export function ProductCard({
|
|||||||
className="group border-0 shadow-lg hover:shadow-2xl transition-all duration-500 rounded-3xl overflow-hidden cursor-pointer transform hover:scale-[1.01]"
|
className="group border-0 shadow-lg hover:shadow-2xl transition-all duration-500 rounded-3xl overflow-hidden cursor-pointer transform hover:scale-[1.01]"
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
onClick={handleCardClick}
|
||||||
>
|
>
|
||||||
{/* 图片区域 */}
|
{/* 图片区域 */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@ -208,13 +216,13 @@ export function ProductCard({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 快捷操作按钮 */}
|
{/* 快捷操作按钮 */}
|
||||||
<div className={`absolute top-4 right-4 flex gap-2 transition-all duration-300 ${
|
<div className={`absolute top-4 right-4 flex flex-col gap-3 transition-all duration-300 ${
|
||||||
isHovered ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-2'
|
isHovered ? 'opacity-100 translate-x-0' : 'opacity-0 translate-x-4'
|
||||||
}`}>
|
}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="bg-white/20 backdrop-blur-md text-white p-2.5 rounded-full shadow-lg hover:bg-white/30 transition-all duration-200"
|
className="bg-white/90 backdrop-blur-sm text-gray-700 p-3 rounded-full shadow-lg hover:bg-white hover:scale-110 transition-all duration-200"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onLike(product.id)
|
onLike(product.id)
|
||||||
@ -225,7 +233,7 @@ export function ProductCard({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="bg-white/20 backdrop-blur-md text-white p-2.5 rounded-full shadow-lg hover:bg-white/30 transition-all duration-200"
|
className="bg-white/90 backdrop-blur-sm text-gray-700 p-3 rounded-full shadow-lg hover:bg-white hover:scale-110 transition-all duration-200"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onBookmark(product.id)
|
onBookmark(product.id)
|
||||||
@ -233,69 +241,77 @@ export function ProductCard({
|
|||||||
>
|
>
|
||||||
<Bookmark className={`w-5 h-5 ${isBookmarked ? 'fill-current text-blue-400' : ''}`} />
|
<Bookmark className={`w-5 h-5 ${isBookmarked ? 'fill-current text-blue-400' : ''}`} />
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="bg-white/90 backdrop-blur-sm text-gray-700 p-3 rounded-full shadow-lg hover:bg-white hover:scale-110 transition-all duration-200"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onShare(product.name)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Send className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 库存提醒 */}
|
{/* 库存警告 */}
|
||||||
{product.stock <= 5 && (
|
{product.stock <= 5 && (
|
||||||
<div className="absolute bottom-4 left-4">
|
<div className="absolute bottom-4 left-4">
|
||||||
<Badge className="bg-red-500/90 backdrop-blur-sm text-white text-xs px-3 py-1 rounded-full shadow-lg animate-pulse">
|
<Badge className="bg-red-500/90 backdrop-blur-sm text-white text-sm px-3 py-1 rounded-full shadow-lg">
|
||||||
仅剩{product.stock}件
|
仅剩{product.stock}件
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 查看详情按钮 */}
|
{/* 查看详情提示 */}
|
||||||
<div className={`absolute bottom-4 right-4 transition-all duration-300 ${
|
<div className={`absolute inset-0 bg-black/40 backdrop-blur-sm flex items-center justify-center transition-all duration-300 ${
|
||||||
isHovered ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'
|
isHovered ? 'opacity-100' : 'opacity-0'
|
||||||
}`}>
|
}`}>
|
||||||
<Button
|
<div className="bg-white/90 backdrop-blur-sm rounded-full px-6 py-3 flex items-center gap-2 shadow-lg transform transition-all duration-300 hover:scale-105">
|
||||||
variant="ghost"
|
<Eye className="w-5 h-5 text-purple-600" />
|
||||||
size="sm"
|
<span className="text-purple-600 font-medium">查看详情</span>
|
||||||
className="bg-white/20 backdrop-blur-md text-white p-2.5 rounded-full shadow-lg hover:bg-white/30"
|
</div>
|
||||||
>
|
|
||||||
<Eye className="w-5 h-5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 内容区域 */}
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="space-y-4">
|
<div className="flex flex-wrap gap-2 mb-3">
|
||||||
{/* 标题和描述 */}
|
{product.tags.slice(0, 2).map((tag, index) => (
|
||||||
<div>
|
<Badge key={index} variant="outline" className="text-purple-600 border-purple-200 text-xs">
|
||||||
|
{tag}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3 className="font-bold text-xl mb-2 line-clamp-2 leading-tight">{product.name}</h3>
|
<h3 className="font-bold text-xl mb-2 line-clamp-2 leading-tight">{product.name}</h3>
|
||||||
<p className="text-sm text-gray-600 line-clamp-2 leading-relaxed">{product.description}</p>
|
<p className="text-sm text-gray-600 line-clamp-2 leading-relaxed">{product.description}</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 评分和评价 */}
|
<div className="flex items-center gap-4 my-4">
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Star className="w-4 h-4 text-yellow-500 fill-current" />
|
<Star className="w-4 h-4 text-yellow-500 fill-current" />
|
||||||
<span className="font-semibold text-sm">{product.rating}</span>
|
<span className="font-medium text-gray-900">{product.rating}</span>
|
||||||
|
<span className="text-sm text-gray-500">({product.reviews}条评价)</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-500">
|
||||||
|
{product.likes} 人喜欢
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-gray-500">{product.reviews} 评价</span>
|
|
||||||
<span className="text-sm text-gray-500">库存 {product.stock}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 价格区域 */}
|
|
||||||
<div className="flex items-baseline gap-3">
|
<div className="flex items-baseline gap-3">
|
||||||
<span className="text-3xl font-bold text-red-500">¥{product.price}</span>
|
<span className="text-3xl font-bold text-red-600">¥{product.price}</span>
|
||||||
{product.originalPrice > product.price && (
|
{product.originalPrice > product.price && (
|
||||||
<span className="text-lg text-gray-400 line-through">¥{product.originalPrice}</span>
|
<span className="text-lg text-gray-400 line-through">¥{product.originalPrice}</span>
|
||||||
)}
|
)}
|
||||||
{discountPercentage > 0 && (
|
<Badge variant="destructive" className="ml-auto">
|
||||||
<Badge variant="secondary" className="text-sm font-semibold">
|
{product.discount}
|
||||||
省¥{product.originalPrice - product.price}
|
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 互动区域 */}
|
<div className="flex gap-3 mt-6">
|
||||||
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
|
|
||||||
<div className="flex items-center gap-6">
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
size="sm"
|
className="flex-1"
|
||||||
className="flex items-center gap-2 text-gray-600 hover:text-red-500 transition-colors"
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onLike(product.id)
|
onLike(product.id)
|
||||||
@ -305,31 +321,28 @@ export function ProductCard({
|
|||||||
<span className="text-sm font-medium">{product.likes}</span>
|
<span className="text-sm font-medium">{product.likes}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
size="sm"
|
|
||||||
className="flex items-center gap-2 text-gray-600 hover:text-blue-500 transition-colors"
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onShare(product.name)
|
onShare(product.name)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Send className="w-5 h-5" />
|
<Send className="w-5 h-5" />
|
||||||
<span className="text-sm font-medium">{product.comments}</span>
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
|
||||||
<Button
|
<Button
|
||||||
className="px-8 py-2.5 bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white font-semibold rounded-full shadow-lg hover:shadow-xl transition-all duration-200 transform hover:scale-105"
|
className="flex-1 bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onPurchase(product.id)
|
onPurchase(product.id)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ShoppingCart className="w-4 h-4 mr-2" />
|
<ShoppingCart className="w-5 h-5 mr-2" />
|
||||||
立即购买
|
立即购买
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ProductCard
|
||||||
302
src/components/product-selector.tsx
Normal file
302
src/components/product-selector.tsx
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { Search, Check, Package, Star, Tag } from 'lucide-react'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { Card, CardContent } from '@/components/ui/card'
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
||||||
|
|
||||||
|
interface Product {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
brand: string
|
||||||
|
price: number
|
||||||
|
originalPrice: number
|
||||||
|
image: string
|
||||||
|
category: string
|
||||||
|
tags: string[]
|
||||||
|
rating: number
|
||||||
|
reviews: number
|
||||||
|
stock: number
|
||||||
|
description: string
|
||||||
|
volume?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProductSelectorProps {
|
||||||
|
selectedProduct?: Product | null
|
||||||
|
onProductSelect: (product: Product) => void
|
||||||
|
placeholder?: string
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProductSelector({
|
||||||
|
selectedProduct,
|
||||||
|
onProductSelect,
|
||||||
|
placeholder = "选择商品",
|
||||||
|
disabled = false
|
||||||
|
}: ProductSelectorProps) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<string>('all')
|
||||||
|
const [products, setProducts] = useState<Product[]>([])
|
||||||
|
|
||||||
|
// 模拟商品数据
|
||||||
|
useEffect(() => {
|
||||||
|
const mockProducts: Product[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: '迪奥小姐香水',
|
||||||
|
brand: 'Dior',
|
||||||
|
price: 899,
|
||||||
|
originalPrice: 1299,
|
||||||
|
image: 'https://picsum.photos/200/200?random=1',
|
||||||
|
category: 'floral',
|
||||||
|
tags: ['热销', '经典'],
|
||||||
|
rating: 4.8,
|
||||||
|
reviews: 1234,
|
||||||
|
stock: 50,
|
||||||
|
description: '经典花香调,优雅迷人',
|
||||||
|
volume: '100ml'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: '香奈儿五号香水',
|
||||||
|
brand: 'Chanel',
|
||||||
|
price: 1299,
|
||||||
|
originalPrice: 1599,
|
||||||
|
image: 'https://picsum.photos/200/200?random=2',
|
||||||
|
category: 'floral',
|
||||||
|
tags: ['经典', '限量'],
|
||||||
|
rating: 4.9,
|
||||||
|
reviews: 2156,
|
||||||
|
stock: 30,
|
||||||
|
description: '永恒经典,女性魅力象征',
|
||||||
|
volume: '100ml'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
name: '爱马仕大地香水',
|
||||||
|
brand: 'Hermès',
|
||||||
|
price: 1199,
|
||||||
|
originalPrice: 1499,
|
||||||
|
image: 'https://picsum.photos/200/200?random=3',
|
||||||
|
category: 'woody',
|
||||||
|
tags: ['男士', '木质'],
|
||||||
|
rating: 4.7,
|
||||||
|
reviews: 856,
|
||||||
|
stock: 25,
|
||||||
|
description: '木质调香水,成熟男士首选',
|
||||||
|
volume: '100ml'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
name: '祖马龙英国梨香水',
|
||||||
|
brand: 'Jo Malone',
|
||||||
|
price: 799,
|
||||||
|
originalPrice: 999,
|
||||||
|
image: 'https://picsum.photos/200/200?random=4',
|
||||||
|
category: 'fruity',
|
||||||
|
tags: ['清新', '果香'],
|
||||||
|
rating: 4.6,
|
||||||
|
reviews: 678,
|
||||||
|
stock: 40,
|
||||||
|
description: '清新果香,日常必备',
|
||||||
|
volume: '100ml'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
name: '汤姆福特黑兰花香水',
|
||||||
|
brand: 'Tom Ford',
|
||||||
|
price: 1599,
|
||||||
|
originalPrice: 1899,
|
||||||
|
image: 'https://picsum.photos/200/200?random=5',
|
||||||
|
category: 'oriental',
|
||||||
|
tags: ['奢华', '东方调'],
|
||||||
|
rating: 4.8,
|
||||||
|
reviews: 432,
|
||||||
|
stock: 15,
|
||||||
|
description: '神秘东方调,奢华体验',
|
||||||
|
volume: '100ml'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
name: '宝格丽紫水晶香水',
|
||||||
|
brand: 'Bulgari',
|
||||||
|
price: 699,
|
||||||
|
originalPrice: 899,
|
||||||
|
image: 'https://picsum.photos/200/200?random=6',
|
||||||
|
category: 'floral',
|
||||||
|
tags: ['女士', '花香'],
|
||||||
|
rating: 4.5,
|
||||||
|
reviews: 789,
|
||||||
|
stock: 35,
|
||||||
|
description: '优雅花香,女性魅力',
|
||||||
|
volume: '65ml'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
setProducts(mockProducts)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
{ id: 'all', name: '全部' },
|
||||||
|
{ id: 'floral', name: '花香调' },
|
||||||
|
{ id: 'woody', name: '木质调' },
|
||||||
|
{ id: 'fruity', name: '果香调' },
|
||||||
|
{ id: 'oriental', name: '东方调' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const filteredProducts = products.filter(product => {
|
||||||
|
const matchesSearch = product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
product.brand.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
const matchesCategory = selectedCategory === 'all' || product.category === selectedCategory
|
||||||
|
return matchesSearch && matchesCategory
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleProductSelect = (product: Product) => {
|
||||||
|
onProductSelect(product)
|
||||||
|
setIsOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start h-auto p-4 border-2 border-dashed border-gray-300 hover:border-purple-400 transition-colors"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{selectedProduct ? (
|
||||||
|
<div className="flex items-center gap-3 w-full">
|
||||||
|
<img
|
||||||
|
src={selectedProduct.image}
|
||||||
|
alt={selectedProduct.name}
|
||||||
|
className="w-12 h-12 rounded-lg object-cover"
|
||||||
|
/>
|
||||||
|
<div className="flex-1 text-left">
|
||||||
|
<div className="font-semibold text-gray-900">{selectedProduct.name}</div>
|
||||||
|
<div className="text-sm text-gray-500">{selectedProduct.brand} · {selectedProduct.volume}</div>
|
||||||
|
<div className="text-sm font-medium text-purple-600">¥{selectedProduct.price}</div>
|
||||||
|
</div>
|
||||||
|
<Check className="w-5 h-5 text-green-500" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-2 text-gray-500">
|
||||||
|
<Package className="w-5 h-5" />
|
||||||
|
<span>{placeholder}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
|
||||||
|
<DialogContent className="!max-w-4xl max-h-[80vh] overflow-hidden w-[95vw] sm:w-[90vw] md:w-[80vw] lg:w-[70vw]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<Package className="w-5 h-5 text-purple-600" />
|
||||||
|
选择商品
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* 搜索栏 */}
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||||
|
<Input
|
||||||
|
placeholder="搜索商品名称或品牌..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="pl-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 分类筛选 */}
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
{categories.map((category) => (
|
||||||
|
<Button
|
||||||
|
key={category.id}
|
||||||
|
variant={selectedCategory === category.id ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setSelectedCategory(category.id)}
|
||||||
|
className={selectedCategory === category.id ? "bg-purple-600 hover:bg-purple-700" : ""}
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 商品列表 */}
|
||||||
|
<div className="max-h-96 overflow-y-auto">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{filteredProducts.map((product) => (
|
||||||
|
<Card
|
||||||
|
key={product.id}
|
||||||
|
className="cursor-pointer hover:shadow-lg transition-all duration-200 hover:scale-[1.02]"
|
||||||
|
onClick={() => handleProductSelect(product)}
|
||||||
|
>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<img
|
||||||
|
src={product.image}
|
||||||
|
alt={product.name}
|
||||||
|
className="w-20 h-20 rounded-lg object-cover flex-shrink-0"
|
||||||
|
/>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-start justify-between mb-2">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-gray-900 truncate">{product.name}</h3>
|
||||||
|
<p className="text-sm text-gray-500">{product.brand} · {product.volume}</p>
|
||||||
|
</div>
|
||||||
|
{selectedProduct?.id === product.id && (
|
||||||
|
<Check className="w-5 h-5 text-green-500 flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Star className="w-3 h-3 text-yellow-400 fill-current" />
|
||||||
|
<span className="text-xs text-gray-600">{product.rating}</span>
|
||||||
|
<span className="text-xs text-gray-400">({product.reviews})</span>
|
||||||
|
</div>
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
库存 {product.stock}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-baseline gap-2">
|
||||||
|
<span className="text-lg font-bold text-purple-600">¥{product.price}</span>
|
||||||
|
{product.originalPrice > product.price && (
|
||||||
|
<span className="text-xs text-gray-400 line-through">¥{product.originalPrice}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
{product.tags.slice(0, 2).map((tag, index) => (
|
||||||
|
<Badge key={index} variant="secondary" className="text-xs">
|
||||||
|
{tag}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs text-gray-500 mt-2 line-clamp-2">{product.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{filteredProducts.length === 0 && (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<Package className="w-12 h-12 text-gray-300 mx-auto mb-4" />
|
||||||
|
<p className="text-gray-500">没有找到匹配的商品</p>
|
||||||
|
<p className="text-sm text-gray-400 mt-1">请尝试调整搜索条件</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -25,13 +25,13 @@ const ToastViewport = React.forwardRef<
|
|||||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
||||||
|
|
||||||
const toastVariants = cva(
|
const toastVariants = cva(
|
||||||
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-lg border p-4 pr-6 shadow-lg backdrop-blur-sm transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "border bg-background text-foreground",
|
default: "border-gray-200 bg-white/95 text-foreground shadow-xl",
|
||||||
destructive:
|
destructive:
|
||||||
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
"destructive group border-red-200 bg-red-50/95 text-red-900 shadow-xl shadow-red-100/50",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
@ -77,7 +77,7 @@ const ToastClose = React.forwardRef<
|
|||||||
<ToastPrimitives.Close
|
<ToastPrimitives.Close
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
toast-close=""
|
toast-close=""
|
||||||
@ -94,7 +94,7 @@ const ToastTitle = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<ToastPrimitives.Title
|
<ToastPrimitives.Title
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
|
className={cn("text-sm font-semibold leading-none tracking-tight", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
@ -106,7 +106,7 @@ const ToastDescription = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<ToastPrimitives.Description
|
<ToastPrimitives.Description
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("text-sm opacity-90", className)}
|
className={cn("text-sm opacity-90 mt-1", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|||||||
@ -36,7 +36,7 @@ function TooltipTrigger({
|
|||||||
|
|
||||||
function TooltipContent({
|
function TooltipContent({
|
||||||
className,
|
className,
|
||||||
sideOffset = 0,
|
sideOffset = 8,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||||
@ -46,13 +46,20 @@ function TooltipContent({
|
|||||||
data-slot="tooltip-content"
|
data-slot="tooltip-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
// 优化后的现代化 tooltip 样式
|
||||||
|
"bg-gray-900/95 text-white backdrop-blur-sm border border-gray-700/50",
|
||||||
|
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
||||||
|
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
"z-50 w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin)",
|
||||||
|
"rounded-lg px-3 py-2 text-sm font-medium leading-relaxed",
|
||||||
|
"shadow-lg shadow-black/20",
|
||||||
|
"text-balance break-words",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
<TooltipPrimitive.Arrow className="fill-gray-900/95 z-50 size-2 translate-y-[calc(-50%_-_1px)] rotate-45" />
|
||||||
</TooltipPrimitive.Content>
|
</TooltipPrimitive.Content>
|
||||||
</TooltipPrimitive.Portal>
|
</TooltipPrimitive.Portal>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import React from 'react'
|
||||||
import { toast } from '@/hooks/use-toast'
|
import { toast } from '@/hooks/use-toast'
|
||||||
|
|
||||||
export const showSuccessToast = (message: string) => {
|
export const showSuccessToast = (message: string) => {
|
||||||
@ -17,8 +18,9 @@ export const showErrorToast = (message: string) => {
|
|||||||
|
|
||||||
export const showLikeToast = () => {
|
export const showLikeToast = () => {
|
||||||
toast({
|
toast({
|
||||||
title: "已点赞",
|
title: "点赞成功 ❤️",
|
||||||
description: "您已为这个内容点赞",
|
description: "感谢您的喜欢!",
|
||||||
|
className: "border-pink-200 bg-gradient-to-r from-pink-50 to-red-50 shadow-lg shadow-pink-100/50",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7,6 +7,7 @@ const config: Config = {
|
|||||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
|||||||
149
说明文档.md
Normal file
149
说明文档.md
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
|
||||||
|
## 一、核心功能模块
|
||||||
|
|
||||||
|
1.1 商品管理系统
|
||||||
|
|
||||||
|
1.1.1 商品基础信息
|
||||||
|
2.1商品属性2.1: 名称、描述、价格、原价、图片、分类、标签
|
||||||
|
2.1库存管理2.1: 库存数量、销量统计、商品状态
|
||||||
|
2.1分类体系2.1: 花香调(floral)、草本调(herbal)等香调分类
|
||||||
|
|
||||||
|
1.1.2 商品展示
|
||||||
|
2.1多视图模式2.1: 网格视图、列表视图切换
|
||||||
|
2.1商品卡片2.1: 包含价格、折扣、评分、评论数、点赞数
|
||||||
|
2.1商品详情2.1: 详细描述、用户评价、相关推荐
|
||||||
|
|
||||||
|
1.2 拼团购买系统
|
||||||
|
|
||||||
|
1.2.1 拼团配置
|
||||||
|
2.1阶梯拼团2.1:
|
||||||
|
3人团:7折优惠,团长获得200积分
|
||||||
|
5人团:5折优惠,团长获得300积分
|
||||||
|
2.1时间限制2.1: 默认24小时拼团时间
|
||||||
|
2.1每日限量2.1: 支持设置每日拼团限量
|
||||||
|
|
||||||
|
1.2.2 拼团流程
|
||||||
|
2.1发起拼团2.1: 用户选择商品和拼团规模
|
||||||
|
2.1邀请参团2.1: 分享拼团链接邀请好友
|
||||||
|
2.1实时进度2.1: 显示当前参团人数和剩余时间
|
||||||
|
2.1自动成团2.1: 达到目标人数自动成团
|
||||||
|
2.1溯源码2.1: 成功拼团生成唯一溯源码
|
||||||
|
|
||||||
|
1.2.3 秒杀功能
|
||||||
|
2.1秒杀配置2.1: 特定时间点(如10:00)开启秒杀
|
||||||
|
2.1限量抢购2.1: 设置秒杀库存和价格
|
||||||
|
2.1防刷机制2.1: 避免恶意刷单
|
||||||
|
|
||||||
|
1.3 订阅盲盒系统
|
||||||
|
|
||||||
|
1.3.1 订阅计划
|
||||||
|
2.1月度订阅2.1: 每月配送香水盲盒
|
||||||
|
2.1季度订阅2.1: 每季度配送,享受更多优惠
|
||||||
|
2.1配送设置2.1: 用户可选择配送频率和日期
|
||||||
|
|
||||||
|
1.3.2 盲盒机制
|
||||||
|
2.1香调排除2.1: 用户可排除不喜欢的香调
|
||||||
|
2.1解锁系统2.1: 逐步解锁新香调
|
||||||
|
2.1个性化推荐2.1: 基于用户偏好推荐
|
||||||
|
|
||||||
|
1.3.3 会员折扣
|
||||||
|
2.1探索会员2.1: 95折优惠
|
||||||
|
2.1品鉴会员2.1: 85折优惠
|
||||||
|
2.1调香会员2.1: 专属折扣和服务
|
||||||
|
|
||||||
|
1.4 会员积分体系
|
||||||
|
|
||||||
|
1.4.1 积分获取
|
||||||
|
2.1购买获得2.1: 消费金额的一定比例
|
||||||
|
2.1拼团奖励2.1: 团长额外积分奖励
|
||||||
|
2.1分销奖励2.1: 推广订单获得积分
|
||||||
|
2.1UGC内容2.1: 发布评价、教程等获得积分
|
||||||
|
|
||||||
|
1.4.2 会员等级
|
||||||
|
2.1探索会员2.1: 无门槛,享受基础权益
|
||||||
|
2.1品鉴会员2.1: 累计消费满500元,享受更多优惠
|
||||||
|
2.1调香会员2.1: 累计消费满2000元,享受VIP服务
|
||||||
|
|
||||||
|
1.4.3 积分使用
|
||||||
|
2.1商品兑换2.1: 积分兑换商品或优惠券
|
||||||
|
2.1线下体验2.1: 积分兑换线下调香体验
|
||||||
|
2.1会员升级2.1: 积分加速会员等级提升
|
||||||
|
|
||||||
|
1.5 分销推广系统
|
||||||
|
|
||||||
|
1.5.1 分销员体系
|
||||||
|
2.1等级制度2.1: 探索、品鉴、调香三级分销员
|
||||||
|
2.1佣金比例2.1:
|
||||||
|
直接推广: 10%佣金
|
||||||
|
间接推广: 5%佣金
|
||||||
|
2.1团队管理2.1: 查看下级分销员和业绩
|
||||||
|
|
||||||
|
1.5.2 推广工具
|
||||||
|
2.1分享链接2.1: 生成专属推广链接
|
||||||
|
2.1素材库2.1: 提供推广图片和文案
|
||||||
|
2.1数据统计2.1: 实时查看推广效果
|
||||||
|
|
||||||
|
1.5.3 佣金结算
|
||||||
|
2.1结算周期2.1: 订单完成后进入结算流程
|
||||||
|
2.1提现功能2.1: 支持佣金提现到账户
|
||||||
|
2.1税务处理2.1: 符合相关税务规定
|
||||||
|
|
||||||
|
1.6 用户生成内容系统
|
||||||
|
|
||||||
|
1.6.1 内容类型
|
||||||
|
2.1商品评价2.1: 用户购买后的使用体验分享
|
||||||
|
2.1使用教程2.1: 香水使用技巧和搭配指南
|
||||||
|
2.1香水故事2.1: 个人香水使用故事和心得
|
||||||
|
|
||||||
|
1.6.2 激励机制
|
||||||
|
2.1积分奖励2.1: 发布优质内容获得积分奖励
|
||||||
|
2.1精选推荐2.1: 优质内容获得平台首页推荐
|
||||||
|
2.1专家认证2.1: 优秀创作者获得专家标识
|
||||||
|
|
||||||
|
1.6.3 内容管理
|
||||||
|
2.1审核机制2.1: 内容发布前进行质量审核
|
||||||
|
2.1互动功能2.1: 支持点赞、评论、分享功能
|
||||||
|
2.1数据统计2.1: 查看内容浏览量和互动数据
|
||||||
|
|
||||||
|
1.7 订单管理系统
|
||||||
|
|
||||||
|
1.7.1 订单流程
|
||||||
|
2.1下单2.1: 选择商品、数量、配送信息
|
||||||
|
2.1支付2.1: 支持多种支付方式
|
||||||
|
2.1发货2.1: 商家发货并提供物流信息
|
||||||
|
2.1收货2.1: 用户确认收货完成交易
|
||||||
|
|
||||||
|
1.7.2 订单状态
|
||||||
|
2.1待支付2.1: 订单创建等待支付
|
||||||
|
2.1已支付2.1: 支付完成等待发货
|
||||||
|
2.1已发货2.1: 商品已发出在途中
|
||||||
|
2.1已完成2.1: 用户确认收货
|
||||||
|
2.1已取消2.1: 订单被取消
|
||||||
|
|
||||||
|
1.7.3 售后服务
|
||||||
|
2.1退换货2.1: 支持7天无理由退换货
|
||||||
|
2.1客服支持2.1: 在线客服解决问题
|
||||||
|
2.1投诉处理2.1: 完善的投诉处理机制
|
||||||
|
|
||||||
|
## 二、用户角色与权限
|
||||||
|
|
||||||
|
2.1 普通用户
|
||||||
|
浏览商品、参与拼团
|
||||||
|
购买商品、管理订单
|
||||||
|
参与订阅服务
|
||||||
|
发布UGC内容
|
||||||
|
查看积分和会员信息
|
||||||
|
|
||||||
|
2.2 分销员
|
||||||
|
普通用户所有权限
|
||||||
|
生成推广链接
|
||||||
|
查看推广数据
|
||||||
|
管理下级分销员
|
||||||
|
佣金提现
|
||||||
|
|
||||||
|
2.3 管理员
|
||||||
|
商品管理
|
||||||
|
订单管理
|
||||||
|
用户管理
|
||||||
|
数据统计
|
||||||
|
系统配置
|
||||||
Loading…
x
Reference in New Issue
Block a user