Przeglądaj źródła

📝 docs(svg2jsx): add svg-to-jsx documentation

- document installation, usage and options for svg-to-jsx module

✨ feat(industry): add industry theme color support

- add IndustryType definition and color mapping
- implement theme color for KeyMetrics and PopupInfoBox components
- pass industry prop through GrainOilDashboard to child components

✨ feat(icons): add GrainIcon and OilIcon components

- create GrainIcon SVG component for grain industry
- create OilIcon SVG component for oil industry
- update IndustryIcon to use new icon components with theme colors
yourname 2 miesięcy temu
rodzic
commit
4ad4653601

+ 72 - 0
docs/svg2jsx.md

@@ -0,0 +1,72 @@
+svg-to-jsx
+Build Status
+
+Simple module that consumes SVG and spits out JSX. As simple as that.
+
+Hey! If you're using gulp you might find gulp-svg-to-jsx interesting. And if you're using webpack you might like svg-jsx-loader that wraps this module for use as a webpack loader.
+
+Installation
+svg-to-jsx is a node module. To install you have to have Node.js and NPM installed on your machine.
+
+npm install svg-to-jsx
+Usage
+You can either use the module in your Node.js project or via command line.
+
+Use as a node module
+var svgtojsx = require('svg-to-jsx');
+var svg = '<svg version="1.1"><path id="myPath" style="font-family: Verdana; margin-bottom: 10px; -webkit-transition: all; ms-transition: all;"/></svg>';
+
+// You can use svgtojsx with old school callbacks
+svgtojsx(svg, function(error, jsx) {
+    // ...
+});
+
+// The returned object is a promise though, you might prefer that
+svgtojsx(svg).then(function(jsx) {
+    // ...
+});
+Options
+root String In case you only want to output single SVG element you can set this to its ID.
+
+passProps Boolean Set this to true in case you want to pass props to the root element.
+
+renderChildren Boolean|String Set this to true in case you want to render this.props.children in the root element. If set to string value, this value is interpreted as an element ID and children are rendered into this element. If element already has some text content children are appended to the end.
+
+refs Object In case you want to be able to access specific elements from your SVG file, you can add refs to them. This object's keys are IDs of elements that will be assigned refs, the values are the ref names, for example:
+
+{
+    mySvgElement: 'refToMySvgElement'
+}
+will result in element with ID mySvgElement to be accessible via this.refs.refToMySvgElement.
+
+Use from command line
+# To output JSX to stdout
+$ svg-to-jsx <path to an SVG file>
+
+# To display usage info
+$ svg-to-jsx --help
+$ svg-to-jsx -h
+
+# To output to file
+$ svg-to-jsx -o <path to JSX file> <path to an SVG file>
+Notes
+<use/> tags are not allowed in JSX. The element referenced by a <use/> tag's xlink:href attribute is looked up, its id is discarded, and it replaces the original <use/> tag.
+
+Suppose you have an SVG file with following structure:
+
+<polygon id="mask-path" points="497,129 537.1,135.3 494.4,215.8"/>
+<clipPath id="mask">
+    <use xlink:href="#mask-path" overflow="visible"/>
+</clipPath>
+<g id="group" clip-path="url(#mask)">
+	<!-- Group contents -->
+</g>
+Then of course React won't support <use/> tags and you would end up unmasked #group. So the <use/> tags are replaced and you end up with following structure which is supported by React:
+
+<polygon id="mask-path" points="497,129 537.1,135.3 494.4,215.8"/>
+<clipPath id="mask">
+	<polygon points="497,129 537.1,135.3 494.4,215.8"/>
+</clipPath>
+<g id="group" clip-path="url(#mask)">
+	<!-- Group contents -->
+</g>

+ 2 - 0
src/client/home/pages/SupplyChainDashboards/GrainOilDashboard.tsx

