Преглед на файлове

feat: 招商门户页面开发

周玉环 преди 1 ден
родител
ревизия
f373796124

+ 1 - 0
xinkeaboard-promotion-portal/components.d.ts

@@ -8,6 +8,7 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    AiAnalysis: typeof import('./src/components/AiAnalysis.vue')['default']
     ColorLevel: typeof import('./src/components/ColorLevel.vue')['default']
     CommonEmpty: typeof import('./src/components/CommonEmpty.vue')['default']
     CompetitorList: typeof import('./src/components/CompetitorList.vue')['default']

+ 5 - 1
xinkeaboard-promotion-portal/package.json

@@ -9,11 +9,15 @@
   "dependencies": {
     "axios": "^1.11.0",
     "crypto-js": "^4.2.0",
+    "dompurify": "^3.2.6",
     "echarts": "^6.0.0",
     "element-plus": "^2.10.7",
+    "markdown-it": "^14.1.0",
     "pinia": "^2.0.0",
     "vue": "^3.2.0",
-    "vue-router": "^4.0.0"
+    "vue-router": "^4.0.0",
+    "html2canvas": "^1.4.1",
+    "jspdf": "^2.5.1"
   },
   "type": "module",
   "devDependencies": {

+ 212 - 0
xinkeaboard-promotion-portal/pnpm-lock.yaml

@@ -14,12 +14,24 @@ importers:
       crypto-js:
         specifier: ^4.2.0
         version: 4.2.0
+      dompurify:
+        specifier: ^3.2.6
+        version: 3.2.6
       echarts:
         specifier: ^6.0.0
         version: 6.0.0
       element-plus:
         specifier: ^2.10.7
         version: 2.10.7(vue@3.5.18(typescript@5.9.2))
+      html2canvas:
+        specifier: ^1.4.1
+        version: 1.4.1
+      jspdf:
+        specifier: ^2.5.1
+        version: 2.5.2
+      markdown-it:
+        specifier: ^14.1.0
+        version: 14.1.0
       pinia:
         specifier: ^2.0.0
         version: 2.3.1(typescript@5.9.2)(vue@3.5.18(typescript@5.9.2))
@@ -91,6 +103,10 @@ packages:
     engines: {node: '>=6.0.0'}
     hasBin: true
 
+  '@babel/runtime@7.28.3':
+    resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==}
+    engines: {node: '>=6.9.0'}
+
   '@babel/types@7.28.2':
     resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
     engines: {node: '>=6.9.0'}
@@ -397,9 +413,15 @@ packages:
   '@types/node@24.2.1':
     resolution: {integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==}
 
+  '@types/raf@3.4.3':
+    resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
+
   '@types/semver@7.7.0':
     resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==}
 
+  '@types/trusted-types@2.0.7':
+    resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
   '@types/web-bluetooth@0.0.16':
     resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
 
@@ -550,12 +572,21 @@ packages:
   asynckit@0.4.0:
     resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
 
+  atob@2.1.2:
+    resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
+    engines: {node: '>= 4.5.0'}
+    hasBin: true
+
   axios@1.11.0:
     resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
 
   balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
 
+  base64-arraybuffer@1.0.2:
+    resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
+    engines: {node: '>= 0.6.0'}
+
   binary-extensions@2.3.0:
     resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
     engines: {node: '>=8'}
@@ -570,6 +601,11 @@ packages:
     resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
     engines: {node: '>=8'}
 
+  btoa@1.2.1:
+    resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==}
+    engines: {node: '>= 0.4.0'}
+    hasBin: true
+
   call-bind-apply-helpers@1.0.2:
     resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
     engines: {node: '>= 0.4'}
@@ -578,6 +614,10 @@ packages:
     resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
     engines: {node: '>=6'}
 
+  canvg@3.0.11:
+    resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==}
+    engines: {node: '>=10.0.0'}
+
   chalk@4.1.2:
     resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
     engines: {node: '>=10'}
