Compare commits

..

231 Commits
xingyy ... main

Author SHA1 Message Date
齐斌
0b000f2eb0 修正股票变动颜色逻辑:将负值显示为绿色,正值显示为红色,确保在不同屏幕尺寸下的一致性。 2025-06-09 12:12:56 +08:00
齐斌
1bdea06916 去掉括号 2025-06-06 18:41:48 +08:00
张 元山
1aad765dc1 fix news 2025-06-06 17:59:21 +08:00
b298d07854 修改时间 2025-06-03 11:11:20 +08:00
710bca3486 新闻事件倒序 2025-06-03 11:01:06 +08:00
张 元山
3029de7916 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-06-03 10:59:20 +08:00
张 元山
a39c4cf620 新闻倒序 2025-06-03 10:59:19 +08:00
1195b8f8e4 新闻新增 2025-06-03 10:50:55 +08:00
fc4f4ab3f2 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-06-03 10:46:18 +08:00
35faed56cb 首页新增新闻 2025-06-03 10:45:56 +08:00
张 元山
460060c087 add news 2025-06-03 10:37:55 +08:00
qibin
a38ea6964f 修改时间为4PM 2025-06-01 22:19:52 +08:00
qibin
ab43f5f8b9 fixbugdate 2025-06-01 22:13:56 +08:00
qibin
ae1a562bcb fixbug 2025-05-31 21:21:33 +08:00
齐斌
e0c28b5522 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-31 04:00:50 +08:00
齐斌
461705f337 修正了英文本地化文件中的标题格式,确保每个标题前有空格,并优化了Cao Yu的介绍内容,使其更流畅。 2025-05-31 04:00:49 +08:00
b841a6d911 Merge branch 'wyfMain-dev-0530' 2025-05-31 02:22:30 +08:00
d2dd262b9e 处理遗漏的图表列表数据页的多分辨率数据获取 2025-05-31 02:21:48 +08:00
张 元山
1949046344 fix 375 768 2025-05-31 02:17:58 +08:00
张 元山
a92f9ca971 fix news 2025-05-31 02:13:12 +08:00
张 元山
697dd232f6 fix myhome 2025-05-31 02:04:46 +08:00
张 元山
61b5a9eaea Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-31 02:03:51 +08:00
张 元山
36d961fcb6 fix news 2025-05-31 02:03:49 +08:00
dbe739ed50 修改首页卡片last price为price,并取对应值 2025-05-31 01:56:26 +08:00
2dc262fbd8 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-31 01:50:43 +08:00
张 元山
01723e2a2f Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-31 01:50:07 +08:00
张 元山
0b5494b55b add sec idx 2025-05-31 01:50:05 +08:00
4cd3e2ae6e Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-31 01:50:04 +08:00
齐斌
49eb1646c2 去掉日期 2025-05-31 01:49:51 +08:00
efd50bc82e Merge branch 'wyfMain-dev-0530' 2025-05-31 01:49:50 +08:00
396015f628 移除暂时不可点击的Full History筛选条件 2025-05-31 01:49:03 +08:00
齐斌
029b3a978a fixbug 2025-05-31 01:48:15 +08:00
651443a95c 全局注释console.log 2025-05-31 01:39:45 +08:00
096fea2b65 Merge branch 'wyfMain-dev-0530' 2025-05-31 01:31:27 +08:00
a13a49f666 调整价格显示 2025-05-31 01:31:03 +08:00
张 元山
9d10a578e5 fix companyoverview 2025-05-31 01:26:46 +08:00
张 元山
5c90cd9486 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-31 01:21:04 +08:00
张 元山
9d6d85490c fix press-releases 2025-05-31 01:21:02 +08:00
齐斌
f5c213eac8 fixbug 2025-05-31 01:12:18 +08:00
c987f08490 Merge branch 'wyfMain-dev-0530' 2025-05-31 00:58:57 +08:00
e7af9a09de 新增3个隐私政策页面;调整change文字显示 2025-05-31 00:58:09 +08:00
齐斌
07ff81ef66 fixfile 2025-05-31 00:50:40 +08:00
齐斌
1fbda73a3f 更新 secFiles.js 文件,添加多个 FORM 4 的文件信息,并在 en.js 文件中更正管理层成员的名称为“Li Wai Chung”。 2025-05-31 00:40:22 +08:00
齐斌
7e0ef75a5e Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-31 00:23:07 +08:00
齐斌
ed85bc2771 更新委员会角色的治理职务,将“Chair”更改为“Member”,并在其他角色中进行相应调整,以确保角色的一致性和准确性。 2025-05-31 00:23:06 +08:00
afe010ae1d Merge branch 'wyfMain-dev-0530' 2025-05-31 00:12:52 +08:00
afec6b8ef1 解决图表y轴移动到右侧,会在移动端被遮挡一部分的问题;解决计算最大日期最近前一天有数据的日期时,如果当天正好没数据又是最后一天,临界值会报错的问题 2025-05-31 00:09:19 +08:00
齐斌
1abd94cdce 修改简介,PC图表 2025-05-31 00:00:04 +08:00
1a031d0be2 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 23:22:08 +08:00
张 元山
1179c99782 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 23:22:02 +08:00
张 元山
3d96acdc4e fix pdf 2025-05-30 23:21:27 +08:00
664b66b414 实现图表完整功能;接入2个新的数据接口 2025-05-30 23:21:20 +08:00
齐斌
51a52f9696 更新 FiEE, Inc. 的商业行为和道德规范、提名与公司治理委员会章程以及审计委员会章程的格式和内容,确保文件的一致性和可读性。 2025-05-30 23:18:51 +08:00
张 元山
2471bcd0a7 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 22:46:10 +08:00
张 元山
917f76dac3 fix press-releases 2025-05-30 22:46:08 +08:00
齐斌
4930853816 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 22:45:46 +08:00
齐斌
ad5d6f7b1a 将所有相关 Vue 组件中的 API 提交地址从 'https://common.szjixun.cn/api/stock/submit/data' 更新为 'https://erpapi-out.szjixun.cn/api/stock/submit/data',以确保正确的 API 调用。 2025-05-30 22:45:45 +08:00
张 元山
fa19e8bc87 fix news 2025-05-30 22:39:30 +08:00
张 元山
dbc8ecb84e fix annualreports 2025-05-30 22:25:23 +08:00
张 元山
afb01fab8e fix home 2025-05-30 22:19:35 +08:00
齐斌
363d1e1f9e Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 21:24:53 +08:00
齐斌
98cd9fbbe0 在所有相关的 Vue 组件中添加了 axios 导入,并将 handleSubmit 函数修改为异步函数,以便在提交表单时处理 API 请求并根据响应状态更新提交状态。 2025-05-30 21:24:52 +08:00
张 元山
af39cb7bb3 fix footer 2025-05-30 20:33:37 +08:00
张 元山
624824e956 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 20:31:02 +08:00
张 元山
047a1ebc88 fix footer 2025-05-30 20:31:01 +08:00
齐斌
4933765a4c 删掉部分成员 2025-05-30 20:17:21 +08:00
齐斌
9bd9c0b95a Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 20:11:43 +08:00
齐斌
5614ddf4ef 删掉计算器 2025-05-30 20:11:42 +08:00
张 元山
59202a8471 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 20:10:48 +08:00
张 元山
ad11498a31 fix myhome 2025-05-30 20:10:47 +08:00
齐斌
f1a90c8b6a Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 20:10:03 +08:00
齐斌
283f73d2c3 更改简介 2025-05-30 20:10:02 +08:00
张 元山
37d9be4c38 fix myhome 2025-05-30 19:46:08 +08:00
张 元山
0971e78597 fix myhome 2025-05-30 19:38:01 +08:00
张 元山
7eb791d38e Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 19:19:14 +08:00
张 元山
a0ada9e66a fix style 2025-05-30 19:19:12 +08:00
齐斌
d0f3fbc1dd 修改文件 2025-05-30 19:00:37 +08:00
张 元山
ae0d82ae16 fix secfilingsDefail 2025-05-30 17:19:04 +08:00
张 元山
7b78b04f38 fix secfilings 2025-05-30 17:17:02 +08:00
齐斌
be833783f2 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 17:04:49 +08:00
齐斌
de9fb51ec3 addData 2025-05-30 17:04:48 +08:00
张 元山
16241a203d fix secfilingsdetail 2025-05-30 16:51:52 +08:00
张 元山
78d8364816 fix year select 2025-05-30 16:46:30 +08:00
8fff836596 Merge branch 'LiWenHao' 2025-05-30 16:32:32 +08:00
3022ec9756 修改 颜色 2025-05-30 16:32:19 +08:00
张 元山
abb1c220cf Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 16:18:55 +08:00
张 元山
81de59b9eb fix secfilings 2025-05-30 16:18:53 +08:00
126bf56327 修改 2025-05-30 16:13:25 +08:00
208a426324 Merge branch 'wyfMain-dev' 2025-05-30 15:44:59 +08:00
75824076ef 接入echarts图表,实现部分样式 2025-05-30 15:44:26 +08:00
f55a24ea63 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-05-30 15:39:05 +08:00
2f94959e19 修改文字 添加时间 删除文字 2025-05-30 15:38:49 +08:00
张 元山
d71e435ee1 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 15:33:37 +08:00
张 元山
42b2313857 add secfilings 2025-05-30 15:33:35 +08:00
齐斌
0a5088449a Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-30 15:27:06 +08:00
齐斌
ba62f71796 addData 2025-05-30 15:27:05 +08:00
969a16577e 删除首页标题 2025-05-30 14:24:15 +08:00
bad5b37b05 修改底部文字,增加4个超链接 2025-05-30 14:07:52 +08:00
齐斌
8a9002a9e3 changestock 2025-05-30 13:53:22 +08:00
60590596b7 修改文字 2025-05-29 16:43:05 +08:00
5d0f85ba9b Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-05-28 15:51:02 +08:00
7c6a61b99f 尺寸修改 2025-05-28 15:50:41 +08:00
齐斌
de8617321d Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-28 15:43:06 +08:00
齐斌
786740cdfa 更新股票变动显示逻辑,优化不同屏幕尺寸下的样式适配,确保变动信息根据涨跌状态动态改变颜色。 2025-05-28 15:43:05 +08:00
caf62145a0 change 适配 2025-05-28 15:23:47 +08:00
f33003eb04 股票时间修改 2025-05-28 15:22:14 +08:00
齐斌
c4b11ec76a 更新股票交易日计算逻辑,修复获取股票数据的API地址,并统一修改页面数据获取函数名称。 2025-05-28 15:07:00 +08:00
39deb7bd70 修改文字自适应尺寸 2025-05-26 19:20:09 +08:00
946652acae 优化修改 2025-05-26 19:12:57 +08:00
张 元山
99ec5382bf Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-26 16:48:46 +08:00
张 元山
d4df67bd76 fix icon 2025-05-26 16:48:44 +08:00
75c377eafe Governance 修改文字 2025-05-26 16:43:58 +08:00
614ae9fce4 修改 股票数据 2025-05-26 16:38:51 +08:00
af91764f94 修改字体样式 2025-05-26 16:23:21 +08:00
张 元山
b06317e581 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-26 16:03:52 +08:00
张 元山
44d3555e06 fix file down 2025-05-26 16:03:51 +08:00
97fc7d966b corporate informain模块 修改标题字体加粗 2025-05-26 16:02:46 +08:00
7c741962d4 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-05-26 15:41:53 +08:00
9cba09c2fb 间距调整 2025-05-26 15:41:25 +08:00
91485a72b4 修改字体大小 以及文字 2025-05-26 15:38:24 +08:00
张 元山
22e9b74173 fix quarterlyresults 2025-05-26 15:30:58 +08:00
b184eba64d Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-05-26 15:14:50 +08:00
28f6123e93 调整样式 修改文字 添加时间 2025-05-26 15:14:44 +08:00
Phoenix
c4a8271816 美国时间显示 2025-05-26 15:13:40 +08:00
Phoenix
f72dd8ad9d Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-26 14:12:57 +08:00
Phoenix
82711a9645 stock-quote时间显示逻辑 2025-05-26 14:12:54 +08:00
张 元山
b4a3a5b72e Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-26 14:11:57 +08:00
张 元山
ce58c9886e fix quarterlyresults 2025-05-26 14:11:56 +08:00
1164d4ef7b 111 2025-05-26 13:40:09 +08:00
24c27d5efb Business Introduction 模块 768尺寸变形问题 2025-05-26 13:12:09 +08:00
Phoenix
af8348107b Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-26 10:44:30 +08:00
Phoenix
fb45f534da 调整 E-Mail Alerts 表单卡片宽度,从 420px 修改为 600px 2025-05-26 10:44:29 +08:00
334bef4b05 Merge branch 'LiWenHao' 2025-05-26 10:39:50 +08:00
Phoenix
309d5fcb25 Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-26 10:38:41 +08:00
f2246e1b69 适配 768 1440 尺寸 2025-05-26 10:38:31 +08:00
Phoenix
bb11173ce1 Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-26 10:38:13 +08:00
张 元山
e052d59c2b 768 1440 样式调整 2025-05-26 10:37:58 +08:00
Phoenix
c44a1bb1c1 重构投资计算器和联系人视图,添加投资类型、金额、分红类型和投资日期的输入功能,并优化样式以提升用户体验。 2025-05-26 10:35:11 +08:00
d5c887f437 去掉标点符号 2025-05-24 16:54:55 +08:00
64b8d83c9e 字体优化 2025-05-24 16:29:54 +08:00
9881122e5f Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-05-24 16:11:31 +08:00
46687211dd 修改文字 2025-05-24 16:11:08 +08:00
张 元山
ee07e825e2 fix quarterlyresults 2025-05-24 15:41:43 +08:00
张 元山
aad1267c61 fix quarterlyresults 2025-05-24 15:00:25 +08:00
108970fd41 h5 新闻模块字体修改 2025-05-23 20:42:48 +08:00
62a565d986 修改样式 2025-05-23 20:22:52 +08:00
Phoenix
bdb29764be 在股票报价模块中添加格式化日期功能,并在相关视图中引入以显示当前日期和时间。 2025-05-23 20:21:14 +08:00
fa00cdd6bc Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-05-23 20:02:41 +08:00
8252812dfb 修改bug 2025-05-23 20:02:35 +08:00
bb57f44b60 Merge branch 'wyfMain-dev' 2025-05-23 19:57:29 +08:00
9bbd983253 修改手机端高度计算适配小屏手机样式 2025-05-23 19:56:47 +08:00
张 元山
abc39b13af Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-23 19:50:24 +08:00
张 元山
8958bf8847 secfilings 2025-05-23 19:50:22 +08:00
张 元山
4b3d0a57d9 fix secfilings 2025-05-23 19:50:19 +08:00
e9e7ad52de Merge branch 'wyfMain-dev' 2025-05-23 19:42:39 +08:00
8eedc65345 调整导航栏样式,适配不同分辨率下文字被挤压导致省略,解决看不全内容的问题 2025-05-23 19:41:42 +08:00
Phoenix
d450e2171c Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-23 18:47:41 +08:00
Phoenix
c4f61ddc3c 添加股票报价页面的时间格式化功能,并更新NASDAQ显示的股票代码 2025-05-23 18:47:40 +08:00
f3ce08a945 修改移动端 文字 表格模块 2025-05-23 16:56:25 +08:00
15b15e0351 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-05-23 16:52:38 +08:00
4ccb9c256b 优化移动端样式 2025-05-23 16:52:24 +08:00
Phoenix
a5b7334c6b 更新股票报价显示逻辑 2025-05-23 16:43:44 +08:00
Phoenix
7c75bab274 Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-23 16:41:19 +08:00
Phoenix
6f95ccb4ab 调整主容器的内边距 2025-05-23 16:41:16 +08:00
a970c7f789 删掉图片 2025-05-23 16:35:06 +08:00
cb9ccb3e1f Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-05-23 16:31:53 +08:00
7e2d26d71c 修改文字 2025-05-23 16:31:43 +08:00
24be7c61e5 Merge branch 'wyfMain-dev' 2025-05-23 16:18:02 +08:00
4b64809397 调整1920到375中间的分辨率统一使用1920/375的样式 2025-05-23 16:17:22 +08:00
张 元山
8c0c7bad57 fix back to top 2025-05-23 16:09:24 +08:00
张 元山
77cd8fddfd Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-23 15:42:03 +08:00
张 元山
9e2833cdee fix file down 2025-05-23 15:42:02 +08:00
3d6ad2475b Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-05-23 15:41:24 +08:00
bf53ba4e3c 修复pdf预览 2025-05-23 15:41:10 +08:00
张 元山
a318455eda Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-23 15:37:17 +08:00
张 元山
6778e09a30 add quarterlyresults file 2025-05-23 15:37:15 +08:00
c5c22124d7 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-05-23 15:32:24 +08:00
cc77c4e482 适配H5 2025-05-23 15:32:12 +08:00
c0f5cc52ed 解决文浩提交记录[df8995070d72b9ee8b275e697c3313dc96fb82d]导致的en.js文件、router.js文件等公共文件导致的代码错漏问题;新增路由重定向解决根路由、不存在路由没有正确重定向问题 2025-05-23 15:23:56 +08:00
Phoenix
8b28bf8736 Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-23 14:55:13 +08:00
Phoenix
9c7aa5b29f 更新股票报价API的请求地址,从HTTP更改为HTTPS,以提高安全性。 2025-05-23 14:55:12 +08:00
5b88e3b552 Merge branch 'wyfMain-dev' 2025-05-23 14:47:24 +08:00
74e990a4ad 修正头脚高度布局计算 2025-05-23 14:46:38 +08:00
f197f8d5bd Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao 2025-05-23 14:42:14 +08:00
5c2bf0cc68 路由代码复制处理 2025-05-23 14:41:54 +08:00
Phoenix
2cde6d853c 移除表单字段的必填属性,以优化用户体验和表单提交流程。 2025-05-23 14:16:42 +08:00
Phoenix
5a9711a936 优化移动端头部和底部样式,移除固定定位,调整主容器样式以改善布局和响应式设计。 2025-05-23 14:13:55 +08:00
Phoenix
c919b78f9c 调整表单字段,移除必填属性,更新未填写提示文本为英文,以优化用户体验。 2025-05-23 13:59:01 +08:00
Phoenix
9cc5eaf021 移除移动端头部和底部内容,调整主容器的内边距以优化布局和样式。 2025-05-23 13:56:39 +08:00
Phoenix
a0806cf79e 优化股票报价组件的样式,调整了价格卡片和信息卡片的布局、字体大小及间距,改善了视觉效果和响应式设计。 2025-05-23 13:50:06 +08:00
Phoenix
660f6f16cf 调整了视图组件的样式,将主容器的最小高度从屏幕高度修改为60vh,以优化布局和响应式设计。 2025-05-23 13:46:42 +08:00
Phoenix
5ab32ac10a 重构投资计算器组件,增加投资类型、金额、分红类型和投资日期的输入字段,优化样式和布局。 2025-05-23 13:45:39 +08:00
Phoenix
bbdb49a655 Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-23 13:41:30 +08:00
Phoenix
4943345467 更新了多个视图组件,调整了表单字段和样式,增加了股票报价信息展示,优化了响应式布局。 2025-05-23 13:41:29 +08:00
b4e8ed0736 图片路径修改 2025-05-23 13:39:31 +08:00
83b64c150a 图片路径修改 2025-05-23 13:29:23 +08:00
张 元山
f60a0e6559 fix i18 2025-05-23 13:14:08 +08:00
张 元山
e0079415dc Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-23 13:07:09 +08:00
张 元山
97503165c1 pdf路径 2025-05-23 13:07:08 +08:00
Phoenix
eea6ef83ba 移除提交成功提示信息 2025-05-23 12:28:26 +08:00
Phoenix
b44c3c0cca 12 2025-05-23 12:23:32 +08:00
fe22244f82 Merge branch 'LiWenHao'
# Conflicts:
#	src/config/headerMenuConfig.js
#	src/locales/en.js
#	src/router/index.js
2025-05-23 12:10:37 +08:00
d6f8995070 111 2025-05-23 12:03:48 +08:00
64e39d49df Merge branch 'wyfMain-dev' 2025-05-23 11:53:28 +08:00
Phoenix
af659cfbb5 Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-23 11:52:04 +08:00
2525ccb249 暂时隐藏Corporate Video菜单;增加年份选择区间 2025-05-23 11:52:04 +08:00
Phoenix
83845414be 新增计算器页面路由 2025-05-23 11:52:03 +08:00
张 元山
d5658ce38e Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-23 11:47:20 +08:00
张 元山
4e5ec7f71f historic-stock 375 2025-05-23 11:47:18 +08:00
Phoenix
e56df423f5 Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-23 11:39:00 +08:00
Phoenix
3a8fa9baa6 12 2025-05-23 11:38:59 +08:00
ece3be4bee Merge branch 'wyfMain-dev' 2025-05-23 11:33:44 +08:00
46e52ef694 维护历史数据条件筛选日期格式 2025-05-23 11:33:10 +08:00
Phoenix
f67179b9e4 Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-23 11:30:18 +08:00
Phoenix
fde7ccbfb8 新增联系人页面路由,并将提交成功提示信息的文本从中文更改为英文。 2025-05-23 11:30:17 +08:00
524caed5f2 Merge branch 'wyfMain-dev' 2025-05-23 11:24:04 +08:00
1c7a139732 处理历史表格数据根据筛选条件调用接口 2025-05-23 11:21:41 +08:00
张 元山
b59215e5b0 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-23 11:21:03 +08:00
张 元山
77fe06694c historic-stock 2025-05-23 11:21:02 +08:00
Phoenix
cfc22f0b1e Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website 2025-05-23 11:04:16 +08:00
Phoenix
a206bcbc4e 新增 countup.js 依赖,并优化股票报价组件样式,调整信息展示的排版和大小。 2025-05-23 11:04:14 +08:00
张 元山
17753d48b3 Merge branch 'zhangyuanshan' 2025-05-23 10:52:10 +08:00
张 元山
3bdb88ca55 historic-stock 2025-05-23 10:51:54 +08:00
Phoenix
b96475ec2f Merge branch 'main' of http://172.16.100.91:3000/scout666/fiee-official-website
# Conflicts:
#	src/views/index/size1920/index.vue   resolved by main version
2025-05-23 10:46:54 +08:00
c357504998 Merge branch 'wyfMain-dev' 2025-05-23 10:46:29 +08:00
aff2cf7960 完成缺省页;完成NR子菜单;调整导航栏样式,增加全局背景图 2025-05-23 10:45:41 +08:00
张 元山
deb9834cd4 historic-stock 2025-05-23 10:22:18 +08:00
张 元山
25efd2b071 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website 2025-05-23 10:01:09 +08:00
张 元山
592122b361 add image 2025-05-23 10:01:08 +08:00
735b9b9ea2 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/fiee-official-website into LiWenHao
# Conflicts:
#	src/locales/en.js
#	src/router/index.js
2025-05-23 09:09:53 +08:00
270aca35a3 111 2025-05-23 09:04:20 +08:00
203 changed files with 62798 additions and 1718 deletions

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/assets/image/icon.png" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>FiEE</title>
</head>

