index.vue 69 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531
  1. <template>
  2. <div class="portal-home min-h-screen">
  3. <div v-if="showDingTalkLoginMask" class="portal-login-mask">
  4. <div class="portal-login-mask__content">
  5. <div class="portal-login-mask__spinner"></div>
  6. <p class="portal-login-mask__text">正在登录,请稍候...</p>
  7. </div>
  8. </div>
  9. <Header />
  10. <main class="px-5 md:px-20 pb-8 pt-20">
  11. <section class="hero-banner overflow-hidden rounded-[6px] relative">
  12. <div class="">
  13. <!-- 轮播容器 -->
  14. <div class="carousel-container relative h-[50%]">
  15. <!-- 轮播项 -->
  16. <div
  17. class="carousel-slide absolute inset-0 transition-opacity duration-500 ease-in-out"
  18. v-for="(slide, index) in slides"
  19. :key="index"
  20. v-show="currentIndex === index"
  21. :class="{
  22. 'opacity-100': currentIndex === index,
  23. 'opacity-0': currentIndex !== index,
  24. 'z-10': currentIndex === index,
  25. 'z-0 pointer-events-none': currentIndex !== index,
  26. }"
  27. >
  28. <div class="hero-copy carousel-caption">
  29. <p class="hero-text mt-6 text-[30px]!">
  30. {{ slide.text }}
  31. </p>
  32. </div>
  33. <!-- 轮播背景图 -->
  34. <div class="hero-visual absolute inset-0">
  35. <img
  36. :src="slide.image"
  37. alt="Banner"
  38. class="w-full h-full object-cover"
  39. />
  40. </div>
  41. </div>
  42. <!-- 指示器 -->
  43. <div
  44. class="carousel-indicators absolute bottom-4 left-1/2 z-30 flex transform -translate-x-1/2 space-x-2"
  45. >
  46. <span
  47. v-for="(_, i) in slides"
  48. :key="i"
  49. @click="currentIndex = i"
  50. :class="{
  51. 'bg-[#0c4eb5]': currentIndex === i,
  52. 'bg-[#2f333c]/50': currentIndex !== i,
  53. }"
  54. class="w-3 h-3 rounded-full transition-colors"
  55. ></span>
  56. </div>
  57. </div>
  58. </div>
  59. </section>
  60. <section class="portal-mobile-shortcuts mt-3 md:hidden!">
  61. <button
  62. type="button"
  63. class="portal-mobile-shortcut flex flex-col items-center justify-center cursor-pointer"
  64. @click="router.push('/flow')"
  65. >
  66. <img src="../assets//images/flow.png" alt="" class="w-10 h-10" />
  67. 流程门户
  68. </button>
  69. <button
  70. v-hasPermi="['portal:dashboard:view']"
  71. type="button"
  72. class="portal-mobile-shortcut flex flex-col items-center justify-center cursor-pointer"
  73. @click="router.push('/drive')"
  74. >
  75. <img src="../assets//images/driveicon.png" alt="" class="w-10 h-10" />
  76. 驾驶舱门户
  77. </button>
  78. <button
  79. type="button"
  80. class="portal-mobile-shortcut flex flex-col items-center justify-center cursor-pointer"
  81. >
  82. <img src="../assets//images/report.png" alt="" class="w-10 h-10" />
  83. 报表门户
  84. </button>
  85. </section>
  86. <section class="mt-3 grid gap-4 xl:grid-cols-[1.74fr_0.74fr] grid-cols-1">
  87. <div class="space-y-4">
  88. <div class="top-nav-bar">
  89. <!-- 左侧标题 -->
  90. <!-- <div class="nav-title">统一入口,快速直达</div> -->
  91. <!-- 中间搜索框 -->
  92. <div class="search-container hidden! md:flex!">
  93. <Icon icon="mdi:magnify" class="search-icon" />
  94. <input
  95. v-model="searchKeyword"
  96. type="text"
  97. placeholder="搜索应用、流程、数据..."
  98. class="search-input"
  99. @keyup.enter="handleSearch"
  100. />
  101. <span class="shortcut-key">⌘K</span>
  102. </div>
  103. <!-- 右侧快捷入口 -->
  104. <div class="quick-access">
  105. <el-dropdown
  106. trigger="click"
  107. popper-class="quick-access-dropdown"
  108. @command="handleQuickAccessCommand"
  109. >
  110. <button type="button" class="quick-btn quick-btn--dropdown">
  111. <img src="../assets//images//td.png" class="w-5 h-5" alt="" />
  112. <span class="btn-label quick-btn__meta">我的待办</span>
  113. <span class="quick-btn__count quick-btn__count--todo">{{
  114. todo
  115. }}</span>
  116. <!-- <Icon icon="mdi:chevron-down" class="quick-btn__arrow" /> -->
  117. </button>
  118. <template #dropdown>
  119. <el-dropdown-menu>
  120. <el-dropdown-item command="todo-oa">
  121. <div class="p-2 flex items-center gap-3 text-[#e26669]!">
  122. <span>OA</span>
  123. <span>{{ oatodo }}</span>
  124. </div>
  125. </el-dropdown-item>
  126. <el-dropdown-item command="todo-crm">
  127. <div class="p-2 flex items-center gap-3 text-[#e26669]!">
  128. <span>CRM</span>
  129. <span>{{ crmtodo }}</span>
  130. </div>
  131. </el-dropdown-item>
  132. <el-dropdown-item command="todo-srm">
  133. <div class="p-2 flex items-center gap-3 text-[#e26669]!">
  134. <span>SRM</span>
  135. <span>{{ srmtodo }}</span>
  136. </div>
  137. </el-dropdown-item>
  138. <el-dropdown-item command="todo-ehr">
  139. <div class="p-2 flex items-center gap-3 text-[#e26669]!">
  140. <span>EHR</span>
  141. <span>{{ ehrtodo }}</span>
  142. </div>
  143. </el-dropdown-item>
  144. </el-dropdown-menu>
  145. </template>
  146. </el-dropdown>
  147. <el-dropdown
  148. trigger="click"
  149. popper-class="quick-access-dropdown"
  150. @command="handleQuickAccessCommand"
  151. >
  152. <button type="button" class="quick-btn quick-btn--dropdown">
  153. <Icon icon="mdi:check" class="btn-icon" />
  154. <span class="btn-label quick-btn__meta">我的已办</span>
  155. <span
  156. class="p-2 inline-block quick-btn__count quick-btn__count--done"
  157. >{{ done }}</span
  158. >
  159. <!-- <Icon icon="mdi:chevron-down" class="quick-btn__arrow" /> -->
  160. </button>
  161. <template #dropdown>
  162. <el-dropdown-menu>
  163. <el-dropdown-item command="done-oa">
  164. <div class="p-2 flex items-center gap-3 text-[#2da04d]!">
  165. <span>OA</span>
  166. <span>{{ oadone }}</span>
  167. </div>
  168. </el-dropdown-item>
  169. <el-dropdown-item command="done-crm">
  170. <div class="p-2 flex items-center gap-3 text-[#2da04d]!">
  171. <span>CRM</span>
  172. <span>{{ crmdone }}</span>
  173. </div></el-dropdown-item
  174. >
  175. <el-dropdown-item command="done-srm">
  176. <div class="p-2 flex items-center gap-3 text-[#2da04d]!">
  177. <span>SRM</span>
  178. <span>{{ srmdone }}</span>
  179. </div></el-dropdown-item
  180. >
  181. </el-dropdown-menu>
  182. </template>
  183. </el-dropdown>
  184. </div>
  185. </div>
  186. <article
  187. v-for="section in filteredSections"
  188. :key="section.code"
  189. class="platform-block rounded-sm md:flex"
  190. :style="{ minHeight: section.height }"
  191. >
  192. <div class="platform-block__header">
  193. <img
  194. v-if="section.title === '数字化运营平台'"
  195. src="../assets//images/icon.png"
  196. alt=""
  197. class="w-20 h-20"
  198. />
  199. <img
  200. v-if="section.title === '智慧指挥平台'"
  201. src="../assets//images/icon2.png"
  202. alt=""
  203. class="w-20 h-20"
  204. />
  205. <img
  206. v-if="section.title === 'Chat BI平台'"
  207. src="../assets//images/icon3.png"
  208. alt=""
  209. class="w-20 h-20"
  210. />
  211. <div class="platform-block__title-wrap">
  212. <p class="platform-block__title">{{ section.title }}</p>
  213. <span class="platform-block__subtitle">{{
  214. section.subtitle
  215. }}</span>
  216. </div>
  217. </div>
  218. <div
  219. v-if="section.apps?.length"
  220. class="grid grid-cols-[repeat(2,minmax(0,1fr))] gap-2 p-6 px-3 md:px-4 md:grid-cols-[repeat(3,minmax(0,1fr))] xl:grid-cols-[repeat(3,minmax(0,1fr))]"
  221. >
  222. <button
  223. v-for="(app, appIndex) in section.apps"
  224. :key="`${section.code}-${appIndex}-${app.label}`"
  225. type="button"
  226. :class="[
  227. 'platform-app',
  228. boldLabes.includes(app.label)
  229. ? 'platform-app--active'
  230. : 'platform-app--ghost',
  231. ]"
  232. @click="handlePortalAppClick(app)"
  233. >
  234. <span class="platform-app__icon">
  235. <img
  236. v-if="app.image"
  237. :src="app.image"
  238. :alt="app.label"
  239. class="h-7 w-7 object-contain rounded-md"
  240. />
  241. <Icon
  242. v-else
  243. :icon="app.icon || 'mdi:dots-grid'"
  244. class="text-[24px]"
  245. />
  246. </span>
  247. <span
  248. class="platform-app__label md:text-sm text-[12px] text-left"
  249. >{{ app.label }}</span
  250. >
  251. </button>
  252. </div>
  253. </article>
  254. </div>
  255. <aside class="space-y-4">
  256. <section class="side-card side-card--notice rounded-md p-2">
  257. <div class="side-card__header w-[95%] ml-[8px]">
  258. <div class="notice-tabs">
  259. <button
  260. v-for="tab in noticeTabs"
  261. :key="tab.key"
  262. type="button"
  263. :class="[
  264. 'notice-tab',
  265. activeNoticeTab === tab.key ? 'notice-tab--active' : '',
  266. ]"
  267. @click="handleNoticeTabChange(tab.key)"
  268. >
  269. {{ tab.label }}
  270. </button>
  271. </div>
  272. <button
  273. type="button"
  274. class="side-card__more"
  275. @click="handleNoticeMoreClick"
  276. >
  277. 更多
  278. <Icon icon="mingcute:right-line" class="text-[18px]" />
  279. </button>
  280. </div>
  281. <div class="space-y-2 p-2 pt-3">
  282. <div
  283. v-if="!userStore.getUser.username"
  284. class="flex h-full items-center justify-center"
  285. >
  286. <div class="text-[#8a9ab0] pt-5">登录后查看</div>
  287. </div>
  288. <div
  289. v-if="!currentNoticeList.length && userStore.getUser.username"
  290. class="flex h-[120px] items-center justify-center text-[#8a9ab0]"
  291. >
  292. 暂无数据
  293. </div>
  294. <article
  295. v-if="userStore.getUser.username"
  296. v-for="notice in currentNoticeList"
  297. :key="notice.id"
  298. class="notice-item cursor-pointer"
  299. @click="handleNoticeItemClick(notice)"
  300. >
  301. <div class="notice-item__desc">{{ notice.docsubject }}</div>
  302. <div class="date text-[12px] text-[#9cadc0]">
  303. {{ notice.docvaliddate }}
  304. </div>
  305. </article>
  306. </div>
  307. </section>
  308. <section
  309. class="side-card side-card--notice rounded-md p-2"
  310. :style="{ minHeight: '190px' }"
  311. >
  312. <div class="side-card__header w-[91%] ml-[15px]">
  313. <div class="notice-badge px-2 pb-2">{{ todoPanelTitle }}</div>
  314. <button
  315. type="button"
  316. class="side-card__more"
  317. @click="router.push('/todo-list')"
  318. >
  319. 全部任务
  320. <Icon icon="mingcute:right-line" class="text-[18px]" />
  321. </button>
  322. </div>
  323. <div class="space-y-2 p-4 pt-2">
  324. <div
  325. v-if="!userStore.getUser.username"
  326. class="flex h-full items-center justify-center"
  327. >
  328. <div class="text-[#8a9ab0] pt-10">登录后查看</div>
  329. </div>
  330. <div
  331. v-if="!oaTasks.length && userStore.getUser.username"
  332. class="flex h-full items-center justify-center"
  333. >
  334. <div class="text-[#8a9ab0] pt-10">暂无待办</div>
  335. </div>
  336. <article
  337. v-if="userStore.getUser.username"
  338. v-for="task in oaTasks"
  339. :key="task.requestId"
  340. @click="handleTask(task)"
  341. class="todo-item rounded-md cursor-pointer"
  342. >
  343. <div class="flex min-w-0 items-start justify-between gap-3">
  344. <div class="min-w-0">
  345. <div class="truncate text-[14px] font-semibold">
  346. {{ task.requestName }}
  347. </div>
  348. <div class="mt-1 text-[12px] text-[#8a9ab0]">
  349. 创建人:{{ task.creatorName }} · {{ task.createTime }}
  350. </div>
  351. </div>
  352. <span
  353. :class="['todo-item__tag', getTagClass(task.requesLevel)]"
  354. >{{ getTagName(task.requesLevel) }}</span
  355. >
  356. </div>
  357. </article>
  358. <div class="quick-todo-bar">
  359. <el-dropdown
  360. trigger="click"
  361. popper-class="quick-access-dropdown"
  362. @command="handleQuickAccessCommand"
  363. >
  364. <button type="button" class="quick-todo-btn">
  365. <div class="number">{{ todo }}</div>
  366. <div class="flex gap-2 items-center">
  367. <div class="!text-[10px] aside-btn">我的待办</div>
  368. <Icon
  369. icon="mdi:clipboard-text-outline"
  370. color="#7f2c33"
  371. class="icon"
  372. />
  373. </div>
  374. </button>
  375. <template #dropdown>
  376. <el-dropdown-menu>
  377. <el-dropdown-item command="todo-oa">
  378. <div
  379. class="p-2 flex items-center gap-3 text-[#f56c6c]!"
  380. >
  381. <span>OA</span>
  382. <span>{{ oatodo }}</span>
  383. </div>
  384. </el-dropdown-item>
  385. <el-dropdown-item command="todo-crm">
  386. <div
  387. class="p-2 flex items-center gap-3 text-[#f56c6c]!"
  388. >
  389. <span>CRM</span>
  390. <span>{{ crmtodo }}</span>
  391. </div>
  392. </el-dropdown-item>
  393. <el-dropdown-item command="todo-srm">
  394. <div
  395. class="p-2 flex items-center gap-3 text-[#e43c2f]!"
  396. >
  397. <span>SRM</span>
  398. <span>{{ srmtodo }}</span>
  399. </div>
  400. </el-dropdown-item>
  401. <el-dropdown-item command="todo-ehr">
  402. <div
  403. class="p-2 flex items-center gap-3 text-[#e26669]!"
  404. >
  405. <span>EHR</span>
  406. <span>{{ ehrtodo }}</span>
  407. </div>
  408. </el-dropdown-item>
  409. </el-dropdown-menu>
  410. </template>
  411. </el-dropdown>
  412. <el-dropdown
  413. trigger="click"
  414. popper-class="quick-access-dropdown"
  415. @command="handleQuickAccessCommand"
  416. >
  417. <button
  418. type="button"
  419. class="quick-todo-btn quick-todo-btn--center"
  420. >
  421. <div class="number text-[#2d8767]!">{{ done }}</div>
  422. <div class="flex gap-2 items-center pl-2">
  423. <div class="!text-[10px] aside-btn">已完成</div>
  424. <Icon
  425. icon="mdi:check-circle-outline"
  426. color="#2d8767"
  427. class="icon"
  428. />
  429. </div>
  430. </button>
  431. <template #dropdown>
  432. <el-dropdown-menu>
  433. <el-dropdown-item command="done-oa">
  434. <div
  435. class="p-2 flex items-center gap-3 text-[#2d8767]!"
  436. >
  437. <span>OA</span>
  438. <span>{{ oadone }}</span>
  439. </div>
  440. </el-dropdown-item>
  441. <el-dropdown-item command="done-crm">
  442. <div
  443. class="p-2 flex items-center gap-3 text-[#2d8767]!"
  444. >
  445. <span>CRM</span>
  446. <span>{{ crmdone }}</span>
  447. </div>
  448. </el-dropdown-item>
  449. <el-dropdown-item command="done-srm">
  450. <div
  451. class="p-2 flex items-center gap-3 text-[#2d8767]!"
  452. >
  453. <span>SRM</span>
  454. <span>{{ srmdone }}</span>
  455. </div>
  456. </el-dropdown-item>
  457. </el-dropdown-menu>
  458. </template>
  459. </el-dropdown>
  460. </div>
  461. </div>
  462. </section>
  463. <section
  464. class="side-card side-card--content p-2 rounded-md"
  465. :style="{ minHeight: '180px' }"
  466. >
  467. <div class="side-card__header w-[90%] ml-[15px]">
  468. <div class="news notice-badge pb-2">{{ newsPanelTitle }}</div>
  469. <button type="button" class="side-card__more" @click="goNews">
  470. 更多
  471. <Icon icon="mingcute:right-line" class="text-[18px]" />
  472. </button>
  473. </div>
  474. <div class="space-y-2 p-4 pt-2">
  475. <div
  476. v-if="!userStore.getUser.username"
  477. class="flex h-full items-center justify-center"
  478. >
  479. <div class="text-[#8a9ab0] pt-10">登录后查看</div>
  480. </div>
  481. <div
  482. v-if="!newsList.length && userStore.getUser.username"
  483. class="flex h-full items-center justify-center"
  484. >
  485. <div class="text-[#8a9ab0] pt-10">暂无新闻</div>
  486. </div>
  487. <article
  488. v-for="news in newsList"
  489. :key="news.id"
  490. class="news-mini cursor-pointer rounded-md"
  491. @click="handleNoticeItemClick(news)"
  492. >
  493. <div class="min-w-0 flex-1">
  494. <div
  495. class="line-clamp-1 text-[13px] font-semibold leading-[1.45]"
  496. >
  497. {{ news.docsubject }}
  498. </div>
  499. <div class="mt-2 text-[12px] text-[#9cadc0]">
  500. {{ news.docvaliddate }}
  501. </div>
  502. </div>
  503. </article>
  504. </div>
  505. </section>
  506. <section
  507. v-if="userStore.getUser.username"
  508. class="side-card side-card--placeholder rounded-md"
  509. :style="{ minHeight: '78px', backgroundColor: '#3575e4' }"
  510. >
  511. <div class="placeholder-panel flex flex-col text-left">
  512. <!-- <div class="text-sm">需要帮助?</div> -->
  513. <div class="text-[10px]">遇到系统操作问题,需要帮助</div>
  514. <div
  515. class="bg-[#79ebfa] text-sm text-center text-[#004098] py-1 px-2 rounded-full mt-2 w-[30%] cursor-pointer"
  516. @click="openConsult"
  517. >
  518. 立即咨询
  519. </div>
  520. </div>
  521. </section>
  522. </aside>
  523. </section>
  524. </main>
  525. <el-dialog v-model="dialogVisible" title="提示" width="500">
  526. <template #header="{ close, titleId, titleClass }">
  527. <div class="my-header">
  528. <h4 :id="titleId" class="flex gap-2 items-center">
  529. <Icon
  530. :icon="'si:warning-line'"
  531. color="#ef6c1a"
  532. size="16"
  533. class="text-[20px]"
  534. /><span class="text-black">提示</span>
  535. </h4>
  536. </div>
  537. </template>
  538. <span class="text-black">账号未授权</span>
  539. <template #footer>
  540. <div class="dialog-footer">
  541. <el-button @click="dialogVisible = false">确定</el-button>
  542. <!-- <el-button type="primary" color="#063e8d" @click="confirmSrm">
  543. 继续访问
  544. </el-button> -->
  545. </div>
  546. </template>
  547. </el-dialog>
  548. <Footer />
  549. </div>
  550. </template>
  551. <script setup lang="ts">
  552. import { computed, onMounted, onUnmounted, watch, ref } from "vue";
  553. import * as authUtil from "@/utils/auth";
  554. import * as dd from "dingtalk-jsapi";
  555. import Header from "@components/home/header.vue";
  556. import Footer from "@components/home/Footer.vue";
  557. import { useRouter } from "vue-router";
  558. import axios from "axios";
  559. import { Icon } from "@iconify/vue";
  560. import { ElLoading } from "element-plus";
  561. import {
  562. getMCSsoToken,
  563. ssoLogin,
  564. zentaoSsoLogin,
  565. getOATasks,
  566. getCRMTasks,
  567. getSRMTasks,
  568. getEHRTasks,
  569. getNotices,
  570. getRedHeadFiles,
  571. getNews,
  572. srmLogin,
  573. ehrLogin,
  574. } from "@/api/user";
  575. import { useUserStore } from "@/stores/useUserStore";
  576. import { useThemeStore } from "@/stores/useThemeStore";
  577. import { getAccessToken } from "@/utils/auth";
  578. import { deleteUserCache } from "@hooks/useCache";
  579. import { manualLogoutKey, reloginCancelKey } from "@/config/axios/service";
  580. import banner1 from "@/assets/images/banner1.png";
  581. import banner2 from "@/assets/images/banner2.png";
  582. import oaimage from "@/assets/images/oa.png";
  583. import crmimage from "@/assets/images/crm.png";
  584. import ehrimage from "@/assets/images/ehr.png";
  585. import scmimage from "@/assets/images/scm.png";
  586. import erpimage from "@/assets/images/fm.png";
  587. import driveimage from "@/assets/images/drive.png";
  588. import pmsimage from "@/assets/images/pms.jpeg";
  589. import zhonghangimage from "@/assets/images/中航.png";
  590. import lianyouimage from "@/assets/images/lianyou.png";
  591. import qhseimage from "@/assets/images/qhse.png";
  592. import zuanjingimage from "@/assets/images/zuanjing.jpeg";
  593. import yalieimage from "@/assets/images/yalie.png";
  594. import zhuqiimage from "@/assets/images/zhuqi.png";
  595. import pmimage from "@/assets/images/pm.png";
  596. import okrimage from "@/assets/images/okr.png";
  597. import dataimage from "@/assets/images/data2.png";
  598. import thinkimage from "@/assets/images/think.png";
  599. import aiimage from "@/assets/images/ai.png";
  600. import agentimage from "@/assets/images/icon2.png";
  601. import videoimage from "@/assets/images/video.png";
  602. import fileagent from "@/assets/images/fileagent.png";
  603. import zhiduagent from "@/assets/images/zhiduagent.png";
  604. import jishuimage2 from "@/assets/images/jishuimage.png";
  605. import zhanlueimage from "@/assets/images/zhanlue.png"; // 战略解码
  606. import safeimage from "@/assets/images/safe.png"; // 安全合规管理
  607. import zuzhiimage from "@/assets/images/zuzhi.png";
  608. import youimage from "@/assets/images/youcnag.png"; // ai智能体
  609. import hongpan from "@/assets/images/pan.png"; // ai智能体
  610. import ask from "@/assets/images/ask.png";
  611. import degitial from "@/assets/images/degitial.png";
  612. import wms from "@/assets/images/wms.png";
  613. import banner2_white from "@/assets/images/banner2_white.png";
  614. import banner1_white from "@/assets/images/banner1_white.png";
  615. type PortalApp = {
  616. label: string;
  617. icon?: string;
  618. image?: string;
  619. active?: boolean;
  620. };
  621. type PortalSection = {
  622. code: string;
  623. title: string;
  624. subtitle: string;
  625. height: string;
  626. apps?: PortalApp[];
  627. };
  628. type NoticeItem = {
  629. id?: string | number;
  630. title?: string;
  631. desc?: string;
  632. docsubject?: string;
  633. };
  634. const router = useRouter();
  635. const userStore = useUserStore();
  636. const themeStore = useThemeStore();
  637. const isDingTalkClient = () => {
  638. const ua = window.navigator.userAgent.toLowerCase();
  639. return ua.includes("dingtalk") || ua.includes("dingtalkwork");
  640. };
  641. const showDingTalkLoginMask = ref(
  642. isDingTalkClient() && !userStore.getUser.username && !getAccessToken(),
  643. );
  644. const todoPanelTitle = "待办中心";
  645. const newsPanelTitle = "新闻中心";
  646. const noticeTabs = [
  647. { key: "redHead", label: "红头文件" },
  648. { key: "notice", label: "通知公告" },
  649. ] as const;
  650. type NoticeTabKey = (typeof noticeTabs)[number]["key"];
  651. const portalSections: PortalSection[] = [
  652. {
  653. code: "数",
  654. title: "数字化运营平台",
  655. subtitle: "高效协同 · 战略洞察",
  656. height: "220px",
  657. apps: [
  658. {
  659. label: "OA办公",
  660. image: oaimage,
  661. active: true,
  662. },
  663. { label: "客户管理(CRM)", image: crmimage },
  664. { label: "人力资源(EHR)", image: ehrimage },
  665. { label: "供应商管理(SRM)", image: scmimage },
  666. { label: "经营驾驶舱(MC)", image: driveimage },
  667. { label: "项目管理(PM)", image: pmimage },
  668. { label: "开发需求管理", image: jishuimage2 },
  669. { label: "鸿盘", image: hongpan },
  670. { label: "绩效管理(OKR)", image: okrimage },
  671. { label: "财务管理(FM)", image: erpimage },
  672. { label: "技术研发管理(R&D)", image: jishuimage2 },
  673. { label: "战略解码与执行", image: zhanlueimage },
  674. { label: "组织资产管理", image: zuzhiimage },
  675. { label: "风控合规管理", image: safeimage },
  676. ],
  677. },
  678. {
  679. code: "智",
  680. title: "智慧指挥平台",
  681. subtitle: "高效协同 · 战略洞察",
  682. height: "210px",
  683. apps: [
  684. { label: "设备管理(PMS)", image: pmsimage, active: true },
  685. { label: "质量安全管理(QHSE)", image: qhseimage },
  686. { label: "中航北斗", image: zhonghangimage },
  687. { label: "智慧连油", image: lianyouimage },
  688. { label: "智慧注气", image: zhuqiimage },
  689. { label: "视频中心(VCS)", image: videoimage },
  690. { label: "智能钻井", image: zuanjingimage },
  691. { label: "仓储管理(WMS)", image: wms },
  692. { label: "智慧压裂", image: yalieimage },
  693. { label: "数字油藏", image: youimage },
  694. ],
  695. },
  696. {
  697. code: "AI",
  698. title: "Chat BI平台",
  699. subtitle: "数据智能 · 决策驱动",
  700. height: "160px",
  701. apps: [
  702. { label: "全局数据治理(数据中台)", image: dataimage, active: true },
  703. { label: "智能决策", image: thinkimage },
  704. { label: "行业AI大模型", image: aiimage },
  705. { label: "AI智能体(智能交互)", image: agentimage },
  706. { label: "工艺文件智能体", image: fileagent },
  707. { label: "集团制度智能体", image: zhiduagent },
  708. { label: "智能问数", image: ask },
  709. { label: "瑞小智(数字人)", image: degitial },
  710. ],
  711. },
  712. ];
  713. let boldLabes = ref([
  714. "OA办公",
  715. "客户管理(CRM)",
  716. "设备管理(PMS)",
  717. "中航北斗",
  718. "智慧连油",
  719. "质量安全管理(QHSE)",
  720. "智慧注气",
  721. "视频中心(VCS)",
  722. "开发需求管理",
  723. "经营驾驶舱(MC)",
  724. "项目管理(PM)",
  725. "鸿盘",
  726. "智能钻井",
  727. "供应商管理(SRM)",
  728. "人力资源(EHR)",
  729. "绩效管理(OKR)",
  730. ]);
  731. const searchKeyword = ref("");
  732. // 2. 实现搜索处理函数
  733. const handleSearch = () => {
  734. if (!searchKeyword.value.trim()) return;
  735. console.log("执行搜索:", searchKeyword.value);
  736. // 如果需要跳转到独立搜索页,可以在这里添加 router.push
  737. };
  738. const handleQuickAccessCommand = (command: string) => {
  739. const ua = window.navigator.userAgent; // const ua = navigator.userAgent;
  740. const isDesktop =
  741. ua.includes("DingTalk") &&
  742. (ua.includes("Windows") || ua.includes("Macintosh"));
  743. let ua2 = navigator.userAgent.toLowerCase();
  744. let isMobile = ua2.indexOf("dingtalk") > -1;
  745. const routeMap: Record<string, string> = {
  746. "todo-oa": "/todo-list?type=oa",
  747. "done-oa": "/oa-done-list?type=oa",
  748. "todo-crm": "/crm-todo-list?type=crm",
  749. "done-crm": "/crm-done-list?type=crm",
  750. "todo-srm": "/srm-todo-list?type=srm",
  751. "done-srm": "/srm-done-list?type=srm",
  752. "todo-ehr": "/ehr-todo-list?type=ehr",
  753. };
  754. const routeMap2: Record<string, string> = {
  755. "todo-oa": "/mobile-todo-list?type=oa",
  756. "done-oa": "/oa-done-list?type=oa",
  757. "todo-crm": "/mobile-crm-todo-list?type=crm",
  758. "done-crm": "/crm-done-list?type=crm",
  759. "todo-srm": "/mobile-srm-todo-list?type=srm",
  760. "done-srm": "/srm-done-list?type=srm",
  761. "todo-ehr": "/ehr-todo-list?type=ehr",
  762. };
  763. let target = null;
  764. if (ua2.includes("dingtalk") || ua2.includes("dingtalkwork")) {
  765. if (isMobile && !isDesktop) {
  766. if (command === "todo-oa" || command === "done-oa") {
  767. dd.biz.util.openLink({
  768. url: `https://yfding.keruioil.com:9443/wxapi/wxclientmenu/61c40c87c45346ce9695f223ea823278`,
  769. onSuccess: () => {},
  770. });
  771. return;
  772. } else {
  773. target = routeMap2[command];
  774. }
  775. } else if (isDesktop) {
  776. target = routeMap[command];
  777. }
  778. } else {
  779. // 浏览器环境
  780. target = routeMap[command];
  781. }
  782. console.log(
  783. "🚀 ~ file: index.vue ~ line 91 ~ handleQuickAccessCommand ~ target",
  784. target,
  785. );
  786. // 跳转
  787. if (target) {
  788. router.push(target);
  789. }
  790. };
  791. const filteredSections = computed(() => {
  792. const keyword = searchKeyword.value.trim().toLowerCase();
  793. // 如果没有关键词,返回原始数据
  794. if (!keyword) {
  795. return portalSections;
  796. }
  797. // 过滤逻辑:遍历每个板块,保留匹配的应用
  798. return portalSections
  799. .map((section) => {
  800. if (!section.apps) return { ...section, apps: [] };
  801. const matchedApps = section.apps.filter((app) =>
  802. app.label.toLowerCase().includes(keyword),
  803. );
  804. return {
  805. ...section,
  806. apps: matchedApps,
  807. };
  808. })
  809. .filter((section) => section.apps && section.apps.length > 0); // 移除没有匹配应用的空板块
  810. });
  811. watch(
  812. () => themeStore.theme,
  813. () => {
  814. if (themeStore.theme === "light") {
  815. slides.value = [
  816. {
  817. image: banner2_white,
  818. text: "数字化转型,驱动未来增长",
  819. },
  820. {
  821. image: banner1_white,
  822. text: "保持热爱,奔赴目标!",
  823. },
  824. {
  825. image: banner1_white,
  826. text: "智慧平台,赋能高效协同",
  827. },
  828. ];
  829. } else {
  830. slides.value = [
  831. {
  832. image: banner2,
  833. text: "数字化转型,驱动未来增长",
  834. },
  835. {
  836. image: banner1,
  837. text: "保持热爱,奔赴目标!",
  838. },
  839. {
  840. image: banner1,
  841. text: "智慧平台,赋能高效协同",
  842. },
  843. ];
  844. }
  845. },
  846. );
  847. // 添加轮播数据
  848. const slides = ref([
  849. {
  850. image: themeStore.theme === "light" ? banner2_white : banner2,
  851. text: "数字化转型,驱动未来增长",
  852. },
  853. {
  854. image: themeStore.theme === "light" ? banner1_white : banner1,
  855. text: "保持热爱,奔赴目标!",
  856. },
  857. {
  858. image: themeStore.theme === "light" ? banner1_white : banner1,
  859. text: "智慧平台,赋能高效协同",
  860. },
  861. ]);
  862. // 当前索引
  863. const currentIndex = ref(0);
  864. let slideTimer: ReturnType<typeof setInterval> | null = null;
  865. // 下一页
  866. const nextSlide = () => {
  867. currentIndex.value = (currentIndex.value + 1) % slides.value.length;
  868. };
  869. const activeNoticeTab = ref<NoticeTabKey>("redHead");
  870. const noticeListMap = ref<Record<NoticeTabKey, NoticeItem[]>>({
  871. notice: [],
  872. redHead: [],
  873. });
  874. const newsList = ref([]);
  875. const currentNoticeList = computed(
  876. () => noticeListMap.value[activeNoticeTab.value] ?? [],
  877. );
  878. let todo = ref(0);
  879. let done = ref(0);
  880. let oatodo = ref(0);
  881. let oadone = ref(0);
  882. let crmtodo = ref(0);
  883. let crmdone = ref(0);
  884. let srmtodo = ref(0);
  885. let srmdone = ref(0);
  886. let ehrtodo = ref(0);
  887. const loadHomeData = async () => {
  888. await loadNoticeList(activeNoticeTab.value);
  889. if (!userStore.getUser.username) return;
  890. const [oaRes, crmRes, crmDoneRes, srmRes, ehrRes, newsRes] =
  891. await Promise.all([
  892. getOATasks({
  893. id: userStore.getUser.username,
  894. pageNum: 1,
  895. pageSize: 10,
  896. }),
  897. await getCRMTasks({
  898. id: userStore.getUser.username,
  899. type: "pending",
  900. pageNum: 1,
  901. pageSize: 10,
  902. }),
  903. await getCRMTasks({
  904. id: userStore.getUser.username,
  905. type: "approved",
  906. pageNum: 1,
  907. pageSize: 10,
  908. }),
  909. await getSRMTasks({
  910. id: userStore.getUser.username,
  911. pageNum: 1,
  912. pageSize: 10,
  913. }),
  914. await getEHRTasks({
  915. id: userStore.getUser.username,
  916. pageNum: 1,
  917. pageSize: 10,
  918. }),
  919. getNews({
  920. pageNum: 1,
  921. pageSize: 10,
  922. }),
  923. ]);
  924. oaTasks.value = oaRes.todoList.slice(0, 3);
  925. todo.value =
  926. Number(oaRes.todoCount) +
  927. crmRes.todoCount +
  928. srmRes.todoCount +
  929. ehrRes.todoCount;
  930. done.value =
  931. Number(oaRes.doneCount) + crmDoneRes.todoCount + srmRes.doneCount;
  932. oatodo.value = oaRes.todoCount;
  933. oadone.value = oaRes.doneCount;
  934. crmtodo.value = crmRes.todoCount;
  935. crmdone.value = crmDoneRes.todoCount;
  936. srmtodo.value = srmRes.todoCount;
  937. srmdone.value = srmRes.doneCount;
  938. ehrtodo.value = ehrRes.todoCount;
  939. newsList.value = newsRes.list.slice(0, 3);
  940. };
  941. const loadNoticeList = async (tabKey: NoticeTabKey) => {
  942. const requestApi = tabKey === "notice" ? getNotices : getRedHeadFiles;
  943. const res = await requestApi({
  944. pageNum: 1,
  945. pageSize: 10,
  946. });
  947. noticeListMap.value[tabKey] = (res?.list || []).slice(0, 3);
  948. };
  949. let currentTabKey = ref<NoticeTabKey>("redHead");
  950. const handleNoticeTabChange = async (tabKey: NoticeTabKey) => {
  951. activeNoticeTab.value = tabKey;
  952. currentTabKey.value = tabKey;
  953. // if (noticeListMap.value[tabKey].length) return;
  954. if (userStore.getUser.username) {
  955. await loadNoticeList(tabKey);
  956. }
  957. };
  958. const handleNoticeMoreClick = () => {
  959. router.push({
  960. path: "/notice-redhead",
  961. query: {
  962. tabKey: currentTabKey.value,
  963. },
  964. });
  965. };
  966. const protectedOpen = (url: string) => {
  967. if (userStore.getUser.username && getAccessToken()) {
  968. window.open(url, "_blank");
  969. } else {
  970. router.push({ path: "/login" });
  971. }
  972. };
  973. let dialogVisible = ref(false);
  974. const handlePortalAppClick = async (app: PortalApp) => {
  975. if (!app.label) return;
  976. if (app.label === "项目管理(PM)") {
  977. protectedOpen(
  978. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}&source=yyhy`,
  979. );
  980. }
  981. if (app.label === "OA办公") {
  982. // if (userStore.getUser.username && getAccessToken()) {
  983. // const res = await ssoLogin({ username: userStore.getUser.username });
  984. // if (res) {
  985. // window.open(
  986. // `https://yfoa.keruioil.com/wui/index.html?ssoToken=${res}#/main`,
  987. // "_blank",
  988. // );
  989. // }
  990. // } else {
  991. // router.push({ path: "/login" });
  992. // }
  993. if (userStore.getUser.username && getAccessToken()) {
  994. const ua = window.navigator.userAgent; // const ua = navigator.userAgent;
  995. const isDesktop =
  996. ua.includes("DingTalk") &&
  997. (ua.includes("Windows") || ua.includes("Macintosh"));
  998. let ua2 = navigator.userAgent.toLowerCase();
  999. let isMobile = ua2.indexOf("dingtalk") > -1;
  1000. const res = await ssoLogin({ username: userStore.getUser.username });
  1001. if (ua2.includes("dingtalk") || ua2.includes("dingtalkwork")) {
  1002. if (isMobile && !isDesktop) {
  1003. dd.biz.util.openLink({
  1004. url: `https://yfding.keruioil.com:9443/wxapi/wxclientmenu/d5605d17b3cd4c1987e333ab0d945070`,
  1005. onSuccess: () => {},
  1006. });
  1007. } else if (isDesktop) {
  1008. if (res) {
  1009. window.open(
  1010. `https://yfoa.keruioil.com/wui/index.html?ssoToken=${res}#/main`,
  1011. "_blank",
  1012. );
  1013. }
  1014. }
  1015. } else {
  1016. // 浏览器
  1017. if (res) {
  1018. window.open(
  1019. `https://yfoa.keruioil.com/wui/index.html?ssoToken=${res}#/main`,
  1020. "_blank",
  1021. );
  1022. }
  1023. }
  1024. } else {
  1025. router.push({ path: "/login" });
  1026. }
  1027. }
  1028. if (app.label === "客户管理(CRM)") {
  1029. if (userStore.getUser.username && getAccessToken()) {
  1030. const ua = window.navigator.userAgent; // const ua = navigator.userAgent;
  1031. const isDesktop =
  1032. ua.includes("DingTalk") &&
  1033. (ua.includes("Windows") || ua.includes("Macintosh"));
  1034. let ua2 = navigator.userAgent.toLowerCase();
  1035. var isMobile = ua2.indexOf("dingtalk") > -1;
  1036. if (ua2.includes("dingtalk") || ua2.includes("dingtalkwork")) {
  1037. if (isMobile && !isDesktop) {
  1038. const originPath =
  1039. "/bff/spa/crmh5/index.html#/home?platform=H5&deviceType=0";
  1040. // 连续三次encodeURIComponent
  1041. let e1 = encodeURIComponent(originPath);
  1042. let e2 = encodeURIComponent(e1);
  1043. let e3 = encodeURIComponent(e2);
  1044. dd.biz.util.openLink({
  1045. url: `https://crm-tencent.xiaoshouyi.com/global/sso/callback/00APEB9EEEA9B2E338B686B7ECFA8585808C.action?token=${getAccessToken()}&returnUri=${e3}`,
  1046. onSuccess: () => {},
  1047. });
  1048. } else if (isDesktop) {
  1049. window.open(
  1050. `https://crm-tencent.xiaoshouyi.com/global/sso/callback/00APEB9EEEA9B2E338B686B7ECFA8585808C.action?token=${getAccessToken()}`,
  1051. "_blank",
  1052. );
  1053. }
  1054. } else {
  1055. window.open(
  1056. `https://crm-tencent.xiaoshouyi.com/global/sso/callback/00APEB9EEEA9B2E338B686B7ECFA8585808C.action?token=${getAccessToken()}`,
  1057. "_blank",
  1058. );
  1059. }
  1060. } else {
  1061. router.push({ path: "/login" });
  1062. }
  1063. }
  1064. if (app.label === "设备管理(PMS)") {
  1065. protectedOpen(
  1066. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}`,
  1067. );
  1068. }
  1069. if (app.label === "中航北斗") {
  1070. protectedOpen("https://zhbdgps.cn");
  1071. }
  1072. if (app.label === "智慧连油") {
  1073. protectedOpen(
  1074. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}&source=zhly`,
  1075. );
  1076. }
  1077. if (app.label === "智慧注气") {
  1078. protectedOpen(
  1079. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}&source=zhzq`,
  1080. );
  1081. }
  1082. if (app.label === "视频中心(VCS)") {
  1083. protectedOpen(
  1084. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}&source=spzx`,
  1085. );
  1086. }
  1087. if (app.label === "质量安全管理(QHSE)") {
  1088. protectedOpen(
  1089. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}&source=qhse_nav`,
  1090. );
  1091. }
  1092. if (app.label === "经营驾驶舱(MC)") {
  1093. if (userStore.getUser.username && getAccessToken()) {
  1094. const res = await getMCSsoToken();
  1095. if (res) {
  1096. window.open(
  1097. `https://report.deepoil.cc/webroot/decision/v10/entry/access/9fb42908-894a-4373-a6be-ce046a42851d?preview=true&page_number=1&ssoToken=${res}`,
  1098. "_blank",
  1099. );
  1100. }
  1101. } else {
  1102. router.push({ path: "/login" });
  1103. }
  1104. }
  1105. if (app.label === "开发需求管理") {
  1106. if (userStore.getUser.username && getAccessToken()) {
  1107. const res = await zentaoSsoLogin({
  1108. username: userStore.getUser.username,
  1109. });
  1110. if (res) {
  1111. window.open(
  1112. `http://project.deepoil.cc/zentao/api.php?m=user&f=apilogin&account=${res.jobNumber}&code=${res.code}&time=${res.timestamp}&token=${res.token}`,
  1113. "_blank",
  1114. );
  1115. }
  1116. } else {
  1117. router.push({ path: "/login" });
  1118. }
  1119. }
  1120. if (app.label === "人力资源(EHR)") {
  1121. if (userStore.getUser.username && getAccessToken()) {
  1122. const res = await ehrLogin({
  1123. username: userStore.getUser.username,
  1124. });
  1125. const ua = window.navigator.userAgent;
  1126. const isDesktop =
  1127. ua.includes("DingTalk") &&
  1128. (ua.includes("Windows") || ua.includes("Macintosh"));
  1129. let ua2 = navigator.userAgent.toLowerCase();
  1130. var isMobile = ua2.indexOf("dingtalk") > -1;
  1131. if (ua2.includes("dingtalk") || ua2.includes("dingtalkwork")) {
  1132. if (isMobile && !isDesktop) {
  1133. dd.biz.util.openLink({
  1134. url: `${res.ehrUrl}/gateway/login/free?loginfree_licence=${res.licence}&signature=${res.sign}&redirect_url=/proxy/h5/home`,
  1135. onSuccess: () => {},
  1136. });
  1137. } else if (isDesktop) {
  1138. window.open(
  1139. `${res.ehrUrl}/gateway/login/free?loginfree_licence=${res.licence}&signature=${res.sign}&redirect_url=${res.redirect}`,
  1140. "_blank",
  1141. );
  1142. }
  1143. } else {
  1144. // 浏览器环境
  1145. window.open(
  1146. `${res.ehrUrl}/gateway/login/free?loginfree_licence=${res.licence}&signature=${res.sign}&redirect_url=${res.redirect}`,
  1147. "_blank",
  1148. );
  1149. }
  1150. } else {
  1151. router.push({ path: "/login" });
  1152. }
  1153. }
  1154. if (app.label === "供应商管理(SRM)") {
  1155. if (userStore.getUser.username && getAccessToken()) {
  1156. const ua = window.navigator.userAgent;
  1157. const isDesktop =
  1158. ua.includes("DingTalk") &&
  1159. (ua.includes("Windows") || ua.includes("Macintosh"));
  1160. let ua2 = navigator.userAgent.toLowerCase();
  1161. let isMobile = ua2.indexOf("dingtalk") > -1;
  1162. const res = await srmLogin({
  1163. username: userStore.getUser.username,
  1164. });
  1165. if (JSON.parse(res).msg === "账号未授权") {
  1166. dialogVisible.value = true;
  1167. return;
  1168. }
  1169. if (ua2.includes("dingtalk") || ua2.includes("dingtalkwork")) {
  1170. if (isMobile && !isDesktop) {
  1171. // alert("SRM判断是钉钉手机端登录");
  1172. dd.biz.util.openLink({
  1173. url: `https://srm-m.deepoil.cc:7777/#/mixed-page/view/MXP00048?Authorization=${JSON.parse(res).msg}`,
  1174. onSuccess: () => {},
  1175. });
  1176. } else if (isDesktop) {
  1177. window.open(
  1178. `https://srm.deepoil.cc/#/mixed-page/view/MXP00048?Authorization=${JSON.parse(res).msg}`,
  1179. "_blank",
  1180. );
  1181. }
  1182. } else {
  1183. // 浏览器环境
  1184. window.open(
  1185. `https://srm.deepoil.cc/#/mixed-page/view/MXP00048?Authorization=${JSON.parse(res).msg}`,
  1186. "_blank",
  1187. );
  1188. }
  1189. } else {
  1190. router.push({ path: "/login" });
  1191. }
  1192. }
  1193. if (app.label === "智能钻井") {
  1194. if (userStore.getUser.username && getAccessToken()) {
  1195. window.open(`http://172.21.0.224:8001/#/login`, "_blank");
  1196. } else {
  1197. router.push({ path: "/login" });
  1198. }
  1199. }
  1200. if (app.label === "鸿盘") {
  1201. if (userStore.getUser.username && getAccessToken()) {
  1202. window.open(`https://pan.keruioil.com:52180`, "_blank");
  1203. } else {
  1204. router.push({ path: "/login" });
  1205. }
  1206. }
  1207. if (app.label === "绩效管理(OKR)") {
  1208. if (userStore.getUser.username && getAccessToken()) {
  1209. dd.biz.util.openLink({
  1210. url: `https://dingokr.eapps.dingtalkcloud.com/web/index.html?corpId=dingbe7f9a7e8cffa2bd35c2f4657eb6378f`,
  1211. onSuccess: () => {},
  1212. });
  1213. } else {
  1214. router.push({ path: "/login" });
  1215. }
  1216. }
  1217. };
  1218. async function loginWithDingTalk() {
  1219. const ddCorpId = import.meta.env.VITE_DD_CORPID;
  1220. const ddClientId = import.meta.env.VITE_DD_CLIENTID;
  1221. if (!ddCorpId || !ddClientId) return false;
  1222. return await new Promise<boolean>((resolve) => {
  1223. dd.requestAuthCode({
  1224. corpId: ddCorpId,
  1225. clientId: ddClientId,
  1226. success: async (res: any) => {
  1227. try {
  1228. const { code } = res;
  1229. const response = await axios.post(
  1230. import.meta.env.BASE_URL + "/admin-api/system/auth/h5SocialLogin",
  1231. {
  1232. code,
  1233. type: 20,
  1234. state: new Date().getTime(),
  1235. },
  1236. {
  1237. headers: {
  1238. "Content-Type": "application/json",
  1239. "tenant-id": 1,
  1240. },
  1241. },
  1242. );
  1243. deleteUserCache();
  1244. userStore.resetState();
  1245. authUtil.setToken(response.data.data);
  1246. sessionStorage.removeItem(manualLogoutKey);
  1247. sessionStorage.removeItem(reloginCancelKey);
  1248. await userStore.setUserInfoAction();
  1249. resolve(true);
  1250. } catch (error) {
  1251. console.log("dingTalk login error :>> ", error);
  1252. resolve(false);
  1253. }
  1254. },
  1255. fail: (err: any) => {
  1256. console.log("err :>> ", err);
  1257. resolve(false);
  1258. },
  1259. });
  1260. });
  1261. }
  1262. async function dingTalkAutoLogin() {
  1263. const ua = window.navigator.userAgent.toLowerCase();
  1264. if (
  1265. (ua.includes("dingtalk") || ua.includes("dingtalkwork")) &&
  1266. !userStore.getUser.username &&
  1267. !getAccessToken()
  1268. ) {
  1269. return await loginWithDingTalk();
  1270. }
  1271. return false;
  1272. }
  1273. const getTagClass = (tag: string) => {
  1274. return tag === "0"
  1275. ? "bg-[#2e1e6d] text-white/90"
  1276. : tag === "1"
  1277. ? "bg-[#2e1e6d] text-white/90"
  1278. : tag === "2"
  1279. ? "bg-[#2e1e6d] text-white/90"
  1280. : "bg-[#2e1e6d] text-white/90";
  1281. };
  1282. const getTagName = (tag: string) => {
  1283. return tag === "0"
  1284. ? "正常"
  1285. : tag === "1"
  1286. ? "重要"
  1287. : tag === "2"
  1288. ? "紧急"
  1289. : "正常";
  1290. };
  1291. let oaTasks = ref([]);
  1292. onMounted(async () => {
  1293. slideTimer = setInterval(nextSlide, 5000);
  1294. if (showDingTalkLoginMask.value) {
  1295. try {
  1296. await dingTalkAutoLogin();
  1297. } finally {
  1298. showDingTalkLoginMask.value = false;
  1299. }
  1300. }
  1301. await loadHomeData();
  1302. });
  1303. const handleNoticeItemClick = async (notice: any) => {
  1304. const res = await ssoLogin({
  1305. username: userStore.getUser.username,
  1306. });
  1307. if (res) {
  1308. const ua = window.navigator.userAgent.toLowerCase();
  1309. if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
  1310. dd.biz.util.openLink({
  1311. url:
  1312. "https://yfoa.keruioil.com/wui/index.html" +
  1313. "?ssoToken=" +
  1314. res +
  1315. "#/main", // 先跳你的 SSO 链接
  1316. onSuccess: () => {
  1317. // 延迟跳目标业务地址(和你原来 setTimeout 逻辑一致)
  1318. setTimeout(() => {
  1319. dd.biz.util.openLink({
  1320. url: `https://yfoa.keruioil.com/spa/document/index.jsp?openAttachment=0&id=${notice.id}`,
  1321. });
  1322. }, 100);
  1323. },
  1324. });
  1325. } else {
  1326. const loading = ElLoading.service({
  1327. lock: true,
  1328. text: "正在跳转,请稍候...",
  1329. background: "rgba(0, 0, 0, 0.7)",
  1330. });
  1331. const newTab = window.open("", "_blank");
  1332. newTab.location.href =
  1333. "https://yfoa.keruioil.com/wui/index.html" +
  1334. "?ssoToken=" +
  1335. res +
  1336. "#/main";
  1337. setTimeout(function () {
  1338. newTab.location.href = `https://yfoa.keruioil.com/spa/document/index.jsp?openAttachment=0&id=${notice.id}`;
  1339. setTimeout(() => {
  1340. loading.close();
  1341. }, 500);
  1342. }, 100);
  1343. }
  1344. }
  1345. };
  1346. const handleTask = async (row: any) => {
  1347. const res = await ssoLogin({
  1348. username: userStore.getUser.username,
  1349. });
  1350. if (res) {
  1351. const ua = window.navigator.userAgent.toLowerCase();
  1352. if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
  1353. dd.biz.util.openLink({
  1354. url:
  1355. "https://yfoa.keruioil.com/wui/index.html" +
  1356. "?ssoToken=" +
  1357. res +
  1358. "#/main", // 先跳你的 SSO 链接
  1359. onSuccess: () => {
  1360. // 延迟跳目标业务地址(和你原来 setTimeout 逻辑一致)
  1361. setTimeout(() => {
  1362. dd.biz.util.openLink({
  1363. url: `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1776063595284#/main/workflow/req?requestid=${row.requestId}`,
  1364. });
  1365. }, 100);
  1366. },
  1367. });
  1368. } else {
  1369. const loading = ElLoading.service({
  1370. lock: true,
  1371. text: "正在跳转,请稍候...",
  1372. background: "rgba(0, 0, 0, 0.7)",
  1373. });
  1374. const newTab = window.open("", "_blank");
  1375. newTab.location.href =
  1376. "https://yfoa.keruioil.com/wui/index.html" +
  1377. "?ssoToken=" +
  1378. res +
  1379. "#/main";
  1380. setTimeout(function () {
  1381. newTab.location.href = `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1776063595284#/main/workflow/req?requestid=${row.requestId}`;
  1382. setTimeout(() => {
  1383. loading.close();
  1384. }, 500);
  1385. }, 100);
  1386. }
  1387. }
  1388. };
  1389. const openConsult = async () => {
  1390. const res = await ssoLogin({
  1391. username: userStore.getUser.username,
  1392. });
  1393. if (res) {
  1394. const ua = window.navigator.userAgent.toLowerCase();
  1395. if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
  1396. dd.biz.util.openLink({
  1397. url:
  1398. "https://yfoa.keruioil.com/wui/index.html" +
  1399. "?ssoToken=" +
  1400. res +
  1401. "#/main", // 先跳你的 SSO 链接
  1402. onSuccess: () => {
  1403. // 延迟跳目标业务地址(和你原来 setTimeout 逻辑一致)
  1404. setTimeout(() => {
  1405. dd.biz.util.openLink({
  1406. url: `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1778289187850#/main/workflow/req?iscreate=1&workflowid=488`,
  1407. });
  1408. }, 100);
  1409. },
  1410. });
  1411. } else {
  1412. const loading = ElLoading.service({
  1413. lock: true,
  1414. text: "正在跳转,请稍候...",
  1415. background: "rgba(0, 0, 0, 0.7)",
  1416. });
  1417. const newTab = window.open("", "_blank");
  1418. newTab.location.href =
  1419. "https://yfoa.keruioil.com/wui/index.html" +
  1420. "?ssoToken=" +
  1421. res +
  1422. "#/main";
  1423. setTimeout(function () {
  1424. newTab.location.href = `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1778289187850#/main/workflow/req?iscreate=1&workflowid=488`;
  1425. setTimeout(() => {
  1426. loading.close();
  1427. }, 500);
  1428. }, 100);
  1429. }
  1430. }
  1431. };
  1432. const goNews = () => {
  1433. router.push("/news");
  1434. };
  1435. onUnmounted(() => {
  1436. if (slideTimer) {
  1437. clearInterval(slideTimer);
  1438. slideTimer = null;
  1439. }
  1440. });
  1441. </script>
  1442. <style scoped>
  1443. .portal-home {
  1444. --portal-text: #17345f;
  1445. --portal-text-muted: #5f6f83;
  1446. --portal-text-soft: #7f8fa6;
  1447. --portal-title: #163867;
  1448. --portal-subtitle: rgba(61, 92, 135, 0.86);
  1449. --portal-line: rgba(126, 156, 201, 0.24);
  1450. --portal-card: rgba(255, 255, 255, 0.82);
  1451. --portal-card-2: rgba(248, 251, 255, 0.94);
  1452. --portal-card-3: rgba(240, 246, 255, 0.88);
  1453. --portal-card-4: rgba(231, 239, 251, 0.92);
  1454. --portal-nav-bg: rgba(255, 255, 255, 0.72);
  1455. --portal-nav-hover: rgba(219, 232, 252, 0.8);
  1456. --portal-input-bg: rgba(255, 255, 255, 0.7);
  1457. --portal-input-hover: rgba(255, 255, 255, 0.92);
  1458. --portal-shadow: 0 18px 40px rgba(23, 52, 95, 0.12);
  1459. --portal-shadow-strong: 0 24px 60px rgba(23, 52, 95, 0.16);
  1460. --portal-accent: #245edb;
  1461. --portal-accent-2: #4e8cff;
  1462. --portal-accent-soft: rgba(36, 94, 219, 0.14);
  1463. --portal-todo-bg: rgba(242, 247, 255, 0.94);
  1464. --portal-todo-hover: rgba(228, 238, 252, 0.95);
  1465. --portal-number-todo: #e15a5a;
  1466. --portal-number-done: #2da04d;
  1467. color: var(--portal-text);
  1468. background:
  1469. radial-gradient(
  1470. circle at 18% 12%,
  1471. rgba(83, 126, 255, 0.14),
  1472. transparent 22%
  1473. ),
  1474. radial-gradient(
  1475. circle at 82% 20%,
  1476. rgba(71, 148, 255, 0.14),
  1477. transparent 20%
  1478. ),
  1479. radial-gradient(
  1480. circle at 50% 100%,
  1481. rgba(97, 142, 247, 0.12),
  1482. transparent 28%
  1483. ),
  1484. linear-gradient(180deg, #eef3f9 0%, #f7faff 46%, #eef3f9 100%);
  1485. background-image:
  1486. /* linear-gradient(rgba(245, 249, 255, 0.82), rgba(237, 244, 253, 0.92)), */
  1487. linear-gradient(rgba(245, 249, 255, 0.9), rgba(218, 233, 251, 0.82)),
  1488. url("../assets//images/bg666.png");
  1489. background-position:
  1490. center center,
  1491. center bottom;
  1492. background-repeat: no-repeat, no-repeat;
  1493. background-size:
  1494. cover,
  1495. 100% auto;
  1496. background-attachment: fixed, fixed;
  1497. }
  1498. .portal-login-mask {
  1499. position: fixed;
  1500. inset: 0;
  1501. z-index: 9999;
  1502. display: flex;
  1503. align-items: center;
  1504. justify-content: center;
  1505. background: rgba(9, 16, 32, 0.52);
  1506. backdrop-filter: blur(6px);
  1507. -webkit-backdrop-filter: blur(6px);
  1508. }
  1509. .portal-login-mask__content {
  1510. display: flex;
  1511. flex-direction: column;
  1512. align-items: center;
  1513. gap: 16px;
  1514. padding: 28px 36px;
  1515. /* border: 1px solid rgba(255, 255, 255, 0.12); */
  1516. border-radius: 18px;
  1517. }
  1518. .portal-login-mask__spinner {
  1519. width: 34px;
  1520. height: 34px;
  1521. border: 3px solid rgba(255, 255, 255, 0.2);
  1522. border-top-color: #ffffff;
  1523. border-radius: 999px;
  1524. animation: portal-login-spin 0.9s linear infinite;
  1525. }
  1526. .portal-login-mask__text {
  1527. margin: 0;
  1528. color: #ffffff;
  1529. font-size: 14px;
  1530. letter-spacing: 0.04em;
  1531. }
  1532. @keyframes portal-login-spin {
  1533. to {
  1534. transform: rotate(360deg);
  1535. }
  1536. }
  1537. :global([data-theme="dark"] .portal-home) {
  1538. --portal-text: #eaf1ff;
  1539. --portal-text-muted: rgba(234, 241, 255, 0.95);
  1540. --portal-text-soft: #8a9ab0;
  1541. --portal-title: #f4f7ff;
  1542. --portal-subtitle: rgba(188, 205, 255, 0.82);
  1543. --portal-line: rgba(97, 129, 206, 0.28);
  1544. --portal-card: rgba(10, 19, 43, 0.8);
  1545. --portal-card-2: rgba(12, 24, 52, 0.92);
  1546. --portal-card-3: rgba(17, 25, 48, 0.8);
  1547. --portal-card-4: rgba(15, 24, 45, 0.82);
  1548. --portal-nav-bg: rgba(10, 19, 43, 0.8);
  1549. --portal-nav-hover: rgba(28, 40, 72, 0.8);
  1550. --portal-input-bg: rgba(255, 255, 255, 0.08);
  1551. --portal-input-hover: rgba(255, 255, 255, 0.12);
  1552. --portal-shadow: 0 16px 34px rgba(0, 0, 0, 0.22);
  1553. --portal-shadow-strong: 0 24px 60px rgba(0, 0, 0, 0.38);
  1554. --portal-accent: #6e7dff;
  1555. --portal-accent-2: #8d4dff;
  1556. --portal-accent-soft: rgba(110, 125, 255, 0.16);
  1557. --portal-todo-bg: #070e20;
  1558. --portal-todo-hover: rgba(28, 40, 72, 0.8);
  1559. --portal-number-todo: #f56c6c;
  1560. --portal-number-done: #ffffff;
  1561. color: var(--portal-text);
  1562. background:
  1563. radial-gradient(
  1564. circle at 18% 12%,
  1565. rgba(79, 82, 221, 0.34),
  1566. transparent 22%
  1567. ),
  1568. radial-gradient(circle at 82% 20%, rgba(28, 95, 255, 0.2), transparent 20%),
  1569. radial-gradient(
  1570. circle at 50% 100%,
  1571. rgba(103, 46, 255, 0.16),
  1572. transparent 28%
  1573. ),
  1574. linear-gradient(180deg, #040814 0%, #060d1d 46%, #040814 100%);
  1575. background-image:
  1576. linear-gradient(rgba(5, 11, 26, 0.62), rgba(5, 11, 26, 0.82)),
  1577. url("../assets//images/bg666.png");
  1578. }
  1579. .hero-banner {
  1580. position: relative;
  1581. min-height: 310px;
  1582. border: 1px solid var(--portal-line);
  1583. border-radius: 10px;
  1584. background:
  1585. linear-gradient(135deg, var(--portal-accent-soft), transparent 32%),
  1586. linear-gradient(180deg, var(--portal-card), var(--portal-card-2));
  1587. box-shadow:
  1588. var(--portal-shadow-strong),
  1589. inset 0 1px 0 rgba(255, 255, 255, 0.06);
  1590. overflow: hidden;
  1591. }
  1592. .hero-banner::before {
  1593. content: "";
  1594. position: absolute;
  1595. inset: 0;
  1596. background:
  1597. linear-gradient(120deg, rgba(128, 77, 255, 0.14) 0%, transparent 26%),
  1598. radial-gradient(
  1599. circle at 20% 36%,
  1600. rgba(123, 84, 255, 0.22),
  1601. transparent 22%
  1602. ),
  1603. radial-gradient(
  1604. circle at 74% 52%,
  1605. rgba(54, 170, 255, 0.18),
  1606. transparent 24%
  1607. ),
  1608. linear-gradient(
  1609. 90deg,
  1610. rgba(255, 255, 255, 0.04),
  1611. transparent 40%,
  1612. rgba(255, 255, 255, 0.02)
  1613. );
  1614. pointer-events: none;
  1615. }
  1616. .hero-copy {
  1617. position: relative;
  1618. z-index: 2;
  1619. }
  1620. .hero-script {
  1621. display: none;
  1622. }
  1623. .hero-text {
  1624. margin-top: 0;
  1625. color: var(--portal-text);
  1626. font-size: 46px;
  1627. font-weight: 800;
  1628. line-height: 1.2;
  1629. letter-spacing: 0.02em;
  1630. text-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  1631. max-width: 540px;
  1632. }
  1633. .hero-visual {
  1634. position: absolute;
  1635. inset: 0;
  1636. min-height: 310px;
  1637. }
  1638. .hero-visual::after {
  1639. content: "";
  1640. position: absolute;
  1641. inset: 0;
  1642. background:
  1643. linear-gradient(90deg, rgba(5, 11, 26, 0.18) 0%, transparent 28%),
  1644. linear-gradient(
  1645. 180deg,
  1646. transparent 0%,
  1647. rgba(5, 11, 26, 0.04) 56%,
  1648. rgba(5, 11, 26, 0.18) 100%
  1649. );
  1650. pointer-events: none;
  1651. }
  1652. .carousel-container {
  1653. position: relative;
  1654. height: 310px;
  1655. overflow: hidden;
  1656. }
  1657. .platform-block {
  1658. /* display: flex; */
  1659. overflow: hidden;
  1660. border: 1px solid var(--portal-line);
  1661. border-radius: 8px;
  1662. background: linear-gradient(180deg, var(--portal-card), var(--portal-card-2));
  1663. box-shadow:
  1664. var(--portal-shadow),
  1665. inset 0 1px 0 rgba(255, 255, 255, 0.04);
  1666. }
  1667. .platform-block__header {
  1668. display: flex;
  1669. align-items: center;
  1670. justify-content: flex-start;
  1671. flex: 0 0 190px;
  1672. width: 190px;
  1673. min-width: 190px;
  1674. min-height: 48px;
  1675. padding: 0 20px 0 0px;
  1676. box-sizing: border-box;
  1677. background: linear-gradient(90deg, var(--portal-accent-soft), transparent);
  1678. }
  1679. .platform-block__title-wrap {
  1680. display: flex;
  1681. flex-direction: column;
  1682. align-items: baseline;
  1683. gap: 18px;
  1684. min-width: 0;
  1685. flex: 1;
  1686. }
  1687. .platform-block__title {
  1688. color: var(--portal-title);
  1689. font-size: 15px;
  1690. font-weight: 800;
  1691. min-width: 0;
  1692. flex: 0 1 auto;
  1693. white-space: nowrap;
  1694. overflow: hidden;
  1695. text-overflow: ellipsis;
  1696. letter-spacing: 0.02em;
  1697. }
  1698. .platform-block__subtitle {
  1699. color: var(--portal-subtitle);
  1700. font-size: 13px;
  1701. font-weight: 500;
  1702. flex: 0 0 auto;
  1703. white-space: nowrap;
  1704. }
  1705. .platform-block__watermark {
  1706. color: rgba(119, 145, 214, 0.24);
  1707. font-size: 42px;
  1708. font-weight: 800;
  1709. line-height: 1;
  1710. letter-spacing: 0.08em;
  1711. }
  1712. .platform-app {
  1713. display: flex;
  1714. width: 100%;
  1715. min-width: 0;
  1716. min-height: 40px;
  1717. align-items: center;
  1718. justify-content: flex-start;
  1719. gap: 6px;
  1720. border: 1px solid var(--portal-line);
  1721. border-radius: 10px;
  1722. padding: 0 8px;
  1723. background: var(--portal-card-3);
  1724. font-size: 14px;
  1725. cursor: pointer;
  1726. transition:
  1727. transform 0.2s ease,
  1728. box-shadow 0.2s ease,
  1729. background 0.2s ease;
  1730. }
  1731. .platform-app:hover {
  1732. transform: translateY(-2px);
  1733. border-color: rgba(133, 163, 255, 0.45);
  1734. box-shadow: 0 10px 26px rgba(34, 54, 110, 0.18);
  1735. }
  1736. .platform-app--active {
  1737. background: linear-gradient(
  1738. 90deg,
  1739. rgba(76, 103, 224, 0.5),
  1740. var(--portal-card)
  1741. );
  1742. color: var(--portal-title);
  1743. border-color: rgba(129, 145, 255, 0.44);
  1744. box-shadow: inset 0 0 0 1px rgba(159, 173, 255, 0.08);
  1745. }
  1746. .platform-app--ghost {
  1747. background: var(--portal-card-4);
  1748. color: var(--portal-text);
  1749. }
  1750. .platform-app__icon {
  1751. display: inline-flex;
  1752. align-items: center;
  1753. justify-content: center;
  1754. flex: 0 0 28px;
  1755. color: var(--portal-accent-2);
  1756. filter: drop-shadow(0 0 8px rgba(90, 122, 255, 0.24));
  1757. }
  1758. .platform-app__label {
  1759. min-width: auto;
  1760. flex: 1;
  1761. white-space: nowrap;
  1762. overflow: visible;
  1763. text-overflow: clip;
  1764. font-size: 13px;
  1765. line-height: 1;
  1766. color: var(--portal-text);
  1767. white-space: nowrap;
  1768. overflow: hidden;
  1769. text-overflow: ellipsis;
  1770. }
  1771. .platform-block > .grid {
  1772. flex: 1;
  1773. align-content: start;
  1774. }
  1775. .portal-mobile-shortcuts {
  1776. display: grid;
  1777. grid-template-columns: repeat(3, minmax(0, 1fr));
  1778. gap: 8px;
  1779. }
  1780. .portal-mobile-shortcut {
  1781. min-width: 0;
  1782. border: 1px solid var(--portal-line);
  1783. border-radius: 12px;
  1784. padding: 10px 8px;
  1785. background: linear-gradient(180deg, var(--portal-card), var(--portal-card-2));
  1786. color: var(--portal-text);
  1787. font-size: 13px;
  1788. font-weight: 600;
  1789. box-shadow: var(--portal-shadow);
  1790. }
  1791. .side-card {
  1792. overflow: hidden;
  1793. border: 1px solid var(--portal-line);
  1794. border-radius: 14px;
  1795. background: linear-gradient(180deg, var(--portal-card), var(--portal-card-2));
  1796. box-shadow:
  1797. var(--portal-shadow),
  1798. inset 0 1px 0 rgba(255, 255, 255, 0.04);
  1799. }
  1800. .side-card--content {
  1801. padding-top: 10px;
  1802. }
  1803. .side-card__header {
  1804. display: flex;
  1805. align-items: center;
  1806. justify-content: space-between;
  1807. min-height: 20px;
  1808. padding-right: 10px;
  1809. /* border-radius: 999px; */
  1810. /* background: rgba(28, 39, 72, 0.86); */
  1811. border-bottom: 1px solid var(--portal-line);
  1812. }
  1813. .notice-badge {
  1814. position: relative;
  1815. display: inline-flex;
  1816. align-items: center;
  1817. justify-content: center;
  1818. height: 25px;
  1819. min-width: 70px;
  1820. color: var(--portal-title);
  1821. font-size: 14px;
  1822. }
  1823. .news.notice-badge {
  1824. position: relative;
  1825. display: inline-flex;
  1826. align-items: center;
  1827. justify-content: center;
  1828. height: 25px;
  1829. min-width: 70px;
  1830. color: var(--portal-title);
  1831. font-size: 14px;
  1832. border-bottom: 2px solid var(--portal-accent);
  1833. }
  1834. .notice-tabs {
  1835. display: inline-flex;
  1836. align-items: center;
  1837. gap: 8px;
  1838. }
  1839. .notice-tab {
  1840. height: 25px;
  1841. min-width: 68px;
  1842. padding: 0 14px;
  1843. border: 0;
  1844. border-radius: 999px;
  1845. background: transparent;
  1846. color: var(--portal-subtitle);
  1847. font-size: 14px;
  1848. cursor: pointer;
  1849. transition:
  1850. background 0.2s ease,
  1851. color 0.2s ease;
  1852. }
  1853. .notice-tab--active {
  1854. position: relative;
  1855. color: var(--portal-title);
  1856. }
  1857. .notice-tab--active::before {
  1858. content: "";
  1859. position: absolute;
  1860. left: 50%;
  1861. bottom: -2px;
  1862. width: 90%;
  1863. height: 2px;
  1864. transform: translateX(-50%);
  1865. border-radius: 999px;
  1866. background: linear-gradient(
  1867. to right,
  1868. #5f3abf 0%,
  1869. #7044c5 30%,
  1870. #7044c5 40%,
  1871. rgba(255, 255, 255, 0.7) 50%,
  1872. #7044c5 60%,
  1873. #7044c5 70%,
  1874. #5f3abf 100%
  1875. );
  1876. box-shadow: 0 0 12px rgba(112, 120, 255, 0.95);
  1877. }
  1878. .notice-badge::after {
  1879. content: "";
  1880. position: absolute;
  1881. top: 0;
  1882. right: -15px;
  1883. }
  1884. .side-card__more {
  1885. display: inline-flex;
  1886. align-items: center;
  1887. justify-content: center;
  1888. border: 0;
  1889. background: transparent;
  1890. color: var(--portal-subtitle);
  1891. cursor: pointer;
  1892. font-size: 12px;
  1893. }
  1894. .notice-item {
  1895. /* background: rgba(14, 22, 42, 0.88); */
  1896. padding: 8px 14px;
  1897. box-shadow: 0 10px 22px rgba(23, 52, 95, 0.08);
  1898. border-radius: 12px;
  1899. }
  1900. .notice-item:hover {
  1901. box-shadow: 0 16px 30px rgba(0, 0, 0, 0.22);
  1902. transition: all 0.2s ease;
  1903. transform: translateY(-1px);
  1904. }
  1905. .notice-item__title,
  1906. .notice-item__desc {
  1907. color: var(--portal-text);
  1908. font-size: 13px;
  1909. line-height: 1.5;
  1910. display: -webkit-box;
  1911. -webkit-box-orient: vertical;
  1912. -webkit-line-clamp: 1;
  1913. overflow: hidden;
  1914. }
  1915. .panel-title {
  1916. padding: 0 16px;
  1917. color: var(--portal-title);
  1918. font-size: 15px;
  1919. font-weight: 700;
  1920. }
  1921. .todo-item {
  1922. border: 1px solid var(--portal-line);
  1923. background: var(--portal-card-4);
  1924. padding: 8px 14px;
  1925. box-shadow: 0 10px 22px rgba(23, 52, 95, 0.08);
  1926. }
  1927. .todo-item__tag {
  1928. display: inline-flex;
  1929. align-items: center;
  1930. border-radius: 999px;
  1931. padding: 2px 8px;
  1932. font-size: 11px;
  1933. white-space: nowrap;
  1934. }
  1935. .news-mini {
  1936. display: flex;
  1937. align-items: center;
  1938. gap: 12px;
  1939. border: 1px solid var(--portal-line);
  1940. background: var(--portal-card-4);
  1941. padding: 5px 12px;
  1942. box-shadow: 0 10px 22px rgba(23, 52, 95, 0.08);
  1943. border-radius: 12px;
  1944. }
  1945. .news-mini:hover {
  1946. box-shadow: 0 16px 30px rgba(0, 0, 0, 0.22);
  1947. transition: all 0.2s ease;
  1948. transform: translateY(-1px);
  1949. }
  1950. .placeholder-panel {
  1951. display: flex;
  1952. height: 100%;
  1953. padding: 16px;
  1954. justify-content: start;
  1955. color: var(--portal-title);
  1956. font-size: 16px;
  1957. font-weight: 600;
  1958. letter-spacing: 0.08em;
  1959. }
  1960. @media (max-width: 1279px) {
  1961. .hero-text {
  1962. font-size: 34px;
  1963. max-width: 420px;
  1964. }
  1965. .hero-visual {
  1966. min-height: 260px;
  1967. }
  1968. }
  1969. @media (max-width: 767px) {
  1970. .platform-block {
  1971. display: block;
  1972. }
  1973. .platform-block__header {
  1974. width: 100%;
  1975. min-width: 0;
  1976. padding: 0 16px 0 18px;
  1977. flex: none;
  1978. justify-content: flex-start;
  1979. }
  1980. .platform-block__title-wrap {
  1981. gap: 12px;
  1982. flex-wrap: wrap;
  1983. }
  1984. .platform-block__title {
  1985. font-size: 16px;
  1986. }
  1987. .platform-block__subtitle {
  1988. font-size: 12px;
  1989. }
  1990. .platform-block__watermark {
  1991. font-size: 34px;
  1992. }
  1993. .hero-banner {
  1994. min-height: 240px;
  1995. }
  1996. .carousel-container {
  1997. height: 240px;
  1998. }
  1999. .carousel-caption {
  2000. left: 22px;
  2001. right: 22px;
  2002. top: 50%;
  2003. transform: translateY(-50%);
  2004. }
  2005. .hero-text {
  2006. font-size: 24px;
  2007. max-width: 100%;
  2008. }
  2009. }
  2010. .carousel-indicators {
  2011. display: flex;
  2012. gap: 10px;
  2013. z-index: 30;
  2014. }
  2015. .carousel-indicators span {
  2016. width: 36px;
  2017. height: 4px;
  2018. border-radius: 999px;
  2019. cursor: pointer;
  2020. transition: all 0.25s ease;
  2021. box-shadow: 0 0 16px rgba(112, 103, 255, 0.35);
  2022. }
  2023. .carousel-slide {
  2024. position: absolute;
  2025. inset: 0;
  2026. width: 100%;
  2027. height: 100%;
  2028. }
  2029. .hero-visual img {
  2030. width: 100%;
  2031. height: 100%;
  2032. object-fit: cover;
  2033. display: block;
  2034. filter: saturate(1.12) contrast(1.06);
  2035. }
  2036. .carousel-caption {
  2037. position: absolute;
  2038. left: 80px;
  2039. top: 44%;
  2040. z-index: 2;
  2041. transform: translateY(-50%);
  2042. text-align: left;
  2043. }
  2044. .carousel-container::before {
  2045. content: "";
  2046. position: absolute;
  2047. inset: auto 0 0;
  2048. height: 120px;
  2049. background: linear-gradient(180deg, transparent, rgba(5, 11, 26, 0.2));
  2050. z-index: 1;
  2051. pointer-events: none;
  2052. }
  2053. :deep(.portal-home .el-dialog) {
  2054. border-radius: 16px;
  2055. }
  2056. .top-nav-bar {
  2057. display: flex;
  2058. align-items: center;
  2059. justify-content: space-between;
  2060. padding: 5px 24px;
  2061. background: var(--portal-nav-bg);
  2062. backdrop-filter: blur(10px);
  2063. -webkit-backdrop-filter: blur(10px);
  2064. border: 1px solid var(--portal-line);
  2065. border-radius: 8px;
  2066. margin-bottom: 10px;
  2067. box-shadow: var(--portal-shadow);
  2068. }
  2069. .nav-title {
  2070. color: var(--portal-title);
  2071. font-size: 14px;
  2072. font-weight: 600;
  2073. letter-spacing: 0.08em;
  2074. }
  2075. .search-container {
  2076. display: flex;
  2077. align-items: center;
  2078. gap: 8px;
  2079. position: relative;
  2080. width: 320px;
  2081. height: 36px;
  2082. border-radius: 20px;
  2083. background: var(--portal-input-bg);
  2084. border: 1px solid var(--portal-line);
  2085. overflow: hidden;
  2086. transition: all 0.2s ease;
  2087. }
  2088. .search-container:hover {
  2089. background: var(--portal-input-hover);
  2090. border-color: rgba(109, 137, 213, 0.4);
  2091. }
  2092. .search-icon {
  2093. flex-shrink: 0;
  2094. font-size: 18px;
  2095. color: var(--portal-text-soft);
  2096. margin-left: 12px;
  2097. }
  2098. .search-input {
  2099. flex: 1;
  2100. border: none;
  2101. background: transparent;
  2102. outline: none;
  2103. font-size: 14px;
  2104. color: var(--portal-text);
  2105. padding: 0;
  2106. margin: 0;
  2107. }
  2108. .shortcut-key {
  2109. position: absolute;
  2110. right: 12px;
  2111. top: 50%;
  2112. transform: translateY(-50%);
  2113. font-size: 12px;
  2114. color: var(--portal-text-soft);
  2115. font-family: "Courier New", monospace;
  2116. }
  2117. .quick-access {
  2118. display: flex;
  2119. gap: 16px;
  2120. }
  2121. .quick-btn {
  2122. display: flex;
  2123. align-items: center;
  2124. gap: 8px;
  2125. padding: 8px 12px;
  2126. /* border: 1px solid rgba(109, 137, 213, 0.16); */
  2127. border-radius: 12px;
  2128. /* background: rgba(17, 25, 48, 0.8); */
  2129. color: var(--portal-text);
  2130. font-size: 13px;
  2131. cursor: pointer;
  2132. transition:
  2133. background 0.2s ease,
  2134. border-color 0.2s ease,
  2135. transform 0.2s ease;
  2136. }
  2137. .quick-btn--dropdown {
  2138. border: none;
  2139. background: transparent;
  2140. outline: none;
  2141. }
  2142. .quick-btn--dropdown:focus-visible {
  2143. outline: none;
  2144. }
  2145. .quick-btn:hover {
  2146. background: var(--portal-nav-hover);
  2147. border-color: rgba(109, 137, 213, 0.4);
  2148. transform: translateY(-1px);
  2149. }
  2150. .btn-icon {
  2151. font-size: 16px;
  2152. color: var(--portal-accent-2);
  2153. }
  2154. .btn-label {
  2155. white-space: nowrap;
  2156. }
  2157. .quick-btn__arrow {
  2158. font-size: 16px;
  2159. color: var(--portal-text-soft);
  2160. }
  2161. .quick-btn__meta {
  2162. color: var(--portal-text-soft);
  2163. }
  2164. .quick-btn__count {
  2165. font-weight: 700;
  2166. }
  2167. .quick-btn__count--todo {
  2168. color: var(--portal-number-todo);
  2169. }
  2170. .quick-btn__count--done {
  2171. color: var(--portal-number-done);
  2172. }
  2173. .aside-btn {
  2174. color: var(--portal-text-soft);
  2175. }
  2176. .badge {
  2177. display: inline-flex;
  2178. align-items: center;
  2179. justify-content: center;
  2180. width: 18px;
  2181. height: 18px;
  2182. border-radius: 50%;
  2183. background: #f56c6c;
  2184. color: white;
  2185. font-size: 10px;
  2186. font-weight: 600;
  2187. }
  2188. .quick-todo-bar {
  2189. display: flex;
  2190. gap: 16px;
  2191. padding: 8px 16px;
  2192. background: var(--portal-todo-bg);
  2193. backdrop-filter: blur(10px);
  2194. border: 1px solid var(--portal-line);
  2195. -webkit-backdrop-filter: blur(10px);
  2196. border-radius: 8px;
  2197. box-shadow: var(--portal-shadow);
  2198. /* margin-bottom: 16px; */
  2199. }
  2200. .quick-todo-btn {
  2201. display: flex;
  2202. flex: 1;
  2203. flex-direction: column;
  2204. align-items: flex-start;
  2205. justify-content: center;
  2206. gap: 8px;
  2207. border: none;
  2208. background: transparent;
  2209. padding: 0;
  2210. color: inherit;
  2211. cursor: pointer;
  2212. outline: none;
  2213. }
  2214. .quick-todo-btn--center {
  2215. align-items: center;
  2216. }
  2217. .quick-todo-btn:hover {
  2218. transform: translateY(-1px);
  2219. }
  2220. .todo-item {
  2221. display: flex;
  2222. align-items: center;
  2223. gap: 8px;
  2224. flex: 1;
  2225. padding: 8px 12px;
  2226. border-radius: 8px;
  2227. transition: all 0.2s ease;
  2228. cursor: pointer;
  2229. }
  2230. .todo-item:hover {
  2231. background: var(--portal-todo-hover);
  2232. transform: translateY(-1px);
  2233. border-color: rgba(109, 137, 213, 0.2);
  2234. }
  2235. .number {
  2236. font-size: 18px;
  2237. font-weight: 700;
  2238. color: var(--portal-number-todo);
  2239. min-width: 24px;
  2240. text-align: center;
  2241. }
  2242. .label {
  2243. font-size: 12px;
  2244. color: var(--portal-text);
  2245. white-space: nowrap;
  2246. }
  2247. .icon {
  2248. font-size: 14px;
  2249. color: var(--portal-accent-2);
  2250. }
  2251. :global(.quick-access-dropdown) {
  2252. padding: 0 !important;
  2253. border: 1px solid var(--portal-line) !important;
  2254. border-radius: 10px !important;
  2255. background: var(--dropdown-menu) !important;
  2256. box-shadow: var(--portal-shadow) !important;
  2257. overflow: hidden !important;
  2258. }
  2259. :global(.quick-access-dropdown .el-dropdown-menu) {
  2260. padding: 0px !important;
  2261. border: none !important;
  2262. background: var(--dropdown-menu) !important;
  2263. }
  2264. :global(.quick-access-dropdown .el-dropdown-menu__item) {
  2265. padding: 0px !important;
  2266. padding-left: 10px !important;
  2267. padding-right: 10px !important;
  2268. margin: 0 !important;
  2269. border: none !important;
  2270. background: var(--dropdown-menu) !important;
  2271. }
  2272. /* :global(.quick-access-dropdown .el-dropdown-menu__item):hover {
  2273. background: var(--dropdown-menu-hover) !important;
  2274. } */
  2275. :global(.quick-access-dropdown .el-popper__arrow::before) {
  2276. background: var(--dropdown-menu) !important;
  2277. border-color: var(--portal-line) !important;
  2278. }
  2279. :deep(.el-dropdown-menu__item):hover {
  2280. background: var(--dropdown-menu-hover) !important;
  2281. color: #fff !important;
  2282. }
  2283. </style>