@@ -610,6 +650,9 @@ packages:
   confbox@0.2.2:
     resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
 
+  core-js@3.45.1:
+    resolution: {integrity: sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==}
+
   cross-spawn@7.0.6:
     resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     engines: {node: '>= 8'}
@@ -617,6 +660,9 @@ packages:
   crypto-js@4.2.0:
     resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
 
+  css-line-break@2.1.0:
+    resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
+
   cssesc@3.0.0:
     resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
     engines: {node: '>=4'}
@@ -657,6 +703,12 @@ packages:
     resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
     engines: {node: '>=6.0.0'}
 
+  dompurify@2.5.8:
+    resolution: {integrity: sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==}
+
+  dompurify@3.2.6:
+    resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==}
+
   dunder-proto@1.0.1:
     resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
     engines: {node: '>= 0.4'}
@@ -806,6 +858,9 @@ packages:
       picomatch:
         optional: true
 
+  fflate@0.8.2:
+    resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
+
   file-entry-cache@6.0.1:
     resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
     engines: {node: ^10.12.0 || >=12.0.0}
@@ -900,6 +955,10 @@ packages:
     resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
     engines: {node: '>= 0.4'}
 
+  html2canvas@1.4.1:
+    resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
+    engines: {node: '>=8.0.0'}
+
   ignore@5.3.2:
     resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
     engines: {node: '>= 4'}
@@ -961,6 +1020,9 @@ packages:
   json-stable-stringify-without-jsonify@1.0.1:
     resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
 
+  jspdf@2.5.2:
+    resolution: {integrity: sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==}
+
   keyv@4.5.4:
     resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
 
@@ -968,6 +1030,9 @@ packages:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
 
+  linkify-it@5.0.0:
+    resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+
   local-pkg@1.1.1:
     resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==}
     engines: {node: '>=14'}
@@ -995,10 +1060,17 @@ packages:
   magic-string@0.30.17:
     resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
 
+  markdown-it@14.1.0:
+    resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
+    hasBin: true
+
   math-intrinsics@1.1.0:
     resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
     engines: {node: '>= 0.4'}
 
+  mdurl@2.0.0:
+    resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
+
   memoize-one@6.0.0:
     resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
 
@@ -1093,6 +1165,9 @@ packages:
   pathe@2.0.3:
     resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
 
+  performance-now@2.1.0:
+    resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
+
   picocolors@1.1.1:
     resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
 
@@ -1146,6 +1221,10 @@ packages:
   proxy-from-env@1.1.0:
     resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
 
+  punycode.js@2.3.1:
+    resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+    engines: {node: '>=6'}
+
   punycode@2.3.1:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
@@ -1156,6 +1235,9 @@ packages:
   queue-microtask@1.2.3:
     resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
 
+  raf@3.4.1:
+    resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
+
   readdirp@3.6.0:
     resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
     engines: {node: '>=8.10.0'}
@@ -1164,6 +1246,9 @@ packages:
     resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
     engines: {node: '>= 14.18.0'}
 
+  regenerator-runtime@0.13.11:
+    resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
+
   resolve-from@4.0.0:
     resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
     engines: {node: '>=4'}
@@ -1172,6 +1257,10 @@ packages:
     resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
     engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
 
+  rgbcolor@1.0.1:
+    resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
+    engines: {node: '>= 0.8.15'}
+
   rimraf@3.0.2:
     resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
     deprecated: Rimraf versions prior to v4 are no longer supported
@@ -1214,6 +1303,10 @@ packages:
     resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
     engines: {node: '>=0.10.0'}
 
+  stackblur-canvas@2.7.0:
+    resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
+    engines: {node: '>=0.1.14'}
+
   strip-ansi@6.0.1:
     resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
     engines: {node: '>=8'}
@@ -1229,6 +1322,13 @@ packages:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
     engines: {node: '>=8'}
 
+  svg-pathdata@6.0.3:
+    resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==}
+    engines: {node: '>=12.0.0'}
+
+  text-segmentation@1.0.3:
+    resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
+
   text-table@0.2.0:
     resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
 
@@ -1265,6 +1365,9 @@ packages:
     engines: {node: '>=14.17'}
     hasBin: true
 
+  uc.micro@2.1.0:
+    resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+
   ufo@1.6.1:
     resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
 
@@ -1314,6 +1417,9 @@ packages:
   util-deprecate@1.0.2:
     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
 
+  utrie@1.0.2:
+    resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
+
   vite@4.5.14:
     resolution: {integrity: sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==}
     engines: {node: ^14.18.0 || >=16.0.0}
@@ -1408,6 +1514,8 @@ snapshots:
     dependencies:
       '@babel/types': 7.28.2
 
+  '@babel/runtime@7.28.3': {}
+
   '@babel/types@7.28.2':
     dependencies:
       '@babel/helper-string-parser': 7.27.1
@@ -1622,8 +1730,14 @@ snapshots:
     dependencies:
       undici-types: 7.10.0
 
+  '@types/raf@3.4.3':
+    optional: true
+
   '@types/semver@7.7.0': {}
 
+  '@types/trusted-types@2.0.7':
+    optional: true
+
   '@types/web-bluetooth@0.0.16': {}
 
   '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)':
@@ -1824,6 +1938,8 @@ snapshots:
 
   asynckit@0.4.0: {}
 
+  atob@2.1.2: {}
+
   axios@1.11.0:
     dependencies:
       follow-redirects: 1.15.11
@@ -1834,6 +1950,8 @@ snapshots:
 
   balanced-match@1.0.2: {}
 
+  base64-arraybuffer@1.0.2: {}
+
   binary-extensions@2.3.0: {}
 
   boolbase@1.0.0: {}
@@ -1847,6 +1965,8 @@ snapshots:
     dependencies:
       fill-range: 7.1.1
 
+  btoa@1.2.1: {}
+
   call-bind-apply-helpers@1.0.2:
     dependencies:
       es-errors: 1.3.0
@@ -1854,6 +1974,18 @@ snapshots:
 
   callsites@3.1.0: {}
 
+  canvg@3.0.11:
+    dependencies:
+      '@babel/runtime': 7.28.3
+      '@types/raf': 3.4.3
+      core-js: 3.45.1
+      raf: 3.4.1
+      regenerator-runtime: 0.13.11
+      rgbcolor: 1.0.1
+      stackblur-canvas: 2.7.0
+      svg-pathdata: 6.0.3
+    optional: true
+
   chalk@4.1.2:
     dependencies:
       ansi-styles: 4.3.0
@@ -1891,6 +2023,9 @@ snapshots:
 
   confbox@0.2.2: {}
 
+  core-js@3.45.1:
+    optional: true
+
   cross-spawn@7.0.6:
     dependencies:
       path-key: 3.1.1
@@ -1899,6 +2034,10 @@ snapshots:
 
   crypto-js@4.2.0: {}
 
+  css-line-break@2.1.0:
+    dependencies:
+      utrie: 1.0.2
+
   cssesc@3.0.0: {}
 
   csstype@3.1.3: {}
@@ -1924,6 +2063,13 @@ snapshots:
     dependencies:
       esutils: 2.0.3
 
+  dompurify@2.5.8:
+    optional: true
+
+  dompurify@3.2.6:
+    optionalDependencies:
+      '@types/trusted-types': 2.0.7
+
   dunder-proto@1.0.1:
     dependencies:
       call-bind-apply-helpers: 1.0.2
@@ -2137,6 +2283,8 @@ snapshots:
     optionalDependencies:
       picomatch: 4.0.3
 
+  fflate@0.8.2: {}
+
   file-entry-cache@6.0.1:
     dependencies:
       flat-cache: 3.2.0
@@ -2239,6 +2387,11 @@ snapshots:
     dependencies:
       function-bind: 1.1.2
 