View File

@ -17,6 +17,8 @@
"@vicons/utils": "^0.1.4",
"axios": "^1.7.3",
"cnjm-postcss-px-to-viewport": "^1.0.1",
"countup.js": "^2.8.2",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"gsap": "^3.12.5",
"jsdom": "^24.0.0",
@ -49,6 +51,7 @@
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.3",
"vite": "^5.3.4",
"vite-plugin-imagemin": "^0.6.1"
"vite-plugin-imagemin": "^0.6.1",
"vite-plugin-vue-devtools": "^7.7.6"
}
}

File diff suppressed because it is too large Load Diff

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,55 @@
<script setup>
import { ref, watch, onMounted } from 'vue'
import { CountUp } from 'countup.js'
const props = defineProps({
endVal: {
type: [Number, String],
required: true
},
duration: {
type: Number,
default: 1.2
},
decimals: {
type: Number,
default: 2
},
prefix: {
type: String,
default: ''
},
suffix: {
type: String,
default: ''
}
})
const el = ref(null)
let countUpInstance = null
const start = (val) => {
if (countUpInstance) countUpInstance.update(val)
else {
countUpInstance = new CountUp(el.value, val, {
duration: props.duration,
decimalPlaces: props.decimals,
prefix: props.prefix,
suffix: props.suffix
})
countUpInstance.start()
}
}
onMounted(() => {
start(props.endVal)
})
watch(() => props.endVal, (val) => {
start(val)
})
</script>
<template>
<span ref="el"></span>
</template>

View File

@ -3,6 +3,8 @@ import { computed } from 'vue'
import { useWindowSize } from '@vueuse/core'
import size375 from '@/components/customDefaultPage/size375/index.vue'
import size768 from '@/components/customDefaultPage/size1920/index.vue'
import size1440 from '@/components/customDefaultPage/size1920/index.vue'
import size1920 from '@/components/customDefaultPage/size1920/index.vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
@ -15,20 +17,22 @@ const viewComponent = computed(() => {
const viewWidth = width.value
if (viewWidth <= 450) {
return size375
}
// else if (viewWidth <= 1100) {
// return size768;
// } else if (viewWidth <= 1500) {
// return size1440;
// }
else if (viewWidth <= 1920 || viewWidth > 1920) {
} else if (viewWidth <= 1100) {
return size768
} else if (viewWidth <= 1500) {
return size1440
} else if (viewWidth <= 1920 || viewWidth > 1920) {
return size1920
}
})
</script>
<template>
<component :is="viewComponent" />
<component :is="viewComponent" v-bind="$attrs">
<template v-for="(_, slot) in $slots" #[slot]="scope">
<slot :name="slot" v-bind="scope" />
</template>
</component>
</template>
<style scoped lang="scss"></style>

View File

@ -1,18 +1,27 @@
<template>
<!-- 通用缺省页 -->
<div class="custom-default-page">
<div class="search-area">
<customSelectSearch></customSelectSearch>
<!-- 通用缺省页 -->
<div class="custom-default-page">
<slot name="content"></slot>
<template v-if="$slots.empty">
<slot name="empty"></slot>
</template>
<template v-else>
<div class="default-empty-icon">
<img
src="@/assets/image/icon/default-empty.png"
alt="empty"
style="width: 476px; height: 262px;"
/>
</div>
</div>
</template>
<script setup>
import customSelectSearch from '@/components/customSelectSearch/index.vue'
</script>
<style scoped lang="scss">
.search-area {
width: 300px;
margin: 100px 300px;
}
</style>
</template>
</div>
</template>
<script setup></script>
<style scoped lang="scss">
.default-empty-icon {
display: flex;
justify-content: center;
align-items: center;
margin: 40px 0 24px 0;
}
</style>

View File

@ -1,18 +1,27 @@
<template>
<!-- 通用缺省页 -->
<div class="custom-default-page">
<div class="search-area">
<customSelectSearch></customSelectSearch>
<!-- 通用缺省页 -->
<div class="custom-default-page">
<slot name="content"></slot>
<template v-if="$slots.empty">
<slot name="empty"></slot>
</template>
<template v-else>
<div class="default-empty-icon">
<img
src="@/assets/image/icon/default-empty.png"
alt="empty"
style="width: 350px; height: 198px; margin: 40px 0 0;"
/>
</div>
</div>
</template>
<script setup>
import customSelectSearch from '@/components/customSelectSearch/index.vue'
</script>
<style scoped lang="scss">
.search-area {
width: 1500px;
margin: 500px 200px;
}
</style>
</template>
</div>
</template>
<script setup></script>
<style scoped lang="scss">
.default-empty-icon {
display: flex;
justify-content: center;
align-items: center;
margin: 40px 0 24px 0;
}
</style>

View File

@ -0,0 +1,34 @@
<script setup>
import { computed } from 'vue'
import { useWindowSize } from '@vueuse/core'
import size375 from '@/components/customEcharts/size375/index.vue'
import size768 from '@/components/customEcharts/size375/index.vue'
import size1440 from '@/components/customEcharts/size1920/index.vue'
import size1920 from '@/components/customEcharts/size1920/index.vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
const router = useRouter()
const { width } = useWindowSize()
const { t } = useI18n()
const viewComponent = computed(() => {
const viewWidth = width.value
if (viewWidth <= 500) {
return size375
} else if (viewWidth <= 960) {
return size768
} else if (viewWidth <= 1500) {
return size1440
} else if (viewWidth <= 1920 || viewWidth > 1920) {
return size1920
}
})
</script>
<template>
<component :is="viewComponent" />
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,602 @@
<template>
<div class="custom-echarts">
<div>
<div class="echarts-header">
<div class="echarts-header-title">
<span>FiEE, Inc. Stock Price History</span>
</div>
<div class="echarts-search-area">
<div class="echarts-search-byRange">
<text style="font-size: 0.9rem; font-weight: 400; color: #666666;">
Range
</text>
<div class="search-range-list">
<div
class="search-range-list-each"
v-for="(item, index) in state.searchRange"
:key="index"
@click="changeSearchRange(item)"
>
<span>{{ item }}</span>
</div>
</div>
</div>
<div class="echarts-search-byDate">
<n-date-picker
v-model:value="state.selectHistoricStartDate"
type="date"
:is-date-disabled="disableAfterDate"
@update:value="changeSearchRangeStartDate"
input-readonly
/>
<!-- <n-icon size="16">
<ArrowForwardOutline />
</n-icon> -->
<span>to</span>
<n-date-picker
v-model:value="state.selectHistoricEndDate"
type="date"
:is-date-disabled="disablePreviousDate"
@update:value="changeSearchRangeEndDate"
input-readonly
/>
</div>
</div>
</div>
</div>
<div id="myEcharts" class="myChart"></div>
</div>
</template>
<script setup>
import { onMounted, watch, reactive } from 'vue'
import * as echarts from 'echarts'
import markPointerIcon from '@/assets/image/icon/echarts_markPointer.png'
import axios from 'axios'
import { NDatePicker, NIcon } from 'naive-ui'
import { ArrowForwardOutline } from '@vicons/ionicons5'
const state = reactive({
searchRange: ['1m', '3m', 'YTD', '1Y', '5Y', '10Y', 'Max'],
selectHistoricStartDate: '2009-10-07',
selectHistoricEndDate: new Date(),
})
let myCharts = null
let historicData = []
let xAxisData = []
//eCharts
const initEcharts = (data) => {
historicData = data
xAxisData = data.map((item) => {
return new Date(item.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
})
const yAxisData = data.map((item) => item.price)
// console.error(xAxisData, yAxisData)
// domecharts
myCharts = echarts.init(document.getElementById('myEcharts'), null, {
renderer: 'canvas',
useDirtyRect: true
})
//
myCharts.setOption({
animation: false,
progressive: 500,
progressiveThreshold: 3000,
// title: {
// text: 'FiEE, Inc. Stock Price History',
// },
grid: {
left: '8%', // '2%'
right: '12%', // yylabel
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
label: {
backgroundColor: '#6a7985',
},
},
formatter: function (params) {
const p = params[0]
return `<span style="font-size: 1.1rem; font-weight: 600;">${p.axisValue}</span><br/><span style="font-size: 0.9rem; font-weight: 400;">Price: ${p.data}</span>`
},
triggerOn: 'mousemove',
confine: true,
hideDelay: 1500
},
xAxis: {
data: xAxisData,
type: 'category',
boundaryGap: false,
inverse: true,
axisLine: {
lineStyle: {
color: '#CCD6EB',
},
},
axisLabel: {
color: '#323232',
fontWeight: 'bold',
interval: 'auto',
hideOverlap: true
},
},
yAxis: {
type: 'value',
position: 'right',
interval: 25,
// max: 75.0,
show: true,
axisLabel: {
color: '#323232',
fontWeight: 'bold',
formatter: function (value) {
return value > 0 ? value.toFixed(2) : value
},
},
},
series: [
{
data: yAxisData,
type: 'line',
sampling: 'lttb',
symbol: 'none',
lineStyle: {
color: '#2c6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#2c6288',
},
{
offset: 1,
color: '#F4F6F8',
},
],
},
},
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [],
},
progressive: 500,
progressiveThreshold: 3000,
large: true,
largeThreshold: 2000
},
],
dataZoom: [
{
type: 'inside',
},
{
type: 'slider',
show: true,
dataBackground: {
lineStyle: {
color: '#2C6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 1, color: '#2c6288' },
{ offset: 0, color: '#F4F6F8' },
],
},
},
},
selectedDataBackground: {
lineStyle: {
color: '#2C6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 1, color: '#2c6288' },
{ offset: 0, color: '#F4F6F8' },
],
},
},
},
fillerColor: 'rgba(44, 98, 136, 0.3)',
realtime: false,
},
],
})
// showTip markPoint
myCharts.on('showTip', function (params) {
if (params) {
const dataIndex = params.dataIndex
const x = myCharts.getOption().xAxis[0].data[dataIndex]
const y = myCharts.getOption().series[0].data[dataIndex]
myCharts.setOption({
series: [
{
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [{ coord: [x, y] }],
},
},
],
})
}
})
// markPoint
myCharts.on('globalout', function () {
myCharts.setOption({
series: [
{
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [],
},
},
],
})
})
myCharts.on('dataZoom', function (params) {
// dataZoom
const option = myCharts.getOption()
const xAxisData = option.xAxis[0].data
const dataZoom = option.dataZoom[1] || option.dataZoom[0]
// dataZoom startValue endValue
let startValue = dataZoom.endValue
let endValue = dataZoom.startValue
//
if (typeof startValue === 'number') {
startValue = xAxisData[startValue]
}
if (typeof endValue === 'number') {
endValue = xAxisData[endValue]
}
//
state.selectHistoricStartDate = new Date(startValue)
state.selectHistoricEndDate = new Date(endValue)
})
}
onMounted(() => {
getHistoricalData()
})
//
const getHistoricalData = async () => {
let now = new Date()
let toDate =
now.getFullYear() +
'-' +
String(now.getMonth() + 1).padStart(2, '0') +
'-' +
String(now.getDate()).padStart(2, '0')
let url =
'https://common.szjixun.cn/api/stock/history/base/list?from=2009-10-07&to=' +
toDate
const res = await axios.get(url)
if (res.status === 200) {
if (res.data.status === 0) {
initEcharts(res.data.data)
}
}
}
//
function findClosestDateIndex(data, targetDateStr) {
let left = 0,
right = data.length - 1
const target = new Date(targetDateStr).getTime()
let res = data.length - 1 //
while (left <= right) {
const mid = Math.floor((left + right) / 2)
const midTime = new Date(data[mid].date).getTime()
if (midTime > target) {
left = mid + 1
} else {
res = mid
right = mid - 1
}
}
return res
}
//
function findClosestDateIndexDescLeft(data, targetDateStr) {
let left = 0,
right = data.length - 1
const target = new Date(targetDateStr).getTime()
let res = -1
while (left <= right) {
const mid = Math.floor((left + right) / 2)
const midTime = new Date(data[mid].date).getTime()
if (midTime > target) {
left = mid + 1 // mid
} else {
res = mid // mid <= target
right = mid - 1
}
}
return res
}
//
const changeSearchRange = (range, dateTime) => {
const now = new Date()
let startDate = ''
let endDate = ''
if (range === '1m') {
const last = new Date(now)
last.setMonth(now.getMonth() - 1)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '3m') {
const last = new Date(now)
last.setMonth(now.getMonth() - 3)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === 'YTD') {
startDate = new Date(now.getFullYear(), 0, 1).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '1Y') {
const last = new Date(now)
last.setFullYear(now.getFullYear() - 1)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '5Y') {
const last = new Date(now)
last.setFullYear(now.getFullYear() - 5)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '10Y') {
const last = new Date(now)
last.setFullYear(now.getFullYear() - 10)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === 'Max') {
startDate = ''
endDate = ''
} else if (range === 'startDateTime') {
startDate = dateTime
endDate = ''
} else if (range === 'endDateTime') {
startDate = ''
endDate = dateTime
}
if (startDate || endDate) {
// historicData xAxisData initEcharts
if (
typeof historicData !== 'undefined' &&
typeof xAxisData !== 'undefined'
) {
let startValue = xAxisData[0]
if (startDate) {
const idx = findClosestDateIndex(historicData, startDate)
// historicData[idx].date xAxisData
startValue = new Date(historicData[idx].date).toLocaleDateString(
'en-US',
{
month: 'short',
day: 'numeric',
year: 'numeric',
},
)
}
let endValue = endDate
if (endDate) {
// console.warn(endDate)
const idx = findClosestDateIndexDescLeft(historicData, endDate)
// console.warn(idx)
// historicData[idx].date xAxisData
endValue = new Date(historicData[idx].date).toLocaleDateString(
'en-US',
{
month: 'short',
day: 'numeric',
year: 'numeric',
},
)
// console.warn(endValue)
}
if (startDate) {
myCharts.setOption({
dataZoom: {
endValue: startValue,
},
})
state.selectHistoricStartDate = new Date(startValue)
}
if (endDate) {
myCharts.setOption({
dataZoom: {
startValue: endValue,
},
})
state.selectHistoricEndDate = new Date(endValue)
}
}
} else {
myCharts.setOption({
dataZoom: {
startValue: '',
endValue: '',
},
})
state.selectHistoricStartDate = new Date('2009-10-07')
state.selectHistoricEndDate = new Date()
}
}
// 2009-10-07
const disableAfterDate = (date) => {
return date < new Date('2009-10-06') || date > new Date()
}
//
const disablePreviousDate = (date) => {
return date < new Date(state.selectHistoricStartDate) || date > new Date()
}
//
const changeSearchRangeStartDate = (date) => {
// console.error(date)
changeSearchRange(
'startDateTime',
new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}),
)
}
//
const changeSearchRangeEndDate = (date) => {
// console.error(date)
changeSearchRange(
'endDateTime',
new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}),
)
}
</script>
<style lang="scss" scoped>
.custom-echarts {
.myChart {
width: 100%;
height: 25rem;
}
.echarts-header {
.echarts-header-title {
span {
font-size: 2rem;
font-weight: 600;
color: #323232;
}
}
.echarts-search-area {
padding: 2rem 0 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.echarts-search-byRange {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 10px;
.search-range-list {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 10px;
.search-range-list-each {
padding: 5px 10px;
border-radius: 5px;
background-color: #f3f4f6;
cursor: pointer;
span {
font-weight: 600;
font-size: 0.9rem;
}
}
}
}
.echarts-search-byDate {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 0.4rem;
}
}
}
}
</style>

View File

@ -0,0 +1,604 @@
<template>
<div class="custom-echarts">
<div>
<div class="echarts-header">
<div class="echarts-header-title">
<span>FiEE, Inc. Stock Price History</span>
</div>
<div class="echarts-search-area">
<div class="echarts-search-byRange">
<text style="font-size: 0.9rem; font-weight: 400; color: #666666;">
Range
</text>
<div class="search-range-list">
<div
class="search-range-list-each"
v-for="(item, index) in state.searchRange"
:key="index"
@click="changeSearchRange(item)"
>
<span>{{ item }}</span>
</div>
</div>
</div>
<div class="echarts-search-byDate">
<n-date-picker
v-model:value="state.selectHistoricStartDate"
type="date"
:is-date-disabled="disableAfterDate"
@update:value="changeSearchRangeStartDate"
input-readonly
/>
<!-- <n-icon size="30">
<ArrowForwardOutline />
</n-icon> -->
<span>to</span>
<n-date-picker
v-model:value="state.selectHistoricEndDate"
type="date"
:is-date-disabled="disablePreviousDate"
@update:value="changeSearchRangeEndDate"
input-readonly
/>
</div>
</div>
</div>
</div>
<div id="myEcharts" class="myChart"></div>
</div>
</template>
<script setup>
import { onMounted, watch, reactive } from 'vue'
import * as echarts from 'echarts'
import markPointerIcon from '@/assets/image/icon/echarts_markPointer.png'
import axios from 'axios'
import { NDatePicker } from 'naive-ui'
import { ArrowForwardOutline } from '@vicons/ionicons5'
const state = reactive({
searchRange: ['1m', '3m', 'YTD', '1Y', '5Y', '10Y', 'Max'],
selectHistoricStartDate: '2009-10-07',
selectHistoricEndDate: new Date(),
})
let myCharts = null
let historicData = []
let xAxisData = []
//eCharts
const initEcharts = (data) => {
historicData = data
xAxisData = data.map((item) => {
return new Date(item.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
})
const yAxisData = data.map((item) => item.price)
// console.error(xAxisData, yAxisData)
// domecharts
myCharts = echarts.init(document.getElementById('myEcharts'))
//
myCharts.setOption({
animation: false,
progressive: 500,
progressiveThreshold: 3000,
// title: {
// text: 'FiEE, Inc. Stock Price History',
// },
grid: {
left: '8%', // '2%'
right: '15%', // yylabel
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
snap: true,
label: {
backgroundColor: '#6a7985',
},
},
formatter: function (params) {
const p = params[0]
return `<span style="font-size: 1.1rem; font-weight: 600;">${p.axisValue}</span><br/><span style="font-size: 0.9rem; font-weight: 400;">Price: ${p.data}</span>`
},
confine: true,
hideDelay: 1500
},
xAxis: {
data: xAxisData,
type: 'category',
boundaryGap: false,
inverse: true,
axisLine: {
lineStyle: {
color: '#CCD6EB',
},
},
axisLabel: {
color: '#323232',
fontWeight: 'bold',
// formatter: function (value) {
// return value ? value.split('-')[0] : ''
// },
// interval: function (index, value) {
// if (index === 0) return true;
// const axisData = this && this.axis && this.axis.data ? this.axis.data : [];
// if (!axisData[index - 1]) return true;
// return value.split('-')[0] !== axisData[index - 1].split('-')[0];
// },
},
},
yAxis: {
type: 'value',
position: 'right',
interval: 25,
// max: 75.0,
show: true,
axisLabel: {
color: '#323232',
fontWeight: 'bold',
formatter: function (value) {
return value > 0 ? value.toFixed(2) : value
},
},
},
series: [
{
data: yAxisData,
type: 'line',
sampling: 'lttb',
symbol: 'none',
lineStyle: {
color: '#2c6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#2c6288',
},
{
offset: 1,
color: '#F4F6F8',
},
],
},
},
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [],
},
},
],
dataZoom: [
{
type: 'inside',
},
{
type: 'slider',
show: true,
dataBackground: {
lineStyle: {
color: '#2C6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 1, color: '#2c6288' },
{ offset: 0, color: '#F4F6F8' },
],
},
},
},
selectedDataBackground: {
lineStyle: {
color: '#2C6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 1, color: '#2c6288' },
{ offset: 0, color: '#F4F6F8' },
],
},
},
},
fillerColor: 'rgba(44, 98, 136, 0.3)',
realtime: false,
},
],
})
// showTip markPoint
myCharts.on('showTip', function (params) {
if (params) {
const dataIndex = params.dataIndex
const x = myCharts.getOption().xAxis[0].data[dataIndex]
const y = myCharts.getOption().series[0].data[dataIndex]
myCharts.setOption({
series: [
{
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [{ coord: [x, y] }],
},
},
],
})
}
})
// markPoint
myCharts.on('globalout', function () {
myCharts.setOption({
series: [
{
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [],
},
},
],
})
})
myCharts.on('dataZoom', function (params) {
// dataZoom
const option = myCharts.getOption()
const xAxisData = option.xAxis[0].data
const dataZoom = option.dataZoom[1] || option.dataZoom[0]
// dataZoom startValue endValue
let startValue = dataZoom.endValue
let endValue = dataZoom.startValue
//
if (typeof startValue === 'number') {
startValue = xAxisData[startValue]
}
if (typeof endValue === 'number') {
endValue = xAxisData[endValue]
}
//
state.selectHistoricStartDate = new Date(startValue)
state.selectHistoricEndDate = new Date(endValue)
})
}
onMounted(() => {
getHistoricalData()
})
//
const getHistoricalData = async () => {
let now = new Date()
let toDate =
now.getFullYear() +
'-' +
String(now.getMonth() + 1).padStart(2, '0') +
'-' +
String(now.getDate()).padStart(2, '0')
let url =
'https://common.szjixun.cn/api/stock/history/base/list?from=2009-10-07&to=' +
toDate
const res = await axios.get(url)
if (res.status === 200) {
if (res.data.status === 0) {
initEcharts(res.data.data)
}
}
}
//
function findClosestDateIndex(data, targetDateStr) {
let left = 0,
right = data.length - 1
const target = new Date(targetDateStr).getTime()
let res = data.length - 1 //
while (left <= right) {
const mid = Math.floor((left + right) / 2)
const midTime = new Date(data[mid].date).getTime()
if (midTime > target) {
left = mid + 1
} else {
res = mid
right = mid - 1
}
}
return res
}
//
function findClosestDateIndexDescLeft(data, targetDateStr) {
let left = 0,
right = data.length - 1
const target = new Date(targetDateStr).getTime()
let res = -1
while (left <= right) {
const mid = Math.floor((left + right) / 2)
const midTime = new Date(data[mid].date).getTime()
if (midTime > target) {
left = mid + 1 // mid
} else {
res = mid // mid <= target
right = mid - 1
}
}
return res
}
//
const changeSearchRange = (range, dateTime) => {
const now = new Date()
let startDate = ''
let endDate = ''
if (range === '1m') {
const last = new Date(now)
last.setMonth(now.getMonth() - 1)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '3m') {
const last = new Date(now)
last.setMonth(now.getMonth() - 3)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === 'YTD') {
startDate = new Date(now.getFullYear(), 0, 1).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '1Y') {
const last = new Date(now)
last.setFullYear(now.getFullYear() - 1)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '5Y') {
const last = new Date(now)
last.setFullYear(now.getFullYear() - 5)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '10Y') {
const last = new Date(now)
last.setFullYear(now.getFullYear() - 10)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === 'Max') {
startDate = ''
endDate = ''
} else if (range === 'startDateTime') {
startDate = dateTime
endDate = ''
} else if (range === 'endDateTime') {
startDate = ''
endDate = dateTime
}
if (startDate || endDate) {
// historicData xAxisData initEcharts
if (
typeof historicData !== 'undefined' &&
typeof xAxisData !== 'undefined'
) {
let startValue = xAxisData[0]
if (startDate) {
const idx = findClosestDateIndex(historicData, startDate)
// historicData[idx].date xAxisData
startValue = new Date(historicData[idx].date).toLocaleDateString(
'en-US',
{
month: 'short',
day: 'numeric',
year: 'numeric',
},
)
}
let endValue = endDate
if (endDate) {
// console.warn(endDate)
const idx = findClosestDateIndexDescLeft(historicData, endDate)
// console.warn(idx)
// historicData[idx].date xAxisData
endValue = new Date(historicData[idx].date).toLocaleDateString(
'en-US',
{
month: 'short',
day: 'numeric',
year: 'numeric',
},
)
// console.warn(endValue)
}
if (startDate) {
myCharts.setOption({
dataZoom: {
endValue: startValue,
},
})
state.selectHistoricStartDate = new Date(startValue)
}
if (endDate) {
myCharts.setOption({
dataZoom: {
startValue: endValue,
},
})
state.selectHistoricEndDate = new Date(endValue)
}
}
} else {
myCharts.setOption({
dataZoom: {
startValue: '',
endValue: '',
},
})
state.selectHistoricStartDate = new Date('2009-10-07')
state.selectHistoricEndDate = new Date()
}
}
// 2009-10-07
const disableAfterDate = (date) => {
return date < new Date('2009-10-06') || date > new Date()
}
//
const disablePreviousDate = (date) => {
return date < new Date(state.selectHistoricStartDate) || date > new Date()
}
//
const changeSearchRangeStartDate = (date) => {
// console.error(date)
changeSearchRange(
'startDateTime',
new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}),
)
}
//
const changeSearchRangeEndDate = (date) => {
// console.error(date)
changeSearchRange(
'endDateTime',
new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}),
)
}
</script>
<style lang="scss" scoped>
.custom-echarts {
.myChart {
width: 100%;
height: 25rem;
}
.echarts-header {
.echarts-header-title {
span {
font-size: 2rem;
font-weight: 600;
color: #323232;
}
}
.echarts-search-area {
padding: 2rem 0 0;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
.echarts-search-byRange {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 0.7rem;
.search-range-list {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 0.7rem;
.search-range-list-each {
padding: 0.2rem 0.3rem;
border-radius: 5px;
background-color: #f3f4f6;
cursor: pointer;
span {
font-weight: 600;
font-size: 0.9rem;
}
}
}
}
.echarts-search-byDate {
padding: 1.5rem 0 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 0.4rem;
}
}
}
}
</style>

