Jelajahi Sumber

feat: 接口对接

chenlei 7 bulan lalu
induk
melakukan
d89fa7de2c

+ 1 - 0
.env.development

@@ -0,0 +1 @@
+VITE_BASE_URL=https://sit-yueyangbwg.4dage.com

+ 1 - 0
.env.production

@@ -0,0 +1 @@
+VITE_BASE_URL=https://sit-yueyangbwg.4dage.com

+ 2 - 0
.npmrc

@@ -0,0 +1,2 @@
+registry=https://registry.npmmirror.com/
+@dage:registry=http://192.168.20.245:4873/

+ 4 - 1
package.json

@@ -10,14 +10,17 @@
   },
   "dependencies": {
     "@amap/amap-jsapi-loader": "^1.0.1",
+    "@dage/service": "^1.0.6",
     "animate.css": "^4.1.1",
-    "axios": "^1.9.0",
+    "axios": "^1.10.0",
     "crypto-js": "^4.2.0",
     "lib-flexible": "^0.3.2",
+    "lodash": "^4.17.21",
     "markdown-it": "^14.1.0",
     "openai": "^4.96.0",
     "pinia": "^3.0.1",
     "swiper": "^5.4.5",
+    "tslib": "^2.8.1",
     "vue": "^3.5.13",
     "vue-router": "^4.5.0",
     "xlsx": "^0.18.5"

File diff ditekan karena terlalu besar
+ 756 - 53
pnpm-lock.yaml


+ 18 - 18
public/static/data/food-old.json

@@ -2,7 +2,7 @@
   {
     "id": 1,
     "name": "青椒铁山鱼头",
-    "desc": "产自铁山水库的雄鱼,头大,体小,颜色黑亮。岳阳人烹饪鱼汤是有讲究的,开始用大火将水烧开,然后转为小火,慢炖几个小时,鱼肉完整鲜嫩,鱼汤会渐至奶白。随着青椒与佐料的加入,轻轻搅动勺子,香气扑面而来,让人瞬间打开味蕾。",
+    "intro": "产自铁山水库的雄鱼,头大,体小,颜色黑亮。岳阳人烹饪鱼汤是有讲究的,开始用大火将水烧开,然后转为小火,慢炖几个小时,鱼肉完整鲜嫩,鱼汤会渐至奶白。随着青椒与佐料的加入,轻轻搅动勺子,香气扑面而来,让人瞬间打开味蕾。",
     "thumb": "青椒铁山鱼头.png",
     "video": "青椒铁山鱼头.mp4",
     "url": "https://www.720yun.com/3dm/14bz672k525",
@@ -11,7 +11,7 @@
   {
     "id": 2,
     "name": "三色蒸回头鱼",
-    "desc": "回头鱼又称江团。三色蒸回头鱼以蒜蓉、豆豉、红剁椒为佐料蒸制,三色对比,既有蒜豉椒香,又得鱼味的自然鲜美。",
+    "intro": "回头鱼又称江团。三色蒸回头鱼以蒜蓉、豆豉、红剁椒为佐料蒸制,三色对比,既有蒜豉椒香,又得鱼味的自然鲜美。",
     "thumb": "三色蒸回头鱼.png",
     "video": "三色蒸回头鱼.mp4",
     "channel": 2,
@@ -20,7 +20,7 @@
   {
     "id": 3,
     "name": "岳阳口味虾",
-    "desc": "岳阳有大小湖泊165个,大小河流280多条,域内活水绵延,全市养虾面积超100万亩。好水出好虾,岳阳小龙虾具有出货早、供货时间长、肚皮干净、肉质Q弹等特点。口味虾重点在高汤现卤,其肉既嫩又辣又爽,给人满口的幸福感。",
+    "intro": "岳阳有大小湖泊165个,大小河流280多条,域内活水绵延,全市养虾面积超100万亩。好水出好虾,岳阳小龙虾具有出货早、供货时间长、肚皮干净、肉质Q弹等特点。口味虾重点在高汤现卤,其肉既嫩又辣又爽,给人满口的幸福感。",
     "thumb": "岳阳口味虾.png",
     "video": "岳阳口味虾.mp4",
     "url": "https://www.720yun.com/3dm/355z4e2Of6k",
@@ -29,7 +29,7 @@
   {
     "id": 4,
     "name": "银鱼炒蛋",
-    "desc": "银鱼古称玉簪鱼,鱼头扁平鱼身无鳞,洁白如银,银鱼体柔,若无骨无肠,漫游水中,快似银箭离弦。银鱼炒蛋讲究香鲜嫩,白黄相间,鱼蛋滑嫩,诱人食欲。",
+    "intro": "银鱼古称玉簪鱼,鱼头扁平鱼身无鳞,洁白如银,银鱼体柔,若无骨无肠,漫游水中,快似银箭离弦。银鱼炒蛋讲究香鲜嫩,白黄相间,鱼蛋滑嫩,诱人食欲。",
     "thumb": "银鱼炒蛋.png",
     "video": "银鱼炒蛋.mp4",
     "url": ""
@@ -37,7 +37,7 @@
   {
     "id": 5,
     "name": "平江酱干",
-    "desc": "平江酱干是一道历史悠久的传统小吃,以黄豆为主要原料,经过多次加工而成。酱干口感独特、味道鲜美,是佐餐佳品。",
+    "intro": "平江酱干是一道历史悠久的传统小吃,以黄豆为主要原料,经过多次加工而成。酱干口感独特、味道鲜美,是佐餐佳品。",
     "thumb": "平江酱干.png",
     "video": "平江酱干.mp4",
     "url": ""
@@ -45,7 +45,7 @@
   {
     "id": 6,
     "name": "华容团子",
-    "desc": "华容团子是岳阳的传统小吃,以其独特的制法和口感而闻名。团子外皮软糯、馅料丰富,有肉馅和菜馅等多种选择。在品尝团子的同时,不妨再配上一碗热气腾腾的米粥,更能感受到其美味。",
+    "intro": "华容团子是岳阳的传统小吃,以其独特的制法和口感而闻名。团子外皮软糯、馅料丰富,有肉馅和菜馅等多种选择。在品尝团子的同时,不妨再配上一碗热气腾腾的米粥,更能感受到其美味。",
     "thumb": "华容团子.png",
     "video": "华容团子.mp4",
     "url": ""
@@ -53,7 +53,7 @@
   {
     "id": 7,
     "name": "姜辣凤爪",
-    "desc": "凤凰是中国传统文化中的神鸟,因为鸡爪的形状类似凤凰的爪子,因此把鸡爪称为凤爪,寓意吉祥如意。姜辣系列是源自岳阳的新派湘菜,近年来风靡大江南北的湘菜馆。烹饪凤爪时,做料头的生姜丁是极多的,成菜味型突出姜香冲辣,姜辣凤爪与姜蒜干辣椒相熬出汁,因味型霸道,刺激味蕾,而独具岳阳地方特色。",
+    "intro": "凤凰是中国传统文化中的神鸟,因为鸡爪的形状类似凤凰的爪子,因此把鸡爪称为凤爪,寓意吉祥如意。姜辣系列是源自岳阳的新派湘菜,近年来风靡大江南北的湘菜馆。烹饪凤爪时,做料头的生姜丁是极多的,成菜味型突出姜香冲辣,姜辣凤爪与姜蒜干辣椒相熬出汁,因味型霸道,刺激味蕾,而独具岳阳地方特色。",
     "thumb": "姜辣凤爪.png",
     "video": "姜辣凤爪.mp4",
     "url": "https://www.720yun.com/3dm/603ze82Of6s"
@@ -61,7 +61,7 @@
   {
     "id": 8,
     "name": "清炒樟树港辣椒",
-    "desc": "湘阴县樟树镇,是一江一湖两港相夹而成的小“盆地”,形成了适合辣椒生长独特的环境。樟树港辣椒清炒时皮肉不分离,清脆柔软,椒香浓烈,曾被食客们戏称为辣椒中的“劳斯莱斯”。",
+    "intro": "湘阴县樟树镇,是一江一湖两港相夹而成的小“盆地”,形成了适合辣椒生长独特的环境。樟树港辣椒清炒时皮肉不分离,清脆柔软,椒香浓烈,曾被食客们戏称为辣椒中的“劳斯莱斯”。",
     "thumb": "清炒樟树港辣椒.png",
     "video": "清炒樟树港辣椒.mp4",
     "url": ""
@@ -69,7 +69,7 @@
   {
     "id": 9,
     "name": "张谷英油豆腐",
-    "desc": "张谷英村做油豆腐已经有几百年历史了,当地人会100多种豆腐做法,因此张谷英也被称作最会做豆腐的“古镇”。做油豆腐,必须用当地黄豆、渭洞茶油和龙涎井清泉来做,只有这样才足够外黄内白、外实内空,更好地吸汁。除了好的食材,采取传统工艺加工而成,也是张谷英油豆腐闻名遐迩的主要原因。",
+    "intro": "张谷英村做油豆腐已经有几百年历史了,当地人会100多种豆腐做法,因此张谷英也被称作最会做豆腐的“古镇”。做油豆腐,必须用当地黄豆、渭洞茶油和龙涎井清泉来做,只有这样才足够外黄内白、外实内空,更好地吸汁。除了好的食材,采取传统工艺加工而成,也是张谷英油豆腐闻名遐迩的主要原因。",
     "thumb": "张谷英油豆腐.png",
     "video": "张谷英油豆腐.mp4",
     "url": ""
@@ -77,7 +77,7 @@
   {
     "id": 11,
     "name": "岳阳豆皮",
-    "desc": "洞庭湖畔的岳阳豆皮,是本土早餐的灵魂。外皮以洞庭绿豆与早稻米(2:1)浸泡9小时磨浆,经发酵、摊凉、炸制等工序,成就金黄酥脆、内里柔韧的独特层次。\n2019年起,其制作技艺先后入选岳阳楼区、市级非遗。凭借咸香地道的口感、饱腹便携的特性,既是市井烟火味的承载,也是岳阳人舌尖的乡愁。",
+    "intro": "洞庭湖畔的岳阳豆皮,是本土早餐的灵魂。外皮以洞庭绿豆与早稻米(2:1)浸泡9小时磨浆,经发酵、摊凉、炸制等工序,成就金黄酥脆、内里柔韧的独特层次。\n2019年起,其制作技艺先后入选岳阳楼区、市级非遗。凭借咸香地道的口感、饱腹便携的特性,既是市井烟火味的承载,也是岳阳人舌尖的乡愁。",
     "thumb": "岳阳豆皮.png",
     "video": "岳阳豆皮.mp4",
     "url": ""
@@ -85,7 +85,7 @@
   {
     "id": 12,
     "name": "岳阳烧烤",
-    "desc": "岳阳烧烤,湘北夜宵江湖的味觉名片,以炭火淬炼湖湘风味。招牌烤牛油在烈焰中化作金黄脆珠,奶香与焦香交织;嫩滑牛肉裹着红亮辣衣,锁住丰盈肉汁;五花肉在高温下蜕变成酥脆脂香,搭配现烤洞庭鲫鱼的剁椒嫩鲜,每一串都诠释着粗犷与精细并存的烧烤哲学。",
+    "intro": "岳阳烧烤,湘北夜宵江湖的味觉名片,以炭火淬炼湖湘风味。招牌烤牛油在烈焰中化作金黄脆珠,奶香与焦香交织;嫩滑牛肉裹着红亮辣衣,锁住丰盈肉汁;五花肉在高温下蜕变成酥脆脂香,搭配现烤洞庭鲫鱼的剁椒嫩鲜,每一串都诠释着粗犷与精细并存的烧烤哲学。",
     "thumb": "岳阳烧烤.png",
     "video": "岳阳烧烤.mp4",
     "url": ""
@@ -93,7 +93,7 @@
   {
     "id": 13,
     "name": "香煎原味翘白",
-    "desc": "翘白鱼就是翘嘴白鱼,其肉白而细,首尾上翘,猎食性强,是我国淡水四大名鱼之一。制作香煎翘白时,得把翘白鱼切成几段,两面煎得金黄,上桌时完美不破皮,外脆内嫩,原汁原味。",
+    "intro": "翘白鱼就是翘嘴白鱼,其肉白而细,首尾上翘,猎食性强,是我国淡水四大名鱼之一。制作香煎翘白时,得把翘白鱼切成几段,两面煎得金黄,上桌时完美不破皮,外脆内嫩,原汁原味。",
     "thumb": "香煎原味翘白.png",
     "video": "香煎原味翘白.mp4",
     "url": ""
@@ -101,7 +101,7 @@
   {
     "id": 14,
     "name": "白灼河虾",
-    "desc": "洞庭湖区人对大湖习惯称为河,如汴河街,下河街等,对洞庭湖广袤水域里的虾习惯称为河虾。白灼河虾就是等清水沸腾后,倒入河虾至红色,辅以佐料即成鲜活美味。",
+    "intro": "洞庭湖区人对大湖习惯称为河,如汴河街,下河街等,对洞庭湖广袤水域里的虾习惯称为河虾。白灼河虾就是等清水沸腾后,倒入河虾至红色,辅以佐料即成鲜活美味。",
     "thumb": "白灼河虾.png",
     "video": "白灼河虾.mp4",
     "url": ""
@@ -109,7 +109,7 @@
   {
     "id": 15,
     "name": "金汤小米桂鱼打边炉",
-    "desc": "这道菜得到湘菜泰斗王墨泉大师的嫡传弟子、祖庵菜技艺传承人、湘菜大师杨龙的亲自指点,把桂鱼肉质的鲜嫩爽滑完美呈现出来。经过特殊熬制的小米,其清香醇厚包裹住细嫩的鱼肉,异常爽滑,久煮不柴。",
+    "intro": "这道菜得到湘菜泰斗王墨泉大师的嫡传弟子、祖庵菜技艺传承人、湘菜大师杨龙的亲自指点,把桂鱼肉质的鲜嫩爽滑完美呈现出来。经过特殊熬制的小米,其清香醇厚包裹住细嫩的鱼肉,异常爽滑,久煮不柴。",
     "thumb": "金汤小米桂鱼打边炉.png",
     "video": "金汤小米桂鱼打边炉.mp4",
     "url": ""
@@ -117,7 +117,7 @@
   {
     "id": 16,
     "name": "腊味芦笋",
-    "desc": "洞庭湖的芦苇荡里遍布一种俯拾皆是的时鲜芦笋,将芦笋煮沸焯水,去掉苦涩,这时鲜嫩的颜色更加讨喜,暖色的鹅黄交融着淡淡的乳白,当香喷喷的腊肉遇上新鲜芦笋,锅中翻炒时,芦笋就像一只只蝴蝶在锅里跳舞,起锅上桌让人口齿留香。",
+    "intro": "洞庭湖的芦苇荡里遍布一种俯拾皆是的时鲜芦笋,将芦笋煮沸焯水,去掉苦涩,这时鲜嫩的颜色更加讨喜,暖色的鹅黄交融着淡淡的乳白,当香喷喷的腊肉遇上新鲜芦笋,锅中翻炒时,芦笋就像一只只蝴蝶在锅里跳舞,起锅上桌让人口齿留香。",
     "thumb": "腊味芦笋.png",
     "video": "腊味芦笋.mp4",
     "url": ""
@@ -125,7 +125,7 @@
   {
     "id": 17,
     "name": "清炒藜蒿",
-    "desc": "正月藜,二月蒿,藜蒿广泛生长在洞庭湖区,冰雪未消融就冲破严寒,簇簇绽放,清香鲜嫩,风味冠春蔬,最是人家美味,只要藜蒿独特香,不炒腊肉饭也光,在烹饪藜蒿时,可以采用简单的方法来保留其最原始的风味,将藜蒿洗净切段,用热油快速翻炒,加入适量的盐和蒜末调味,便能品尝到藜蒿的鲜嫩于清香。",
+    "intro": "正月藜,二月蒿,藜蒿广泛生长在洞庭湖区,冰雪未消融就冲破严寒,簇簇绽放,清香鲜嫩,风味冠春蔬,最是人家美味,只要藜蒿独特香,不炒腊肉饭也光,在烹饪藜蒿时,可以采用简单的方法来保留其最原始的风味,将藜蒿洗净切段,用热油快速翻炒,加入适量的盐和蒜末调味,便能品尝到藜蒿的鲜嫩于清香。",
     "thumb": "清炒藜蒿.png",
     "video": "清炒藜蒿.mp4",
     "url": ""
@@ -133,9 +133,9 @@
   {
     "id": 18,
     "name": "荷塘三宝",
-    "desc": "洞庭湖区民间早就有荷莲一身宝,秋藕最补人的说法,家住大湖边,荷塘随处是,吃菱角,闻荷香,尝藕尖,吃莲子,品湖藕,记忆中的味道如此根深蒂固地成为一抹淡淡地乡愁。湖区人眼里地荷塘三宝就是湖藕,莲子,菱角。烹饪时要把新鲜的湖藕与菱角切丁与莲子合炒,怎么炒都能诱人品尝,清香可口。",
+    "intro": "洞庭湖区民间早就有荷莲一身宝,秋藕最补人的说法,家住大湖边,荷塘随处是,吃菱角,闻荷香,尝藕尖,吃莲子,品湖藕,记忆中的味道如此根深蒂固地成为一抹淡淡地乡愁。湖区人眼里地荷塘三宝就是湖藕,莲子,菱角。烹饪时要把新鲜的湖藕与菱角切丁与莲子合炒,怎么炒都能诱人品尝,清香可口。",
     "thumb": "荷塘三宝.png",
     "video": "荷塘三宝.mp4",
     "url": ""
   }
-]
+]

+ 11 - 11
public/static/data/food.json

@@ -6,7 +6,7 @@
     "url": "https://www.720yun.com/3dm/14bz672k525",
     "video": "岳阳口味虾.mp4",
     "thumb": "岳阳口味虾.jpg",
-    "desc": "岳阳有大小湖泊165个,大小河流280多条,域内活水绵延,全市养虾面积超100万亩。好水出好虾,岳阳小龙虾具有出货早、供货时间长、肚皮干净、肉质Q弹等特点。口味虾重点在高汤现卤,其肉既嫩又辣又爽,给人满口的幸福感。"
+    "intro": "岳阳有大小湖泊165个,大小河流280多条,域内活水绵延,全市养虾面积超100万亩。好水出好虾,岳阳小龙虾具有出货早、供货时间长、肚皮干净、肉质Q弹等特点。口味虾重点在高汤现卤,其肉既嫩又辣又爽,给人满口的幸福感。"
   },
   {
     "id": 2,
@@ -15,7 +15,7 @@
     "url": "https://www.720yun.com/3dm/857z502k52t",
     "video": "岳阳烧烤.mp4",
     "thumb": "牛肉串.jpg",
-    "desc": "岳阳烧烤,湘北夜宵江湖的味觉名片,以炭火淬炼湖湘风味。招牌烤牛油在烈焰中化作金黄脆珠,奶香与焦香交织;嫩滑牛肉裹着红亮辣衣,锁住丰盈肉汁;五花肉在高温下蜕变成酥脆脂香,搭配现烤洞庭鲫鱼的剁椒嫩鲜,每一串都诠释着粗犷与精细并存的烧烤哲学。"
+    "intro": "岳阳烧烤,湘北夜宵江湖的味觉名片,以炭火淬炼湖湘风味。招牌烤牛油在烈焰中化作金黄脆珠,奶香与焦香交织;嫩滑牛肉裹着红亮辣衣,锁住丰盈肉汁;五花肉在高温下蜕变成酥脆脂香,搭配现烤洞庭鲫鱼的剁椒嫩鲜,每一串都诠释着粗犷与精细并存的烧烤哲学。"
   },
   {
     "id": 3,
@@ -24,7 +24,7 @@
     "url": "https://www.720yun.com/3dm/b36zcb2k52u",
     "video": "三色蒸回头鱼.mp4",
     "thumb": "生态回头鱼.jpg",
-    "desc": "洞庭湖回头鱼是湖南省的传统名菜之一,以其独特的风味和制作工艺广受赞誉。这道菜以洞庭湖的鲜鱼为主要食材,配以丰富的调味料,经过精心烹制,呈现出令人回味无穷的美味。其名“回头鱼”来源于食客品尝后,其味道令人难以忘怀,让人忍不住回头再来的美好感受。"
+    "intro": "洞庭湖回头鱼是湖南省的传统名菜之一,以其独特的风味和制作工艺广受赞誉。这道菜以洞庭湖的鲜鱼为主要食材,配以丰富的调味料,经过精心烹制,呈现出令人回味无穷的美味。其名“回头鱼”来源于食客品尝后,其味道令人难以忘怀,让人忍不住回头再来的美好感受。"
   },
   {
     "id": 4,
@@ -33,7 +33,7 @@
     "url": "https://www.720yun.com/3dm/129z032k85s",
     "video": "汨罗粽子.mp4",
     "thumb": "汨罗粽子.jpg",
-    "desc": "每年农历五月初五为纪念屈原忌日,百姓投粽子于汨罗江,由此演变成独特的端午习俗。汨罗粽子也成为独具特色的端午节美食,汨罗粽是长条形,像牛角一样。粽体颜色橙黄,晶莹透亮。入口糯软略粘,清香味甜,粽叶一定要采用汨罗玉池山上的野生箬叶。汨罗传统的牛角粽只有两种食材,糯米和食用碱,既不放甜馅,也不放咸馅,只保留糯米和箬叶的清香。"
+    "intro": "每年农历五月初五为纪念屈原忌日,百姓投粽子于汨罗江,由此演变成独特的端午习俗。汨罗粽子也成为独具特色的端午节美食,汨罗粽是长条形,像牛角一样。粽体颜色橙黄,晶莹透亮。入口糯软略粘,清香味甜,粽叶一定要采用汨罗玉池山上的野生箬叶。汨罗传统的牛角粽只有两种食材,糯米和食用碱,既不放甜馅,也不放咸馅,只保留糯米和箬叶的清香。"
   },
   {
     "id": 5,
@@ -42,7 +42,7 @@
     "url": "https://www.720yun.com/3dm/4e4zc72k50O",
     "video": "蒜苗炒腊肉.mp4",
     "thumb": "乡里腊肉.jpg",
-    "desc": "湖南乡里炒腊肉不仅是一道美食,还承载着湖南人的饮食文化和历史记忆。腊肉的制作历史悠久,最早可以追溯到周代。其主要食材包括蒜叶和腊肉,通过炒制而成,口感香辣可口,深受湖南人喜爱‌。"
+    "intro": "湖南乡里炒腊肉不仅是一道美食,还承载着湖南人的饮食文化和历史记忆。腊肉的制作历史悠久,最早可以追溯到周代。其主要食材包括蒜叶和腊肉,通过炒制而成,口感香辣可口,深受湖南人喜爱‌。"
   },
   {
     "id": 6,
@@ -51,7 +51,7 @@
     "url": "https://www.720yun.com/3dm/a0az822k88u",
     "video": "清炒樟树港辣椒.mp4",
     "thumb": "樟树港辣椒.jpg",
-    "desc": "湘阴县樟树镇,是一江一湖两港相夹而成的小“盆地”,形成了适合辣椒生长独特的环境。樟树港辣椒清炒时皮肉不分离,清脆柔软,椒香浓烈,曾被食客们戏称为辣椒中的“劳斯莱斯”。"
+    "intro": "湘阴县樟树镇,是一江一湖两港相夹而成的小“盆地”,形成了适合辣椒生长独特的环境。樟树港辣椒清炒时皮肉不分离,清脆柔软,椒香浓烈,曾被食客们戏称为辣椒中的“劳斯莱斯”。"
   },
   {
     "id": 7,
@@ -59,7 +59,7 @@
     "url": "https://www.720yun.com/3dm/603ze82Of6s",
     "video": "姜辣凤爪.mp4",
     "thumb": "姜辣凤爪.jpg",
-    "desc": "凤凰是中国传统文化中的神鸟,因为鸡爪的形状类似凤凰的爪子,因此把鸡爪称为凤爪,寓意吉祥如意。姜辣系列是源自岳阳的新派湘菜,近年来风靡大江南北的湘菜馆。烹饪凤爪时,做料头的生姜丁是极多的,成菜味型突出姜香冲辣,姜辣凤爪与姜蒜干辣椒相熬出汁,因味型霸道,刺激味蕾,而独具岳阳地方特色。"
+    "intro": "凤凰是中国传统文化中的神鸟,因为鸡爪的形状类似凤凰的爪子,因此把鸡爪称为凤爪,寓意吉祥如意。姜辣系列是源自岳阳的新派湘菜,近年来风靡大江南北的湘菜馆。烹饪凤爪时,做料头的生姜丁是极多的,成菜味型突出姜香冲辣,姜辣凤爪与姜蒜干辣椒相熬出汁,因味型霸道,刺激味蕾,而独具岳阳地方特色。"
   },
   {
     "id": 8,
@@ -67,7 +67,7 @@
     "url": "https://www.720yun.com/3dm/de6z432k50r",
     "video": "香煎原味翘白.mp4",
     "thumb": "手撕翘白鱼.jpg",
-    "desc": "翘白鱼就是翘嘴白鱼,其肉白而细,首尾上翘,猎食性强,是我国淡水四大名鱼之一。制作香煎翘白时,得把翘白鱼切成几段,两面煎得金黄,上桌时完美不破皮,外脆内嫩,原汁原味。"
+    "intro": "翘白鱼就是翘嘴白鱼,其肉白而细,首尾上翘,猎食性强,是我国淡水四大名鱼之一。制作香煎翘白时,得把翘白鱼切成几段,两面煎得金黄,上桌时完美不破皮,外脆内嫩,原汁原味。"
   },
   {
     "id": 9,
@@ -75,7 +75,7 @@
     "url": "https://www.720yun.com/3dm/abfz512Of8k",
     "video": "黄喉炒牛筋1.mp4",
     "thumb": "黄喉牛筋.jpg",
-    "desc": "黄喉炒牛筋的口味特点是咸鲜香辣,黄喉的脆弹和牛筋的黏糯相互映衬,口感丰富。配以小米椒、蒜米子、青蒜苗等调料,更是提升了整体的风味‌。猪油的加入也为这道菜增添了独特的香气,使得味道更加浓郁‌。"
+    "intro": "黄喉炒牛筋的口味特点是咸鲜香辣,黄喉的脆弹和牛筋的黏糯相互映衬,口感丰富。配以小米椒、蒜米子、青蒜苗等调料,更是提升了整体的风味‌。猪油的加入也为这道菜增添了独特的香气,使得味道更加浓郁‌。"
   },
   {
     "id": 10,
@@ -83,6 +83,6 @@
     "url": "https://www.720yun.com/3dm/03bz6d2k5fs",
     "video": "招牌鸭锅.mp4",
     "thumb": "招牌鸭锅.jpg",
-    "desc": "岳阳鸭锅的特色在于其鸭肉卤制得非常入味,煮得软烂而不柴,完全没有鸭腥味。火锅的汤底越煮越有味道,让人上瘾‌。鸭肉质地鲜美且富有嚼劲,搭配上店家特制的酱料,味道独特且令人难忘‌。"
+    "intro": "岳阳鸭锅的特色在于其鸭肉卤制得非常入味,煮得软烂而不柴,完全没有鸭腥味。火锅的汤底越煮越有味道,让人上瘾‌。鸭肉质地鲜美且富有嚼劲,搭配上店家特制的酱料,味道独特且令人难忘‌。"
   }
-]
+]

File diff ditekan karena terlalu besar
+ 609 - 896
public/static/data/map.json


+ 132 - 0
public/static/data/transformMap.js

@@ -0,0 +1,132 @@
+import { readFileSync, writeFileSync } from "fs";
+import { join, dirname } from "path";
+import { fileURLToPath } from "url";
+
+// 获取当前文件路径
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+// 权重配置
+const weightConfig = {
+  1: [1, 72, 43, 49, 11, 15, 7, 5, 80, 39],
+  2: [42, 2, 112, 90, 95, 8, 61, 51, 53],
+  3: [74, 25, 21, 114, 67, 35, 10, 94, 27],
+  4: [149, 136, 132, 126],
+  5: [117, 121, 123, 116],
+  6: [102],
+  "3d": [1, 157, 159, 14, 13, 15, 153, 2, 11, 158],
+};
+
+function transformJsonData(originalData) {
+  const newImages = [];
+  let imageIdCounter = 1;
+
+  // 首先处理权重分配
+  const weightedItems = originalData.map((item) => {
+    const newItem = { ...item };
+
+    // 遍历权重配置
+    for (const [type, ids] of Object.entries(weightConfig)) {
+      if (String(newItem.type) === type) {
+        const index = ids.findIndex((id) => id === newItem.id);
+        if (index !== -1) {
+          // 计算权重:100 - (index * 10)
+          newItem.weight = 100 - index * 10;
+          break; // 找到匹配后跳出循环
+        }
+      }
+    }
+
+    return newItem;
+  });
+
+  // 然后进行其他转换
+  const transformedData = weightedItems.map((item) => {
+    const newItem = { ...item };
+
+    // 重命名字段
+    if (newItem.title !== undefined) {
+      newItem.type = newItem.title;
+      delete newItem.title;
+    }
+    if (newItem.desc !== undefined) {
+      newItem.intro = newItem.desc;
+      delete newItem.desc;
+    }
+    if (newItem.vrLink !== undefined) {
+      newItem.vrUrl = newItem.vrLink;
+      delete newItem.vrLink;
+    }
+    if (newItem.modelLink !== undefined) {
+      newItem.modelUrl = newItem.modelLink;
+      delete newItem.modelLink;
+    }
+    if (newItem.audio !== undefined) {
+      newItem.audioPath = newItem.audio;
+      delete newItem.audio;
+    }
+
+    // 拆分location
+    if (newItem.location !== undefined) {
+      const [lon, lat] = newItem.location.split(",");
+      newItem.lat = parseFloat(lat);
+      newItem.lon = parseFloat(lon);
+      delete newItem.location;
+    }
+
+    // 处理images
+    if (newItem.images && Array.isArray(newItem.images)) {
+      const imageIds = [];
+      newItem.images.forEach((imagePath) => {
+        newImages.push({
+          id: imageIdCounter,
+          goodsId: newItem.id,
+          path: imagePath,
+        });
+        imageIds.push(imageIdCounter.toString());
+        imageIdCounter++;
+      });
+      newItem.imageIds = imageIds.join(",");
+      delete newItem.images;
+    }
+
+    return newItem;
+  });
+
+  return {
+    transformedData,
+    newImages,
+  };
+}
+
+// 主函数
+async function main() {
+  try {
+    // 读取原始JSON文件
+    const inputFilePath = join(__dirname, "./map.json");
+    const originalData = JSON.parse(readFileSync(inputFilePath, "utf-8"));
+
+    // 转换数据
+    const result = transformJsonData(originalData);
+
+    // 写入转换后的主数据
+    const outputMainPath = join(__dirname, "transformed_main.json");
+    writeFileSync(
+      outputMainPath,
+      JSON.stringify(result.transformedData, null, 2)
+    );
+
+    // 写入新的图片数据
+    const outputImagesPath = join(__dirname, "transformed_images.json");
+    writeFileSync(outputImagesPath, JSON.stringify(result.newImages, null, 2));
+
+    console.log("转换完成!");
+    console.log(`主数据已保存到: ${outputMainPath}`);
+    console.log(`图片数据已保存到: ${outputImagesPath}`);
+  } catch (error) {
+    console.error("处理过程中发生错误:", error);
+  }
+}
+
+// 执行主函数
+main();

+ 6 - 1
src/App.vue

@@ -1,5 +1,10 @@
 <template>
-  <audio id="globalMusic" loop="loop" :autoplay="false" src="./static/image/全部/云上岳阳-智游岳阳音频.mp3"></audio>
+  <audio
+    id="globalMusic"
+    loop="loop"
+    :autoplay="false"
+    src="./static/image/全部/云上岳阳-智游岳阳音频.mp3"
+  ></audio>
   <RouterView />
 </template>
 <script setup>

+ 17 - 0
src/api/index.js

@@ -0,0 +1,17 @@
+import { requestByGet, requestByPost } from "@dage/service";
+
+export const getSitePageApi = (params) => {
+  return requestByPost("/api/show/site/page", params);
+};
+
+export const getSiteApi = (id) => {
+  return requestByGet(`/api/show/site/detail/${id}`);
+};
+
+export const getWayPageApi = (params) => {
+  return requestByPost("/api/show/way/page", params);
+};
+
+export const getWayApi = (id) => {
+  return requestByGet(`/api/show/way/detail/${id}`);
+};

+ 23 - 0
src/configure.js

@@ -0,0 +1,23 @@
+import { compose, initial } from "@dage/service";
+
+initial({
+  fetch: window.fetch.bind(window),
+  baseURL: import.meta.env.VITE_BASE_URL,
+  interceptor: compose(async (request, next) => {
+    request.headers["Content-Type"] = "application/json";
+
+    const response = await next();
+    const { showError = true } = request.meta;
+
+    if (response.code !== 0 && request.name.indexOf("someData") < 0) {
+      const message = response.__raw__.data.msg ?? "系统出差中";
+      // 错误信息映射
+      response.errorMessage = message;
+      if (showError) {
+        console.log(message);
+      }
+    }
+
+    return response;
+  }),
+});

+ 4 - 3
src/main.js

@@ -1,16 +1,17 @@
 import "./assets/main.css";
+import "./configure";
 
 import { createApp } from "vue";
 import { createPinia } from "pinia";
 import "lib-flexible/flexible";
 import App from "./App.vue";
 import router from "./router";
-import {initJssdk} from "./utils/wxConfig";
-import 'animate.css';
+import { initJssdk } from "./utils/wxConfig";
+import "animate.css";
 
 // console.error(initJssdk)
 const app = createApp(App);
-initJssdk()
+initJssdk();
 app.use(createPinia());
 app.use(router);
 

+ 1 - 1
src/stores/audio.js

@@ -2,7 +2,7 @@
 import { nextTick, onMounted, ref } from "vue";
 import { defineStore } from "pinia";
 
-export const useAudioStore = defineStore("audio", {
+export const useAudioStore = defineStore("audioPath", {
   state: () => {
     return { audioStatus: false };
   },

+ 26 - 0
src/utils/index.js

@@ -0,0 +1,26 @@
+export const TABS = [
+  {
+    title: "历史文化",
+    type: 1,
+  },
+  {
+    title: "自然探索",
+    type: 2,
+  },
+  {
+    title: "亲子休闲",
+    type: 3,
+  },
+  {
+    title: "城市烟火",
+    type: 4,
+  },
+  {
+    title: "交通住宿",
+    type: 5,
+  },
+  {
+    title: "政府驻地",
+    type: 6,
+  },
+];

+ 16 - 5
src/utils/wxConfig/index.js

@@ -12,11 +12,20 @@ const isWechat = () => {
   }
 };
 //需要使用的JS接口列表
-let jsApiList = ["checkJsApi", "getLocation", "openLocation", "getNetworkType", "onMenuShareTimeline", "onMenuShareAppMessage", "hideMenuItems"];
+let jsApiList = [
+  "checkJsApi",
+  "getLocation",
+  "openLocation",
+  "getNetworkType",
+  "onMenuShareTimeline",
+  "onMenuShareAppMessage",
+  "hideMenuItems",
+];
 // 生成随机字符
 function randomString(len) {
   len = len || 32;
-  var $chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
+  var $chars =
+    "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
   var maxPos = $chars.length;
   var pwd = "";
   for (let i = 0; i < len; i++) {
@@ -28,7 +37,7 @@ function randomString(len) {
 
 let shareOptions = {
   title: "云上岳阳",
-  desc: "云上岳阳三维VR地图\r\n大美江湖 天下岳阳",
+  intro: "云上岳阳三维VR地图\r\n大美江湖 天下岳阳",
   link: "https://hn3dreal.org.cn/ysyy/index.html#/",
   imgUrl: "https://hn3dreal.org.cn/ysyy/static/wxShare/share.png",
 };
@@ -44,7 +53,9 @@ export const initJssdk = (ticket) => {
       let url = window.location.href.split("#")[0];
       let timestamp = Math.round(new Date().getTime() / 1000).toString(); // 时间戳
       let nonceStr = randomString(); // 随机字符
-      let signature = CryptoJS.SHA1(`jsapi_ticket=${ticket}&noncestr=${nonceStr}&timestamp=${timestamp}&url=${url}`).toString();
+      let signature = CryptoJS.SHA1(
+        `jsapi_ticket=${ticket}&noncestr=${nonceStr}&timestamp=${timestamp}&url=${url}`
+      ).toString();
 
       // console.error(signature);
 
@@ -78,7 +89,7 @@ export const initJssdk = (ticket) => {
           title: shareOptions.title, // 分享标题
           link: shareOptions.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
           imgUrl: shareOptions.imgUrl, // 分享图标
-          desc: shareOptions.desc, // 描述
+          intro: shareOptions.intro, // 描述
           success: function (res) {
             console.log("分享成功");
           },

+ 30 - 12
src/views/answer/index.vue

@@ -20,14 +20,23 @@
               @click="changeOption(index, i)"
               class="item"
               v-for="(i, index) in custom[answerIndex].options"
-              :class="{ isTrue: optionIndex != -1 && i.correct, isFalse: optionIndex == index && !i.correct }"
+              :class="{
+                isTrue: optionIndex != -1 && i.correct,
+                isFalse: optionIndex == index && !i.correct,
+              }"
             >
               <span>{{ i.text }}</span>
               <!-- <div v-if="optionIndex != -1 && i.correct" class="icon true"></div>
               <div v-if="optionIndex == index && !i.correct" class="icon false"></div> -->
             </div>
           </div>
-          <div class="viewer" :class="{ show: isCheck }" @click="showReview = true">查看解析</div>
+          <div
+            class="viewer"
+            :class="{ show: isCheck }"
+            @click="showReview = true"
+          >
+            查看解析
+          </div>
         </div>
       </div>
     </div>
@@ -36,14 +45,23 @@
         <div class="text-box">
           <div class="header">
             <p class="review-title">{{ custom[answerIndex].content.title }}</p>
-            <span class="review-author">{{ custom[answerIndex].content.time }} {{ custom[answerIndex].content.author }} </span>
-            <p class="review-content" v-html="custom[answerIndex].content.text"></p>
+            <span class="review-author"
+              >{{ custom[answerIndex].content.time }}
+              {{ custom[answerIndex].content.author }}
+            </span>
+            <p
+              class="review-content"
+              v-html="custom[answerIndex].content.text"
+            ></p>
           </div>
           <div class="body">
-            <p class="desc-title">诗词译文</p>
-            <p class="desc" v-html="custom[answerIndex].content.explain"></p>
-            <p class="desc-title">诗词赏析</p>
-            <p class="desc" v-html="custom[answerIndex].content.appreciate"></p>
+            <p class="intro-title">诗词译文</p>
+            <p class="intro" v-html="custom[answerIndex].content.explain"></p>
+            <p class="intro-title">诗词赏析</p>
+            <p
+              class="intro"
+              v-html="custom[answerIndex].content.appreciate"
+            ></p>
           </div>
         </div>
         <div class="controls-box">
@@ -251,7 +269,7 @@ const reStartOrContinue = (type = 1) => {
     .review-bg {
       width: 100%;
       height: calc(100% - 1.0667rem);
-      padding: 1.6rem .5333rem 1.3333rem;
+      padding: 1.6rem 0.5333rem 1.3333rem;
       // overflow-y: auto;
       background: url("@/assets/images/reviewBg.png") no-repeat;
       background-size: 100% 100%;
@@ -289,7 +307,7 @@ const reStartOrContinue = (type = 1) => {
             font-size: 0.3733rem;
             color: #3d2924;
             line-height: 0.6533rem;
-            letter-spacing: .0667rem;
+            letter-spacing: 0.0667rem;
             display: flex;
             margin-top: 0.2rem;
             align-items: center;
@@ -300,7 +318,7 @@ const reStartOrContinue = (type = 1) => {
           width: 90%;
           // padding: 0 0.4rem 0.5333rem;
           margin: 0 auto;
-          .desc-title {
+          .intro-title {
             font-weight: bold;
             font-size: 0.4667rem;
             color: #6a4934;
@@ -315,7 +333,7 @@ const reStartOrContinue = (type = 1) => {
               margin-right: 0.16rem;
             }
           }
-          .desc {
+          .intro {
             font-weight: 400;
             font-size: 0.3333rem;
             color: rgba(106, 73, 52, 0.8);

+ 16 - 8
src/views/data/index.vue

@@ -8,7 +8,15 @@ vue3
 </template>
 
 <script setup>
-import { reactive, ref, toRefs, onBeforeMount, onMounted, nextTick, watch } from "vue";
+import {
+  reactive,
+  ref,
+  toRefs,
+  onBeforeMount,
+  onMounted,
+  nextTick,
+  watch,
+} from "vue";
 import * as XLSX from "xlsx";
 const readFile = (e) => {
   const files = e.target.files;
@@ -109,13 +117,13 @@ const procressData = (list) => {
           break;
         case "大场景链接":
           if (item[key]) {
-            obj["vrLink"] = item[key];
+            obj["vrUrl"] = item[key];
           }
 
           break;
         case "三维模型":
           if (item[key]) {
-            obj["modelLink"] = item[key];
+            obj["modelUrl"] = item[key];
           }
 
           break;
@@ -130,12 +138,12 @@ const procressData = (list) => {
 
           break;
         case "简介":
-          obj["desc"] = item[key];
+          obj["intro"] = item[key];
 
           break;
         case "音频名称":
           if (item[key]) {
-            obj["audio"] = item[key] + ".MP3";
+            obj["audioPath"] = item[key] + ".MP3";
           }
 
           break;
@@ -151,7 +159,7 @@ const procressData = (list) => {
           break;
       }
     }
-    // obj.vrLink = "https://www.4dkankan.com/panorama/show.html?id=WK1768236336482967552&vr=fd720_NZ0i3ASIq&lang=zh"
+    // obj.vrUrl = "https://www.4dkankan.com/panorama/show.html?id=WK1768236336482967552&vr=fd720_NZ0i3ASIq&lang=zh"
     array.push(obj);
   });
 
@@ -216,7 +224,7 @@ const procressFoodData = (list) => {
 
           break;
         case "介绍":
-          obj["desc"] = item[key];
+          obj["intro"] = item[key];
 
           break;
         case "图片":
@@ -241,7 +249,7 @@ const procressFoodData = (list) => {
           break;
       }
     }
-    // obj.vrLink = "https://www.4dkankan.com/panorama/show.html?id=WK1768236336482967552&vr=fd720_NZ0i3ASIq&lang=zh"
+    // obj.vrUrl = "https://www.4dkankan.com/panorama/show.html?id=WK1768236336482967552&vr=fd720_NZ0i3ASIq&lang=zh"
     array.push(obj);
   });
 

+ 37 - 11
src/views/food/index.vue

@@ -6,11 +6,21 @@
       <div class="food-title"></div>
       <div class="food-container">
         <div class="swiper-wrapper">
-          <div class="swiper-slide" :class="{ 'swiper-no-swiping': isLandscape }" @click="goToView(i)" v-for="i in foodList">
+          <div
+            class="swiper-slide"
+            :class="{ 'swiper-no-swiping': isLandscape }"
+            @click="goToView(i)"
+            v-for="i in foodList"
+          >
             <div class="slide-bg"></div>
 
             <!-- <img :src="`./static/image/美食/封面/${getThumb(i.thumb)}`" alt="" /> -->
-            <img :src="`./static/image/美食/封面/${isLandscape ? 'pc' : 'mobile'}/${getThumb(i.thumb)}`" alt="" />
+            <img
+              :src="`./static/image/美食/封面/${
+                isLandscape ? 'pc' : 'mobile'
+              }/${getThumb(i.thumb)}`"
+              alt=""
+            />
             <!-- <div class="food-name">{{ foodList[swipeIndex].name }}</div> -->
           </div>
 
@@ -25,20 +35,28 @@
 
     <div class="food-msg">
       <div class="btn-box">
-        <div class="model-btn" @click="goToView(foodList[swipeIndex])" v-if="foodList[swipeIndex].url">
+        <div
+          class="model-btn"
+          @click="goToView(foodList[swipeIndex])"
+          v-if="foodList[swipeIndex].url"
+        >
           <!-- <div></div>
           <span>3D美食</span> -->
         </div>
 
-        <div class="video-btn" @click="videoUrl = foodList[swipeIndex].video" v-if="foodList[swipeIndex].video">
+        <div
+          class="video-btn"
+          @click="videoUrl = foodList[swipeIndex].video"
+          v-if="foodList[swipeIndex].video"
+        >
           <!-- <div></div>
           <span>视频教程</span> -->
         </div>
       </div>
 
-      <div class="desc">
+      <div class="intro">
         <div>
-          {{ foodList[swipeIndex].desc }}
+          {{ foodList[swipeIndex].intro }}
         </div>
       </div>
       <div class="logo"></div>
@@ -50,7 +68,14 @@
 
   <div class="video-layer" :class="{ isLandscape }" v-if="videoUrl">
     <div class="video-box">
-      <video controls :autoplay="true" :src="`./static/image/美食/视频/${foodList[swipeIndex].video}`" x5-playsinline playsinline webkit-playsinline="true"></video>
+      <video
+        controls
+        :autoplay="true"
+        :src="`./static/image/美食/视频/${foodList[swipeIndex].video}`"
+        x5-playsinline
+        playsinline
+        webkit-playsinline="true"
+      ></video>
     </div>
     <div class="close-btn" @click="videoUrl = null"></div>
   </div>
@@ -99,7 +124,7 @@ const openData = ref(null);
 // const openData = ref({
 //   id: 7,
 //   name: "姜辣凤爪",
-//   desc: "凤凰是中国传统文化中的神鸟,因为鸡爪的形状类似凤凰的爪子,因此把鸡爪称为凤爪,寓意吉祥如意。姜辣系列是源自岳阳的新派湘菜,近年来风靡大江南北的湘菜馆。烹饪凤爪时,做料头的生姜丁是极多的,成菜味型突出姜香冲辣,姜辣凤爪与姜蒜干辣椒相熬出汁,因味型霸道,刺激味蕾,而独具岳阳地方特色。",
+//   intro: "凤凰是中国传统文化中的神鸟,因为鸡爪的形状类似凤凰的爪子,因此把鸡爪称为凤爪,寓意吉祥如意。姜辣系列是源自岳阳的新派湘菜,近年来风靡大江南北的湘菜馆。烹饪凤爪时,做料头的生姜丁是极多的,成菜味型突出姜香冲辣,姜辣凤爪与姜蒜干辣椒相熬出汁,因味型霸道,刺激味蕾,而独具岳阳地方特色。",
 //   thumb: "姜辣凤爪.png",
 //   video: "姜辣凤爪.mp4",
 //   url: "https://www.720yun.com/3dm/603ze82Of6s",
@@ -111,7 +136,8 @@ const odorPlay = async () => {
   } else {
   }
 
-  let channel = route.query.channel || foodList.value[swipeIndex.value]?.channel || 1;
+  let channel =
+    route.query.channel || foodList.value[swipeIndex.value]?.channel || 1;
   let times = route.query.times;
   let data = {
     mac,
@@ -323,7 +349,7 @@ onMounted(() => {
       }
     }
 
-    .desc {
+    .intro {
       padding: 0 1.3333rem;
       width: 100%;
       // height: 50%;
@@ -614,7 +640,7 @@ onMounted(() => {
       }
     }
 
-    .desc {
+    .intro {
       margin-top: 0;
       bottom: 10%;
       top: auto;

+ 65 - 43
src/views/line/index.vue

@@ -5,7 +5,11 @@
     <div id="amap" />
     <div id="panel"></div>
 
-    <Dialog v-if="dialogData" :dialogData="dialogData" @close="dialogData = null" />
+    <Dialog
+      v-if="dialogData"
+      :dialogData="dialogData"
+      @close="dialogData = null"
+    />
     <div class="line-content">
       <div class="top">
         <div class="title">
@@ -18,16 +22,23 @@
         </div>
       </div>
       <div class="bottom">
-        <div class="item" @click="gotoItem(i)" :class="{ active: i.id == activeTagData?.id }" v-for="(i, index) in lineData">
+        <div
+          class="item"
+          @click="gotoItem(i)"
+          :class="{ active: i.id == activeTagData?.id }"
+          v-for="(i, index) in lineData"
+        >
           <div class="icon">{{ index + 1 }}</div>
           <p class="name">{{ i.name }}</p>
         </div>
       </div>
     </div>
   </div>
-  <div class="vr-content" v-if="vrLink">
-    <div class="vr-back" @click="vrLink = ''"><img src="@/assets/images/vr-back.png" alt="" /></div>
-    <iframe :src="vrLink" frameborder="0"></iframe>
+  <div class="vr-content" v-if="vrUrl">
+    <div class="vr-back" @click="vrUrl = ''">
+      <img src="@/assets/images/vr-back.png" alt="" />
+    </div>
+    <iframe :src="vrUrl" frameborder="0"></iframe>
   </div>
 </template>
 
@@ -49,8 +60,9 @@ import tagIcon6 from "@/assets/images/tag-6.png";
 import tagIcon from "@/assets/images/tagIcon.png";
 import router from "@/router/index.js";
 import { useLineStore } from "@/stores/line";
+import { getWayApi, getSitePageApi, getSiteApi } from "@/api";
+import { TABS } from "@/utils";
 
-import axios from "axios";
 const route = useRoute();
 const lineId = ref(route.params.id || 1);
 const lineStore = useLineStore();
@@ -69,18 +81,20 @@ const dialogData = ref(null);
 const activeId = ref(0);
 let infoWindow = null;
 const activeTagData = ref(null);
-const vrLink = ref(null);
+const vrUrl = ref(null);
 const mapData = ref(null);
-const trvelList = ref(null);
+const trvelData = ref(null);
 const getData = () => {
-  axios
-    .get("./static/data/map.json")
+  getSitePageApi({
+    pageNum: 1,
+    pageSize: 9999,
+  })
     .then((res) => {
-      mapData.value = res.data;
-      axios
-        .get("./static/data/line.json")
+      mapData.value = res.records;
+      getWayApi(lineId.value)
         .then((res) => {
-          trvelList.value = res.data;
+          trvelData.value = res;
+          lineData.value = res.sites;
           initMap();
         })
         .catch((error) => {
@@ -93,18 +107,21 @@ const getData = () => {
 };
 
 const goVr = () => {
-  vrLink.value = props.dialogData.vrLink;
+  vrUrl.value = props.dialogData.vrUrl;
 };
 
 const overTime = ref(3600);
 const lineTitle = ref("");
 
-const getHtml = (data) => {
-  activeTagData.value = data;
+const getHtml = async (data) => {
+  const siteDetail = await getSiteApi(data.id);
+  activeTagData.value = siteDetail;
   let html = `<div class="line-window">
       <div class="img-box"  id="vrBtn">
-        <img src="./static/image/全部/${data.images[0]}">
-        <div style="${data.vrLink ? "" : "display:none"}" class="vr-btn"></div>
+        <img src="${import.meta.env.VITE_BASE_URL}/${
+    siteDetail.files?.[0].filePath
+  }">
+        <div style="${data.vrUrl ? "" : "display:none"}" class="vr-btn"></div>
         <div class="close-btn" id="closeBtn"></div>
         </div>
       <div class="line-window-controls">
@@ -119,11 +136,9 @@ const openDetails = (data) => {
   dialogData.value = activeTagData.value;
 };
 const openVrLink = () => {
-  // vrLink.value = activeTagData.value.vrLink;
-  vrLink.value = activeTagData.value.vrLink;
-  if (activeTagData.value.vrLink) {
-    // vrLink.value = activeTagData.value.vrLink;
-    vrLink.value = activeTagData.value.vrLink;
+  if (activeTagData.value.vrUrl) {
+    // vrUrl.value = activeTagData.value.vrUrl;
+    vrUrl.value = activeTagData.value.vrUrl;
   } else {
     openDetails();
   }
@@ -137,13 +152,13 @@ const closeTag = (e) => {
   infoWindow.close();
   infoWindow = null;
 };
-const hanlderMakerEvent = (e) => {
+const hanlderMakerEvent = async (e) => {
   console.log("click", e.target.getExtData());
   let data = e.target.getExtData();
   // dialogData.value = data;
 
   infoWindow = new AMap.InfoWindow({
-    content: getHtml(data), // 设置弹窗内容为自定义的HTML元素
+    content: await getHtml(data), // 设置弹窗内容为自定义的HTML元素
     offset: new AMap.Pixel(0, -30), // 设置弹窗偏移量,可根据需要调整
   });
   infoWindow.open(map, e.target.getPosition());
@@ -166,26 +181,26 @@ const hanlderMakerEvent = (e) => {
     // closeBtn.addEventListener("touchstart", closeTag);
 
     // map.setZoomAndCenter(map.getZoom() + 2);
-    let center = data.location.split(",");
+    let center = [data.lon, data.lat];
     console.error(center);
     map.setZoomAndCenter(map.getZoom(), center, false, 300);
   }, 100);
 };
 let markers = [];
 const lineData = ref([]);
-const gotoItem = (data) => {
+const gotoItem = async (data) => {
   let zoom = map.getZoom();
   let item = markers.find((i) => i.getExtData().id == data.id);
   // let data = item.getExtData();
   infoWindow = new AMap.InfoWindow({
-    content: getHtml(data), // 设置弹窗内容为自定义的HTML元素
+    content: await getHtml(data), // 设置弹窗内容为自定义的HTML元素
     offset: new AMap.Pixel(0, -30), // 设置弹窗偏移量,可根据需要调整
   });
 
   // infoWindow.open(map, e.target.getPosition());
   infoWindow.open(map, item.getPosition());
 
-  let center = data.location.split(",");
+  let center = [data.lon, data.lat];
   map.setZoomAndCenter(zoom, center, false, 300);
   setTimeout(() => {
     setTimeout(() => {
@@ -200,13 +215,8 @@ const gotoItem = (data) => {
   }, 100);
 };
 const setMarker = () => {
-  let data = trvelList.value.find((item) => item.id == lineId.value);
-  overTime.value = data.time;
-  lineTitle.value = data.name;
-  data.list.forEach((item) => {
-    let info = mapData.value.find((mapItem) => item == mapItem.id);
-    lineData.value.push(info);
-  });
+  overTime.value = Number(trvelData.value.expect);
+  lineTitle.value = trvelData.value.name;
 
   // let data = mapData.filter((item) => item.type == 4);
   // lineData.value = data;
@@ -224,11 +234,16 @@ const setMarker = () => {
     offset = new AMap.Pixel(-(iconW / 2), -(iconH / 2));
     customIcon = new AMap.Icon({
       size: new AMap.Size(iconW, iconH),
-      image: iconList[`tagIcon${lineData.value[i].type}`],
+      image:
+        iconList[
+          `tagIcon${
+            TABS.findIndex((ii) => ii.title === lineData.value[i].type) + 1
+          }`
+        ],
       imageSize: new AMap.Size(iconW, iconH),
     });
 
-    let pos = lineData.value[i].location.split(",");
+    let pos = [lineData.value[i].lon, lineData.value[i].lat];
     let extData = lineData.value[i];
     let marker = new AMap.Marker({
       position: new AMap.LngLat(pos[0], pos[1]),
@@ -263,15 +278,15 @@ const createdLine = () => {
     //驾车路线规划策略,0是速度优先的策略
     policy: AMap.DrivingPolicy.LEAST_TIME,
   });
-  var startLngLat = [data[0].location.split(",")[0], data[0].location.split(",")[1]]; //起始点坐标
-  var endLngLat = [data[data.length - 1].location.split(",")[0], data[data.length - 1].location.split(",")[1]]; //终点坐标
+  var startLngLat = [data[0].lon, data[0].lat]; //起始点坐标
+  var endLngLat = [data[data.length - 1].lon, data[data.length - 1].lat]; //终点坐标
   var opts = {
     waypoints: [], //途经点参数,最多支持传入16个途经点
   };
 
   data.forEach((item, index) => {
     if (index != 0 || index != data.length - 1) {
-      opts.waypoints.push([item.location.split(",")[0], item.location.split(",")[1]]);
+      opts.waypoints.push([item.lon, item.lat]);
     }
   });
 
@@ -330,7 +345,14 @@ const initMap = async () => {
   const _AMap = await AMapLoader.load({
     key: mapConfig.key,
     version: "2.0",
-    plugins: ["AMap.MoveAnimation", "AMap.ImageLayer", "AMap.TileLayer", "AMap.DistrictSearch", "AMap.Driving", "AMap.Walking"],
+    plugins: [
+      "AMap.MoveAnimation",
+      "AMap.ImageLayer",
+      "AMap.TileLayer",
+      "AMap.DistrictSearch",
+      "AMap.Driving",
+      "AMap.Walking",
+    ],
   });
   AMap = _AMap;
 

+ 58 - 28
src/views/map/dialog.vue

@@ -5,8 +5,13 @@
     <div class="title">智游岳阳</div>
   </div>
   <div class="dialog">
-    <!-- <audio ref="audioRef" v-if="dialogData.audio" :autoplay="true" :src="`./static/image/${dialogData.title}/${dialogData.name}/${dialogData.audio}`"></audio> -->
-    <audio ref="audioRef" v-if="dialogData.audio" :autoplay="true" :src="`./static/image/全部/${dialogData.audio}`"></audio>
+    <!-- <audio ref="audioRef" v-if="dialogData.audioPath" :autoplay="true" :src="`./static/image/${dialogData.title}/${dialogData.name}/${dialogData.audioPath}`"></audio> -->
+    <audio
+      ref="audioRef"
+      v-if="dialogData.audioPath"
+      :autoplay="true"
+      :src="`${BASE_URL}/${dialogData.audioPath}`"
+    ></audio>
 
     <div class="dialog-conntent">
       <div class="dialog-title">
@@ -16,9 +21,9 @@
       <div class="img-box">
         <div class="dialog-swiper-container">
           <div class="swiper-wrapper">
-            <div class="swiper-slide" v-for="(i, index) in dialogData.images">
+            <div class="swiper-slide" v-for="(i, index) in dialogData.files">
               <!-- <img :src="`./static/image/${dialogData.title}/${dialogData.name}/${i}`" alt="" /> -->
-              <img :src="`./static/image/全部/${i}`" alt="" />
+              <img :src="`${BASE_URL}/${i.filePath}`" alt="" />
             </div>
           </div>
           <!-- 如果需要分页器 -->
@@ -26,13 +31,18 @@
           <!-- <div class="swiper-button-next"></div>
           <div class="swiper-button-prev"></div> -->
         </div>
-        <div class="audio-icon" :class="{ active: isPlay }" v-if="dialogData.audio" @click="plauMusic"></div>
+        <div
+          class="audioPath-icon"
+          :class="{ active: isPlay }"
+          v-if="dialogData.audioPath"
+          @click="plauMusic"
+        ></div>
       </div>
       <div class="text-box">
         <div class="text-scroll">
-          <div class="desc-info">
+          <div class="intro-info">
             <p>
-              {{ dialogData.desc }}
+              {{ dialogData.intro }}
             </p>
           </div>
 
@@ -40,7 +50,9 @@
             <div class="adress">
               <div class="adress-icon"></div>
               <span class="name"
-                ><span class="pos-text">位置</span>湖南省岳阳市<span v-if="dialogData.type != 6">{{ dialogData.address }}</span
+                ><span class="pos-text">位置</span>湖南省岳阳市<span
+                  v-if="dialogData.type != 6"
+                  >{{ dialogData.address }}</span
                 >{{ dialogData.name }}</span
               >
             </div>
@@ -50,27 +62,45 @@
         </div>
 
         <div class="text-bottom">
-          <div class="" v-if="dialogData.vrLink" @click="goVr">查看全景</div>
-          <div class="model-btn" v-if="dialogData.modelLink" @click="goModel">三维景点</div>
+          <div class="" v-if="dialogData.vrUrl" @click="goVr">查看全景</div>
+          <div class="model-btn" v-if="dialogData.modelUrl" @click="goModel">
+            三维景点
+          </div>
         </div>
       </div>
     </div>
   </div>
-  <div class="vr-content" v-if="vrLink">
-    <div class="vr-back" @click="vrLink = ''"><img src="@/assets/images/vr-back.png" alt="" /></div>
-    <iframe :src="vrLink" frameborder="0"></iframe>
+  <div class="vr-content" v-if="vrUrl">
+    <div class="vr-back" @click="vrUrl = ''">
+      <img src="@/assets/images/vr-back.png" alt="" />
+    </div>
+    <iframe :src="vrUrl" frameborder="0"></iframe>
   </div>
-  <div class="vr-content" v-if="modelLink">
-    <div class="vr-back" :class="{ pd: tabType == '3d' }" @click="closeModel"><img src="@/assets/images/vr-back.png" alt="" /></div>
-    <iframe :src="modelLink" frameborder="0"></iframe>
+  <div class="vr-content" v-if="modelUrl">
+    <div class="vr-back" :class="{ pd: tabType == '3d' }" @click="closeModel">
+      <img src="@/assets/images/vr-back.png" alt="" />
+    </div>
+    <iframe :src="modelUrl" frameborder="0"></iframe>
   </div>
 </template>
 
 <script setup>
 import Swiper from "swiper";
 import "swiper/css/swiper.css";
-import { reactive, ref, toRefs, onBeforeMount, onMounted, nextTick, defineEmits, defineProps, onUnmounted, onBeforeUnmount } from "vue";
+import {
+  reactive,
+  ref,
+  toRefs,
+  onBeforeMount,
+  onMounted,
+  nextTick,
+  defineEmits,
+  defineProps,
+  onUnmounted,
+  onBeforeUnmount,
+} from "vue";
 import Header from "@/components/header/index.vue";
+const BASE_URL = import.meta.env.VITE_BASE_URL;
 const emits = defineEmits(["close"]);
 const props = defineProps({
   dialogData: {
@@ -84,8 +114,8 @@ const close = () => {
 };
 
 const goLocation = () => {
-  let lat = props.dialogData.location.split(",")[0] - 0;
-  let lon = props.dialogData.location.split(",")[1] - 0;
+  let lat = props.dialogData.lon;
+  let lon = props.dialogData.lat;
   let ad = `湖南省岳阳市${props.dialogData.address}${props.dialogData.name}`;
   console.error(lat, lon, ad);
   wx.openLocation({
@@ -97,8 +127,8 @@ const goLocation = () => {
   });
 };
 
-const vrLink = ref("");
-const modelLink = ref("");
+const vrUrl = ref("");
+const modelUrl = ref("");
 const isPlay = ref(false);
 const audioRef = ref(null);
 const plauMusic = () => {
@@ -109,7 +139,7 @@ const plauMusic = () => {
   }
 };
 const closeModel = () => {
-  modelLink.value = "";
+  modelUrl.value = "";
   tabType.value = null;
 };
 const onPause = () => {
@@ -119,15 +149,15 @@ const onPlay = () => {
   isPlay.value = true;
 };
 const goVr = () => {
-  vrLink.value = props.dialogData.vrLink;
+  vrUrl.value = props.dialogData.vrUrl;
 };
 const goModel = () => {
   tabType.value = "3d";
-  modelLink.value = props.dialogData.modelLink;
+  modelUrl.value = props.dialogData.modelUrl;
 };
 onMounted(() => {
   nextTick(() => {
-    if (props.dialogData.audio) {
+    if (props.dialogData.audioPath) {
       audioRef.value.addEventListener("pause", onPause);
       audioRef.value.addEventListener("play", onPlay);
     }
@@ -146,7 +176,7 @@ onMounted(() => {
   });
 });
 onBeforeUnmount(() => {
-  if (props.dialogData.audio) {
+  if (props.dialogData.audioPath) {
     audioRef.value.removeEventListener("pause", onPause);
     audioRef.value.removeEventListener("play", onPlay);
   }
@@ -254,7 +284,7 @@ onBeforeUnmount(() => {
       background: #f2f2f2;
       position: relative;
       // overflow-x: hidden;
-      .audio-icon {
+      .audioPath-icon {
         width: 0.72rem;
         height: 0.72rem;
         background: url("@/assets/images/audio-icon.png") no-repeat;
@@ -343,7 +373,7 @@ onBeforeUnmount(() => {
         width: 100%;
         height: calc(100% - 2.9333rem);
         // overflow-y: auto;
-        .desc-info {
+        .intro-info {
           width: 100%;
           max-height: calc(100% - 1.8667rem);
           overflow: auto;

+ 197 - 137
src/views/map/index.vue

@@ -14,10 +14,23 @@
       </div>
     </div> -->
     <div ref="compass" class="compass-box">
-      <div class="circle-box" :style="`transform: translateX(-50%)rotate(${rotateNum + tabNum}deg)`">
-        <div class="bg-active" :style="`transform:rotate(${getDeg()}deg);`"></div>
+      <div
+        class="circle-box"
+        :style="`transform: translateX(-50%)rotate(${rotateNum + tabNum}deg)`"
+      >
+        <div
+          class="bg-active"
+          :style="`transform:rotate(${getDeg()}deg);`"
+        ></div>
         <div class="circle-content">
-          <div class="circle-item" :class="{ active: tabType == i.type }" @click="chooseItem(i, index)" v-for="(i, index) in typeData">{{ i.title }}</div>
+          <div
+            class="circle-item"
+            :class="{ active: tabType == i.title }"
+            @click="chooseItem(i, index)"
+            v-for="(i, index) in typeData"
+          >
+            {{ i.title }}
+          </div>
         </div>
       </div>
       <div class="line-btn" @click="openLineList()"></div>
@@ -41,12 +54,24 @@
     <!-- </div> -->
 
     <!-- <div class="open-line" @click="showLineList = true"></div> -->
-    <div class="open-chat" @click="switchChat()" :class="roleShow ? 'in' : 'out'"></div>
+    <div
+      class="open-chat"
+      @click="switchChat()"
+      :class="roleShow ? 'in' : 'out'"
+    ></div>
     <div class="slide-tab">
       <div class="btn" @click="slideToView('/food?mac=1')"></div>
       <div class="btn" @click="slideToView('/video')"></div>
-      <div class="td-btn" :class="{ active: tabType == '3d' }" @click="goTd"></div>
-      <div class="audio-btn" :class="{ active: audioStatus }" @click="switchAudio"></div>
+      <div
+        class="td-btn"
+        :class="{ active: tabType == '3d' }"
+        @click="goTd"
+      ></div>
+      <div
+        class="audioPath-btn"
+        :class="{ active: audioStatus }"
+        @click="switchAudio"
+      ></div>
     </div>
     <div class="chat-box" :class="{ show: showChat }">
       <!-- <div class="icon" @click="switchChat()"></div> -->
@@ -63,7 +88,13 @@
         <div class="chat-msg" ref="scrollRef">
           <div class="default-question">
             <div class="question-scroll" :style="`width:${questW}px;`">
-              <div class="quest-item" @click="hanlderQuest(i)" v-for="i in questList">{{ i }}</div>
+              <div
+                class="quest-item"
+                @click="hanlderQuest(i)"
+                v-for="i in questList"
+              >
+                {{ i }}
+              </div>
             </div>
           </div>
           <template v-for="(i, index) in messages">
@@ -77,7 +108,13 @@
         </div>
         <div class="ipt-box">
           <div class="ipt-bg">
-            <div class="chat-text"><input v-model.trim="chat.content" type="text" placeholder="发消息" /></div>
+            <div class="chat-text">
+              <input
+                v-model.trim="chat.content"
+                type="text"
+                placeholder="发消息"
+              />
+            </div>
             <div class="send-btn" @click="sendChat"></div>
           </div>
         </div>
@@ -85,14 +122,29 @@
     </div>
 
     <div class="line-layer" @click="closeLineList()" v-show="showLineList">
-      <transition appear name="fade" enter-active-class="animate__animated animate__slideInUp short faster" leave-active-class="animate__animated animate__slideOutDown short faster">
+      <transition
+        appear
+        name="fade"
+        enter-active-class="animate__animated animate__slideInUp short faster"
+        leave-active-class="animate__animated animate__slideOutDown short faster"
+      >
         <div class="line-box" v-show="showLineBox">
           <div class="line-list">
-            <div class="list-item" @click.stop="goLine(i)" v-for="i in trvelList">
+            <div
+              v-for="i in trvelList"
+              :key="i.id"
+              class="list-item"
+              :style="{
+                background: `url(${BASE_URL}${i.thumb}) no-repeat center / cover`,
+              }"
+              @click.stop="goLine(i)"
+            >
               <div class="content">
                 <p class="title">{{ i.name }}</p>
-                <span class="tag">拍照必打卡{{ lines }}</span>
-                <p class="desc">预计用时:{{ i.time }}日,共{{ i.list.length }}个景点</p>
+                <span class="tag">{{ i.tag }}</span>
+                <p class="intro">
+                  预计用时:{{ i.expect }}日,共{{ i.sites.length }}个景点
+                </p>
               </div>
               <div class="ticket"></div>
             </div>
@@ -102,16 +154,23 @@
     </div>
   </div>
 
-  <Dialog v-if="dialogData" :tabType="tabType" :dialogData="dialogData" @close="closeDetails" />
+  <Dialog
+    v-if="dialogData"
+    :tabType="tabType"
+    :dialogData="dialogData"
+    @close="closeDetails"
+  />
 
-  <div class="vr-content" v-if="vrLink">
-    <div class="vr-back" :class="{ pd: tabType == '3d' }" @click="linkBack"><img src="@/assets/images/vr-back.png" alt="" /></div>
-    <iframe :src="vrLink" frameborder="0"></iframe>
+  <div class="vr-content" v-if="vrUrl">
+    <div class="vr-back" :class="{ pd: tabType == '3d' }" @click="linkBack">
+      <img src="@/assets/images/vr-back.png" alt="" />
+    </div>
+    <iframe :src="vrUrl" frameborder="0"></iframe>
   </div>
 </template>
 
 <script setup>
-import { nextTick, onMounted, ref, watch, computed } from "vue";
+import { nextTick, onMounted, ref, computed } from "vue";
 import { mapConfig, deepseekConfig } from "@/utils/config.js";
 
 import Dialog from "./dialog.vue";
@@ -130,13 +189,15 @@ import OpenAI from "openai";
 import Swiper from "swiper";
 import AMapLoader from "@amap/amap-jsapi-loader";
 import axios from "axios";
+import { debounce } from "lodash";
+import { getSitePageApi, getWayPageApi, getSiteApi } from "@/api";
 
 import { useAudioStore } from "@/stores/audio";
 const audioStore = useAudioStore();
 const audioStatus = computed(() => audioStore.audioStatus);
 const mapData = ref(null);
 const typeData = ref(null);
-const weightData = ref(null);
+const BASE_URL = import.meta.env.VITE_BASE_URL;
 let isPlay = true;
 const switchAudio = () => {
   audioStore.setState();
@@ -146,7 +207,7 @@ const linkBack = () => {
   if (isPlay) {
     audioStore.setState({ status: true });
   }
-  vrLink.value = "";
+  vrUrl.value = "";
 };
 const getData = async () => {
   axios
@@ -175,32 +236,24 @@ const getData = async () => {
     .catch((error) => {
       console.log(error);
     });
-  await axios
-    .get("./static/data/weight.json")
-    .then((res) => {
-      weightData.value = res.data;
+  await getSitePageApi({
+    pageNum: 1,
+    pageSize: 9999,
+  }).then((siteDate) => {
+    mapData.value = siteDate.records;
+
+    getWayPageApi({
+      pageNum: 1,
+      pageSize: 9999,
     })
-    .catch((error) => {
-      console.log(error);
-    });
-  await axios
-    .get("./static/data/map.json")
-    .then((res) => {
-      mapData.value = res.data;
-
-      axios
-        .get("./static/data/line.json")
-        .then((res) => {
-          trvelList.value = res.data;
-          initMap();
-        })
-        .catch((error) => {
-          console.log(error);
-        });
-    })
-    .catch((error) => {
-      console.log(error);
-    });
+      .then((res) => {
+        trvelList.value = res.records;
+        initMap();
+      })
+      .catch((error) => {
+        console.log(error);
+      });
+  });
 };
 let iconList = {
   tagIcon1,
@@ -217,19 +270,21 @@ const showLineList = ref(false);
 const showLineBox = ref(false);
 const initTrvel = () => {
   trvelList.value.forEach((item, index) => {
-    let stratObj = mapData.value.find((i) => item.list[0] == i.id);
-    let endObj = mapData.value.find((i) => item.list[item.list.length - 1] == i.id);
+    let stratObj = mapData.value.find((i) => item.sites[0].id == i.id);
+    let endObj = mapData.value.find(
+      (i) => item.sites[item.sites.length - 1].id == i.id
+    );
 
-    var startLngLat = [stratObj.location.split(",")[0], stratObj.location.split(",")[1]]; //起始点坐标
-    var endLngLat = [endObj.location.split(",")[0], endObj.location.split(",")[1]]; //终点坐标
+    var startLngLat = [stratObj.lon, stratObj.lat]; //起始点坐标
+    var endLngLat = [endObj.lon, endObj.lat]; //终点坐标
 
     var opts = {
       waypoints: [], //途经点参数,最多支持传入16个途经点
     };
-    item.list.map((j_item, j_index) => {
-      if (j_index != 0 || j_index != item.list.length - 1) {
-        let obj = mapData.value.find((data) => j_item == data.id);
-        opts.waypoints.push([obj.location.split(",")[0], obj.location.split(",")[1]]);
+    item.sites.map((j_item, j_index) => {
+      if (j_index != 0 || j_index != item.sites.length - 1) {
+        let obj = mapData.value.find((data) => j_item.id == data.id);
+        opts.waypoints.push([obj.lon, obj.lat]);
       }
     });
 
@@ -253,23 +308,28 @@ const initTrvel = () => {
   });
 };
 const goTd = () => {
-  chooseItem({ type: "3d" });
+  chooseItem({ title: "3d" });
 };
 
-const getHtml = (data) => {
-  activeTagData.value = data;
+const getHtml = async (data) => {
+  const siteDetail = await getSiteApi(data.id);
+  activeTagData.value = siteDetail;
   let calssName = "vr-btn";
-  if (tabType.value == "3d" && data.modelLink) {
+  if (tabType.value == "3d" && siteDetail.modelUrl) {
     calssName = "vr-btn model-btn";
   }
   let html = `<div class="map-window" id="mapWindow">
       <div class="img-box" id="vrBtn">
-        <img src="./static/image/全部/${data.images[0]}">
-        <div style="${data.vrLink || (tabType.value == "3d" && data.modelLink) ? "" : "display:none"}"  class="${calssName}" ></div>
+        <img src="${BASE_URL}/${siteDetail.files?.[0].filePath}">
+        <div style="${
+          siteDetail.vrUrl || (tabType.value == "3d" && siteDetail.modelUrl)
+            ? ""
+            : "display:none"
+        }"  class="${calssName}" ></div>
         <div class="close-btn" id="closeBtn"></div>
         </div>
       <div class="map-window-controls">
-        <div class="title">${data.name}</div>
+        <div class="title">${siteDetail.name}</div>
         <div class="details-btn" id="detailsBtn">详情</div>
         </div>
       </div>`;
@@ -277,7 +337,7 @@ const getHtml = (data) => {
   return html;
 };
 const activeTagData = ref(null);
-const vrLink = ref(null);
+const vrUrl = ref(null);
 const openDetails = (data) => {
   dialogData.value = activeTagData.value;
   audioStore.setState({ status: false });
@@ -289,21 +349,21 @@ const closeDetails = () => {
   }
 };
 const openVrLink = () => {
-  // vrLink.value = activeTagData.value.vrLink;
+  // vrUrl.value = activeTagData.value.vrUrl;
 
   if (tabType.value == "3d") {
-    vrLink.value = activeTagData.value.modelLink;
-    if (activeTagData.value.modelLink) {
-      // vrLink.value = activeTagData.value.vrLink;
-      vrLink.value = activeTagData.value.modelLink;
+    vrUrl.value = activeTagData.value.modelUrl;
+    if (activeTagData.value.modelUrl) {
+      // vrUrl.value = activeTagData.value.vrUrl;
+      vrUrl.value = activeTagData.value.modelUrl;
     } else {
       openDetails();
     }
   } else {
-    vrLink.value = activeTagData.value.vrLink;
-    if (activeTagData.value.vrLink) {
-      // vrLink.value = activeTagData.value.vrLink;
-      vrLink.value = activeTagData.value.vrLink;
+    vrUrl.value = activeTagData.value.vrUrl;
+    if (activeTagData.value.vrUrl) {
+      // vrUrl.value = activeTagData.value.vrUrl;
+      vrUrl.value = activeTagData.value.vrUrl;
     } else {
       openDetails();
     }
@@ -337,22 +397,22 @@ const closeTag = (e) => {
 const getDeg = () => {
   let deg = 180;
   switch (tabType.value) {
-    case 1:
+    case "历史文化":
       deg = -90;
       break;
-    case 2:
+    case "自然探索":
       deg = -60;
       break;
-    case 3:
+    case "亲子休闲":
       deg = -30;
       break;
-    case 4:
+    case "城市烟火":
       deg = 0;
       break;
-    case 5:
+    case "交通住宿":
       deg = 30;
       break;
-    case 6:
+    case "政府驻地":
       deg = 60;
       break;
   }
@@ -392,7 +452,7 @@ let AMap = null;
 let map = null;
 let imageLayer = null;
 
-const tabType = ref(6);
+const tabType = ref("政府驻地");
 let openaiA = new OpenAI(deepseekConfig);
 const questList = ref([]);
 
@@ -475,7 +535,7 @@ const initTab = () => {
 
 const dialogData = ref(null);
 const chooseItem = (i, index) => {
-  tabType.value = i.type;
+  tabType.value = i.title;
   closeTag();
   switchView(false);
   if (markers.length) {
@@ -506,38 +566,17 @@ let clusters = null;
 
 const setMarkerCluster = () => {
   let pointers = [];
-  let weights = weightData.value[tabType.value];
   mapData.value.forEach((item, j_index) => {
-    let weight = 1;
+    let weight = item.weight ?? 1;
     if (tabType.value == "3d") {
-      if (item.modelLink) {
-        let pos = item.location.split(",");
-        if (weights) {
-          let index = weights.findIndex((w) => w == item.id);
-          if (index >= 0) {
-            // console.error(item.name, index, weights.length);
-            weight = weights.length - index + 1;
-          }
-        }
-        pointers.push({ weight, lnglat: [pos[0], pos[1]], info: item });
+      if (item.modelUrl) {
+        pointers.push({ weight, lnglat: [item.lon, item.lat], info: item });
 
         // return true;
       }
     } else {
       if (item.type == tabType.value) {
-        let pos = item.location.split(",");
-        if (weights) {
-          let index = weights.findIndex((w) => w == item.id);
-          if (index >= 0) {
-            // console.error(item.name, index, weights.length);
-            weight = weights.length - index + 1 + mapData.value.length;
-          } else {
-            weight = j_index;
-          }
-        } else {
-          weight = j_index;
-        }
-        pointers.push({ weight, lnglat: [pos[0], pos[1]], info: item });
+        pointers.push({ weight, lnglat: [item.lon, item.lat], info: item });
 
         // return true;
       }
@@ -574,7 +613,19 @@ const setMarkerCluster = () => {
         if (tabType.value == "3d") {
           tagType = "3d";
         }
-        marker.setContent(`<div class="custom-marker"><p>${extData.name}</p><img style="width:${iconW}px;height:${iconH}px;" src="${iconList[`tagIcon${tagType}`]}" /></div>`);
+        marker.setContent(
+          `<div class="custom-marker"><p>${
+            extData.name
+          }</p><img style="width:${iconW}px;height:${iconH}px;" src="${
+            iconList[
+              `tagIcon${
+                tagType === "3d"
+                  ? "3d"
+                  : typeData.value.findIndex((i) => i.title === tagType) + 1
+              }`
+            ]
+          }" /></div>`
+        );
         marker.setOffset(offset);
       },
       style: {
@@ -582,22 +633,21 @@ const setMarkerCluster = () => {
       },
     }
   );
-  clusters.on("click", hanlderClusterEvent);
+  clusters.on("click", debounce(hanlderClusterEvent, 200));
 };
 let wiondowButton,
   vrBtn,
   closeBtn = null;
-const hanlderClusterEvent = (e) => {
+const hanlderClusterEvent = async (e) => {
   if (e.clusterData.length == 1) {
     // dialogData.value = e.clusterData[0].info;
     let data = e.clusterData[0].info;
-    let center = data.location.split(",");
 
-    map.setZoomAndCenter(map.getZoom(), center, false, 100);
+    map.setZoomAndCenter(map.getZoom(), [data.lon, data.lat], false, 100);
     // return
     infoWindow = new AMap.InfoWindow({
       zIndex: 1000,
-      content: getHtml(data), // 设置弹窗内容为自定义的HTML元素
+      content: await getHtml(data), // 设置弹窗内容为自定义的HTML元素
       offset: new AMap.Pixel(0, -30), // 设置弹窗偏移量,可根据需要调整
     });
 
@@ -614,7 +664,7 @@ const hanlderClusterEvent = (e) => {
     }, 100);
   } else {
     setTimeout(() => {
-      let center = e.clusterData[0].info.location.split(",");
+      let center = [e.clusterData[0].info.lon, e.clusterData[0].info.lat];
       console.error(center);
       map.setZoomAndCenter(map.getZoom() + 2, center, false, 300);
     }, 0);
@@ -628,7 +678,17 @@ const initMap = async () => {
   const _AMap = await AMapLoader.load({
     key: mapConfig.key, //玉琦账号
     version: "2.0",
-    plugins: ["AMap.MoveAnimation", "AMap.ImageLayer", "AMap.TileLayer", "AMap.Geolocation", "AMap.DistrictSearch", "AMap.Driving", "AMap.Walking", "AMap.MarkerCluster", "AMap.InfoWindow"],
+    plugins: [
+      "AMap.MoveAnimation",
+      "AMap.ImageLayer",
+      "AMap.TileLayer",
+      "AMap.Geolocation",
+      "AMap.DistrictSearch",
+      "AMap.Driving",
+      "AMap.Walking",
+      "AMap.MarkerCluster",
+      "AMap.InfoWindow",
+    ],
   });
   AMap = _AMap;
 
@@ -674,24 +734,24 @@ const switchView = (effect = true) => {
   let scale = 8.2;
   if (data.length) {
     switch (tabType.value) {
-      case 1:
+      case "历史文化":
         center = [113.333049, 29.101023];
         scale = 8.45;
         break;
-      case 3:
+      case "亲子休闲":
         scale = 9.09;
         center = [113.272171, 28.979474];
         break;
-      case 5:
-      case 4:
+      case "交通住宿":
+      case "城市烟火":
         scale = 12.4;
         center = [113.129162, 29.369332];
         break;
-      case 5:
+      case "交通住宿":
         scale = 10.6;
         center = [113.207352, 29.366234];
         break;
-      case 3:
+      case "亲子休闲":
         // scale = 12;
         break;
     }
@@ -979,7 +1039,7 @@ onMounted(() => {
         background-size: 100% 100%;
       }
     }
-    .audio-btn {
+    .audioPath-btn {
       width: 0.8933rem;
       height: 0.8933rem;
       background: url("@/assets/images/mapMusic.png") no-repeat;
@@ -1095,7 +1155,13 @@ onMounted(() => {
       justify-content: space-between;
       padding: 0 0.5333rem 0 0;
       border-bottom: 1px solid;
-      border-image: linear-gradient(90deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0)) 1 1;
+      border-image: linear-gradient(
+          90deg,
+          rgba(0, 0, 0, 0),
+          rgba(0, 0, 0, 0.4),
+          rgba(0, 0, 0, 0)
+        )
+        1 1;
 
       > div {
         display: flex;
@@ -1150,12 +1216,22 @@ onMounted(() => {
         justify-content: center;
 
         border-top: 1px solid;
-        border-image: linear-gradient(90deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0)) 1 1;
+        border-image: linear-gradient(
+            90deg,
+            rgba(0, 0, 0, 0),
+            rgba(0, 0, 0, 0.4),
+            rgba(0, 0, 0, 0)
+          )
+          1 1;
         .ipt-bg {
           margin-top: 0.1333rem;
           width: 8.0933rem;
           height: 1.5733rem;
-          background: linear-gradient(180deg, #ccb299 0%, rgba(188, 188, 188, 0) 100%);
+          background: linear-gradient(
+            180deg,
+            #ccb299 0%,
+            rgba(188, 188, 188, 0) 100%
+          );
           border-radius: 0.0933rem;
           display: flex;
           align-items: center;
@@ -1288,22 +1364,6 @@ onMounted(() => {
         align-items: center;
         justify-content: space-between;
         padding: 0 0.56rem 0 0.5333rem;
-        &:nth-of-type(1) {
-          background: url("@/assets/images/line1.png") no-repeat;
-          background-size: 100% 100%;
-        }
-        &:nth-of-type(2) {
-          background: url("@/assets/images/line2.png") no-repeat;
-          background-size: 100% 100%;
-        }
-        &:nth-of-type(3) {
-          background: url("@/assets/images/line3.png") no-repeat;
-          background-size: 100% 100%;
-        }
-        &:nth-of-type(4) {
-          background: url("@/assets/images/line4.png") no-repeat;
-          background-size: 100% 100%;
-        }
         .content {
           .title {
             font-size: 0.4267rem;
@@ -1319,7 +1379,7 @@ onMounted(() => {
             display: inline-block;
             margin-bottom: 0.16rem;
           }
-          .desc {
+          .intro {
             font-size: 0.2667rem;
             color: #6a4934;
           }

+ 3 - 3
vite.config.js

@@ -22,10 +22,10 @@ export default defineConfig({
     host: "0.0.0.0",
     proxy: {
       "/api": {
-        // target: 'https://sit-yueyangbwg.4dage.com', // 后端服务地址
+        target: "https://sit-yueyangbwg.4dage.com", // 后端服务地址
         // target: "https://txiaobo.qiweiwangguo.com", // 后端服务地址
-        target: "https://hn3dreal.org.cn", // 后端服务地址
-        changeOrigin: true, // 是否改变源地址
+        // target: "https://hn3dreal.org.cn", // 后端服务地址
+        changeOrigin: false, // 是否改变源地址
         // rewrite: (path) => path.replace(/^\/api/, ""),
       },
     },