+  html2canvas@1.4.1:
+    dependencies:
+      css-line-break: 2.1.0
+      text-segmentation: 1.0.3
+
   ignore@5.3.2: {}
 
   immutable@5.1.3: {}
@@ -2285,6 +2438,18 @@ snapshots:
 
   json-stable-stringify-without-jsonify@1.0.1: {}
 
+  jspdf@2.5.2:
+    dependencies:
+      '@babel/runtime': 7.28.3
+      atob: 2.1.2
+      btoa: 1.2.1
+      fflate: 0.8.2
+    optionalDependencies:
+      canvg: 3.0.11
+      core-js: 3.45.1
+      dompurify: 2.5.8
+      html2canvas: 1.4.1
+
   keyv@4.5.4:
     dependencies:
       json-buffer: 3.0.1
@@ -2294,6 +2459,10 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
+  linkify-it@5.0.0:
+    dependencies:
+      uc.micro: 2.1.0
+
   local-pkg@1.1.1:
     dependencies:
       mlly: 1.7.4
@@ -2320,8 +2489,19 @@ snapshots:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.4
 
+  markdown-it@14.1.0:
+    dependencies:
+      argparse: 2.0.1
+      entities: 4.5.0
+      linkify-it: 5.0.0
+      mdurl: 2.0.0
+      punycode.js: 2.3.1
+      uc.micro: 2.1.0
+
   math-intrinsics@1.1.0: {}
 
+  mdurl@2.0.0: {}
+
   memoize-one@6.0.0: {}
 
   merge2@1.4.1: {}
@@ -2404,6 +2584,9 @@ snapshots:
 
   pathe@2.0.3: {}
 
+  performance-now@2.1.0:
+    optional: true
+
   picocolors@1.1.1: {}
 
   picomatch@2.3.1: {}
@@ -2457,22 +2640,35 @@ snapshots:
 
   proxy-from-env@1.1.0: {}
 
+  punycode.js@2.3.1: {}
+
   punycode@2.3.1: {}
 
   quansync@0.2.10: {}
 
   queue-microtask@1.2.3: {}
 
+  raf@3.4.1:
+    dependencies:
+      performance-now: 2.1.0
+    optional: true
+
   readdirp@3.6.0:
     dependencies:
       picomatch: 2.3.1
 
   readdirp@4.1.2: {}
 
+  regenerator-runtime@0.13.11:
+    optional: true
+
   resolve-from@4.0.0: {}
 
   reusify@1.1.0: {}
 
+  rgbcolor@1.0.1:
+    optional: true
+
   rimraf@3.0.2:
     dependencies:
       glob: 7.2.3
@@ -2507,6 +2703,9 @@ snapshots:
 
   source-map-js@1.2.1: {}
 
+  stackblur-canvas@2.7.0:
+    optional: true
+
   strip-ansi@6.0.1:
     dependencies:
       ansi-regex: 5.0.1
@@ -2521,6 +2720,13 @@ snapshots:
     dependencies:
       has-flag: 4.0.0
 
+  svg-pathdata@6.0.3:
+    optional: true
+
+  text-segmentation@1.0.3:
+    dependencies:
+      utrie: 1.0.2
+
   text-table@0.2.0: {}
 
   tinyglobby@0.2.14:
@@ -2549,6 +2755,8 @@ snapshots:
 
   typescript@5.9.2: {}
 
+  uc.micro@2.1.0: {}
+
   ufo@1.6.1: {}
 
   undici-types@7.10.0: {}
@@ -2614,6 +2822,10 @@ snapshots:
 
   util-deprecate@1.0.2: {}
 
+  utrie@1.0.2:
+    dependencies:
+      base64-arraybuffer: 1.0.2
+
   vite@4.5.14(@types/node@24.2.1)(sass@1.90.0):
     dependencies:
       esbuild: 0.18.20

+ 4 - 2
xinkeaboard-promotion-portal/src/App.vue

@@ -1,11 +1,13 @@
 <template>
-  <router-view :key="route.path"></router-view>
+  <router-view></router-view>
 </template>
 
 <script setup lang="ts">
 import { useRoute } from 'vue-router';
 // 这里写你的业务逻辑
-const route = useRoute();
+// const route = useRoute();
+
+
 </script>
 
 <style>

BIN
xinkeaboard-promotion-portal/src/assets/images/ai-analysis.png


+ 79 - 0
xinkeaboard-promotion-portal/src/components/AiAnalysis.vue

@@ -0,0 +1,79 @@
+<template>
+  <div class="ai-analysis">
+    <div class="ai-analysis-empty" v-if="loading">
+      <Empty :autoFinish="autoFinish" :fail="fail"></Empty>
+    </div>
+    <div class="ai-analysis-text" v-html="renderedMarkdown" v-else></div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import DOMPurify from 'dompurify';
+import MarkdownIt from 'markdown-it';
+
+import { ref, computed } from 'vue';
+import { useMainStore } from '@/store';
+import Empty from '@/components/CommonEmpty.vue';
+
+const mainStore = useMainStore();
+const loading = ref<boolean>(true);
+const autoFinish = ref<boolean>(false);
+const fail = ref<boolean>(false);
+
+const rawText = ref<string>('');
+
+const md = new MarkdownIt({
+  html: true,
+  linkify: true,
+  typographer: true
+});
+
+// 去掉最外层 ```,渲染内部 Markdown
+const renderedMarkdown = computed(() => {
+  let content = rawText.value.trim();
+  content = content.replace(/^```[\s\S]*?\n?/, '').replace(/```$/, '');
+  return DOMPurify.sanitize(md.render(content));
+});
+
+const getQualitativeInfo = () => {
+  return mainStore
+    .getQualitative()
+    .then((res) => {
+      rawText.value = res.msg;
+      autoFinish.value = true;
+      setTimeout(() => {
+        loading.value = false;
+      }, 300);
+    })
+    .catch((err) => {
+      fail.value = true;
+    });
+};
+
+getQualitativeInfo();
+</script>
+
+<style lang="scss" scoped>
+.ai-analysis {
+  width: 100%;
+  height: auto;
+  padding: 40px;
+  box-sizing: border-box;
+  background-color: #ffffff;
+
+  &-empty {
+    width: 100%;
+    height: 376px;
+  }
+
+  &-text {
+    width: 100%;
+    word-break: break-all;
+    white-space: pre-wrap;
+    overflow: hidden;
+    font-weight: 400;
+    font-size: 20px;
+    color: #282e30;
+  }
+}
+</style>

+ 1 - 19
xinkeaboard-promotion-portal/src/components/TopContent.vue

@@ -39,12 +39,7 @@ import CountrySelct from '@/components/CountrySelct.vue';
 import ProductDescription from '@/components/ProductDescription.vue';
 import CompetitorWebsite from '@/components/CompetitorWebsite.vue';
 import Logo from '@/assets/images/logo.svg';
-import {
-  analysisKeyword,
-  analysisSuggestions,
-  analysisRival,
-  analysisQualitative
-} from '@/utils/api';
+
 import { showMessage } from '@/utils/common';
 const mainStore = useMainStore();
 const router = useRouter();
@@ -122,19 +117,6 @@ const acceptRecod = () => {
   }
   mainStore.setFormData(formData)
   router.push('/record');
-
-  // console.log(locationName, productName, description, competitorWebsite, '-=-=-=-=');
-  // Promise.all([
-  //   analysisKeyword({ productName: '自行车', locationName: 'United States' }),
-  //   analysisSuggestions({ productName: '自行车', locationName: 'United States' }),
-  //   analysisRival({
-  //     competitorWebsite: 'https://www.popmart.com,https://us.louisvuitton.com/eng-us/homepage',
-  //     locationName: 'United States'
-  //   }),
-  //   analysisQualitative('自行车')
-  // ]).then((res) => {
-  //   console.log(res, '=========');
-  // });
 };
 </script>
 