@@ -107,6 +107,7 @@ const GrainOilDashboard: React.FC<GrainOilDashboardProps> = () => {
       <KeyMetrics
         title="优质稻米"
         subtitle="产业链联合体"
+        industry={activeTab}
       />
 
       {/* 弹出框 */}
@@ -115,6 +116,7 @@ const GrainOilDashboard: React.FC<GrainOilDashboardProps> = () => {
           data={popupState.data}
           position={popupState.position}
           onClose={handleClosePopup}
+          industry={activeTab}
         />
       )}
     </div>

+ 22 - 3
src/client/home/pages/SupplyChainDashboards/components/KeyMetrics.tsx

@@ -1,4 +1,5 @@
 import React from 'react';
+import { IndustryType } from './icons/IndustryIcon';
 
 // 定义指标数据类型
 interface MetricData {
@@ -12,15 +13,31 @@ interface KeyMetricsProps {
   metrics?: MetricData[];
   title?: string;
   subtitle?: string;
+  industry?: IndustryType;
 }
 
+// 产业主题色映射
+const industryColors: Record<IndustryType, string> = {
+  "粮食": "text-[#c5ff92]",
+  "油脂": "text-[#f6b02e]",
+  "种业": "text-[#5def8b]",
+  "果蔬": "text-[#fff586]",
+  "畜牧": "text-[#f593a5]",
+  "水产": "text-[#6be9ff]",
+  "鲜食": "text-[#de7e66]",
+  "泛盐": "text-[#92a5fe]"
+};
+
 // 数据卡片组件
 const DataCard: React.FC<{
   title: string;
   value: string;
   unit: string;
   digits: string[];
-}> = ({ title, value, unit, digits }) => {
+  industry?: IndustryType;
+}> = ({ title, value, unit, digits, industry = "粮食" }) => {
+  const themeColor = industryColors[industry];
+
   return (
     <div className="box-border content-stretch flex flex-col gap-[20px] items-start px-0 py-[20px] relative shrink-0 w-[278px]">
       <div className="box-border content-stretch flex gap-[10px] items-center px-0 py-[4px] relative shrink-0 w-full">
@@ -33,7 +50,7 @@ const DataCard: React.FC<{
           {digits.map((digit, index) => (
             <div key={index} className="h-[80px] relative shrink-0 w-[53.333px]">
               <div className="absolute bg-gradient-to-b border-2 border-[#5e697e] border-solid from-[#474e60] inset-0 rounded-[6px] to-[#2b2f39]" />
-              <div className="absolute flex flex-col font-bold inset-[16.16%_13.74%] justify-center leading-[0] not-italic text-[#c5ff92] text-[60px] text-center tracking-[6px]">
+              <div className={`absolute flex flex-col font-bold inset-[16.16%_13.74%] justify-center leading-[0] not-italic ${themeColor} text-[60px] text-center tracking-[6px]`}>
                 <p className="leading-[normal] whitespace-pre-wrap">{digit}</p>
               </div>
             </div>
@@ -52,7 +69,8 @@ const DataCard: React.FC<{
 const KeyMetrics: React.FC<KeyMetricsProps> = ({
   metrics = [],
   title = "优质稻米",
-  subtitle = "产业链联合体"
+  subtitle = "产业链联合体",
+  industry = "粮食"
 }) => {
   // 默认指标数据
   const defaultMetrics: MetricData[] = [
@@ -100,6 +118,7 @@ const KeyMetrics: React.FC<KeyMetricsProps> = ({
               value={metric.value}
               unit={metric.unit}
               digits={metric.digits}
+              industry={industry}
             />
           ))}
         </div>

+ 20 - 4
src/client/home/pages/SupplyChainDashboards/components/PopupInfoBox.tsx

@@ -1,4 +1,5 @@
 import React from 'react';
+import { IndustryType } from './icons/IndustryIcon';
 
 // 定义弹出框数据类型
 interface PopupData {
@@ -15,13 +16,28 @@ interface PopupInfoBoxProps {
   data?: PopupData;
   position?: { x: number; y: number };
   onClose?: () => void;
+  industry?: IndustryType;
 }
 
+// 产业主题色映射
+const industryColors: Record<IndustryType, string> = {
+  "粮食": "text-lime-200",
+  "油脂": "text-[#f6b02e]",
+  "种业": "text-[#5def8b]",
+  "果蔬": "text-[#fff586]",
+  "畜牧": "text-[#f593a5]",
+  "水产": "text-[#6be9ff]",
+  "鲜食": "text-[#de7e66]",
+  "泛盐": "text-[#92a5fe]"
+};
+
 const PopupInfoBox: React.FC<PopupInfoBoxProps> = ({
   data,
   position = { x: 717.28, y: 273.13 },
-  onClose
+  onClose,
+  industry = "粮食"
 }) => {
+  const themeColor = industryColors[industry];
   // 默认数据
   const defaultData: PopupData = {
     title: "源头",
@@ -61,7 +77,7 @@ const PopupInfoBox: React.FC<PopupInfoBoxProps> = ({
           {/* 标题区域 */}
           <div className="content-stretch flex flex-col gap-[12px] items-start relative shrink-0 w-full">
             <div className="box-border content-stretch flex gap-[10px] items-center px-0 py-[4px] relative shrink-0">
-              <div className="flex flex-col font-bold justify-center leading-[0] not-italic relative shrink-0 text-[36px] text-lime-200 whitespace-nowrap">
+              <div className={`flex flex-col font-bold justify-center leading-[0] not-italic relative shrink-0 text-[36px] whitespace-nowrap ${themeColor}`}>
                 <p className="leading-[32px]">{displayData.title}</p>
               </div>
             </div>
@@ -82,7 +98,7 @@ const PopupInfoBox: React.FC<PopupInfoBoxProps> = ({
                 </div>
               </div>
               <div className="box-border content-stretch flex gap-[10px] items-center px-0 py-[4px] relative shrink-0">
-                <div className="flex flex-col font-bold justify-center leading-[0] not-italic relative shrink-0 text-[36px] text-lime-200 whitespace-nowrap">
+                <div className={`flex flex-col font-bold justify-center leading-[0] not-italic relative shrink-0 text-[36px] whitespace-nowrap ${themeColor}`}>
                   <p className="leading-[32px]">{displayData.metrics[0]?.value}</p>
                 </div>
               </div>
@@ -100,7 +116,7 @@ const PopupInfoBox: React.FC<PopupInfoBoxProps> = ({
                 </div>
               </div>
               <div className="box-border content-stretch flex gap-[10px] items-center px-0 py-[4px] relative shrink-0">
-                <div className="flex flex-col font-bold justify-center leading-[0] not-italic relative shrink-0 text-[36px] text-lime-200 whitespace-nowrap">
+                <div className={`flex flex-col font-bold justify-center leading-[0] not-italic relative shrink-0 text-[36px] whitespace-nowrap ${themeColor}`}>
                   <p className="leading-[32px]">{displayData.metrics[1]?.value}</p>
                 </div>
               </div>

Plik diff jest za duży
+ 23 - 0
src/client/home/pages/SupplyChainDashboards/components/icons/GrainIcon.tsx


+ 38 - 22
src/client/home/pages/SupplyChainDashboards/components/icons/IndustryIcon.tsx

@@ -1,4 +1,6 @@
 import React from 'react';
+import GrainIcon from './GrainIcon';
+import OilIcon from './OilIcon';
 
 // 定义产业类型
 export type IndustryType = "粮食" | "油脂" | "种业" | "果蔬" | "畜牧" | "水产" | "鲜食" | "泛盐";
@@ -10,16 +12,16 @@ interface IndustryIconProps {
   isActive?: boolean;
 }
 
-// 产业图标URL映射
-const industryIconUrls: Record<IndustryType, string> = {
-  "粮食": "/supply-chain/union.svg",
-  "油脂": "/supply-chain/img5.svg",
-  "种业": "", // 待补充
-  "果蔬": "", // 待补充
-  "畜牧": "", // 待补充
-  "水产": "", // 待补充
-  "鲜食": "", // 待补充
-  "泛盐": "", // 待补充
+// 产业主题色映射
+const industryColors: Record<IndustryType, string> = {
+  "粮食": "text-[#c5ff92]",
+  "油脂": "text-[#f6b02e]",
+  "种业": "text-[#5def8b]",
+  "果蔬": "text-[#fff586]",
+  "畜牧": "text-[#f593a5]",
+  "水产": "text-[#6be9ff]",
+  "鲜食": "text-[#de7e66]",
+  "泛盐": "text-[#92a5fe]"
 };
 
 // 统一的产业图标组件
@@ -28,22 +30,36 @@ const IndustryIcon: React.FC<IndustryIconProps> = ({
   industry,
   isActive = true
 }) => {
-  const iconUrl = industryIconUrls[industry];
+  const themeColor = industryColors[industry];
+  const iconColor = isActive ? themeColor : "text-white opacity-50";
 
-  if (!iconUrl) {
-    console.warn(`未找到产业 ${industry} 的图标URL`);
-    return null;
-  }
+  // 根据产业类型渲染对应的图标组件
+  const renderIcon = () => {
+    switch (industry) {
+      case "粮食":
+        return <GrainIcon className={iconColor} />;
+      case "油脂":
+        return <OilIcon className={iconColor} />;
+      case "种业":
+      case "果蔬":
+      case "畜牧":
+      case "水产":
+      case "鲜食":
+      case "泛盐":
+        // 暂时使用默认图标,后续可以添加更多图标组件
+        return (
+          <div className="flex items-center justify-center w-full h-full bg-gray-400 rounded">
+            <span className="text-white text-sm">{industry}</span>
+          </div>
+        );
+      default:
+        return null;
+    }
+  };
 
   return (
     <div className={`overflow-clip relative shrink-0 size-[50px] ${className}`}>
-      <div className="absolute inset-0">
-        <img
-          alt={`${industry}图标`}
-          className="block max-w-none size-full"
-          src={iconUrl}
-        />
-      </div>
+      {renderIcon()}
     </div>
   );
 };

+ 31 - 0
src/client/home/pages/SupplyChainDashboards/components/icons/OilIcon.tsx

@@ -0,0 +1,31 @@
+import React from 'react';
+
+interface OilIconProps {
+  className?: string;
+  color?: string;
+}
+
+const OilIcon: React.FC<OilIconProps> = ({
+  className = "",
+  color = "currentColor"
+}) => {
+  return (
+    <svg
+      width="50"
+      height="50"
+      viewBox="0 0 50 50"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      className={className}
+    >
+      <path
+        opacity="0.78"
+        d="M28.1795 7.10254C28.1795 7.10254 27.0088 6.45224 25 6.5028C22.9911 6.45224 21.8222 7.10254 21.8222 7.10254V9.54132H28.1795V7.10254L28.1795 7.10254ZM36.8945 40.2447C36.8945 40.2447 36.8945 19.4698 36.8945 18.3318C36.8945 17.1937 36.1755 16.8143 36.1755 16.8143L28.2103 10.4699H21.7915L13.8263 16.8143C13.8263 16.8143 13.1055 17.1937 13.1055 18.3318C13.1055 19.4698 13.1055 40.2447 13.1055 40.2447C13.1055 40.2447 12.8273 43.5 16.3753 43.5H21.8222C25.0542 43.5 28.1795 43.5 28.1795 43.5H33.6263C37.1727 43.5 36.8945 40.2447 36.8945 40.2447ZM33.6245 32.372C33.6245 32.7296 33.3247 33.0223 32.9561 33.0223H28.0874C27.7189 33.0223 27.4208 32.7296 27.4208 32.372V21.6268C27.4208 21.2656 27.7189 20.9747 28.0874 20.9747H32.9562C33.3247 20.9747 33.6246 21.2656 33.6246 21.6268V32.372L33.6245 32.372Z"
+        fill={color}
+        fillOpacity="0.5"
+      />
+    </svg>
+  );
+};
+
+export default OilIcon;

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików