diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 44a8cc8..d155639 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -141,7 +141,7 @@ OOIN Creator Center is a React application built with Vite that allows users to ### UI Components - `SearchBar`: Reusable search component used across different pages - `DatabaseFilter`: Complex filter component for creator discovery -- `DatabaseList`: Displays creator data in a tabular format +- `CreatorList`: Displays creator data in a tabular format - `ChatWindow` and `ChatInput`: Used for creator communications - `RangeSlider`: Custom slider component for range-based filtering diff --git a/package-lock.json b/package-lock.json index 21c7b69..1a74721 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,12 +16,14 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@reduxjs/toolkit": "^2.8.1", "bootstrap": "^5.3.3", + "chart.js": "^4.4.9", "date-fns": "^4.1.0", "lodash": "^4.17.21", "lucide-react": "^0.508.0", "react": "^19.1.0", "react-bootstrap": "^2.10.9", "react-bootstrap-range-slider": "^3.0.8", + "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", "react-redux": "^9.2.0", "react-router-dom": "^7.6.0", @@ -1057,6 +1059,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==" + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.1.tgz", @@ -2143,6 +2150,17 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chart.js": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", + "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -3999,6 +4017,15 @@ "react-dom": ">=17.0.0" } }, + "node_modules/react-chartjs-2": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", + "integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-dom": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", diff --git a/package.json b/package.json index a21b503..f9ca440 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,14 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@reduxjs/toolkit": "^2.8.1", "bootstrap": "^5.3.3", + "chart.js": "^4.4.9", "date-fns": "^4.1.0", "lodash": "^4.17.21", "lucide-react": "^0.508.0", "react": "^19.1.0", "react-bootstrap": "^2.10.9", "react-bootstrap-range-slider": "^3.0.8", + "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", "react-redux": "^9.2.0", "react-router-dom": "^7.6.0", diff --git a/src/App.jsx b/src/App.jsx index 1170607..9fcdbbe 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,6 +3,9 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { fas } from '@fortawesome/free-solid-svg-icons'; import Router from './router'; import './styles/Campaign.scss'; +import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js"; + +ChartJS.register(ArcElement, Tooltip, Legend); // Add Font Awesome icons to library library.add(faTiktok, fas, faYoutube, faInstagram); diff --git a/src/components/DatabaseList.jsx b/src/components/CreatorList.jsx similarity index 87% rename from src/components/DatabaseList.jsx rename to src/components/CreatorList.jsx index 991c987..db13521 100644 --- a/src/components/DatabaseList.jsx +++ b/src/components/CreatorList.jsx @@ -11,8 +11,9 @@ import { import { setSortBy } from '../store/slices/filtersSlice'; import '../styles/DatabaseList.scss'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Link } from 'react-router-dom'; -export default function DatabaseList({ path }) { +export default function CreatorList({ path, pageType = 'database' }) { const dispatch = useDispatch(); const { creators, status, selectedCreators } = useSelector((state) => state.creators); const { sortBy, sortDirection } = useSelector((state) => state.filters); @@ -20,7 +21,7 @@ export default function DatabaseList({ path }) { // 组件加载时获取创作者数据 useEffect(() => { if (status === 'idle') { - dispatch(fetchCreators({path})); + dispatch(fetchCreators({ path })); } }, [dispatch, status]); @@ -121,6 +122,13 @@ export default function DatabaseList({ path }) { handleSort('avgViews')}> Avg. Video Views {renderSortIcon('avgViews')} + {pageType === 'private' && ( + <> + Pricing + # Collab + Latest Collab. + + )} E-commerce Profile @@ -148,7 +156,9 @@ export default function DatabaseList({ path }) { {creator.name} {creator.verified && } -
{creator.name}
+ + {creator.name} + @@ -170,6 +180,13 @@ export default function DatabaseList({ path }) {
Items Sold: {creator.soldPercentage}
{creator.avgViews} + {pageType === 'private' && ( + <> + {creator.pricing} + {creator.collabCount} + {creator.latestCollab} + + )} {creator.hasEcommerce ?
: null} diff --git a/src/components/DatabaseFilter.jsx b/src/components/DatabaseFilter.jsx index 6a8c206..a0f4df5 100644 --- a/src/components/DatabaseFilter.jsx +++ b/src/components/DatabaseFilter.jsx @@ -9,15 +9,14 @@ import { toggleExposureRating, toggleGmvRange, setViewsRange, - resetFilters, + setPricingRange, } from '../store/slices/filtersSlice'; import { fetchCreators } from '../store/slices/creatorsSlice'; import '../styles/DatabaseFilter.scss'; -export default function DatabaseFilter({ path }) { +export default function DatabaseFilter({ path, pageType = 'database' }) { const dispatch = useDispatch(); const filters = useSelector((state) => state.filters); - // 类别选项数据 const categories = [ 'Phones & Electronics', @@ -46,7 +45,7 @@ export default function DatabaseFilter({ path }) { // 预定义的离散点值 const discreteValues = [0, 100, 1000, 10000, 100000, 250000, 500000]; - + const discretePricingValues = [0, 200, 400, 600, 800, 1000, 3000]; // 找到最接近的离散值索引 const findClosestDiscreteIndex = (value) => { let closestIndex = 0; @@ -66,12 +65,16 @@ export default function DatabaseFilter({ path }) { // 本地状态用于表单控制 const [minViews, setMinViews] = useState(filters.viewsRange[0]); const [maxViews, setMaxViews] = useState(filters.viewsRange[1]); + const [minPricing, setMinPricing] = useState(filters.pricingRange[0]); + const [maxPricing, setMaxPricing] = useState(filters.pricingRange[1]); // 监听Redux状态变化,更新本地表单状态 useEffect(() => { setMinViews(filters.viewsRange[0]); setMaxViews(filters.viewsRange[1]); - }, [filters.viewsRange]); + setMinPricing(filters.pricingRange[0]); + setMaxPricing(filters.pricingRange[1]); + }, [filters.viewsRange, filters.pricingRange]); // 组件加载时获取数据 useEffect(() => { @@ -115,6 +118,22 @@ export default function DatabaseFilter({ path }) { setMaxViews(inputValue); }; + const handlePricingRangeChange = (newRange) => { + dispatch(setPricingRange(newRange)); + }; + + // 处理min pricing input变更 + const handleMinPricingChange = (e) => { + const inputValue = parseInt(e.target.value) || 0; + setMinPricing(inputValue); + }; + + // 处理max pricing input变更 + const handleMaxPricingChange = (e) => { + const inputValue = parseInt(e.target.value) || 0; + setMaxPricing(inputValue); + }; + // 当输入框失去焦点时,转换为最接近的离散值 const handleInputBlur = (type) => { if (type === 'min') { @@ -140,6 +159,29 @@ export default function DatabaseFilter({ path }) { } }; + // 处理min pricing input失去焦点 + const handlePricingInputBlur = (type) => { + if (type === 'min') { + const closestIndex = findClosestDiscreteIndex(minPricing); + const discreteValue = discretePricingValues[closestIndex]; + + // 确保最小值不超过最大值 + const finalValue = Math.min(discreteValue, maxPricing); + + setMinPricing(finalValue); + dispatch(setPricingRange([finalValue, filters.pricingRange[1]])); + } else { + const closestIndex = findClosestDiscreteIndex(maxPricing); + const discreteValue = discretePricingValues[closestIndex]; + + // 确保最大值不小于最小值 + const finalValue = Math.max(discreteValue, minPricing); + + setMaxPricing(finalValue); + dispatch(setPricingRange([filters.pricingRange[0], finalValue])); + } + }; + // 格式化显示值的函数 const formatValue = (value) => { if (value >= 1000000) { @@ -227,7 +269,7 @@ export default function DatabaseFilter({ path }) { {/* 视频观看量筛选 */}
Views
-
+
+ + {/* Pricing 筛选 */} + {pageType === 'private' && ( +
+
Pricing
+
+ + +
+ + + + + handlePricingInputBlur('min')} + /> + + - + + + + + handlePricingInputBlur('max')} + /> + +
+
+
+ )}
); diff --git a/src/components/Layouts/Sidebar.jsx b/src/components/Layouts/Sidebar.jsx index ce0e022..8725b0a 100644 --- a/src/components/Layouts/Sidebar.jsx +++ b/src/components/Layouts/Sidebar.jsx @@ -59,7 +59,26 @@ const menuItems = [ path: '/private-creators', icon: , hasSubmenu: true, - submenuItems: [], + submenuItems: [ + { + id: 'tiktok', + title: 'TikTok', + path: '/private-creators/tiktok', + icon: , + }, + { + id: 'instagram', + title: 'Instagram', + path: '/private-creators/instagram', + icon: , + }, + { + id: 'youtube', + title: 'YouTube', + path: '/private-creators/youtube', + icon: , + }, + ], }, { id: 'deep-analysis', diff --git a/src/pages/CreatorDetail.jsx b/src/pages/CreatorDetail.jsx new file mode 100644 index 0000000..aa75abc --- /dev/null +++ b/src/pages/CreatorDetail.jsx @@ -0,0 +1,174 @@ +import { ArrowLeft, Instagram, Link, Mail, MapPin } from 'lucide-react'; +import { useEffect } from 'react'; +import { Card } from 'react-bootstrap'; +import { useDispatch, useSelector } from 'react-redux'; +import { useNavigate, useParams } from 'react-router-dom'; +import { selectCreator, clearCreator } from '../store/slices/creatorsSlice'; +import { Doughnut } from 'react-chartjs-2'; + +const data = { + labels: ['Red', 'Blue', 'Green', 'Purple'], + datasets: [ + { + label: 'GMV', + data: [12, 19, 5, 2], + backgroundColor: ['rgba(217, 107, 139)', 'rgba(101, 105, 225)', 'rgba(93, 200, 179)', 'rgba(122, 87, 218)'], + }, + ], +}; + +const options = { + cutout: 70, + plugins: { + legend: { + position: 'bottom', + labels: { + generateLabels: function (chart) { + return chart.data.labels.map((label, index) => { + const value = chart.data.datasets[0].data[index]; + return { + text: `${label} ${value}%`, + fillStyle: chart.data.datasets[0].backgroundColor[index], + strokeStyle: chart.data.datasets[0].backgroundColor[index], + index: index, + }; + }); + }, + }, + }, + }, +}; + +export default function CreatorDetail({}) { + const { id } = useParams(); + const navigate = useNavigate(); + const dispatch = useDispatch(); + const { selectedCreator } = useSelector((state) => state.creators); + + const handleBack = () => { + navigate(-1); + }; + useEffect(() => { + dispatch(selectCreator(id)); + return () => { + dispatch(clearCreator()); + }; + }, [dispatch, id]); + console.log(selectedCreator); + return ( +
+
+ Go back +
+ {selectedCreator ? ( +
+
+
+
+ {selectedCreator.name} +
+
+
{selectedCreator.name}
+
{selectedCreator.description || '--'}
+
{selectedCreator.category || '--'}
+
+ + {selectedCreator.location || ' --'} +
+
{selectedCreator.liveTime || '--'}
+
+
+
+
+
Category
+
{selectedCreator.category}
+
+
+
MCN
+
{selectedCreator.mcn || '--'}
+
+
+
Pricing
+
{selectedCreator.pricing}
+
+
+
Collab.
+
{selectedCreator.collab || '--'}
+
+
+
+
+
+ +
+
{selectedCreator.email || '--'}
+
+
+
+ +
+
{selectedCreator.instagram || '--'}
+
+
+
+ +
+
{selectedCreator.url || '--'}
+
+
+
+
+
+
+
E-commerce Level
+
{selectedCreator.ecommerceLevel || '--'}
+
+
+
Exposure Level
+
{selectedCreator.exposureLevel || '--'}
+
+
+
+
+
{selectedCreator.followers || '--'}
+
Followers
+
+
+
{selectedCreator.gmv || '--'}
+
GMV
+
+
+
{selectedCreator.avgVideoViews || '--'}
+
Avg Video Views
+
+
+
{selectedCreator.itemsSold || '--'}
+
Items Sold
+
+
+
{selectedCreator.gpm || '--'}
+
GPM
+
+
+
{selectedCreator.gpmPerCustomer || '--'}
+
GMV per customer
+
+
+
+
+
GMV per sales channel
+ +
+
+
GMV by product category
+ +
+
+
+
+ ) : ( +
No creator found
+ )} +
+ ); +} diff --git a/src/pages/Database.jsx b/src/pages/Database.jsx index 9669853..74be3c5 100644 --- a/src/pages/Database.jsx +++ b/src/pages/Database.jsx @@ -1,6 +1,6 @@ import React from 'react'; import DatabaseFilter from '../components/DatabaseFilter'; -import DatabaseList from '../components/DatabaseList'; +import CreatorList from '../components/CreatorList'; import SearchBar from '../components/SearchBar'; import { Button } from 'react-bootstrap'; export default function Database({ path }) { @@ -16,8 +16,8 @@ export default function Database({ path }) { {path === 'instagram' &&
Instagram
} {path === 'youtube' &&
YouTube
} - - + + ); } diff --git a/src/pages/PrivateCreator.jsx b/src/pages/PrivateCreator.jsx new file mode 100644 index 0000000..f87d371 --- /dev/null +++ b/src/pages/PrivateCreator.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import SearchBar from '../components/SearchBar'; +import { Button } from 'react-bootstrap'; +import DatabaseFilter from '../components/DatabaseFilter'; +import CreatorList from '../components/CreatorList'; + +export default function PrivateCreator({ path }) { + + return ( + +
+ + +
+
+
Private Creators
+ {path === 'tiktok' &&
TikTok
} + {path === 'instagram' &&
Instagram
} + {path === 'youtube' &&
YouTube
} +
+ + +
+ ); +} diff --git a/src/router/index.jsx b/src/router/index.jsx index 1b8d8f3..bd2c7af 100644 --- a/src/router/index.jsx +++ b/src/router/index.jsx @@ -9,6 +9,8 @@ import BrandsDetail from '@/pages/BrandsDetail'; import CampaignDetail from '@/pages/CampaignDetail'; import Login from '@/pages/Login'; import CreatorDiscovery from '@/pages/CreatorDiscovery'; +import PrivateCreator from '../pages/PrivateCreator'; +import CreatorDetail from '../pages/CreatorDetail'; // Routes configuration object const routes = [ @@ -25,7 +27,7 @@ const routes = [ children: [ { path: '', - element: , + element: , }, { path: 'tiktok', @@ -42,8 +44,25 @@ const routes = [ ], }, { - path: '/private-creators/*', - element: , + path: '/private-creators', + children: [ + { + path: '', + element: , + }, + { + path: 'tiktok', + element: , + }, + { + path: 'instagram', + element: , + }, + { + path: 'youtube', + element: , + }, + ], }, { path: '/deep-analysis', @@ -65,6 +84,10 @@ const routes = [ path: '/settings', element: , }, + { + path: '/creator/:id', + element: , + }, ]; // Create router with routes wrapped in the layout diff --git a/src/store/slices/creatorsSlice.js b/src/store/slices/creatorsSlice.js index 7a389a2..b1d5463 100644 --- a/src/store/slices/creatorsSlice.js +++ b/src/store/slices/creatorsSlice.js @@ -158,6 +158,7 @@ const initialState = { status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' error: null, selectedCreators: [], + selectedCreator: null, }; const creatorsSlice = createSlice({ @@ -169,7 +170,7 @@ const creatorsSlice = createSlice({ const isSelected = state.selectedCreators.includes(creatorId); if (isSelected) { - state.selectedCreators = state.selectedCreators.filter((id) => id !== creatorId); + state.selectedCreators = state.selectedCreators.filter((id) => id.toString() !== creatorId.toString()); } else { state.selectedCreators.push(creatorId); } @@ -180,6 +181,13 @@ const creatorsSlice = createSlice({ clearCreatorSelection: (state) => { state.selectedCreators = []; }, + selectCreator: (state, action) => { + const id = action.payload; + state.selectedCreator = state.creators.find((creator) => creator.id.toString() === id.toString()); + }, + clearCreator: (state) => { + state.selectedCreator = null; + }, }, extraReducers: (builder) => { builder @@ -198,6 +206,7 @@ const creatorsSlice = createSlice({ }, }); -export const { toggleCreatorSelection, selectAllCreators, clearCreatorSelection } = creatorsSlice.actions; +export const { toggleCreatorSelection, selectAllCreators, clearCreatorSelection, selectCreator, clearCreator } = + creatorsSlice.actions; export default creatorsSlice.reducer; diff --git a/src/store/slices/filtersSlice.js b/src/store/slices/filtersSlice.js index bd478a0..0b9c9a9 100644 --- a/src/store/slices/filtersSlice.js +++ b/src/store/slices/filtersSlice.js @@ -6,10 +6,10 @@ const initialState = { exposureRatings: [], gmvRanges: ['$5k - $25k', '$25k - $60k'], viewsRange: [0, 100000], + pricingRange: [0, 3000], sortBy: 'followers', sortDirection: 'desc', }; - const filtersSlice = createSlice({ name: 'filters', initialState, @@ -49,6 +49,9 @@ const filtersSlice = createSlice({ setViewsRange: (state, action) => { state.viewsRange = action.payload; }, + setPricingRange: (state, action) => { + state.pricingRange = action.payload; + }, setSortBy: (state, action) => { // 如果选择了当前已激活的排序项,则切换排序方向 if (state.sortBy === action.payload) { @@ -71,6 +74,7 @@ export const { toggleExposureRating, toggleGmvRange, setViewsRange, + setPricingRange, setSortBy, resetFilters, } = filtersSlice.actions; diff --git a/src/styles/CreatorDiscovery.scss b/src/styles/CreatorDiscovery.scss index 9395b51..15086b6 100644 --- a/src/styles/CreatorDiscovery.scss +++ b/src/styles/CreatorDiscovery.scss @@ -51,3 +51,168 @@ border: 1px solid #171a1f12; } } + +.creator-detail-page { + .creator-info-detail-container { + display: flex; + flex-direction: row; + gap: 1rem; + justify-content: space-between; + + .creator-info-container { + width: 40%; + display: flex; + flex-flow: column nowrap; + background: #ffffffff; /* white */ + border-radius: 8px; /* border-l */ + box-shadow: 0px 0px 1px #171a1f12, 0px 0px 2px #171a1f1f; /* shadow-xs */ + gap: 1rem; + padding: 1rem; + + .creator-info-1 { + display: flex; + flex-flow: row nowrap; + gap: 1rem; + align-items: center; + border-bottom: 2px solid $neutral-300; + padding-bottom: 1rem; + .creator-avatar { + width: 115px; + height: 115px; + border-radius: 50%; + overflow: hidden; + } + .creator-info-right { + display: flex; + flex-flow: column nowrap; + gap: 0.5rem; + .creator-name { + font-size: 1.5rem; + font-weight: 700; + color: $primary; + } + } + } + .creator-info-2 { + display: flex; + flex-flow: column nowrap; + gap: 1rem; + border-bottom: 2px solid $neutral-300; + padding-bottom: 1rem; + .creator-info-item { + display: flex; + flex-flow: row nowrap; + color: $neutral-900; + + .creator-info-label { + font-weight: 700; + width: 90px; + } + .creator-info-value { + } + } + } + .creator-info-3 { + display: flex; + flex-flow: column nowrap; + gap: 1rem; + .creator-info-item { + display: flex; + flex-flow: row nowrap; + color: $neutral-900; + gap: 0.5rem; + + .creator-info-label { + font-weight: 700; + padding: 0.45rem; + border-radius: 50%; + color: #fff; + display: flex; + align-items: center; + justify-content: center; + + &.mail { + background-color: $primary-500; + } + &.social { + background-color: $secondary-500; + } + &.link { + background-color: $info-500; + } + } + } + } + } + .creator-data { + width: 65%; + background: #ffffffff; /* white */ + border-radius: 8px; /* border-l */ + box-shadow: 0px 0px 1px #171a1f12, 0px 0px 2px #171a1f1f; /* shadow-xs */ + padding: 1rem; + display: flex; + flex-flow: column nowrap; + gap: 1rem; + + .levels { + display: flex; + flex-flow: row nowrap; + .level-item { + flex: 1; + text-align: start; + display: flex; + flex-flow: row nowrap; + gap: 1rem; + .name { + font-weight: 700; + } + .value { + border-radius: 1rem; + background-color: $primary-100; + color: $primary; + font-size: .75rem; + line-height: 1.5; + padding: 0.25rem 0.5rem; + } + } + } + .data-cards { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: .5rem; + .data-card { + display: flex; + flex-flow: column nowrap; + align-items: center; + justify-content: center; + background-color: $neutral-150; + border-radius: 6px; + padding: 12px 0; + .value { + font-weight: 700; + line-height: 1.5rem; + } + .name { + color: $neutral-500; + } + } + } + .data-charts { + display: flex; + flex-flow: row nowrap; + .data-chart { + flex: 1; + .chart-title { + font-weight: 700; + color: $neutral-900; + margin-bottom: 1rem; + } + canvas { + max-width: 260px; + max-height: 260px; + } + } + } + } + } +} diff --git a/src/styles/DatabaseFilter.scss b/src/styles/DatabaseFilter.scss index d78e2af..3e2af6e 100644 --- a/src/styles/DatabaseFilter.scss +++ b/src/styles/DatabaseFilter.scss @@ -29,7 +29,7 @@ gap: 0.5rem; flex: 1; - &.filter-views { + &.filter-range-slider { flex-direction: row; flex-wrap: nowrap; gap: 1rem; diff --git a/src/styles/DatabaseList.scss b/src/styles/DatabaseList.scss index 348509c..fcb1d00 100644 --- a/src/styles/DatabaseList.scss +++ b/src/styles/DatabaseList.scss @@ -36,7 +36,8 @@ } .creator-name { - font-weight: 500; + font-weight: 700; + color: #090909 } } diff --git a/src/styles/RangeSlider.scss b/src/styles/RangeSlider.scss index 5bdf45b..cc558b6 100644 --- a/src/styles/RangeSlider.scss +++ b/src/styles/RangeSlider.scss @@ -44,7 +44,7 @@ position: absolute; height: 5px; border-radius: 3px; - background-color: $indigo-500; + background-color: $primary; } &__steps { @@ -65,8 +65,8 @@ transition: all 0.2s ease; &.active { - background-color: $indigo-500; - border-color: $indigo-500; + background-color: $primary; + border-color: $primary; transform: scale(1.2); } } @@ -83,7 +83,7 @@ transform: translateX(-50%); font-size: 0.75rem; color: white; - background-color: $indigo-500; + background-color: $primary; padding: 2px 6px; border-radius: 10px; white-space: nowrap; @@ -100,7 +100,7 @@ height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; - border-bottom: 5px solid $indigo-500; + border-bottom: 5px solid $primary; } } } @@ -123,13 +123,13 @@ height: 20px; border-radius: 50%; background-color: white; - border: 2px solid $indigo-500; + border: 2px solid $primary; cursor: pointer; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); transition: all 0.2s ease; &:hover { - background-color: $indigo-500; + background-color: $primary; transform: scale(1.1); } @@ -146,13 +146,13 @@ height: 20px; border-radius: 50%; background-color: white; - border: 2px solid $indigo-500; + border: 2px solid $primary; cursor: pointer; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); transition: all 0.2s ease; &:hover { - background-color: $indigo-500; + background-color: $primary; transform: scale(1.1); } @@ -190,7 +190,7 @@ /* For Chrome browsers */ .thumb::-webkit-slider-thumb { background-color: #fff; - border: 2px solid $indigo-500; + border: 2px solid $primary; border-radius: 50%; box-shadow: 0 0 3px rgba(0, 0, 0, 0.2); cursor: pointer; @@ -204,7 +204,7 @@ /* For Firefox browsers */ .thumb::-moz-range-thumb { background-color: #fff; - border: 2px solid $indigo-500; + border: 2px solid $primary; border-radius: 50%; box-shadow: 0 0 3px rgba(0, 0, 0, 0.2); cursor: pointer; diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss index ed4733c..6a55220 100644 --- a/src/styles/_variables.scss +++ b/src/styles/_variables.scss @@ -1,26 +1,183 @@ // 主题颜色变量 -$primary: #636AE8FF; // 靛蓝色 -$secondary: #6c757d; // 灰色 -$success: #198754; // 绿色 -$info: #0dcaf0; // 浅蓝色 -$warning: #ffc107; // 黄色 -$danger: #dc3545; // 红色 $light: #f8f9fa; // 浅色 $dark: #212529; // 深色 -// 自定义颜色变量 $primary-100: #F2F2FDFF; $primary-150: #E0E1FAFF; +$primary-200: #CED0F8FF; +$primary-250: #BCBFF5FF; +$primary-300: #ABAEF2FF; +$primary-350: #999DF0FF; +$primary-400: #878CEDFF; +$primary-450: #757BEAFF; $primary-500: #636AE8FF; -$indigo-50: #eef2ff; -$indigo-100: #e0e7ff; -$indigo-500: #6366f1; -$violet-50: #f5f3ff; -$violet-100: #ede9fe; -$violet-400: #a78bfa; +$primary-550: #4850E4FF; +$primary-600: #2C35E0FF; +$primary-650: #1F27CDFF; +$primary-700: #1B22B1FF; +$primary-750: #161D96FF; +$primary-800: #12177AFF; +$primary-850: #0E125EFF; +$primary-900: #0A0D42FF; +$primary: #636AE8FF; + +$secondary-100: #FDF1F5FF; +$secondary-150: #FBE0E8FF; +$secondary-200: #F8CEDBFF; +$secondary-250: #F5BCCEFF; +$secondary-300: #F3AAC1FF; +$secondary-350: #F098B4FF; +$secondary-400: #EE86A7FF; +$secondary-450: #EB759AFF; +$secondary-500: #E8618CFF; +$secondary-550: #E44578FF; +$secondary-600: #E02862FF; +$secondary-650: #C91D53FF; +$secondary-700: #AC1947FF; +$secondary-750: #8E143BFF; +$secondary-800: #71102FFF; +$secondary-850: #530C22FF; +$secondary-900: #360816FF; +$secondary: #E8618CFF; + +$info-100: #F1F8FDFF; +$info-150: #DAECFAFF; +$info-200: #C3E1F8FF; +$info-250: #ACD5F5FF; +$info-300: #94C9F2FF; +$info-350: #7DBEEFFF; +$info-400: #66B2ECFF; +$info-450: #4FA6E9FF; +$info-500: #379AE6FF; +$info-550: #1D8DE3FF; +$info-600: #197DCAFF; +$info-650: #166DB0FF; +$info-700: #125D95FF; +$info-750: #0F4C7BFF; +$info-800: #0C3C61FF; +$info-850: #092C47FF; +$info-900: #061C2DFF; +$info: #379AE6FF; + +$warning-100: #FEF9EEFF; +$warning-150: #FCF0D7FF; +$warning-200: #FAE7C0FF; +$warning-250: #F8DEA9FF; +$warning-300: #F6D491FF; +$warning-350: #F4CB7AFF; +$warning-400: #F2C263FF; +$warning-450: #F0B94BFF; +$warning-500: #EFB034FF; +$warning-550: #ECA517FF; +$warning-600: #D29211FF; +$warning-650: #B57E0FFF; +$warning-700: #98690CFF; +$warning-750: #7A550AFF; +$warning-800: #5D4108FF; +$warning-850: #402C05FF; +$warning-900: #221803FF; +$warning: #EFB034FF; + +$danger-100: #FDF2F2FF; +$danger-150: #F9DBDCFF; +$danger-200: #F5C4C6FF; +$danger-250: #F1ADAF; +$danger-300: #ED9699FF; +$danger-350: #E97F83FF; +$danger-400: #E5696DFF; +$danger-450: #E25256FF; +$danger-500: #DE3B40FF; +$danger-550: #D9252BFF; +$danger-600: #C12126FF; +$danger-650: #AA1D22FF; +$danger-700: #93191DFF; +$danger-750: #7B1518FF; +$danger-800: #641114FF; +$danger-850: #4D0D0FFF; +$danger-900: #36090BFF; +$danger: #DE3B40FF; + +$success-100: #EEFDF3FF; +$success-150: #D3F9E0FF; +$success-200: #B8F5CDFF; +$success-250: #9DF2B9FF; +$success-300: #82EEA6FF; +$success-350: #67EA93FF; +$success-400: #4CE77FFF; +$success-450: #31E36CFF; +$success-500: #1DD75BFF; +$success-550: #1AC052FF; +$success-600: #17A948FF; +$success-650: #14923EFF; +$success-700: #117B34FF; +$success-750: #0E642AFF; +$success-800: #0A4D20FF; +$success-850: #073517FF; +$success-900: #041E0DFF; +$success: #1DD75BFF; + +$color-3-100: #EFFCFAFF; +$color-3-150: #D4F8F2FF; +$color-3-200: #BAF3EBFF; +$color-3-250: #9FEFE3FF; +$color-3-300: #84EADBFF; +$color-3-350: #69E6D3FF; +$color-3-400: #4EE1CBFF; +$color-3-450: #33DCC3FF; +$color-3-500: #22CCB2FF; +$color-3-550: #1FB7A0FF; +$color-3-600: #1BA18DFF; +$color-3-650: #188B7AFF; +$color-3-700: #147567FF; +$color-3-750: #105F53FF; +$color-3-800: #0C4940FF; +$color-3-850: #09332DFF; +$color-3-900: #051D1AFF; +$color-3: #22CCB2FF; + +$color-4-100: #F5F2FDFF; +$color-4-150: #E7DFF9FF; +$color-4-200: #D8CBF5FF; +$color-4-250: #C9B8F2FF; +$color-4-300: #BBA4EEFF; +$color-4-350: #AC91EBFF; +$color-4-400: #9D7EE7FF; +$color-4-450: #8F6AE4FF; +$color-4-500: #7F55E0FF; +$color-4-550: #6D3EDCFF; +$color-4-600: #5B27D5FF; +$color-4-650: #5123BCFF; +$color-4-700: #461EA4FF; +$color-4-750: #3B198BFF; +$color-4-800: #311572FF; +$color-4-850: #261059FF; +$color-4-900: #1C0C40FF; +$color-4: #7F55E0FF; + +$color-5-100: #FDF5F1FF; +$color-5-150: #FBE8E1FF; +$color-5-200: #F8DBD0FF; +$color-5-250: #F6CFBFFF; +$color-5-300: #F4C2AFFF; +$color-5-350: #F1B59EFF; +$color-5-400: #EFA98DFF; +$color-5-450: #EC9C7CFF; +$color-5-500: #EA916EFF; +$color-5-550: #E5784CFF; +$color-5-600: #E1602CFF; +$color-5-650: #CC4F1DFF; +$color-5-700: #AC4219FF; +$color-5-750: #8D3614FF; +$color-5-800: #6D2A10FF; +$color-5-850: #4D1E0BFF; +$color-5-900: #2D1206FF; +$color-5: #EA916EFF; + $neutral-150: #f8f9faff; $neutral-200: #f3f4f6ff; +$neutral-300: #DEE1E6FF; /* neutral-300 */; $neutral-350: #cfd2daff; +$neutral-500: #9095A0FF; $neutral-600: #565e6cff; $neutral-700: #323842ff; $neutral-900: #171a1fff; diff --git a/src/styles/custom-theme.scss b/src/styles/custom-theme.scss index 997c774..97be9b8 100644 --- a/src/styles/custom-theme.scss +++ b/src/styles/custom-theme.scss @@ -55,7 +55,7 @@ a { .table { border-collapse: separate; border-spacing: 0; - + border: 1px solid #171a1f1f; th { background-color: #f8f9fa; border-top: none; diff --git a/src/styles/global.scss b/src/styles/global.scss index 166e5b4..b857c6d 100644 --- a/src/styles/global.scss +++ b/src/styles/global.scss @@ -24,3 +24,7 @@ gap: 0.25rem; } } + +.back-button { + cursor: pointer; +} \ No newline at end of file