View File

@ -3,6 +3,8 @@ import { computed } from 'vue'
import { useWindowSize } from '@vueuse/core'
import size375 from '@/components/customFooter/size375/index.vue'
import size768 from '@/components/customFooter/size768/index.vue'
import size1440 from '@/components/customFooter/size1920/index.vue'
import size1920 from '@/components/customFooter/size1920/index.vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
@ -13,15 +15,13 @@ const { t } = useI18n()
const viewComponent = computed(() => {
const viewWidth = width.value
if (viewWidth <= 450) {
if (viewWidth <= 500) {
return size375
}
// else if (viewWidth <= 1100) {
// return size768;
// } else if (viewWidth <= 1500) {
// return size1440;
// }
else if (viewWidth <= 1920 || viewWidth > 1920) {
} else if (viewWidth <= 1100) {
return size768
} else if (viewWidth <= 1500) {
return size1440
} else if (viewWidth <= 1920 || viewWidth > 1920) {
return size1920
}
})

View File

@ -1,26 +1,69 @@
<template>
<!-- 通用页脚 -->
<div class="custom-footer">
<span>Copyright &copy; 2024-2027 FiEE</span>
<!-- 通用页脚 -->
<div class="custom-footer">
<div class="custom-footer-box">
<span>&copy; 2025 FiEE, Inc. All Rights Reserved.</span>
<div class="footer-links">
<span @click="handleLink('privacyPolicy')">Privacy Policy</span>
<span @click="handleLink('termsOfUse')">Terms of use</span>
<span @click="handleLink('siteMap')">Site Map</span>
</div>
</div>
</template>
<script setup></script>
<style scoped lang="scss">
.custom-footer {
width: 100%;
text-align: center;
padding: 24px 0;
color: #888;
font-size: 15px;
background: #f7f8fa;
</div>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
import privacyPolicy from "@/assets/file/footer/FiEE, Inc. _ Privacy policy.pdf";
import termsOfUse from "@/assets/file/footer/FiEE, Inc. _ Terms of Use.pdf";
import siteMap from "@/assets/file/footer/FiEE, Inc. _ Site Map.pdf";
//
const handleLink = (link) => {
// if (link === "privacyPolicy") {
// window.open(privacyPolicy, "_blank");
// } else if (link === "termsOfUse") {
// window.open(termsOfUse, "_blank");
// } else if (link === "siteMap") {
// window.open(siteMap, "_blank");
// }
router.push(link)
};
</script>
<style scoped lang="scss">
.custom-footer {
width: 100%;
background: #f7f8fa;
border-top: 1px solid #ececec;
z-index: 100;
.custom-footer-box {
max-width: 1700px;
margin: 0 auto;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
letter-spacing: 1px;
border-top: 1px solid #ececec;
position: fixed;
left: 0;
bottom: 0;
z-index: 100;
color: #888;
// font-size: 15px;
font-size: 1.05rem;
padding: 1rem 40px;
text-align: center;
}
</style>
.footer-links {
margin: 0.4rem 0 0;
span {
border-right: 1px solid #d2d2d7;
padding: 0 10px;
cursor: pointer;
}
span:nth-last-child(1) {
border: 0;
}
}
}
</style>

View File

@ -1,26 +1,70 @@
<template>
<!-- 通用页脚 -->
<div class="custom-footer">
<span>Copyright &copy; 2024-2027 FiEE</span>
<!-- 通用页脚 -->
<div class="custom-footer">
<span>&copy; 2025 FiEE, Inc. All Rights Reserved.</span>
<div class="footer-links-box">
<div class="footer-links">
<span @click="handleLink('privacyPolicy')">Privacy Policy</span>
<span @click="handleLink('termsOfUse')">Terms of use</span>
<span @click="handleLink('siteMap')">Site Map</span>
</div>
</div>
</template>
<script setup></script>
<style scoped lang="scss">
.custom-footer {
width: 100%;
text-align: center;
padding: 120px 0;
color: #888;
font-size: 75px;
background: #f7f8fa;
letter-spacing: 5px;
border-top: 5px solid #ececec;
position: fixed;
left: 0;
bottom: 0;
z-index: 100;
</div>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
import privacyPolicy from "@/assets/file/footer/FiEE, Inc. _ Privacy policy.pdf";
import termsOfUse from "@/assets/file/footer/FiEE, Inc. _ Terms of Use.pdf";
import siteMap from "@/assets/file/footer/FiEE, Inc. _ Site Map.pdf";
//
const handleLink = (link) => {
// if (link === "privacyPolicy") {
// window.open(privacyPolicy, "_blank");
// } else if (link === "termsOfUse") {
// window.open(termsOfUse, "_blank");
// } else if (link === "siteMap") {
// window.open(siteMap, "_blank");
// }
router.push(link)
};
</script>
<style scoped lang="scss">
.custom-footer {
width: 100%;
text-align: center;
padding: 1rem 0;
color: #888;
font-size: 0.9rem;
background: #f7f8fa;
letter-spacing: 5px;
border-top: 5px solid #ececec;
z-index: 100;
.footer-links-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0.6rem 0 0;
.footer-links {
span {
border-right: 1px solid #d2d2d7;
padding: 0 0.8rem;
cursor: pointer;
font-size: 0.75rem;
display: inline-block;
text-align: left;
}
span:nth-last-child(1) {
border: 0;
}
}
}
</style>
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<!-- 通用页脚 -->
<div class="custom-footer">
<span>&copy; 2025 FiEE, Inc. All Rights Reserved.</span>
<div class="footer-links">
<span @click="handleLink('privacyPolicy')">Privacy Policy</span>
<span @click="handleLink('termsOfUse')">Terms of use</span>
<span @click="handleLink('siteMap')">Site Map</span>
</div>
</div>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
import privacyPolicy from "@/assets/file/footer/FiEE, Inc. _ Privacy policy.pdf";
import termsOfUse from "@/assets/file/footer/FiEE, Inc. _ Terms of Use.pdf";
import siteMap from "@/assets/file/footer/FiEE, Inc. _ Site Map.pdf";
//
const handleLink = (link) => {
// if (link === "privacyPolicy") {
// window.open(privacyPolicy, "_blank");
// } else if (link === "termsOfUse") {
// window.open(termsOfUse, "_blank");
// } else if (link === "siteMap") {
// window.open(siteMap, "_blank");
// }
router.push(link)
};
</script>
<style scoped lang="scss">
.custom-footer {
width: 100%;
text-align: center;
padding: 24px 0;
color: #888;
// font-size: 15px;
font-size: 1.05rem;
background: #f7f8fa;
letter-spacing: 1px;
border-top: 1px solid #ececec;
z-index: 100;
padding: 1rem 0;
.footer-links {
margin: 0.4rem 0 0;
span {
border-right: 1px solid #d2d2d7;
padding: 0 10px;
cursor: pointer;
}
span:nth-last-child(1) {
border: 0;
}
}
}
</style>

View File

@ -3,6 +3,8 @@ import { computed } from 'vue'
import { useWindowSize } from '@vueuse/core'
import size375 from '@/components/customHeader/size375/index.vue'
import size768 from '@/components/customHeader/size375/index.vue'
import size1440 from '@/components/customHeader/size1440/index.vue'
import size1920 from '@/components/customHeader/size1920/index.vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
@ -15,13 +17,11 @@ const viewComponent = computed(() => {
const viewWidth = width.value
if (viewWidth <= 450) {
return size375
}
// else if (viewWidth <= 1100) {
// return size768;
// } else if (viewWidth <= 1500) {
// return size1440;
// }
else if (viewWidth <= 1920 || viewWidth > 1920) {
} else if (viewWidth <= 835) {
return size768
} else if (viewWidth <= 1640) {
return size1440
} else if (viewWidth <= 1920 || viewWidth > 1920) {
return size1920
}
})

View File

@ -0,0 +1,230 @@
<template>
<!-- 通用页头 -->
<NLayoutHeader
class="custom-header"
:class="{ 'header-scrolled': isScrolled }"
>
<div class="header-container">
<div class="logo" @click="handleToHome">
<NImage width="160" height="50" :src="FiEELogo" preview-disabled />
</div>
<div class="header-menu">
<NMenu
mode="horizontal"
:options="menuOptions"
:inverted="isScrolled"
v-model:value="selectedKey"
@update:value="handleMenuSelect"
/>
</div>
</div>
</NLayoutHeader>
</template>
<script setup>
import FiEELogo from '@/assets/image/header/logo.png'
import { ref, onMounted, onUnmounted } from 'vue'
import { NMenu, NLayoutHeader, NImage } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useHeaderMenuConfig } from '@/config/headerMenuConfig'
const { t } = useI18n()
const router = useRouter()
// 使
const menuOptions = useHeaderMenuConfig()
const selectedKey = ref(null)
const isScrolled = ref(false)
//
function findMenuOptionByKey(options, key) {
for (const option of options) {
if (option.key === key) return option
if (option.children) {
const found = findMenuOptionByKey(option.children, key)
if (found) return found
}
}
return null
}
//
const handleMenuSelect = (key) => {
const option = findMenuOptionByKey(menuOptions, key)
if (option && option.href) {
router.push(option.href)
}
}
//
const handleScroll = () => {
//100pxheader
isScrolled.value = window.scrollY >= 100
}
onMounted(() => {
window.addEventListener('scroll', handleScroll)
})
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
})
//
const handleToHome = () => {
router.push('/myhome')
selectedKey.value = null //
}
</script>
<style scoped lang="scss">
.custom-header {
--header-height: 5rem;
--primary-color: #8b59f7;
transition: all 0.3s ease;
background: transparent;
height: var(--header-height);
&.header-scrolled {
background: rgba(220, 207, 248, 0.95);
backdrop-filter: blur(8px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
}
.header-container {
max-width: 1700px;
margin: 0 auto;
padding: 0 40px;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
flex-shrink: 0;
cursor: pointer;
transition: transform 0.3s ease;
margin-right: 100px;
&:hover {
transform: scale(1.05);
}
&:active {
transform: scale(0.98);
}
}
.header-menu {
display: block;
flex: 1;
:deep(.n-menu) {
background: transparent;
justify-content: flex-end;
}
:deep(.n-menu-item) {
position: relative;
margin: 0 10px;
transition: all 0.3s ease;
font-weight: 700;
// font-size: 16px;
font-size: 0.875rem;
min-width: 120px;
text-align: center;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 2px;
background: var(--primary-color);
transition: all 0.3s ease;
transform: translateX(-50%);
opacity: 0;
border-radius: 2px;
}
&:hover {
&::after {
width: 80px;
height: 3px;
opacity: 1;
}
}
//
&.n-menu-item--selected {
&::after {
width: 40px;
opacity: 1;
}
}
}
//
:deep(.n-submenu) {
.n-submenu-children {
backdrop-filter: blur(16px);
background: rgba(255, 255, 255, 0.9);
border-radius: 12px;
padding: 8px 0;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
transform-origin: top;
animation: dropDown 0.3s ease;
.n-menu-item {
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--primary-color);
transform: translateX(-100%);
transition: transform 0.3s ease;
opacity: 0.1;
z-index: -1;
}
&:hover {
&::before {
transform: translateX(0);
}
}
}
}
}
}
@keyframes dropDown {
from {
opacity: 0;
transform: translateY(-10px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
</style>
<style>
.header-menu .n-menu .n-menu-item-content .n-menu-item-content-header {
word-break: break-word;
white-space: unset !important;
}
.header-menu .n-menu .n-submenu .n-menu-item-content{
padding: 0 8px!important;
}
</style>

View File

@ -41,32 +41,32 @@ const isScrolled = ref(false)
//
function findMenuOptionByKey(options, key) {
for (const option of options) {
if (option.key === key) return option;
if (option.key === key) return option
if (option.children) {
const found = findMenuOptionByKey(option.children, key);
if (found) return found;
const found = findMenuOptionByKey(option.children, key)
if (found) return found
}
}
return null;
return null
}
//
const handleMenuSelect = (key) => {
const option = findMenuOptionByKey(menuOptions, key);
const option = findMenuOptionByKey(menuOptions, key)
if (option && option.href) {
router.push(option.href);
router.push(option.href)
}
};
}
//
const handleScroll = () => {
//100pxheader
isScrolled.value = window.scrollY >= 100;
};
isScrolled.value = window.scrollY >= 100
}
onMounted(() => {
window.addEventListener("scroll", handleScroll);
});
window.addEventListener('scroll', handleScroll)
})
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
@ -74,26 +74,21 @@ onUnmounted(() => {
//
const handleToHome = () => {
router.push('/')
router.push('/myhome')
selectedKey.value = null //
}
</script>
<style scoped lang="scss">
.custom-header {
--header-height: 80px;
--primary-color: #8B59F7;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
--header-height: 5rem;
--primary-color: #8b59f7;
transition: all 0.3s ease;
background: transparent;
height: var(--header-height);
&.header-scrolled {
background: rgba(255, 255, 255, 0.95);
background: rgba(220, 207, 248, 0.95);
backdrop-filter: blur(8px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
@ -138,7 +133,8 @@ const handleToHome = () => {
margin: 0 20px;
transition: all 0.3s ease;
font-weight: 700;
font-size: 16px;
// font-size: 16px;
font-size: 1.05rem;
min-width: 120px;
text-align: center;

View File

@ -108,11 +108,6 @@ const handleToHome = () => {
<style scoped lang="scss">
.custom-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
transition: all 0.3s ease;
background: transparent;
height: 320px;
@ -192,7 +187,7 @@ const handleToHome = () => {
top: 320px;
left: 0;
width: 100vw;
background: #fff;
background: rgba(220, 207, 248, 0.95);
z-index: 1100;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
padding: 40px 0 80px 0;

View File

@ -1,38 +0,0 @@
<template>
<div class="custom-select-search">
<n-select
:options="state.selectOptions"
v-model:value="state.selectedValue"
/>
<n-input
v-model:value="state.inputValue"
type="text"
placeholder="Search"
/>
<n-button type="primary">Go</n-button>
</div>
</template>
<script setup>
import { reactive } from 'vue'
import { NSelect, NInput, NButton } from 'naive-ui'
const state = reactive({
selectedValue: 'all_years', //
selectOptions: [
{
label: 'All Years',
value: 'all_years',
},
], //
inputValue: '', //
})
</script>
<style scoped lang="scss">
.custom-select-search {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 10px;
}
</style>

View File

@ -1,105 +1,122 @@
import { useI18n } from 'vue-i18n'
import { useI18n } from "vue-i18n";
export const useHeaderMenuConfig = () => {
const { t } = useI18n()
const { t } = useI18n();
return [
{
label: t('header_menu.corporate_information.title'),
key: 'corporate_information',
label: t("header_menu.corporate_information.title"),
key: "corporate_information",
children: [
{
label: t('header_menu.corporate_information.company_overview'),
key: 'company_overview',
label: t("header_menu.corporate_information.company_overview"),
key: "company_overview",
href: "/companyoverview",
},
{
label: t('header_menu.corporate_information.business_introduction'),
key: 'business_introduction',
label: t("header_menu.corporate_information.business_introduction"),
key: "business_introduction",
href: "/businessservices",
},
{
label: t('header_menu.corporate_information.management'),
key: 'management',
label: t("header_menu.corporate_information.management"),
key: "management",
href: "/manage",
},
{
label: t('header_menu.corporate_information.board_of_directors'),
key: 'board_of_directors',
label: t("header_menu.corporate_information.board_of_directors"),
key: "board_of_directors",
href: "/boarddirectors",
},
{
label: t('header_menu.corporate_information.committee_appointments'),
key: 'committee_appointments',
label: t("header_menu.corporate_information.committee_appointments"),
key: "committee_appointments",
href: "/committeeappointment",
},
{
label: t('header_menu.corporate_information.governance'),
key: 'governance',
label: t("header_menu.corporate_information.governance"),
key: "governance",
href: "/govern",
},
// {
// label: t("header_menu.corporate_information.corporate_video"),
// key: "corporate_video",
// },//暂时隐藏本菜单
],
},
{
label: t("header_menu.financial_information.title"),
key: "financial_information",
children: [
{
label: t("header_menu.financial_information.sec_filings"),
key: "sec_filings",
href: "/secfilings",
},
{
label: t("header_menu.financial_information.annual_reports"),
key: "annual_reports",
href: "/annualreports",
},
{
label: t('header_menu.corporate_information.corporate_video'),
key: 'corporate_video',
label: t("header_menu.financial_information.quarterly_reports"),
key: "quarterly_reports",
href: "/quarterlyreports",
},
],
},
{
label: t('header_menu.financial_information.title'),
key: 'financial_information',
label: t("header_menu.stock_information.title"),
key: "stock_information",
children: [
{
label: t('header_menu.financial_information.sec_filings'),
key: 'sec_filings',
href: "/secfilings",
label: t("header_menu.stock_information.stock_quote"),
key: "stock_quote",
href:'/stock-quote'
},
{
label: t('header_menu.financial_information.quarterly_results'),
key: 'quarterly_results',
href: "/quarterlyresults",
label: t("header_menu.stock_information.historic_stock_price"),
key: "historic_stock_price",
href:'/historic-stock'
},
// {
// label: t("header_menu.stock_information.investment_calculator"),
// key: "investment_calculator",
// href:'/calculator'
// },
],
},
{
label: t("header_menu.news_releases.title"),
key: "news_releases",
children: [
{
label: t("header_menu.news_releases.press_releases"),
key: "press_releases",
href: "/press-releases",
},
{
label: t("header_menu.news_releases.events_calendar"),
key: "events_calendar",
href: "/events-calendar",
},
],
},
{
label: t('header_menu.stock_information.title'),
key: 'stock_information',
label: t("header_menu.investor_resources.title"),
key: "investor_resources",
children: [
{
label: t('header_menu.stock_information.stock_quote'),
key: 'stock_quote',
label: t("header_menu.investor_resources.ir_contacts"),
key: "ir_contacts",
href:'/contacts'
},
{
label: t('header_menu.stock_information.historic_stock_price'),
key: 'historic_stock_price',
},
{
label: t('header_menu.stock_information.investment_calculator'),
key: 'investment_calculator',
label: t("header_menu.investor_resources.email_alerts"),
key: "email_alerts",
href:'/email-alerts'
},
],
},
{
label: t('header_menu.news_releases.title'),
key: 'news_releases',
children: [
{
label: t('header_menu.news_releases.press_releases'),
key: 'press_releases',
href: '/press-releases',
},
{
label: t('header_menu.news_releases.events_calendar'),
key: 'events_calendar',
},
],
},
{
label: t('header_menu.investor_resources.title'),
key: 'investor_resources',
children: [
{
label: t('header_menu.investor_resources.ir_contacts'),
key: 'ir_contacts',
},
{
label: t('header_menu.investor_resources.email_alerts'),
key: 'email_alerts',
},
],
},
]
}
];
};

35754
src/dict/secFiles.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,16 @@
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { setupRouterGuards } from './router-guards'
import { createRouter, createWebHistory } from "vue-router";
import { setupRouterGuards } from "./router-guards";
const routes = [
{
path: '/',
name: 'index',
component: () => import('@/views/index/index.vue'),
path: "/",
redirect: '/myhome'
},
{
path: "/",
name: "index",
component: () => import("@/views/index/index.vue"),
// beforeEnter: (to, from, next) => {
// localStorage.clear()
@ -14,59 +18,132 @@ const routes = [
// }
children: [
{
path: 'home',
name: 'home',
component: () => import('@/views/home/index.vue'),
// beforeEnter: (to, from, next) => {
// localStorage.clear()
// next()
// }
},
{
path: '/stock-quote',
name: 'stock-quote',
component: () => import('@/views/stock-quote/index.vue'),
},
{
path: '/historic-stock',
name: 'historic-stock',
component: () => import('@/views/historic-stock/index.vue'),
},
{
path: '/contacts',
name: 'contacts',
component: () => import('@/views/contacts/index.vue'),
},
{
path: '/email-alerts',
name: 'email-alerts',
component: () => import('@/views/email-alerts/index.vue'),
path: "/contacts",
name: "contacts",
component: () => import("@/views/contacts/index.vue"),
},
{
path: 'press-releases',
name: 'press-releases',
component: () => import('@/views/press-releases/index.vue'),
path: "/calculator",
name: "calculator",
component: () => import("@/views/calculator/index.vue"),
},
{
path: '/quarterlyresults',
name: 'QuarterlyResults',
path: "/stock-quote",
name: "stock-quote",
component: () => import("@/views/stock-quote/index.vue"),
},
{
path: "/historic-stock",
name: "historic-stock",
component: () => import("@/views/historic-stock/index.vue"),
},
{
path: "/contacts",
name: "contacts",
component: () => import("@/views/contacts/index.vue"),
},
{
path: "/email-alerts",
name: "email-alerts",
component: () => import("@/views/email-alerts/index.vue"),
},
{
path: "/quarterlyreports",
name: "quarterlyreports",
component: () =>
import('@/views/financialinformation/quarterlyresults/index.vue'),
import("@/views/financialinformation/quarterlyreports/index.vue"),
},
{
path: '/secfilings',
name: 'SecFilings',
path: "/secfilings",
name: "SecFilings",
component: () =>
import('@/views/financialinformation/secfilings/index.vue'),
import("@/views/financialinformation/secfilings/index.vue"),
},
{
path: "/secfilingsDefail",
name: "SecFilingsDetail",
component: () =>
import("@/views/financialinformation/secfilingsdetail/index.vue"),
},
{
path: "/annualreports",
name: "AnnualReports",
component: () =>
import("@/views/financialinformation/annualreports/index.vue"),
},
{
path: "/press-releases",
name: "press-releases",
component: () => import("@/views/press-releases/index.vue"),
},
{
path: "/news",
name: "news",
component: () => import("@/views/news/index.vue"),
},
{
path: "/events-calendar",
name: "events-calendar",
component: () => import("@/views/events-calendar/index.vue"),
},
{
path: "/companyoverview",
name: "companyoverview",
component: () => import("@/views/companyoverview/index.vue"),
},
{
path: "myhome",
name: "myHome",
component: () => import("@/views/myHome/index.vue"),
},
{
path: "/businessservices",
name: "BusinessServices",
component: () => import("@/views/BusinessServices/index.vue"),
},
{
path: "/manage",
name: "manage",
component: () => import("@/views/manage/index.vue"),
},
{
path: "/boarddirectors",
name: "boarddirectors",
component: () => import("@/views/boarddirectors/index.vue"),
},
{
path: "/committeeappointment",
name: "CommitteeAppointment",
component: () => import("@/views/CommitteeAppointment/index.vue"),
},
{
path: "/govern",
name: "govern",
component: () => import("@/views/govern/index.vue"),
},
{
path: "/privacyPolicy",
name: "privacyPolicy",
component: () => import("@/views/footerLinks/privacyPolicy/index.vue"),
},
{
path: "/termsOfUse",
name: "termsOfUse",
component: () => import("@/views/footerLinks/termsOfUse/index.vue"),
},
{
path: "/cookiesSettings",
name: "cookiesSettings",
component: () => import("@/views/footerLinks/cookiesSettings/index.vue"),
},
{
path: "/siteMap",
name: "siteMap",
component: () => import("@/views/footerLinks/siteMap/index.vue"),
},
],
},
{
path: '/contacts',
name: 'contacts',
component: () => import('@/views/contacts/index.vue'),
},
// {
// path: '/companyprofil',
// name: 'Companyprofil',
@ -92,17 +169,21 @@ const routes = [
// name: 'Investorhandbook',
// component: () => import('@/views/investorhandbook/index.vue'),
// },
]
{
path: '/:pathMatch(.*)*',
redirect: '/myhome'
}
];
const router = createRouter({
history: createWebHistory(),
routes,
})
});
router.beforeEach((to, from, next) => {
if (to.meta?.title) {
document.title = to.meta.title
document.title = to.meta.title;
}
next()
})
setupRouterGuards(router)
export default router
next();
});
setupRouterGuards(router);
export default router;

View File

@ -15,7 +15,7 @@ import { useRouter } from "vue-router";
import { showImagePreview } from "vant";
export const useAuth = createGlobalState(() => {
console.log("useRouter", useRouter);
// console.log("useRouter", useRouter);
const router = useRouter();
const token = useStorage("token", "", localStorage);
const workUid = useStorage("workUid", "", localStorage);
@ -44,7 +44,7 @@ export const useAuth = createGlobalState(() => {
const millisecondsIn48Hours = 48 * 60 * 60 * 1000;
voteToken.value.expireTime = currentTimestamp + millisecondsIn48Hours;
voteToken.value.authorization = res.data?.authorization;
console.log("voteToken", voteToken.value);
// console.log("voteToken", voteToken.value);
}
};
const sendVote = async () => {

View File

@ -1,6 +1,9 @@
import { ref } from 'vue'
import { createGlobalState, useLocalStorage } from '@vueuse/core'
import axios from 'axios'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
export const useStockQuote = createGlobalState(() => {
const stockQuote = useLocalStorage('stockQuote', {
@ -14,11 +17,65 @@ export const useStockQuote = createGlobalState(() => {
""
]
})
const getStockQuate= async()=>{
const res = await axios.get('http://localhost:3213/api/minm/open')
stockQuote.value=res.data
}
const date = new Date();
const options = {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true,
timeZone: 'America/New_York',
timeZoneName: 'short'
};
let lastTradingDay
dayjs.extend(utc)
dayjs.extend(timezone)
/*
美股的常规发行日交易日为周一至周五遇到法定假日则顺延
如果你只需要上一个交易日不考虑法定假日的情况下
获取当前美东时间
如果今天是周一则上一个交易日为上周五
如果今天是周日则上一个交易日为上周五
如果今天是周六则上一个交易日为周五
其他情况上一个交易日为昨天
*/
const getLastTradingDay = async () => {
const toDate = dayjs().format('YYYY-MM-DD');
const finalFromDate = dayjs().subtract(7, 'day').format('YYYY-MM-DD');
let url =
'https://common.szjixun.cn/api/stock/history/list?from=' +
finalFromDate +
'&to=' +
toDate;
const res = await axios.get(url)
if (res.status === 200) {
if (res.data.status === 0) {
lastTradingDay = dayjs(res.data.data[0].date)
}
return lastTradingDay.format('MMM D, YYYY') + ' 4:00 PM EDT'
}
}
const formatted = ref(null)
const init = async () => {
formatted.value = await getLastTradingDay()
}
init()
const getStockQuate = async () => {
// const res = await axios.get('https://saas-test.szjixun.cn/api/fiee/chart/forward/test')
const res = await axios.get('https://common.szjixun.cn/api/stock/company/data')
// console.error(res)
if (res.status === 200) {
if (res.data.status === 0) {
stockQuote.value = res.data.data
}
}
}
return {
formatted,
getStockQuate,
stockQuote
}

View File

@ -0,0 +1,34 @@
<script setup>
import { computed } from "vue";
import { useWindowSize } from "@vueuse/core";
import size375 from "./size375/index.vue";
import size768 from "./size768/index.vue";
import size1440 from "./size1440/index.vue";
import size1920 from "./size1920/index.vue";
import { useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
const router = useRouter();
const { width } = useWindowSize();
const { t } = useI18n();
const viewComponent = computed(() => {
const viewWidth = width.value;
if (viewWidth <= 450) {
return size375;
} else if (viewWidth <= 1100) {
return size768;
} else if (viewWidth <= 1500) {
return size1440;
} else if (viewWidth <= 1920 || viewWidth > 1920) {
return size1920;
}
});
</script>
<template>
<component :is="viewComponent" />
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,265 @@
<template>
<div class="home-page">
<div class="business-page">
<!-- 渐变背景标题区 - 增加层次感 -->
<section class="hero-section">
<div class="container">
<h1 style="font-size: 40px" class="hero-title">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.TITLE") }}
</h1>
<div style="font-size: 18px" class="hero-description">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.CONTENT") }}
</div>
</div>
</section>
<!-- 业务核心解决方案 -->
<main style="margin-top: 40px" class="container">
<section>
<h1 class="hero-title" style="font-size: 22px">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.CONTENTTWO") }}
</h1>
</section>
<div class="solution-grid">
<!-- 统一使用弹性列布局通过媒体查询控制排列方式 -->
<div
v-for="(solution, sIndex) in solutions"
:key="sIndex"
class="featured-solution"
>
<div
class="solution-card"
:style="{ '--delay': `${sIndex * 0.2}s` }"
>
<div class="card-header">
<div class="decorative-line"></div>
<h2 class="card-title">{{ solution.title }}</h2>
</div>
<ul class="card-content">
<li
v-for="(point, pIndex) in solution.points"
:key="pIndex"
class="content-point"
>
<div class="point-icon"></div>
<div style="font-size: 18px" class="point-text">
{{ point }}
</div>
</li>
</ul>
</div>
</div>
</div>
</main>
</div>
</div>
</template>
<script setup>
import { useI18n } from "vue-i18n";
import { computed } from "vue";
const { t } = useI18n();
const solutions = computed(() => [
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.CONTENTTWO"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.CONTENTTWO"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENTTWO"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENTTHREE"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENTTWO"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENTTHREE"),
],
},
]);
</script>
<style scoped>
/* 基础样式 */
.hero-title {
font-size: 40px;
color: black;
margin-bottom: 2rem;
animation: slideIn 1s ease;
}
.hero-description {
margin: 0 auto;
font-size: 1.1rem;
line-height: 1.8;
color: black;
}
:root {
--primary-color: #895bff;
--primary-light: #a07cff;
--primary-dark: #6a11cb;
--primary-gradient: linear-gradient(
135deg,
var(--primary-light) 0%,
var(--primary-color) 100%
);
}
.home-page {
background-image: url("@/assets/image/bg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.container {
max-width: 1280px;
margin: 0 auto;
padding: 0 2rem;
}
/* 标题区 - 紫色渐变 */
.hero-section {
background: var(--primary-gradient);
padding: 5rem 0 0rem;
position: relative;
overflow: hidden;
color: white;
}
.title-decoration {
position: absolute;
bottom: -15px;
left: 0;
width: 80%;
height: 4px;
background: rgba(255, 255, 255, 0.5);
border-radius: 2px;
}
.solution-group {
display: flex;
flex-direction: column;
gap: 3rem;
margin-bottom: 4rem;
}
/* 桌面端布局(>=768px */
@media (min-width: 768px) {
.solution-group {
flex-direction: row;
gap: 4rem;
}
.featured-solution {
flex: 1;
margin-top: 20px;
}
}
/* 移动端布局(<768px */
@media (max-width: 767px) {
.home-page {
background-image: url("@/assets/image/bg-mobile.png");
}
.hero-title {
font-size: 1.8rem;
}
.solution-group {
flex-direction: column;
gap: 2rem;
}
.featured-solution {
width: 100% !important; /* 强制占满容器 */
margin-bottom: 2rem;
}
.solution-card {
padding: 2rem;
height: auto;
}
.content-point {
padding: 1rem 0;
}
}
/* 卡片公共样式 */
.solution-card {
border-radius: 16px;
padding: 2.5rem;
transform: translateY(20px);
opacity: 0;
animation: cardEnter 0.6s ease forwards;
border: 1px solid rgba(137, 91, 255, 0.2);
background: linear-gradient(135deg, #f9f6ff 0%, #f0e9ff 100%);
}
.card-header {
margin-bottom: 2rem;
}
.decorative-line {
width: 50px;
height: 3px;
background: var(--primary-gradient);
margin-bottom: 1rem;
border-radius: 3px;
}
.solution-card:hover .decorative-line {
width: 80px;
}
.card-title {
font-size: 1.3rem;
color: #2c0850;
font-weight: 600;
}
.content-point {
display: flex;
gap: 1rem;
padding: 1rem 0;
border-bottom: 1px solid rgba(137, 91, 255, 0.1);
}
.point-icon {
color: #4a3a6b;
font-size: 1.2rem;
flex-shrink: 0;
}
.point-text {
color: #4a3a6b;
line-height: 1.6;
font-size: 18px;
}
@keyframes cardEnter {
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -0,0 +1,265 @@
<template>
<div class="home-page">
<div class="business-page">
<!-- 渐变背景标题区 - 增加层次感 -->
<section class="hero-section">
<div class="container">
<h2 class="hero-title">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.TITLE") }}
</h2>
<div style="font-size: 18px" class="hero-description">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.CONTENT") }}
</div>
</div>
</section>
<!-- 业务核心解决方案 -->
<main style="margin-top: 40px" class="container">
<section>
<h1 style="font-size: 30px" class="hero-title">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.CONTENTTWO") }}
</h1>
</section>
<div class="solution-grid">
<!-- 统一使用弹性列布局通过媒体查询控制排列方式 -->
<div
v-for="(solution, sIndex) in solutions"
:key="sIndex"
class="featured-solution"
>
<div
class="solution-card"
:style="{ '--delay': `${sIndex * 0.2}s` }"
>
<div class="card-header">
<div class="decorative-line"></div>
<h2 class="card-title">{{ solution.title }}</h2>
</div>
<ul class="card-content">
<li
v-for="(point, pIndex) in solution.points"
:key="pIndex"
class="content-point"
>
<div class="point-icon"></div>
<div style="font-size: 18px" class="point-text">
{{ point }}
</div>
</li>
</ul>
</div>
</div>
</div>
</main>
</div>
</div>
</template>
<script setup>
import { useI18n } from "vue-i18n";
import { computed } from "vue";
const { t } = useI18n();
const solutions = computed(() => [
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.CONTENTTWO"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.CONTENTTWO"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENTTWO"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENTTHREE"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENTTWO"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENTTHREE"),
],
},
]);
</script>
<style scoped>
/* 基础样式 */
.hero-title {
font-size: 40px;
color: black;
margin-bottom: 2rem;
animation: slideIn 1s ease;
}
.hero-description {
margin: 0 auto;
font-size: 1.1rem;
line-height: 1.8;
color: black;
}
:root {
--primary-color: #895bff;
--primary-light: #a07cff;
--primary-dark: #6a11cb;
--primary-gradient: linear-gradient(
135deg,
var(--primary-light) 0%,
var(--primary-color) 100%
);
}
.home-page {
background-image: url("@/assets/image/bg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.container {
max-width: 1280px;
margin: 0 auto;
padding: 0 2rem;
}
/* 标题区 - 紫色渐变 */
.hero-section {
background: var(--primary-gradient);
padding: 5rem 0 0rem;
position: relative;
overflow: hidden;
color: white;
}
.title-decoration {
position: absolute;
bottom: -15px;
left: 0;
width: 80%;
height: 4px;
background: rgba(255, 255, 255, 0.5);
border-radius: 2px;
}
.solution-group {
display: flex;
flex-direction: column;
gap: 3rem;
margin-bottom: 4rem;
}
/* 桌面端布局(>=768px */
@media (min-width: 768px) {
.solution-group {
flex-direction: row;
gap: 4rem;
}
.featured-solution {
flex: 1;
margin-top: 20px;
}
}
/* 移动端布局(<768px */
@media (max-width: 767px) {
.home-page {
background-image: url("@/assets/image/bg-mobile.png");
}
.hero-title {
font-size: 1.8rem;
}
.solution-group {
flex-direction: column;
gap: 2rem;
}
.featured-solution {
width: 100% !important; /* 强制占满容器 */
margin-bottom: 2rem;
}
.solution-card {
padding: 2rem;
height: auto;
}
.content-point {
padding: 1rem 0;
}
}
/* 卡片公共样式 */
.solution-card {
border-radius: 16px;
padding: 2.5rem;
transform: translateY(20px);
opacity: 0;
animation: cardEnter 0.6s ease forwards;
border: 1px solid rgba(137, 91, 255, 0.2);
background: linear-gradient(135deg, #f9f6ff 0%, #f0e9ff 100%);
}
.card-header {
margin-bottom: 2rem;
}
.decorative-line {
width: 50px;
height: 3px;
background: var(--primary-gradient);
margin-bottom: 1rem;
border-radius: 3px;
}
.solution-card:hover .decorative-line {
width: 80px;
}
.card-title {
font-size: 1.3rem;
color: #2c0850;
font-weight: 600;
}
.content-point {
display: flex;
gap: 1rem;
padding: 1rem 0;
border-bottom: 1px solid rgba(137, 91, 255, 0.1);
}
.point-icon {
color: #4a3a6b;
font-size: 1.2rem;
flex-shrink: 0;
}
.point-text {
color: #4a3a6b;
line-height: 1.6;
font-size: 18px;
}
@keyframes cardEnter {
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -0,0 +1,250 @@
<template>
<div class="home-page">
<div class="business-page">
<!-- 渐变背景标题区 -->
<section class="hero-section">
<div class="container">
<h1 style="font-size: 40px" class="hero-title">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.TITLE") }}
</h1>
<div style="font-size: 18px" class="hero-description">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.CONTENT") }}
</div>
</div>
</section>
<!-- 业务核心解决方案 -->
<main style="margin-top: 40px" class="container">
<section>
<h1 style="font-size: 40px" class="hero-title">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.CONTENTTWO") }}
</h1>
</section>
<!-- 解决方案网格 - 响应式弹性布局 -->
<div class="solution-grid">
<!-- 统一使用弹性列布局通过媒体查询控制排列方式 -->
<div
v-for="(solution, sIndex) in solutions"
:key="sIndex"
class="featured-solution"
>
<div
class="solution-card"
:style="{ '--delay': `${sIndex * 0.2}s` }"
>
<div class="card-header">
<div class="decorative-line"></div>
<h2 class="card-title">{{ solution.title }}</h2>
</div>
<ul class="card-content">
<li
v-for="(point, pIndex) in solution.points"
:key="pIndex"
class="content-point"
>
<div class="point-icon"></div>
<div style="font-size: 18px" class="point-text">
{{ point }}
</div>
</li>
</ul>
</div>
</div>
</div>
</main>
</div>
</div>
</template>
<script setup>
import { useI18n } from "vue-i18n";
import { computed } from "vue";
const { t } = useI18n();
const solutions = computed(() => [
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.CONTENTTWO"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.CONTENTTWO"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENTTWO"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENTTHREE"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENTTWO"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENTTHREE"),
],
},
]);
</script>
<style scoped>
.container {
margin: 0 auto;
padding: 0 2rem;
}
/* 标题区 - 紫色渐变 */
.hero-section {
background: var(--primary-gradient);
padding: 5rem 0 0rem;
position: relative;
overflow: hidden;
color: white;
}
.hero-title {
font-size: 40px;
color: black;
margin-bottom: 2rem;
animation: slideIn 1s ease;
}
.hero-description {
margin: 0 auto;
font-size: 1.1rem;
line-height: 1.8;
color: black;
}
:root {
--primary-color: #895bff;
--primary-light: #a07cff;
--primary-dark: #6a11cb;
--primary-gradient: linear-gradient(
135deg,
var(--primary-light) 0%,
var(--primary-color) 100%
);
}
.home-page {
background-image: url("@/assets/image/bg-mobile.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
/* 解决方案网格 - 响应式布局 */
.solution-grid {
padding: 0rem 0 4rem;
display: grid;
grid-template-columns: 1fr; /* 默认单列(移动端) */
gap: 3rem;
}
/* 中等屏幕768px-1439px - 单列布局 */
@media (min-width: 768px) and (max-width: 1439px) {
.solution-grid {
grid-template-columns: 1fr;
}
}
/* 大屏幕≥1440px - 双列布局 */
@media (min-width: 1440px) {
.solution-grid {
grid-template-columns: 1fr 1fr;
}
}
/* 卡片样式 */
.solution-card {
background: white;
border-radius: 16px;
padding: 2.5rem;
box-shadow: 0 10px 40px rgba(137, 91, 255, 0.1);
transform: translateY(20px);
opacity: 0;
animation: cardEnter 0.6s ease forwards;
position: relative;
overflow: hidden;
border: 1px solid rgba(137, 91, 255, 0.2);
transition: all 0.3s ease;
}
.solution-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: var(--primary-gradient);
}
.solution-card:hover {
transform: translateY(-8px);
box-shadow: 0 15px 50px rgba(137, 91, 255, 0.2);
}
.card-header {
margin-bottom: 2rem;
}
.decorative-line {
width: 50px;
height: 3px;
background: var(--primary-gradient);
margin-bottom: 1rem;
border-radius: 3px;
transition: width 0.3s ease;
}
.solution-card:hover .decorative-line {
width: 80px;
}
.card-title {
font-size: 1.3rem;
color: #2c0850;
font-weight: 600;
}
.content-point {
display: flex;
gap: 1rem;
padding: 1rem 0;
border-bottom: 1px solid rgba(137, 91, 255, 0.1);
}
.content-point:last-child {
border-bottom: none;
}
.point-icon {
color: #4a3a6b;
font-size: 1.2rem;
flex-shrink: 0;
}
.point-text {
color: #4a3a6b;
line-height: 1.6;
font-size: 1rem;
}
@keyframes cardEnter {
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -0,0 +1,251 @@
<template>
<div class="home-page">
<div class="business-page">
<!-- 渐变背景标题区 -->
<section class="hero-section">
<div class="container">
<h1 style="font-size: 40px" class="hero-title">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.TITLE") }}
</h1>
<div style="font-size: 18px" class="hero-description">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.CONTENT") }}
</div>
</div>
</section>
<!-- 业务核心解决方案 -->
<main style="margin-top: 40px" class="container">
<section>
<h1 style="font-size: 40px" class="hero-title">
{{ $t("BusinessiIntroduction.CONTAIN.TITLEONE.CONTENTTWO") }}
</h1>
</section>
<!-- 解决方案网格 - 响应式弹性布局 -->
<div class="solution-grid">
<!-- 统一使用弹性列布局通过媒体查询控制排列方式 -->
<div
v-for="(solution, sIndex) in solutions"
:key="sIndex"
class="featured-solution"
>
<div
class="solution-card"
:style="{ '--delay': `${sIndex * 0.2}s` }"
>
<div class="card-header">
<div class="decorative-line"></div>
<h2 class="card-title">{{ solution.title }}</h2>
</div>
<ul class="card-content">
<li
v-for="(point, pIndex) in solution.points"
:key="pIndex"
class="content-point"
>
<div class="point-icon"></div>
<div style="font-size: 18px" class="point-text">
{{ point }}
</div>
</li>
</ul>
</div>
</div>
</div>
</main>
</div>
</div>
</template>
<script setup>
import { useI18n } from "vue-i18n";
import { computed } from "vue";
const { t } = useI18n();
const solutions = computed(() => [
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.ONE.CONTENTTWO"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.TWO.CONTENTTWO"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENTTWO"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.THREE.CONTENTTHREE"),
],
},
{
title: t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.TITLE"),
points: [
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENT"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENTTWO"),
t("BusinessiIntroduction.CONTAIN.TITLEONE.paragraph.FOUR.CONTENTTHREE"),
],
},
]);
</script>
<style scoped>
.container {
max-width: 1280px;
margin: 0 auto;
padding: 0 2rem;
}
/* 标题区 - 紫色渐变 */
.hero-section {
background: var(--primary-gradient);
padding: 5rem 0 0rem;
position: relative;
overflow: hidden;
color: white;
}
.hero-title {
font-size: 40px;
color: black;
margin-bottom: 2rem;
animation: slideIn 1s ease;
}
.hero-description {
margin: 0 auto;
font-size: 1.1rem;
line-height: 1.8;
color: black;
}
:root {
--primary-color: #895bff;
--primary-light: #a07cff;
--primary-dark: #6a11cb;
--primary-gradient: linear-gradient(
135deg,
var(--primary-light) 0%,
var(--primary-color) 100%
);
}
.home-page {
background-image: url("@/assets/image/bg-mobile.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
/* 解决方案网格 - 响应式布局 */
.solution-grid {
padding: 0rem 0 4rem;
display: grid;
grid-template-columns: 1fr; /* 默认单列(移动端) */
gap: 3rem;
}
/* 中等屏幕768px-1439px - 单列布局 */
@media (min-width: 768px) and (max-width: 1439px) {
.solution-grid {
grid-template-columns: 1fr;
}
}
/* 大屏幕≥1440px - 双列布局 */
@media (min-width: 1440px) {
.solution-grid {
grid-template-columns: 1fr 1fr;
}
}
/* 卡片样式 */
.solution-card {
background: white;
border-radius: 16px;
padding: 2.5rem;
box-shadow: 0 10px 40px rgba(137, 91, 255, 0.1);
transform: translateY(20px);
opacity: 0;
animation: cardEnter 0.6s ease forwards;
position: relative;
overflow: hidden;
border: 1px solid rgba(137, 91, 255, 0.2);
transition: all 0.3s ease;
}
.solution-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: var(--primary-gradient);
}
.solution-card:hover {
transform: translateY(-8px);
box-shadow: 0 15px 50px rgba(137, 91, 255, 0.2);
}
.card-header {
margin-bottom: 2rem;
}
.decorative-line {
width: 50px;
height: 3px;
background: var(--primary-gradient);
margin-bottom: 1rem;
border-radius: 3px;
transition: width 0.3s ease;
}
.solution-card:hover .decorative-line {
width: 80px;
}
.card-title {
font-size: 1.3rem;
color: #2c0850;
font-weight: 600;
}
.content-point {
display: flex;
gap: 1rem;
padding: 1rem 0;
border-bottom: 1px solid rgba(137, 91, 255, 0.1);
}
.content-point:last-child {
border-bottom: none;
}
.point-icon {
color: #4a3a6b;
font-size: 1.2rem;
flex-shrink: 0;
}
.point-text {
color: #4a3a6b;
line-height: 1.6;
font-size: 1rem;
}
@keyframes cardEnter {
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -0,0 +1,34 @@
<script setup>
import { computed } from "vue";
import { useWindowSize } from "@vueuse/core";
import size375 from "./size375/index.vue";
import size768 from "./size768/index.vue";
import size1440 from "./size1440/index.vue";
import size1920 from "./size1920/index.vue";
import { useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
const router = useRouter();
const { width } = useWindowSize();
const { t } = useI18n();
const viewComponent = computed(() => {
const viewWidth = width.value;
if (viewWidth <= 450) {
return size375;
} else if (viewWidth <= 1100) {
return size768;
} else if (viewWidth <= 1500) {
return size1440;
} else if (viewWidth <= 1920 || viewWidth > 1920) {
return size1920;
}
});
</script>
<template>
<component :is="viewComponent" />
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,373 @@
<template>
<div class="committees-page">
<!-- 标题区 -->
<div class="title mb-[50px] text-center">
<h1 style="font-size: 40px; margin-top: 60px">Committee Composition</h1>
</div>
<!-- 委员会表格 -->
<div class="container">
<div class="committees-table">
<!-- 表头 - 委员会名称 -->
<div class="table-header">
<div class="director-cell"></div>
<div class="committee-cell">
<h3>Audit Committee</h3>
</div>
<div class="committee-cell">
<h3>Compensation Committee</h3>
</div>
<div class="committee-cell">
<h3>Nominating and Corporate Governance Committee</h3>
</div>
</div>
<!-- 表格内容 - 每位董事 -->
<div
class="table-row"
v-for="(director, index) in otherDirectors"
:key="index"
>
<!-- 董事姓名 -->
<div class="director-cell">
<div class="director-info">
<div class="avatar"></div>
<div>
<router-link
:to="'/boarddirectors'"
style="font-size: 18px"
class="director-link"
>
{{ director.name }}
</router-link>
<!-- <p class="director-title">{{ director.title }}</p> -->
</div>
</div>
</div>
<!-- 委员会职位 -->
<div class="committee-cell">
<div class="role-badges">
<template v-if="getCommitteeRole(director.name, 'Audit')">
<div
class="role-badge"
:class="{
[getCommitteeRole(
director.name,
'Audit'
)?.toLowerCase()]: true,
chair: getCommitteeRole(director.name, 'Audit') === 'Chair',
}"
>
{{ getCommitteeRole(director.name, "Audit") }}
</div>
</template>
</div>
</div>
<div class="committee-cell">
<div class="role-badges">
<template v-if="getCommitteeRole(director.name, 'Compensation')">
<div
class="role-badge"
:class="{
[getCommitteeRole(
director.name,
'Compensation'
)?.toLowerCase()]: true,
chair:
getCommitteeRole(director.name, 'Compensation') ===
'Chair',
}"
>
{{ getCommitteeRole(director.name, "Compensation") }}
</div>
</template>
</div>
</div>
<div class="committee-cell">
<div class="role-badges">
<template v-if="getCommitteeRole(director.name, 'Governance')">
<div
class="role-badge"
:class="{
[getCommitteeRole(
director.name,
'Governance'
)?.toLowerCase()]: true,
chair:
getCommitteeRole(director.name, 'Governance') === 'Chair',
}"
>
{{ getCommitteeRole(director.name, "Governance") }}
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
const otherDirectors = [
// {
// name: "Cao Yu",
// title: "Chief Financial Officer, Secretary, Treasurer and Director",
// },
// { name: "David Lazar", title: "Director" },
{ name: "Hu Bin", title: "Director" },
{ name: "David Natan", title: "Director" },
{ name: "Chan Oi Fat", title: "Director" },
];
// Updated committee roles according to requirements
const committeeRoles = {
"Cao Yu": {},
"David Lazar": {},
"Hu Bin": {
Audit: "Member",
Compensation: "Member",
Governance: "Member",
},
"David Natan": {
Audit: "Chair",
Compensation: "Member",
Governance: "Member",
},
"Chan Oi Fat": {
Audit: "Member",
Compensation: "Chair",
Governance: "Chair",
},
};
const getCommitteeRole = (name, committee) => {
return committeeRoles[name]?.[committee] || null;
};
</script>
<style scoped>
.role-badge.chair {
color: orange;
}
.title h1 {
position: relative;
&::after {
content: "";
position: absolute;
bottom: -14px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 3px;
background: #895bff;
border-radius: 3px;
transition: width 0.3s ease;
}
&:hover::after {
width: 120px;
}
}
/* 紫色主题变量 */
:root {
--primary: #895bff;
--primary-light: #a07cff;
--primary-dark: #6a11cb;
--primary-transparent: rgba(137, 91, 255, 0.1);
}
.committees-page {
background-image: url("@/assets/image/bg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
min-height: 100vh;
}
/* 标题区设计 */
.hero-section {
background: linear-gradient(
135deg,
var(--primary-light) 0%,
var(--primary) 100%
);
padding: 6rem 2rem;
text-align: center;
color: #895bff;
}
.hero-section h1 {
font-size: 2.8rem;
margin-bottom: 1rem;
}
.hero-section p {
font-size: 1.2rem;
opacity: 0.9;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
}
/* 表格设计 */
.committees-table {
margin: 4rem 0;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(137, 91, 255, 0.08);
}
.table-header,
.table-row {
display: grid;
grid-template-columns: 1.5fr repeat(3, 1fr);
border-bottom: 1px solid #f0f0f0;
}
.table-header {
background: #f9f6ff;
position: sticky;
top: 0;
z-index: 2;
}
.committee-cell {
padding: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
border-right: 1px solid #f0f0f0;
}
.committee-cell:last-child {
border-right: none;
}
.committee-cell h3 {
color: var(--primary-dark);
font-size: 1.1rem;
font-weight: 600;
}
.director-cell {
padding: 1.5rem;
border-right: 1px solid #f0f0f0;
}
.director-info {
display: flex;
align-items: center;
gap: 1.2rem;
}
.director-info h4 {
font-size: 1.1rem;
color: #333;
margin-bottom: 0.3rem;
}
.director-title {
font-size: 0.85rem;
color: #666;
}
/* 职位徽章设计 */
.role-badges {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.role-badge {
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 80px;
}
.badge-icon {
width: 16px;
height: 16px;
margin-left: 0.5rem;
background: currentColor;
mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5L20 7"/></svg>')
no-repeat center;
}
/* 悬停效果 */
.table-row {
transition: all 0.3s ease;
}
.table-row:hover {
background: #fdfcff;
transform: translateY(-1px);
box-shadow: 0 5px 15px rgba(137, 91, 255, 0.05);
}
/* 响应式设计 */
@media (max-width: 1024px) {
.committees-table {
overflow-x: auto;
}
.table-header,
.table-row {
grid-template-columns: 250px repeat(3, 200px);
width: max-content;
min-width: 100%;
}
}
@media (max-width: 768px) {
.hero-section {
padding: 4rem 1rem;
}
.hero-section h1 {
font-size: 2.2rem;
}
.director-info {
flex-direction: column;
text-align: center;
gap: 0.8rem;
}
.director-cell {
padding: 1rem;
}
.committee-cell {
padding: 1rem 0.5rem;
}
}
.director-link {
color: #895bff;
font-size: 18px;
font-weight: bold;
text-decoration: none;
transition: color 0.3s ease;
}
.director-link:hover {
color: var(--primary);
text-decoration: underline;
}
</style>

View File

@ -0,0 +1,370 @@
<template>
<div class="committees-page">
<!-- 标题区 -->
<div class="title mb-[50px] text-center">
<h1 style="font-size: 40px; margin-top: 60px">Committee Composition</h1>
</div>
<!-- 委员会表格 -->
<div class="container">
<div class="committees-table">
<!-- 表头 - 委员会名称 -->
<div class="table-header">
<div class="director-cell"></div>
<div class="committee-cell">
<h3>Audit Committee</h3>
</div>
<div class="committee-cell">
<h3>Compensation Committee</h3>
</div>
<div class="committee-cell">
<h3>Nominating and Corporate Governance Committee</h3>
</div>
</div>
<!-- 表格内容 - 每位董事 -->
<div
class="table-row"
v-for="(director, index) in otherDirectors"
:key="index"
>
<!-- 董事姓名 -->
<div class="director-cell">
<div class="director-info">
<div class="avatar"></div>
<div>
<router-link
:to="'/boarddirectors'"
style="font-size: 18px"
class="director-link"
>
{{ director.name }}
</router-link>
</div>
</div>
</div>
<!-- 委员会职位 -->
<div class="committee-cell">
<div class="role-badges">
<template v-if="getCommitteeRole(director.name, 'Audit')">
<div
class="role-badge"
:class="{
[getCommitteeRole(
director.name,
'Audit'
)?.toLowerCase()]: true,
chair: getCommitteeRole(director.name, 'Audit') === 'Chair',
}"
>
{{ getCommitteeRole(director.name, "Audit") }}
</div>
</template>
</div>
</div>
<div class="committee-cell">
<div class="role-badges">
<template v-if="getCommitteeRole(director.name, 'Compensation')">
<div
class="role-badge"
:class="{
[getCommitteeRole(
director.name,
'Compensation'
)?.toLowerCase()]: true,
chair:
getCommitteeRole(director.name, 'Compensation') ===
'Chair',
}"
>
{{ getCommitteeRole(director.name, "Compensation") }}
</div>
</template>
</div>
</div>
<div class="committee-cell">
<div class="role-badges">
<template v-if="getCommitteeRole(director.name, 'Governance')">
<div
class="role-badge"
:class="{
[getCommitteeRole(
director.name,
'Governance'
)?.toLowerCase()]: true,
chair:
getCommitteeRole(director.name, 'Governance') === 'Chair',
}"
>
{{ getCommitteeRole(director.name, "Governance") }}
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
const otherDirectors = [
// {
// name: "Cao Yu",
// title: "Chief Financial Officer, Secretary, Treasurer and Director",
// },
// { name: "David Lazar", title: "Director" },
{ name: "Hu Bin", title: "Director" },
{ name: "David Natan", title: "Director" },
{ name: "Chan Oi Fat", title: "Director" },
];
// Updated committee roles according to requirements
const committeeRoles = {
"Cao Yu": {},
"David Lazar": {},
"Hu Bin": {
Audit: "Member",
Compensation: "Member",
Governance: "Member",
},
"David Natan": {
Audit: "Chair",
Compensation: "Member",
Governance: "Member",
},
"Chan Oi Fat": {
Audit: "Member",
Compensation: "Chair",
Governance: "Chair",
},
};
const getCommitteeRole = (name, committee) => {
return committeeRoles[name]?.[committee] || null;
};
</script>
<style scoped>
/* 紫色主题变量 */
.role-badge.chair {
color: orange;
}
:root {
--primary: #895bff;
--primary-light: #a07cff;
--primary-dark: #6a11cb;
--primary-transparent: rgba(137, 91, 255, 0.1);
}
.committees-page {
background-image: url("@/assets/image/bg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
min-height: 100vh;
}
.title h1 {
position: relative;
&::after {
content: "";
position: absolute;
bottom: -14px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 3px;
background: #895bff;
border-radius: 3px;
transition: width 0.3s ease;
}
&:hover::after {
width: 120px;
}
}
/* 标题区设计 */
.hero-section {
background: linear-gradient(
135deg,
var(--primary-light) 0%,
var(--primary) 100%
);
padding: 6rem 2rem;
text-align: center;
color: #895bff;
}
.hero-section h1 {
font-size: 2.8rem;
margin-bottom: 1rem;
}
.hero-section p {
font-size: 1.2rem;
opacity: 0.9;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
}
/* 表格设计 */
.committees-table {
margin: 4rem 0;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(137, 91, 255, 0.08);
}
.table-header,
.table-row {
display: grid;
grid-template-columns: 1.5fr repeat(3, 1fr);
border-bottom: 1px solid #f0f0f0;
}
.table-header {
background: #f9f6ff;
position: sticky;
top: 0;
z-index: 2;
}
.committee-cell {
padding: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
border-right: 1px solid #f0f0f0;
}
.committee-cell:last-child {
border-right: none;
}
.committee-cell h3 {
color: var(--primary-dark);
font-size: 1.1rem;
font-weight: 600;
}
.director-cell {
padding: 1.5rem;
border-right: 1px solid #f0f0f0;
}
.director-info {
display: flex;
align-items: center;
gap: 1.2rem;
}
.director-info h4 {
font-size: 1.1rem;
color: #333;
margin-bottom: 0.3rem;
}
.director-title {
font-size: 0.85rem;
color: #666;
}
/* 职位徽章设计 */
.role-badges {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.role-badge {
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 80px;
}
.badge-icon {
width: 16px;
height: 16px;
margin-left: 0.5rem;
background: currentColor;
mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5L20 7"/></svg>')
no-repeat center;
}
/* 悬停效果 */
.table-row {
transition: all 0.3s ease;
}
.table-row:hover {
background: #fdfcff;
transform: translateY(-1px);
box-shadow: 0 5px 15px rgba(137, 91, 255, 0.05);
}
/* 响应式设计 */
@media (max-width: 1024px) {
.committees-table {
overflow-x: auto;
}
.table-header,
.table-row {
grid-template-columns: 250px repeat(3, 200px);
width: max-content;
min-width: 100%;
}
}
@media (max-width: 768px) {
.hero-section {
padding: 4rem 1rem;
}
.hero-section h1 {
font-size: 2.2rem;
}
.director-info {
flex-direction: column;
text-align: center;
gap: 0.8rem;
}
.director-cell {
padding: 1rem;
}
.committee-cell {
padding: 1rem 0.5rem;
}
}
.director-link {
color: #895bff;
font-size: 18px;
font-weight: bold;
text-decoration: none;
transition: color 0.3s ease;
}
.director-link:hover {
color: var(--primary);
text-decoration: underline;
}
</style>

View File

@ -0,0 +1,337 @@
<template>
<div class="board-members-page">
<!-- 页面头部 -->
<div class="title mb-[50px] text-center">
<h1 style="font-size: 40px; margin-top: 60px">Committee Composition</h1>
</div>
<!-- 移动端视图 -->
<div class="container">
<div
class="director-card"
v-for="(director, index) in otherDirectors"
:key="director.name"
:style="{ '--delay': index * 0.1 + 's' }"
>
<div class="card-header">
<div class="director-info">
<div class="avatar">
<span class="initials">{{ getInitials(director.name) }}</span>
</div>
<div>
<router-link
:to="`/boarddirectors/${director.name}`"
class="director-name"
>
{{ director.name }}
</router-link>
<!-- <p class="director-title">{{ director.title }}</p> -->
</div>
</div>
</div>
<div class="committee-groups">
<!-- 委员会职位 -->
<div
class="committee-group"
v-if="getCommittees(director.name).length > 0"
>
<div class="role-badges">
<template
v-for="(committee, idx) in getCommittees(director.name)"
:key="idx"
>
<div class="committee-position">
<div
class="role-badge"
:class="
getCommitteeRole(director.name, committee).toLowerCase()
"
>
<span>{{ getCommitteeShortName(committee) }}</span>
</div>
<div style="font-size: 16px" class="role-title">
{{ getCommitteeRole(director.name, committee) }}
</div>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
//
const otherDirectors = [
// {
// name: "Cao Yu",
// title: "Chief Financial Officer, Secretary, Treasurer and Director",
// },
// { name: "David Lazar", title: "Director" },
{ name: "Hu Bin", title: "Director" },
{ name: "David Natan", title: "Director" },
{ name: "Chan Oi Fat", title: "Director" },
];
// - (Chair/Member)
const committeeRoles = {
"Cao Yu": {},
"David Lazar": {},
"Hu Bin": {
Audit: "Member",
Compensation: "Member",
Governance: "Member",
},
"David Natan": {
Audit: "Chair",
Compensation: "Member",
Governance: "Member",
},
"Chan Oi Fat": {
Audit: "Member",
Compensation: "Chair",
Governance: "Chair",
},
};
//
const committeeFullNames = {
Audit: "Audit Committee",
Compensation: "Compensation Committee",
Governance: "Nominating and Corporate Governance Committee",
};
//
const getCommittees = (name) => {
return Object.keys(committeeRoles[name] || {});
};
// (Chair/Member)
const getCommitteeRole = (name, committee) => {
return committeeRoles[name]?.[committee] || "";
};
//
const getCommitteeShortName = (committee) => {
const names = {
Audit: "Audit",
Compensation: "Comp.",
Governance: "Governance",
};
return names[committee] || committee;
};
//
const getInitials = (name) => {
return name
.split(" ")
.map((word) => word[0])
.join("")
.toUpperCase();
};
</script>
<style scoped>
/* 添加这些样式来显示职位类型 */
.committee-position {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 8px;
}
.role-title {
font-size: 12px;
color: #666;
margin-top: 4px;
text-transform: capitalize;
}
/* 保持原有的角色徽章样式 */
.role-badge {
padding: 6px 12px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
display: inline-flex;
align-items: center;
}
.role-badge.chair {
background-color: #e6f2ff;
color: #0066cc;
}
.role-badge.member {
background-color: #f0f0f0;
color: #555;
}
.title h1 {
position: relative;
&::after {
content: "";
position: absolute;
bottom: -14px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 3px;
background: #895bff;
border-radius: 3px;
transition: width 0.3s ease;
}
&:hover::after {
width: 120px;
}
}
/* 基础变量 */
:root {
--primary: #895bff;
--primary-light: #a07cff;
--primary-dark: #6a11cb;
--text-primary: #333;
--text-secondary: #666;
--bg-light: #f9f6ff;
--border-radius: 12px;
}
/* 页面样式 */
.board-members-page {
background-image: url("@/assets/image/bg-mobile.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
min-height: 100vh;
}
.container {
padding: 0 16px;
margin: 0 auto;
}
/* 头部样式 */
.hero-section {
background: linear-gradient(
135deg,
var(--primary-light) 0%,
var(--primary) 100%
);
padding: 3rem 1rem;
text-align: center;
color: #895bff;
margin-bottom: 2rem;
}
.page-title {
font-size: clamp(1.75rem, 5vw, 2.25rem);
margin-bottom: 0.5rem;
font-weight: 600;
}
.page-subtitle {
font-size: clamp(1rem, 3vw, 1.25rem);
opacity: 0.9;
}
/* 董事卡片 */
.director-card {
background: white;
border-radius: var(--border-radius);
margin-bottom: 1.5rem;
box-shadow: 0 5px 20px rgba(137, 91, 255, 0.08);
overflow: hidden;
transform: translateY(20px);
opacity: 0;
animation: fadeIn 0.5s ease forwards;
animation-delay: var(--delay);
}
@keyframes fadeIn {
to {
opacity: 1;
transform: translateY(0);
}
}
.card-header {
padding: 1.25rem;
background: var(--bg-light);
border-bottom: 1px solid #f0f0f0;
}
.director-info {
display: flex;
align-items: center;
gap: 1rem;
}
.avatar {
width: 56px;
height: 56px;
background: var(--primary-transparent);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.initials {
font-size: 1.25rem;
font-weight: bold;
color: var(--primary);
}
.director-name {
color: var(--text-primary);
text-decoration: none;
font-size: 1.2rem;
font-weight: 500;
display: block;
margin-bottom: 0.25rem;
}
.director-title {
font-size: 0.9rem;
color: var(--text-secondary);
line-height: 1.3;
}
/* 委员会职位 */
.committee-groups {
padding: 1.25rem;
}
.role-badges {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.role-badge {
padding: 0.35rem 0.75rem;
border-radius: 16px;
font-size: 0.8rem;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 0.35rem;
background: rgba(137, 91, 255, 0.08);
color: var(--primary);
}
.role-badge.chair {
background: rgba(137, 91, 255, 0.15);
color: var(--primary-dark);
}
.committee-name {
font-size: 0.75rem;
opacity: 0.8;
}
</style>

View File

@ -0,0 +1,337 @@
<template>
<div class="board-members-page">
<!-- 页面头部 -->
<div class="title mb-[50px] text-center">
<h1 style="font-size: 40px; margin-top: 60px">Committee Composition</h1>
</div>
<!-- 移动端视图 -->
<div class="container">
<div
class="director-card"
v-for="(director, index) in otherDirectors"
:key="director.name"
:style="{ '--delay': index * 0.1 + 's' }"
>
<div class="card-header">
<div class="director-info">
<div class="avatar">
<span class="initials">{{ getInitials(director.name) }}</span>
</div>
<div>
<router-link
:to="`/boarddirectors/${director.name}`"
class="director-name"
>
{{ director.name }}
</router-link>
<!-- <p class="director-title">{{ director.title }}</p> -->
</div>
</div>
</div>
<div class="committee-groups">
<!-- 委员会职位 -->
<div
class="committee-group"
v-if="getCommittees(director.name).length > 0"
>
<div class="role-badges">
<template
v-for="(committee, idx) in getCommittees(director.name)"
:key="idx"
>
<div class="committee-position">
<div
class="role-badge"
:class="
getCommitteeRole(director.name, committee).toLowerCase()
"
>
<span>{{ getCommitteeShortName(committee) }}</span>
</div>
<div style="font-size: 16px" class="role-title">
{{ getCommitteeRole(director.name, committee) }}
</div>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
//
const otherDirectors = [
// {
// name: "Cao Yu",
// title: "Chief Financial Officer, Secretary, Treasurer and Director",
// },
// { name: "David Lazar", title: "Director" },
{ name: "Hu Bin", title: "Director" },
{ name: "David Natan", title: "Director" },
{ name: "Chan Oi Fat", title: "Director" },
];
// - (Chair/Member)
const committeeRoles = {
"Cao Yu": {},
"David Lazar": {},
"Hu Bin": {
Audit: "Member",
Compensation: "Member",
Governance: "Member",
},
"David Natan": {
Audit: "Chair",
Compensation: "Member",
Governance: "Member",
},
"Chan Oi Fat": {
Audit: "Member",
Compensation: "Chair",
Governance: "Chair",
},
};
//
const committeeFullNames = {
Audit: "Audit Committee",
Compensation: "Compensation Committee",
Governance: "Nominating and Corporate Governance Committee",
};
//
const getCommittees = (name) => {
return Object.keys(committeeRoles[name] || {});
};
// (Chair/Member)
const getCommitteeRole = (name, committee) => {
return committeeRoles[name]?.[committee] || "";
};
//
const getCommitteeShortName = (committee) => {
const names = {
Audit: "Audit",
Compensation: "Comp.",
Governance: "Governance",
};
return names[committee] || committee;
};
//
const getInitials = (name) => {
return name
.split(" ")
.map((word) => word[0])
.join("")
.toUpperCase();
};
</script>
<style scoped>
/* 添加这些样式来显示职位类型 */
.committee-position {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 8px;
}
.role-title {
font-size: 12px;
color: #666;
margin-top: 4px;
text-transform: capitalize;
}
/* 保持原有的角色徽章样式 */
.role-badge {
padding: 6px 12px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
display: inline-flex;
align-items: center;
}
.role-badge.chair {
background-color: #e6f2ff;
color: #0066cc;
}
.role-badge.member {
background-color: #f0f0f0;
color: #555;
}
.title h1 {
position: relative;
&::after {
content: "";
position: absolute;
bottom: -14px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 3px;
background: #895bff;
border-radius: 3px;
transition: width 0.3s ease;
}
&:hover::after {
width: 120px;
}
}
/* 基础变量 */
:root {
--primary: #895bff;
--primary-light: #a07cff;
--primary-dark: #6a11cb;
--text-primary: #333;
--text-secondary: #666;
--bg-light: #f9f6ff;
--border-radius: 12px;
}
/* 页面样式 */
.board-members-page {
background-image: url("@/assets/image/bg-mobile.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
min-height: 100vh;
}
.container {
padding: 0 16px;
margin: 0 auto;
}
/* 头部样式 */
.hero-section {
background: linear-gradient(
135deg,
var(--primary-light) 0%,
var(--primary) 100%
);
padding: 3rem 1rem;
text-align: center;
color: #895bff;
margin-bottom: 2rem;
}
.page-title {
font-size: clamp(1.75rem, 5vw, 2.25rem);
margin-bottom: 0.5rem;
font-weight: 600;
}
.page-subtitle {
font-size: clamp(1rem, 3vw, 1.25rem);
opacity: 0.9;
}
/* 董事卡片 */
.director-card {
background: white;
border-radius: var(--border-radius);
margin-bottom: 1.5rem;
box-shadow: 0 5px 20px rgba(137, 91, 255, 0.08);
overflow: hidden;
transform: translateY(20px);
opacity: 0;
animation: fadeIn 0.5s ease forwards;
animation-delay: var(--delay);
}
@keyframes fadeIn {
to {
opacity: 1;
transform: translateY(0);
}
}
.card-header {
padding: 1.25rem;
background: var(--bg-light);
border-bottom: 1px solid #f0f0f0;
}
.director-info {
display: flex;
align-items: center;
gap: 1rem;
}
.avatar {
width: 56px;
height: 56px;
background: var(--primary-transparent);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.initials {
font-size: 1.25rem;
font-weight: bold;
color: var(--primary);
}
.director-name {
color: var(--text-primary);
text-decoration: none;
font-size: 1.2rem;
font-weight: 500;
display: block;
margin-bottom: 0.25rem;
}
.director-title {
font-size: 0.9rem;
color: var(--text-secondary);
line-height: 1.3;
}
/* 委员会职位 */
.committee-groups {
padding: 1.25rem;
}
.role-badges {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.role-badge {
padding: 0.35rem 0.75rem;
border-radius: 16px;
font-size: 0.8rem;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 0.35rem;
background: rgba(137, 91, 255, 0.08);
color: var(--primary);
}
.role-badge.chair {
background: rgba(137, 91, 255, 0.15);
color: var(--primary-dark);
}
.committee-name {
font-size: 0.75rem;
opacity: 0.8;
}
</style>

View File

@ -0,0 +1,22 @@
<script setup>
import size1920 from "@/views/boarddirectors/size1920/index.vue";
import size375 from "@/views/boarddirectors/size375/index.vue";
import { computed } from "vue";
import { useWindowSize } from "@vueuse/core";
const { width } = useWindowSize();
const viewComponent = computed(() => {
const viewWidth = width.value;
if (viewWidth <= 450) {
return size375;
} else if (viewWidth <= 1920 || viewWidth > 1920) {
return size1920;
}
});
</script>
<template>
<component :is="viewComponent" />
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,137 @@
<template>
<div class="home-page">
<div class="directors-page">
<h1 class="page-title">Board of Directors</h1>
<n-divider />
<div class="directors-list">
<div
v-for="(director, index) in otherDirectors"
:key="index"
class="director-item"
>
<n-h2 style="font-size: 18px" class="director-name">{{
director.name
}}</n-h2>
<n-text style="font-size: 16px" class="director-title">{{
director.title
}}</n-text>
<n-divider class="divider" />
<n-p class="director-bio">{{ director.contain }}</n-p>
</div>
</div>
</div>
</div>
</template>
<script setup>
const otherDirectors = [
{
name: "Hu Bin",
title: "Chairman of the Board of Directors",
contain:
"Served as a director of DC International Service Trade GmbH since December 2024. Prior to that, Mr. Hu worked as a freelancer in the tourism industry from April 2001 to October 2024. From April 1994 to October 2000, he served as the general manager of Suzhou Wintime Advertising Co., Ltd. Before that, he served as the general manager of Suzhou Bauhaus Advertising Design Co., Ltd. from August 1992 to April 1994, where he was engaged in computer-aided design and 3D computer animation production. Mr. Hu began his career at Suzhou Advertising Company in October 1989, where he worked as a designer responsible for graphic design, platemaking, printing, and interior decoration. Mr. Hu graduated from Suzhou Academy of Arts in 1989.",
},
{
name: "Cao Yu",
title: "Chief Financial Officer, Secretary, Treasurer and Director",
contain:
"Previously served as the treasury director of Taifeng Cultural Communication Co., Ltd where she oversees its financial matters from November 2018 to November 2024. Prior to that, Ms. Cao served as a business manager of Yangfeng Art Exchange Co., Ltd from February 2016 to October 2018. From March 2011 to January 2016, she served as the treasury officer of financial department of Suzhou Industrial Park Xinfushida Plastic Profile Products Co., Ltd.",
},
{
name: "David E. Lazar",
title: "Director",
contain:
"Served as the Chief Executive Officer of OpGen, Inc., a precision medicine company listed on the Nasdaq (OPGN) since April 11, 2024, where he also servs as a director and board chairman, beginning on March 25, 2024. Mr. Lazar served as the Chief Executive Officer of Titan Pharmaceuticals Inc. listed on the Nasdaq (TTNP) from August 2022 through April 11, 2024, where he also served as a director and board chairman from August 2022 until October 2023. He has also served as the CEO of Custodian Ventures LLC, a company which specializes in assisting distressed public companies through custodianship, since February 2018, and Activist Investing LLC, an actively managed private investment fund, since March 2018. Previously, Mr. Lazar served as Managing Partner at Zenith Partners International Inc., a boutique consulting firm, from July 2012 to April 2018. In his role as Chief Executive Officer of Custodian Ventures LLC, Mr. Lazar has successfully served as a custodian to numerous public companies across a wide range of industries.",
},
{
name: "David Natan",
title: "Director",
contain:
"Currently serves as President and Chief Executive Officer of Natan & Associates, LLC, a consulting firm offering chief financial officer services to public and private companies in a variety of industries, since 2007. Mr. Natan previously served as a Director of the Company from November 2023 to February 2025. From February 2010 to May 2020, Mr. Natan served as Chief Executive Officer of ForceField Energy, Inc. (OTCMKTS: FNRG), a company focused on the solar industry and LED lighting products. From February 2002 to November 2007, Mr. Natan served as Executive Vice President of Reporting and Chief Financial Officer of PharmaNet Development Group, Inc., a drug development services company, and, from June 1995 to February 2002, as Chief Financial Officer and Vice President of Global Technovations, Inc., a manufacturer and marketer of oil analysis instruments and speakers and speaker components. Prior to that, Mr. Natan served in various roles of increasing responsibility with Deloitte & Touche LLP, a global consulting firm. Mr. Natan currently serves as a member of the Board of Directors and Chair of the Audit Committee of Sunshine Biopharma, Inc. (Nasdaq: SBFM), a pharmaceutical and nutritional supplement company, since February 2022. Previously, Mr. Natan has served as a director for the following public companies: Global Technovations, Forcefield Energy, Titan Pharmaceuticals (Nasdaq: TTNP), Vivakor Inc. (Nasdaq: VIVK), NetBrands Corp. (OTC: NBND), and OpGen Inc. (OTC: OPGN), and Cyclacel Pharmaceuticals (Nasdaq: CYCC). Mr. Natan holds a B.A. in Economics from Boston University.",
},
{
name: "Chan Oi Fat",
title: "Director",
contain:
"Served as Vice President Finance of SML Group Corporation since March 2018 and as Company Secretary of China Leon Inspection Holding Limited (HKEX: 1586) since February 2018 and of Raily Aesthetic Medicine International Holdings Limited (HKEX: 2135) since November 2020. He is an independent non-executive director of Huajin International Holdings Limited (HKEX: 2738) (since March 2025) and UBoT Holding Limited (HKEX GEM: 8529) (since May 2024) and previously served as an independent non-executive director of China Saftower International Holding Group Limited (HKEX GEM: 8623) from June 2020 to December 2023 and Shanghai Prime Machinery Company Limited (HKEX: 2345) from June 2014 to January 2021. Mr. Chan holds a B.B.A. (Hons) in Accountancy from the City University of Hong Kong (2000) and is a member of the Association of Chartered Certified Accountants (since 2003) and the Hong Kong Institute of Certified Public Accountants (since 2004).",
},
];
</script>
<style scoped>
.home-page {
background-image: url("@/assets/image/bg.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.directors-page {
max-width: 900px;
margin: 0 auto;
padding: 60px 24px;
}
.page-title {
font-size: 2.5rem; /* 18px */
margin-bottom: 30px;
color: #333;
}
.directors-list {
display: flex;
flex-direction: column;
gap: 48px;
margin-top: 40px;
}
.director-item {
padding-bottom: 48px;
border-bottom: 1px solid #f0f0f0;
}
.director-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.director-name {
margin-bottom: 8px;
font-size: 28px;
font-weight: 500;
color: #1a1a1a;
}
.director-title {
font-size: 18px;
color: #666;
display: block;
margin-bottom: 24px;
}
.divider {
margin: 16px 0;
background-color: #f0f0f0;
}
.director-bio {
line-height: 1.8;
color: #4a4a4a;
font-size: 16px;
}
@media (min-width: 768px) {
.directors-page {
padding: 80px 40px;
}
.director-name {
font-size: 32px;
}
.director-title {
font-size: 20px;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More