+ 1 - 1
xinkeaboard-promotion-portal/src/components/competitor/item.vue

@@ -70,7 +70,7 @@ const getRivalData = () => {
     });
 };
 
-// getRivalData();
+getRivalData();
 </script>
 
 <style lang="scss" scoped>

+ 1 - 1
xinkeaboard-promotion-portal/src/components/keyword/search.vue

@@ -27,7 +27,7 @@ const keywordInfo = computed(() => mainStore.getKeywordInfo);
 
 const loading = computed(() => keywordInfo.value.loading);
 
-const data = computed(() => keywordInfo.value.data);
+const data = computed(() => keywordInfo.value.data ?? {});
 
 const year = computed(() => data.value.monthlySearchesBO?.year);
 const month = computed(() => data.value.monthlySearchesBO?.month);

+ 19 - 4
xinkeaboard-promotion-portal/src/store/index.ts

@@ -1,6 +1,11 @@
 import { defineStore } from 'pinia';
 import { safeJsonParse } from '../utils/common';
-import { analysisKeyword, analysisSuggestions, analysisRival } from '../utils/api';
+import {
+  analysisKeyword,
+  analysisSuggestions,
+  analysisRival,
+  analysisQualitative
+} from '../utils/api';
 import type { FormDataInfo, KeywordInfo, RelatedInfoBOItem, CompetitorBOSItem } from '@/types';
 
 export const useMainStore = defineStore('main', {
@@ -16,9 +21,13 @@ export const useMainStore = defineStore('main', {
       autoFinish: false,
       data: [] as RelatedInfoBOItem[],
       fail: false
-    }
+    },
+    expanded: false
   }),
   actions: {
+    setExpanded(val: boolean) {
+      this.expanded = val;
+    },
     setCurrentStep(val: number) {
       this.currentStep = val;
     },
@@ -27,7 +36,10 @@ export const useMainStore = defineStore('main', {
       this.formData = data;
     },
     // 获取定性分析
-    getQualitative() {},
+    getQualitative(): Promise<any> {
+      const { productName, description } = this.getFormData;
+      return analysisQualitative(encodeURIComponent(productName + description));
+    },
     // 获取竞品
     getRival(website: string): Promise<any> {
       const { locationName } = this.getFormData;
@@ -44,7 +56,7 @@ export const useMainStore = defineStore('main', {
             this.suggestionsInfo.loading = false;
           }, 300);
         })
-        .catch(() => this.suggestionsInfo.fail = true);
+        .catch(() => (this.suggestionsInfo.fail = true));
     },
     // 获取关键词
     async getKeywordData() {
@@ -64,6 +76,9 @@ export const useMainStore = defineStore('main', {
     }
   },
   getters: {
+    getExpanded(): boolean {
+      return this.expanded;
+    },
     getCurrentStep(): number {
       return this.currentStep;
     },

+ 38 - 0
xinkeaboard-promotion-portal/src/utils/pdf.ts

@@ -0,0 +1,38 @@
+import { ref, nextTick, computed } from 'vue';
+import { useMainStore } from '@/store';
+import html2canvas from 'html2canvas';
+import jsPDF from 'jspdf';
+
+const mainStore = useMainStore();
+
+const expanded = computed(() => mainStore.getExpanded);
+
+export const downloadPDF = async (pdfContent: HTMLElement) => {
+//   if (!expanded.value) {
+//     expanded.value = true;
+//     await nextTick();
+//   }
+//   showWatermark.value = true;
+  await nextTick();
+  const canvas = await html2canvas(pdfContent, {
+    scale: 1,
+    useCORS: true,
+    backgroundColor: '#fff' // 防止透明背景
+  });
+  const imgData = canvas.toDataURL('image/png', 0.7);
+  const pdf = new jsPDF('p', 'mm', 'a4');
+  // const pdf = new jsPDF("landscape", "mm", "a4"); // 横向 A4
+  // pdf.setFontSize(100); // 设置当前字体大小为 12(单位:pt)
+  const pageWidth = pdf.internal.pageSize.getWidth();
+  const pageHeight = pdf.internal.pageSize.getHeight();
+  const ratio = Math.min(pageWidth / canvas.width, pageHeight / canvas.height);
+  const imgWidth = canvas.width * ratio;
+  const imgHeight = canvas.height * ratio;
+  const x = (pageWidth - imgWidth) / 2;
+  const y = (pageHeight - imgHeight) / 2;
+  pdf.addImage(imgData, 'JPEG', x, y, imgWidth, imgHeight);
+//   showWatermark.value = false;
+//   expanded.value = false;
+
+  pdf.save('charts-and-table.pdf');
+};

+ 56 - 3
xinkeaboard-promotion-portal/src/views/Record.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="record">
     <div class="record-head"></div>
-    <div class="record-wrap">
+    <div class="record-wrap" ref="pdfContent">
       <div class="record-wrap-content">
         <div class="record-wrap-content__overview" v-loading="loading">
           <span>概述关于你的产品的描述,通常的英语表述为</span>
@@ -11,7 +11,9 @@
         </div>
         <div class="overview-left">概述</div>
         <div class="overview-right">
-          <el-button>下载报告</el-button>
+          <el-button v-loading.fullscreen.lock="fullscreenLoading" @click="download"
+            >下载报告</el-button
+          >
         </div>
         <div class="record-wrap-content__keyword">
           <img :src="keywordPng" />
@@ -30,27 +32,48 @@
             <CompetitorList></CompetitorList>
           </div>
         </div>
+        <div class="record-wrap-content__ai">
+          <img :src="AiAnalysisPng" />
+          <AiAnalysis></AiAnalysis>
+        </div>
+        <div class="record-wrap-content__tips">
+          <span
+            >以上内容为AI根据三方网站获取的数据生成的内容,仅供参考,请仔细甄别。我司不对报告的真伪负责。</span
+          >
+          <span>如果需要更详细研究请联系我们:xxx@163.com。</span>
+        </div>
       </div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { onMounted, computed } from 'vue';
+import { ref, computed } from 'vue';
 import { useMainStore } from '../store';
 import KeywordSearch from '../components/keyword/search.vue';
 import KeywordTable from '../components/keyword/table.vue';
 import CompetitorList from '../components/competitor/list.vue';
+import AiAnalysis from '@/components/AiAnalysis.vue';
 import keywordPng from '../assets/images/keyword.png';
 import CompetitorPng from '../assets/images/competitor.png';
+import AiAnalysisPng from '../assets/images/ai-analysis.png';
+import { downloadPDF } from '@/utils/pdf';
 
 const mainStore = useMainStore();
 mainStore.initData();
 
+const fullscreenLoading = ref<boolean>(false);
+const pdfContent = ref<HTMLElement | null>(null);
 const keywordData = computed(() => mainStore.getKeywordInfo);
 const loading = computed(() => keywordData.value.loading);
 const keywordEn = computed(() => keywordData.value.data?.keywordEn);
 const keywords = computed(() => keywordData.value?.data?.keywords?.join(','));
+
+const download = () => {
+  fullscreenLoading.value = true;
+  downloadPDF(pdfContent.value!);
+  fullscreenLoading.value = false;
+};
 </script>
 
 <style lang="scss" scoped>
@@ -189,6 +212,36 @@ const keywords = computed(() => keywordData.value?.data?.keywords?.join(','));
           margin-top: 20px;
         }
       }
+
+      &__ai {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        margin-top: 60px;
+
+        img {
+          width: 624px;
+          height: 166px;
+        }
+      }
+
+      &__tips {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        width: 100%;
+        height: 124px;
+        padding-left: 40px;
+        box-sizing: border-box;
+        background: #ecf0f3;
+
+        span {
+          font-weight: 400;
+          font-size: 16px;
+          color: #d20000;
+        }
+      }
     }
   }
 }