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.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 (
+
+
+ {selectedCreator ? (
+
+
+
+
+

+
+
+
{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