index.vue 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568
  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 dataimage from "@/assets/images/data2.png";
  597. import thinkimage from "@/assets/images/think.png";
  598. import aiimage from "@/assets/images/ai.png";
  599. import agentimage from "@/assets/images/icon2.png";
  600. import videoimage from "@/assets/images/video.png";
  601. import fileagent from "@/assets/images/fileagent.png";
  602. import zhiduagent from "@/assets/images/zhiduagent.png";
  603. import jishuimage2 from "@/assets/images/jishuimage.png";
  604. import zhanlueimage from "@/assets/images/zhanlue.png"; // 战略解码
  605. import safeimage from "@/assets/images/safe.png"; // 安全合规管理
  606. import zuzhiimage from "@/assets/images/zuzhi.png";
  607. import youimage from "@/assets/images/youcnag.png"; // ai智能体
  608. import hongpan from "@/assets/images/pan.png"; // ai智能体
  609. import ask from "@/assets/images/ask.png";
  610. import degitial from "@/assets/images/degitial.png";
  611. import wms from "@/assets/images/wms.png";
  612. import banner2_white from "@/assets/images/banner2_white.png";
  613. import banner1_white from "@/assets/images/banner1_white.png";
  614. type PortalApp = {
  615. label: string;
  616. icon?: string;
  617. image?: string;
  618. active?: boolean;
  619. };
  620. type PortalSection = {
  621. code: string;
  622. title: string;
  623. subtitle: string;
  624. height: string;
  625. apps?: PortalApp[];
  626. };
  627. type NoticeItem = {
  628. id?: string | number;
  629. title?: string;
  630. desc?: string;
  631. docsubject?: string;
  632. };
  633. const router = useRouter();
  634. const userStore = useUserStore();
  635. const themeStore = useThemeStore();
  636. const isDingTalkClient = () => {
  637. const ua = window.navigator.userAgent.toLowerCase();
  638. return ua.includes("dingtalk") || ua.includes("dingtalkwork");
  639. };
  640. const showDingTalkLoginMask = ref(
  641. isDingTalkClient() && !userStore.getUser.username && !getAccessToken(),
  642. );
  643. const todoPanelTitle = "待办中心";
  644. const newsPanelTitle = "新闻中心";
  645. const noticeTabs = [
  646. { key: "redHead", label: "红头文件" },
  647. { key: "notice", label: "通知公告" },
  648. ] as const;
  649. type NoticeTabKey = (typeof noticeTabs)[number]["key"];
  650. const portalSections: PortalSection[] = [
  651. {
  652. code: "数",
  653. title: "数字化运营平台",
  654. subtitle: "高效协同 · 战略洞察",
  655. height: "220px",
  656. apps: [
  657. {
  658. label: "OA办公",
  659. image: oaimage,
  660. active: true,
  661. },
  662. { label: "客户管理(CRM)", image: crmimage },
  663. { label: "人力资源(EHR)", image: ehrimage },
  664. { label: "供应商管理(SRM)", image: scmimage },
  665. { label: "经营驾驶舱(MC)", image: driveimage },
  666. { label: "项目管理(PM)", image: pmimage },
  667. { label: "开发需求管理", image: jishuimage2 },
  668. { label: "鸿盘", image: hongpan },
  669. { label: "财务管理(FM)", image: erpimage },
  670. { label: "技术研发管理(R&D)", image: jishuimage2 },
  671. { label: "战略解码与执行", image: zhanlueimage },
  672. { label: "组织资产管理", image: zuzhiimage },
  673. { label: "风控合规管理", image: safeimage },
  674. // { label: "OKR方式1", image: safeimage },
  675. // { label: "OKR方式2", image: safeimage },
  676. // { label: "OKR方式3", image: safeimage },
  677. // { label: "OKR方式4", image: safeimage },
  678. ],
  679. },
  680. {
  681. code: "智",
  682. title: "智慧指挥平台",
  683. subtitle: "高效协同 · 战略洞察",
  684. height: "210px",
  685. apps: [
  686. { label: "设备管理(PMS)", image: pmsimage, active: true },
  687. { label: "质量安全管理(QHSE)", image: qhseimage },
  688. { label: "中航北斗", image: zhonghangimage },
  689. { label: "智慧连油", image: lianyouimage },
  690. { label: "智慧注气", image: zhuqiimage },
  691. { label: "视频中心(VCS)", image: videoimage },
  692. { label: "智能钻井", image: zuanjingimage },
  693. { label: "仓储管理(WMS)", image: wms },
  694. { label: "智慧压裂", image: yalieimage },
  695. { label: "数字油藏", image: youimage },
  696. ],
  697. },
  698. {
  699. code: "AI",
  700. title: "Chat BI平台",
  701. subtitle: "数据智能 · 决策驱动",
  702. height: "160px",
  703. apps: [
  704. { label: "全局数据治理(数据中台)", image: dataimage, active: true },
  705. { label: "智能决策", image: thinkimage },
  706. { label: "行业AI大模型", image: aiimage },
  707. { label: "AI智能体(智能交互)", image: agentimage },
  708. { label: "工艺文件智能体", image: fileagent },
  709. { label: "集团制度智能体", image: zhiduagent },
  710. { label: "智能问数", image: ask },
  711. { label: "瑞小智(数字人)", image: degitial },
  712. ],
  713. },
  714. ];
  715. let boldLabes = ref([
  716. "OA办公",
  717. "客户管理(CRM)",
  718. "设备管理(PMS)",
  719. "中航北斗",
  720. "智慧连油",
  721. "质量安全管理(QHSE)",
  722. "智慧注气",
  723. "视频中心(VCS)",
  724. "开发需求管理",
  725. "经营驾驶舱(MC)",
  726. "项目管理(PM)",
  727. "鸿盘",
  728. "智能钻井",
  729. "供应商管理(SRM)",
  730. "人力资源(EHR)",
  731. ]);
  732. const searchKeyword = ref("");
  733. // 2. 实现搜索处理函数
  734. const handleSearch = () => {
  735. if (!searchKeyword.value.trim()) return;
  736. console.log("执行搜索:", searchKeyword.value);
  737. // 如果需要跳转到独立搜索页,可以在这里添加 router.push
  738. };
  739. const handleQuickAccessCommand = (command: string) => {
  740. const ua = window.navigator.userAgent; // const ua = navigator.userAgent;
  741. const isDesktop =
  742. ua.includes("DingTalk") &&
  743. (ua.includes("Windows") || ua.includes("Macintosh"));
  744. let ua2 = navigator.userAgent.toLowerCase();
  745. let isMobile = ua2.indexOf("dingtalk") > -1;
  746. const routeMap: Record<string, string> = {
  747. "todo-oa": "/todo-list?type=oa",
  748. "done-oa": "/oa-done-list?type=oa",
  749. "todo-crm": "/crm-todo-list?type=crm",
  750. "done-crm": "/crm-done-list?type=crm",
  751. "todo-srm": "/srm-todo-list?type=srm",
  752. "done-srm": "/srm-done-list?type=srm",
  753. "todo-ehr": "/ehr-todo-list?type=ehr",
  754. };
  755. const routeMap2: Record<string, string> = {
  756. "todo-oa": "/mobile-todo-list?type=oa",
  757. "done-oa": "/oa-done-list?type=oa",
  758. "todo-crm": "/mobile-crm-todo-list?type=crm",
  759. "done-crm": "/crm-done-list?type=crm",
  760. "todo-srm": "/mobile-srm-todo-list?type=srm",
  761. "done-srm": "/srm-done-list?type=srm",
  762. "todo-ehr": "/ehr-todo-list?type=ehr",
  763. };
  764. let target = null;
  765. if (ua2.includes("dingtalk") || ua2.includes("dingtalkwork")) {
  766. if (isMobile && !isDesktop) {
  767. if (command === "todo-oa" || command === "done-oa") {
  768. dd.biz.util.openLink({
  769. url: `https://yfding.keruioil.com:9443/wxapi/wxclientmenu/61c40c87c45346ce9695f223ea823278`,
  770. onSuccess: () => {},
  771. });
  772. return;
  773. } else {
  774. target = routeMap2[command];
  775. }
  776. } else if (isDesktop) {
  777. target = routeMap[command];
  778. }
  779. } else {
  780. // 浏览器环境
  781. target = routeMap[command];
  782. }
  783. console.log(
  784. "🚀 ~ file: index.vue ~ line 91 ~ handleQuickAccessCommand ~ target",
  785. target,
  786. );
  787. // 跳转
  788. if (target) {
  789. router.push(target);
  790. }
  791. };
  792. const filteredSections = computed(() => {
  793. const keyword = searchKeyword.value.trim().toLowerCase();
  794. // 如果没有关键词,返回原始数据
  795. if (!keyword) {
  796. return portalSections;
  797. }
  798. // 过滤逻辑:遍历每个板块,保留匹配的应用
  799. return portalSections
  800. .map((section) => {
  801. if (!section.apps) return { ...section, apps: [] };
  802. const matchedApps = section.apps.filter((app) =>
  803. app.label.toLowerCase().includes(keyword),
  804. );
  805. return {
  806. ...section,
  807. apps: matchedApps,
  808. };
  809. })
  810. .filter((section) => section.apps && section.apps.length > 0); // 移除没有匹配应用的空板块
  811. });
  812. watch(
  813. () => themeStore.theme,
  814. () => {
  815. if (themeStore.theme === "light") {
  816. slides.value = [
  817. {
  818. image: banner2_white,
  819. text: "数字化转型,驱动未来增长",
  820. },
  821. {
  822. image: banner1_white,
  823. text: "保持热爱,奔赴目标!",
  824. },
  825. {
  826. image: banner1_white,
  827. text: "智慧平台,赋能高效协同",
  828. },
  829. ];
  830. } else {
  831. slides.value = [
  832. {
  833. image: banner2,
  834. text: "数字化转型,驱动未来增长",
  835. },
  836. {
  837. image: banner1,
  838. text: "保持热爱,奔赴目标!",
  839. },
  840. {
  841. image: banner1,
  842. text: "智慧平台,赋能高效协同",
  843. },
  844. ];
  845. }
  846. },
  847. );
  848. // 添加轮播数据
  849. const slides = ref([
  850. {
  851. image: themeStore.theme === "light" ? banner2_white : banner2,
  852. text: "数字化转型,驱动未来增长",
  853. },
  854. {
  855. image: themeStore.theme === "light" ? banner1_white : banner1,
  856. text: "保持热爱,奔赴目标!",
  857. },
  858. {
  859. image: themeStore.theme === "light" ? banner1_white : banner1,
  860. text: "智慧平台,赋能高效协同",
  861. },
  862. ]);
  863. // 当前索引
  864. const currentIndex = ref(0);
  865. let slideTimer: ReturnType<typeof setInterval> | null = null;
  866. // 下一页
  867. const nextSlide = () => {
  868. currentIndex.value = (currentIndex.value + 1) % slides.value.length;
  869. };
  870. const activeNoticeTab = ref<NoticeTabKey>("redHead");
  871. const noticeListMap = ref<Record<NoticeTabKey, NoticeItem[]>>({
  872. notice: [],
  873. redHead: [],
  874. });
  875. const newsList = ref([]);
  876. const currentNoticeList = computed(
  877. () => noticeListMap.value[activeNoticeTab.value] ?? [],
  878. );
  879. let todo = ref(0);
  880. let done = ref(0);
  881. let oatodo = ref(0);
  882. let oadone = ref(0);
  883. let crmtodo = ref(0);
  884. let crmdone = ref(0);
  885. let srmtodo = ref(0);
  886. let srmdone = ref(0);
  887. let ehrtodo = ref(0);
  888. const loadHomeData = async () => {
  889. await loadNoticeList(activeNoticeTab.value);
  890. if (!userStore.getUser.username) return;
  891. const [oaRes, crmRes, crmDoneRes, srmRes, ehrRes, newsRes] =
  892. await Promise.all([
  893. getOATasks({
  894. id: userStore.getUser.username,
  895. pageNum: 1,
  896. pageSize: 10,
  897. }),
  898. await getCRMTasks({
  899. id: userStore.getUser.username,
  900. type: "pending",
  901. pageNum: 1,
  902. pageSize: 10,
  903. }),
  904. await getCRMTasks({
  905. id: userStore.getUser.username,
  906. type: "approved",
  907. pageNum: 1,
  908. pageSize: 10,
  909. }),
  910. await getSRMTasks({
  911. id: userStore.getUser.username,
  912. pageNum: 1,
  913. pageSize: 10,
  914. }),
  915. await getEHRTasks({
  916. id: userStore.getUser.username,
  917. pageNum: 1,
  918. pageSize: 10,
  919. }),
  920. getNews({
  921. pageNum: 1,
  922. pageSize: 10,
  923. }),
  924. ]);
  925. oaTasks.value = oaRes.todoList.slice(0, 3);
  926. todo.value =
  927. Number(oaRes.todoCount) +
  928. crmRes.todoCount +
  929. srmRes.todoCount +
  930. ehrRes.todoCount;
  931. done.value =
  932. Number(oaRes.doneCount) + crmDoneRes.todoCount + srmRes.doneCount;
  933. oatodo.value = oaRes.todoCount;
  934. oadone.value = oaRes.doneCount;
  935. crmtodo.value = crmRes.todoCount;
  936. crmdone.value = crmDoneRes.todoCount;
  937. srmtodo.value = srmRes.todoCount;
  938. srmdone.value = srmRes.doneCount;
  939. ehrtodo.value = ehrRes.todoCount;
  940. newsList.value = newsRes.list.slice(0, 3);
  941. };
  942. const loadNoticeList = async (tabKey: NoticeTabKey) => {
  943. const requestApi = tabKey === "notice" ? getNotices : getRedHeadFiles;
  944. const res = await requestApi({
  945. pageNum: 1,
  946. pageSize: 10,
  947. });
  948. noticeListMap.value[tabKey] = (res?.list || []).slice(0, 3);
  949. };
  950. let currentTabKey = ref<NoticeTabKey>("redHead");
  951. const handleNoticeTabChange = async (tabKey: NoticeTabKey) => {
  952. activeNoticeTab.value = tabKey;
  953. currentTabKey.value = tabKey;
  954. // if (noticeListMap.value[tabKey].length) return;
  955. if (userStore.getUser.username) {
  956. await loadNoticeList(tabKey);
  957. }
  958. };
  959. const handleNoticeMoreClick = () => {
  960. router.push({
  961. path: "/notice-redhead",
  962. query: {
  963. tabKey: currentTabKey.value,
  964. },
  965. });
  966. };
  967. const protectedOpen = (url: string) => {
  968. if (userStore.getUser.username && getAccessToken()) {
  969. window.open(url, "_blank");
  970. } else {
  971. router.push({ path: "/login" });
  972. }
  973. };
  974. let dialogVisible = ref(false);
  975. const handlePortalAppClick = async (app: PortalApp) => {
  976. if (!app.label) return;
  977. if (app.label === "项目管理(PM)") {
  978. protectedOpen(
  979. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}&source=yyhy`,
  980. );
  981. }
  982. if (app.label === "OA办公") {
  983. // if (userStore.getUser.username && getAccessToken()) {
  984. // const res = await ssoLogin({ username: userStore.getUser.username });
  985. // if (res) {
  986. // window.open(
  987. // `https://yfoa.keruioil.com/wui/index.html?ssoToken=${res}#/main`,
  988. // "_blank",
  989. // );
  990. // }
  991. // } else {
  992. // router.push({ path: "/login" });
  993. // }
  994. if (userStore.getUser.username && getAccessToken()) {
  995. const ua = window.navigator.userAgent; // const ua = navigator.userAgent;
  996. const isDesktop =
  997. ua.includes("DingTalk") &&
  998. (ua.includes("Windows") || ua.includes("Macintosh"));
  999. let ua2 = navigator.userAgent.toLowerCase();
  1000. let isMobile = ua2.indexOf("dingtalk") > -1;
  1001. const res = await ssoLogin({ username: userStore.getUser.username });
  1002. if (ua2.includes("dingtalk") || ua2.includes("dingtalkwork")) {
  1003. if (isMobile && !isDesktop) {
  1004. dd.biz.util.openLink({
  1005. url: `https://yfding.keruioil.com:9443/wxapi/wxclientmenu/d5605d17b3cd4c1987e333ab0d945070`,
  1006. onSuccess: () => {},
  1007. });
  1008. } else if (isDesktop) {
  1009. if (res) {
  1010. window.open(
  1011. `https://yfoa.keruioil.com/wui/index.html?ssoToken=${res}#/main`,
  1012. "_blank",
  1013. );
  1014. }
  1015. }
  1016. } else {
  1017. // 浏览器
  1018. if (res) {
  1019. window.open(
  1020. `https://yfoa.keruioil.com/wui/index.html?ssoToken=${res}#/main`,
  1021. "_blank",
  1022. );
  1023. }
  1024. }
  1025. } else {
  1026. router.push({ path: "/login" });
  1027. }
  1028. }
  1029. if (app.label === "客户管理(CRM)") {
  1030. if (userStore.getUser.username && getAccessToken()) {
  1031. const ua = window.navigator.userAgent; // const ua = navigator.userAgent;
  1032. const isDesktop =
  1033. ua.includes("DingTalk") &&
  1034. (ua.includes("Windows") || ua.includes("Macintosh"));
  1035. let ua2 = navigator.userAgent.toLowerCase();
  1036. var isMobile = ua2.indexOf("dingtalk") > -1;
  1037. if (ua2.includes("dingtalk") || ua2.includes("dingtalkwork")) {
  1038. if (isMobile && !isDesktop) {
  1039. const originPath =
  1040. "/bff/spa/crmh5/index.html#/home?platform=H5&deviceType=0";
  1041. // 连续三次encodeURIComponent
  1042. let e1 = encodeURIComponent(originPath);
  1043. let e2 = encodeURIComponent(e1);
  1044. let e3 = encodeURIComponent(e2);
  1045. dd.biz.util.openLink({
  1046. url: `https://crm-tencent.xiaoshouyi.com/global/sso/callback/00APEB9EEEA9B2E338B686B7ECFA8585808C.action?token=${getAccessToken()}&returnUri=${e3}`,
  1047. onSuccess: () => {},
  1048. });
  1049. } else if (isDesktop) {
  1050. window.open(
  1051. `https://crm-tencent.xiaoshouyi.com/global/sso/callback/00APEB9EEEA9B2E338B686B7ECFA8585808C.action?token=${getAccessToken()}`,
  1052. "_blank",
  1053. );
  1054. }
  1055. } else {
  1056. window.open(
  1057. `https://crm-tencent.xiaoshouyi.com/global/sso/callback/00APEB9EEEA9B2E338B686B7ECFA8585808C.action?token=${getAccessToken()}`,
  1058. "_blank",
  1059. );
  1060. }
  1061. } else {
  1062. router.push({ path: "/login" });
  1063. }
  1064. }
  1065. if (app.label === "设备管理(PMS)") {
  1066. protectedOpen(
  1067. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}`,
  1068. );
  1069. }
  1070. if (app.label === "中航北斗") {
  1071. protectedOpen("https://zhbdgps.cn");
  1072. }
  1073. if (app.label === "智慧连油") {
  1074. protectedOpen(
  1075. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}&source=zhly`,
  1076. );
  1077. }
  1078. if (app.label === "智慧注气") {
  1079. protectedOpen(
  1080. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}&source=zhzq`,
  1081. );
  1082. }
  1083. if (app.label === "视频中心(VCS)") {
  1084. protectedOpen(
  1085. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}&source=spzx`,
  1086. );
  1087. }
  1088. if (app.label === "质量安全管理(QHSE)") {
  1089. protectedOpen(
  1090. `${import.meta.env.VITE_PMS_URL}/portalLogin?username=${userStore.getUser.username}&source=qhse_nav`,
  1091. );
  1092. }
  1093. if (app.label === "经营驾驶舱(MC)") {
  1094. if (userStore.getUser.username && getAccessToken()) {
  1095. const res = await getMCSsoToken();
  1096. if (res) {
  1097. window.open(
  1098. `https://report.deepoil.cc/webroot/decision/v10/entry/access/9fb42908-894a-4373-a6be-ce046a42851d?preview=true&page_number=1&ssoToken=${res}`,
  1099. "_blank",
  1100. );
  1101. }
  1102. } else {
  1103. router.push({ path: "/login" });
  1104. }
  1105. }
  1106. if (app.label === "开发需求管理") {
  1107. if (userStore.getUser.username && getAccessToken()) {
  1108. const res = await zentaoSsoLogin({
  1109. username: userStore.getUser.username,
  1110. });
  1111. if (res) {
  1112. window.open(
  1113. `http://project.deepoil.cc/zentao/api.php?m=user&f=apilogin&account=${res.jobNumber}&code=${res.code}&time=${res.timestamp}&token=${res.token}`,
  1114. "_blank",
  1115. );
  1116. }
  1117. } else {
  1118. router.push({ path: "/login" });
  1119. }
  1120. }
  1121. if (app.label === "人力资源(EHR)") {
  1122. if (userStore.getUser.username && getAccessToken()) {
  1123. const res = await ehrLogin({
  1124. username: userStore.getUser.username,
  1125. });
  1126. const ua = window.navigator.userAgent;
  1127. const isDesktop =
  1128. ua.includes("DingTalk") &&
  1129. (ua.includes("Windows") || ua.includes("Macintosh"));
  1130. let ua2 = navigator.userAgent.toLowerCase();
  1131. var isMobile = ua2.indexOf("dingtalk") > -1;
  1132. if (ua2.includes("dingtalk") || ua2.includes("dingtalkwork")) {
  1133. if (isMobile && !isDesktop) {
  1134. dd.biz.util.openLink({
  1135. url: `${res.ehrUrl}/gateway/login/free?loginfree_licence=${res.licence}&signature=${res.sign}&redirect_url=/proxy/h5/home`,
  1136. onSuccess: () => {},
  1137. });
  1138. } else if (isDesktop) {
  1139. window.open(
  1140. `${res.ehrUrl}/gateway/login/free?loginfree_licence=${res.licence}&signature=${res.sign}&redirect_url=${res.redirect}`,
  1141. "_blank",
  1142. );
  1143. }
  1144. } else {
  1145. // 浏览器环境
  1146. window.open(
  1147. `${res.ehrUrl}/gateway/login/free?loginfree_licence=${res.licence}&signature=${res.sign}&redirect_url=${res.redirect}`,
  1148. "_blank",
  1149. );
  1150. }
  1151. } else {
  1152. router.push({ path: "/login" });
  1153. }
  1154. }
  1155. if (app.label === "供应商管理(SRM)") {
  1156. if (userStore.getUser.username && getAccessToken()) {
  1157. const ua = window.navigator.userAgent;
  1158. const isDesktop =
  1159. ua.includes("DingTalk") &&
  1160. (ua.includes("Windows") || ua.includes("Macintosh"));
  1161. let ua2 = navigator.userAgent.toLowerCase();
  1162. let isMobile = ua2.indexOf("dingtalk") > -1;
  1163. const res = await srmLogin({
  1164. username: userStore.getUser.username,
  1165. });
  1166. if (JSON.parse(res).msg === "账号未授权") {
  1167. dialogVisible.value = true;
  1168. return;
  1169. }
  1170. if (ua2.includes("dingtalk") || ua2.includes("dingtalkwork")) {
  1171. if (isMobile && !isDesktop) {
  1172. // alert("SRM判断是钉钉手机端登录");
  1173. dd.biz.util.openLink({
  1174. url: `https://srm-m.deepoil.cc:7777/#/mixed-page/view/MXP00048?Authorization=${JSON.parse(res).msg}`,
  1175. onSuccess: () => {},
  1176. });
  1177. } else if (isDesktop) {
  1178. window.open(
  1179. `https://srm.deepoil.cc/#/mixed-page/view/MXP00048?Authorization=${JSON.parse(res).msg}`,
  1180. "_blank",
  1181. );
  1182. }
  1183. } else {
  1184. // 浏览器环境
  1185. window.open(
  1186. `https://srm.deepoil.cc/#/mixed-page/view/MXP00048?Authorization=${JSON.parse(res).msg}`,
  1187. "_blank",
  1188. );
  1189. }
  1190. } else {
  1191. router.push({ path: "/login" });
  1192. }
  1193. }
  1194. if (app.label === "智能钻井") {
  1195. if (userStore.getUser.username && getAccessToken()) {
  1196. window.open(`http://172.21.0.224:8001/#/login`, "_blank");
  1197. } else {
  1198. router.push({ path: "/login" });
  1199. }
  1200. }
  1201. if (app.label === "鸿盘") {
  1202. if (userStore.getUser.username && getAccessToken()) {
  1203. window.open(`https://pan.keruioil.com:52180`, "_blank");
  1204. } else {
  1205. router.push({ path: "/login" });
  1206. }
  1207. }
  1208. // if (app.label === "OKR方式1") {
  1209. // if (userStore.getUser.username && getAccessToken()) {
  1210. // dd.biz.util.openLink({
  1211. // url: `dingtalk://dingtalkclient/page/link?url=https%3A%2F%2Fokr.dingtalk.com%2Fhomepage%23%2Fokr&ddtab=true`,
  1212. // onSuccess: () => {},
  1213. // });
  1214. // } else {
  1215. // router.push({ path: "/login" });
  1216. // }
  1217. // }
  1218. // if (app.label === "OKR方式2") {
  1219. // if (userStore.getUser.username && getAccessToken()) {
  1220. // dd.biz.util.openLink({
  1221. // url: `dingtalk://dingtalkclient/page/link?url=https%3A%2F%2Fokr.dingtalk.com%2Fhomepage%23%2Fokr&pc_slide=true`,
  1222. // onSuccess: () => {},
  1223. // });
  1224. // } else {
  1225. // router.push({ path: "/login" });
  1226. // }
  1227. // }
  1228. // if (app.label === "OKR方式3") {
  1229. // if (userStore.getUser.username && getAccessToken()) {
  1230. // dd.biz.util.openLink({
  1231. // url: `dingtalk://dingtalkclient/action/open_platform_link?pcLink=https%3A%2F%2Fokr.dingtalk.com%2Fhomepage%23%2Fokr&mobileLink=https%3A%2F%2Fokr.dingtalk.com%2Fmobile`,
  1232. // onSuccess: () => {},
  1233. // });
  1234. // } else {
  1235. // router.push({ path: "/login" });
  1236. // }
  1237. // }
  1238. // if (app.label === "OKR方式4") {
  1239. // if (userStore.getUser.username && getAccessToken()) {
  1240. // dd.biz.util.openLink({
  1241. // url: `https://applink.dingtalk.com/page/link?url=https%3A%2F%2Fokr.dingtalk.com%2Fhomepage%23%2Fokr`,
  1242. // onSuccess: () => {},
  1243. // });
  1244. // } else {
  1245. // router.push({ path: "/login" });
  1246. // }
  1247. // }
  1248. };
  1249. async function loginWithDingTalk() {
  1250. const ddCorpId = import.meta.env.VITE_DD_CORPID;
  1251. const ddClientId = import.meta.env.VITE_DD_CLIENTID;
  1252. if (!ddCorpId || !ddClientId) return false;
  1253. return await new Promise<boolean>((resolve) => {
  1254. dd.requestAuthCode({
  1255. corpId: ddCorpId,
  1256. clientId: ddClientId,
  1257. success: async (res: any) => {
  1258. try {
  1259. const { code } = res;
  1260. const response = await axios.post(
  1261. import.meta.env.BASE_URL + "/admin-api/system/auth/h5SocialLogin",
  1262. {
  1263. code,
  1264. type: 20,
  1265. state: new Date().getTime(),
  1266. },
  1267. {
  1268. headers: {
  1269. "Content-Type": "application/json",
  1270. "tenant-id": 1,
  1271. },
  1272. },
  1273. );
  1274. deleteUserCache();
  1275. userStore.resetState();
  1276. authUtil.setToken(response.data.data);
  1277. sessionStorage.removeItem(manualLogoutKey);
  1278. sessionStorage.removeItem(reloginCancelKey);
  1279. await userStore.setUserInfoAction();
  1280. resolve(true);
  1281. } catch (error) {
  1282. console.log("dingTalk login error :>> ", error);
  1283. resolve(false);
  1284. }
  1285. },
  1286. fail: (err: any) => {
  1287. console.log("err :>> ", err);
  1288. resolve(false);
  1289. },
  1290. });
  1291. });
  1292. }
  1293. async function dingTalkAutoLogin() {
  1294. const ua = window.navigator.userAgent.toLowerCase();
  1295. if (
  1296. (ua.includes("dingtalk") || ua.includes("dingtalkwork")) &&
  1297. !userStore.getUser.username &&
  1298. !getAccessToken()
  1299. ) {
  1300. return await loginWithDingTalk();
  1301. }
  1302. return false;
  1303. }
  1304. const getTagClass = (tag: string) => {
  1305. return tag === "0"
  1306. ? "bg-[#2e1e6d] text-white/90"
  1307. : tag === "1"
  1308. ? "bg-[#2e1e6d] text-white/90"
  1309. : tag === "2"
  1310. ? "bg-[#2e1e6d] text-white/90"
  1311. : "bg-[#2e1e6d] text-white/90";
  1312. };
  1313. const getTagName = (tag: string) => {
  1314. return tag === "0"
  1315. ? "正常"
  1316. : tag === "1"
  1317. ? "重要"
  1318. : tag === "2"
  1319. ? "紧急"
  1320. : "正常";
  1321. };
  1322. let oaTasks = ref([]);
  1323. onMounted(async () => {
  1324. slideTimer = setInterval(nextSlide, 5000);
  1325. if (showDingTalkLoginMask.value) {
  1326. try {
  1327. await dingTalkAutoLogin();
  1328. } finally {
  1329. showDingTalkLoginMask.value = false;
  1330. }
  1331. }
  1332. await loadHomeData();
  1333. });
  1334. const handleNoticeItemClick = async (notice: any) => {
  1335. const res = await ssoLogin({
  1336. username: userStore.getUser.username,
  1337. });
  1338. if (res) {
  1339. const ua = window.navigator.userAgent.toLowerCase();
  1340. if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
  1341. dd.biz.util.openLink({
  1342. url:
  1343. "https://yfoa.keruioil.com/wui/index.html" +
  1344. "?ssoToken=" +
  1345. res +
  1346. "#/main", // 先跳你的 SSO 链接
  1347. onSuccess: () => {
  1348. // 延迟跳目标业务地址(和你原来 setTimeout 逻辑一致)
  1349. setTimeout(() => {
  1350. dd.biz.util.openLink({
  1351. url: `https://yfoa.keruioil.com/spa/document/index.jsp?openAttachment=0&id=${notice.id}`,
  1352. });
  1353. }, 100);
  1354. },
  1355. });
  1356. } else {
  1357. const loading = ElLoading.service({
  1358. lock: true,
  1359. text: "正在跳转,请稍候...",
  1360. background: "rgba(0, 0, 0, 0.7)",
  1361. });
  1362. const newTab = window.open("", "_blank");
  1363. newTab.location.href =
  1364. "https://yfoa.keruioil.com/wui/index.html" +
  1365. "?ssoToken=" +
  1366. res +
  1367. "#/main";
  1368. setTimeout(function () {
  1369. newTab.location.href = `https://yfoa.keruioil.com/spa/document/index.jsp?openAttachment=0&id=${notice.id}`;
  1370. setTimeout(() => {
  1371. loading.close();
  1372. }, 500);
  1373. }, 100);
  1374. }
  1375. }
  1376. };
  1377. const handleTask = async (row: any) => {
  1378. const res = await ssoLogin({
  1379. username: userStore.getUser.username,
  1380. });
  1381. if (res) {
  1382. const ua = window.navigator.userAgent.toLowerCase();
  1383. if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
  1384. dd.biz.util.openLink({
  1385. url:
  1386. "https://yfoa.keruioil.com/wui/index.html" +
  1387. "?ssoToken=" +
  1388. res +
  1389. "#/main", // 先跳你的 SSO 链接
  1390. onSuccess: () => {
  1391. // 延迟跳目标业务地址(和你原来 setTimeout 逻辑一致)
  1392. setTimeout(() => {
  1393. dd.biz.util.openLink({
  1394. url: `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1776063595284#/main/workflow/req?requestid=${row.requestId}`,
  1395. });
  1396. }, 100);
  1397. },
  1398. });
  1399. } else {
  1400. const loading = ElLoading.service({
  1401. lock: true,
  1402. text: "正在跳转,请稍候...",
  1403. background: "rgba(0, 0, 0, 0.7)",
  1404. });
  1405. const newTab = window.open("", "_blank");
  1406. newTab.location.href =
  1407. "https://yfoa.keruioil.com/wui/index.html" +
  1408. "?ssoToken=" +
  1409. res +
  1410. "#/main";
  1411. setTimeout(function () {
  1412. newTab.location.href = `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1776063595284#/main/workflow/req?requestid=${row.requestId}`;
  1413. setTimeout(() => {
  1414. loading.close();
  1415. }, 500);
  1416. }, 100);
  1417. }
  1418. }
  1419. };
  1420. const openConsult = async () => {
  1421. const res = await ssoLogin({
  1422. username: userStore.getUser.username,
  1423. });
  1424. if (res) {
  1425. const ua = window.navigator.userAgent.toLowerCase();
  1426. if (ua.includes("dingtalk") || ua.includes("dingtalkwork")) {
  1427. dd.biz.util.openLink({
  1428. url:
  1429. "https://yfoa.keruioil.com/wui/index.html" +
  1430. "?ssoToken=" +
  1431. res +
  1432. "#/main", // 先跳你的 SSO 链接
  1433. onSuccess: () => {
  1434. // 延迟跳目标业务地址(和你原来 setTimeout 逻辑一致)
  1435. setTimeout(() => {
  1436. dd.biz.util.openLink({
  1437. url: `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1778289187850#/main/workflow/req?iscreate=1&workflowid=488`,
  1438. });
  1439. }, 100);
  1440. },
  1441. });
  1442. } else {
  1443. const loading = ElLoading.service({
  1444. lock: true,
  1445. text: "正在跳转,请稍候...",
  1446. background: "rgba(0, 0, 0, 0.7)",
  1447. });
  1448. const newTab = window.open("", "_blank");
  1449. newTab.location.href =
  1450. "https://yfoa.keruioil.com/wui/index.html" +
  1451. "?ssoToken=" +
  1452. res +
  1453. "#/main";
  1454. setTimeout(function () {
  1455. newTab.location.href = `https://yfoa.keruioil.com/spa/workflow/static4form/index.html?_rdm=1778289187850#/main/workflow/req?iscreate=1&workflowid=488`;
  1456. setTimeout(() => {
  1457. loading.close();
  1458. }, 500);
  1459. }, 100);
  1460. }
  1461. }
  1462. };
  1463. const goNews = () => {
  1464. router.push("/news");
  1465. };
  1466. onUnmounted(() => {
  1467. if (slideTimer) {
  1468. clearInterval(slideTimer);
  1469. slideTimer = null;
  1470. }
  1471. });
  1472. </script>
  1473. <style scoped>
  1474. .portal-home {
  1475. --portal-text: #17345f;
  1476. --portal-text-muted: #5f6f83;
  1477. --portal-text-soft: #7f8fa6;
  1478. --portal-title: #163867;
  1479. --portal-subtitle: rgba(61, 92, 135, 0.86);
  1480. --portal-line: rgba(126, 156, 201, 0.24);
  1481. --portal-card: rgba(255, 255, 255, 0.82);
  1482. --portal-card-2: rgba(248, 251, 255, 0.94);
  1483. --portal-card-3: rgba(240, 246, 255, 0.88);
  1484. --portal-card-4: rgba(231, 239, 251, 0.92);
  1485. --portal-nav-bg: rgba(255, 255, 255, 0.72);
  1486. --portal-nav-hover: rgba(219, 232, 252, 0.8);
  1487. --portal-input-bg: rgba(255, 255, 255, 0.7);
  1488. --portal-input-hover: rgba(255, 255, 255, 0.92);
  1489. --portal-shadow: 0 18px 40px rgba(23, 52, 95, 0.12);
  1490. --portal-shadow-strong: 0 24px 60px rgba(23, 52, 95, 0.16);
  1491. --portal-accent: #245edb;
  1492. --portal-accent-2: #4e8cff;
  1493. --portal-accent-soft: rgba(36, 94, 219, 0.14);
  1494. --portal-todo-bg: rgba(242, 247, 255, 0.94);
  1495. --portal-todo-hover: rgba(228, 238, 252, 0.95);
  1496. --portal-number-todo: #e15a5a;
  1497. --portal-number-done: #2da04d;
  1498. color: var(--portal-text);
  1499. background:
  1500. radial-gradient(
  1501. circle at 18% 12%,
  1502. rgba(83, 126, 255, 0.14),
  1503. transparent 22%
  1504. ),
  1505. radial-gradient(
  1506. circle at 82% 20%,
  1507. rgba(71, 148, 255, 0.14),
  1508. transparent 20%
  1509. ),
  1510. radial-gradient(
  1511. circle at 50% 100%,
  1512. rgba(97, 142, 247, 0.12),
  1513. transparent 28%
  1514. ),
  1515. linear-gradient(180deg, #eef3f9 0%, #f7faff 46%, #eef3f9 100%);
  1516. background-image:
  1517. /* linear-gradient(rgba(245, 249, 255, 0.82), rgba(237, 244, 253, 0.92)), */
  1518. linear-gradient(rgba(245, 249, 255, 0.9), rgba(218, 233, 251, 0.82)),
  1519. url("../assets//images/bg666.png");
  1520. background-position:
  1521. center center,
  1522. center bottom;
  1523. background-repeat: no-repeat, no-repeat;
  1524. background-size:
  1525. cover,
  1526. 100% auto;
  1527. background-attachment: fixed, fixed;
  1528. }
  1529. .portal-login-mask {
  1530. position: fixed;
  1531. inset: 0;
  1532. z-index: 9999;
  1533. display: flex;
  1534. align-items: center;
  1535. justify-content: center;
  1536. background: rgba(9, 16, 32, 0.52);
  1537. backdrop-filter: blur(6px);
  1538. -webkit-backdrop-filter: blur(6px);
  1539. }
  1540. .portal-login-mask__content {
  1541. display: flex;
  1542. flex-direction: column;
  1543. align-items: center;
  1544. gap: 16px;
  1545. padding: 28px 36px;
  1546. /* border: 1px solid rgba(255, 255, 255, 0.12); */
  1547. border-radius: 18px;
  1548. }
  1549. .portal-login-mask__spinner {
  1550. width: 34px;
  1551. height: 34px;
  1552. border: 3px solid rgba(255, 255, 255, 0.2);
  1553. border-top-color: #ffffff;
  1554. border-radius: 999px;
  1555. animation: portal-login-spin 0.9s linear infinite;
  1556. }
  1557. .portal-login-mask__text {
  1558. margin: 0;
  1559. color: #ffffff;
  1560. font-size: 14px;
  1561. letter-spacing: 0.04em;
  1562. }
  1563. @keyframes portal-login-spin {
  1564. to {
  1565. transform: rotate(360deg);
  1566. }
  1567. }
  1568. :global([data-theme="dark"] .portal-home) {
  1569. --portal-text: #eaf1ff;
  1570. --portal-text-muted: rgba(234, 241, 255, 0.95);
  1571. --portal-text-soft: #8a9ab0;
  1572. --portal-title: #f4f7ff;
  1573. --portal-subtitle: rgba(188, 205, 255, 0.82);
  1574. --portal-line: rgba(97, 129, 206, 0.28);
  1575. --portal-card: rgba(10, 19, 43, 0.8);
  1576. --portal-card-2: rgba(12, 24, 52, 0.92);
  1577. --portal-card-3: rgba(17, 25, 48, 0.8);
  1578. --portal-card-4: rgba(15, 24, 45, 0.82);
  1579. --portal-nav-bg: rgba(10, 19, 43, 0.8);
  1580. --portal-nav-hover: rgba(28, 40, 72, 0.8);
  1581. --portal-input-bg: rgba(255, 255, 255, 0.08);
  1582. --portal-input-hover: rgba(255, 255, 255, 0.12);
  1583. --portal-shadow: 0 16px 34px rgba(0, 0, 0, 0.22);
  1584. --portal-shadow-strong: 0 24px 60px rgba(0, 0, 0, 0.38);
  1585. --portal-accent: #6e7dff;
  1586. --portal-accent-2: #8d4dff;
  1587. --portal-accent-soft: rgba(110, 125, 255, 0.16);
  1588. --portal-todo-bg: #070e20;
  1589. --portal-todo-hover: rgba(28, 40, 72, 0.8);
  1590. --portal-number-todo: #f56c6c;
  1591. --portal-number-done: #ffffff;
  1592. color: var(--portal-text);
  1593. background:
  1594. radial-gradient(
  1595. circle at 18% 12%,
  1596. rgba(79, 82, 221, 0.34),
  1597. transparent 22%
  1598. ),
  1599. radial-gradient(circle at 82% 20%, rgba(28, 95, 255, 0.2), transparent 20%),
  1600. radial-gradient(
  1601. circle at 50% 100%,
  1602. rgba(103, 46, 255, 0.16),
  1603. transparent 28%
  1604. ),
  1605. linear-gradient(180deg, #040814 0%, #060d1d 46%, #040814 100%);
  1606. background-image:
  1607. linear-gradient(rgba(5, 11, 26, 0.62), rgba(5, 11, 26, 0.82)),
  1608. url("../assets//images/bg666.png");
  1609. }
  1610. .hero-banner {
  1611. position: relative;
  1612. min-height: 310px;
  1613. border: 1px solid var(--portal-line);
  1614. border-radius: 10px;
  1615. background:
  1616. linear-gradient(135deg, var(--portal-accent-soft), transparent 32%),
  1617. linear-gradient(180deg, var(--portal-card), var(--portal-card-2));
  1618. box-shadow:
  1619. var(--portal-shadow-strong),
  1620. inset 0 1px 0 rgba(255, 255, 255, 0.06);
  1621. overflow: hidden;
  1622. }
  1623. .hero-banner::before {
  1624. content: "";
  1625. position: absolute;
  1626. inset: 0;
  1627. background:
  1628. linear-gradient(120deg, rgba(128, 77, 255, 0.14) 0%, transparent 26%),
  1629. radial-gradient(
  1630. circle at 20% 36%,
  1631. rgba(123, 84, 255, 0.22),
  1632. transparent 22%
  1633. ),
  1634. radial-gradient(
  1635. circle at 74% 52%,
  1636. rgba(54, 170, 255, 0.18),
  1637. transparent 24%
  1638. ),
  1639. linear-gradient(
  1640. 90deg,
  1641. rgba(255, 255, 255, 0.04),
  1642. transparent 40%,
  1643. rgba(255, 255, 255, 0.02)
  1644. );
  1645. pointer-events: none;
  1646. }
  1647. .hero-copy {
  1648. position: relative;
  1649. z-index: 2;
  1650. }
  1651. .hero-script {
  1652. display: none;
  1653. }
  1654. .hero-text {
  1655. margin-top: 0;
  1656. color: var(--portal-text);
  1657. font-size: 46px;
  1658. font-weight: 800;
  1659. line-height: 1.2;
  1660. letter-spacing: 0.02em;
  1661. text-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  1662. max-width: 540px;
  1663. }
  1664. .hero-visual {
  1665. position: absolute;
  1666. inset: 0;
  1667. min-height: 310px;
  1668. }
  1669. .hero-visual::after {
  1670. content: "";
  1671. position: absolute;
  1672. inset: 0;
  1673. background:
  1674. linear-gradient(90deg, rgba(5, 11, 26, 0.18) 0%, transparent 28%),
  1675. linear-gradient(
  1676. 180deg,
  1677. transparent 0%,
  1678. rgba(5, 11, 26, 0.04) 56%,
  1679. rgba(5, 11, 26, 0.18) 100%
  1680. );
  1681. pointer-events: none;
  1682. }
  1683. .carousel-container {
  1684. position: relative;
  1685. height: 310px;
  1686. overflow: hidden;
  1687. }
  1688. .platform-block {
  1689. /* display: flex; */
  1690. overflow: hidden;
  1691. border: 1px solid var(--portal-line);
  1692. border-radius: 8px;
  1693. background: linear-gradient(180deg, var(--portal-card), var(--portal-card-2));
  1694. box-shadow:
  1695. var(--portal-shadow),
  1696. inset 0 1px 0 rgba(255, 255, 255, 0.04);
  1697. }
  1698. .platform-block__header {
  1699. display: flex;
  1700. align-items: center;
  1701. justify-content: flex-start;
  1702. flex: 0 0 190px;
  1703. width: 190px;
  1704. min-width: 190px;
  1705. min-height: 48px;
  1706. padding: 0 20px 0 0px;
  1707. box-sizing: border-box;
  1708. background: linear-gradient(90deg, var(--portal-accent-soft), transparent);
  1709. }
  1710. .platform-block__title-wrap {
  1711. display: flex;
  1712. flex-direction: column;
  1713. align-items: baseline;
  1714. gap: 18px;
  1715. min-width: 0;
  1716. flex: 1;
  1717. }
  1718. .platform-block__title {
  1719. color: var(--portal-title);
  1720. font-size: 15px;
  1721. font-weight: 800;
  1722. min-width: 0;
  1723. flex: 0 1 auto;
  1724. white-space: nowrap;
  1725. overflow: hidden;
  1726. text-overflow: ellipsis;
  1727. letter-spacing: 0.02em;
  1728. }
  1729. .platform-block__subtitle {
  1730. color: var(--portal-subtitle);
  1731. font-size: 13px;
  1732. font-weight: 500;
  1733. flex: 0 0 auto;
  1734. white-space: nowrap;
  1735. }
  1736. .platform-block__watermark {
  1737. color: rgba(119, 145, 214, 0.24);
  1738. font-size: 42px;
  1739. font-weight: 800;
  1740. line-height: 1;
  1741. letter-spacing: 0.08em;
  1742. }
  1743. .platform-app {
  1744. display: flex;
  1745. width: 100%;
  1746. min-width: 0;
  1747. min-height: 40px;
  1748. align-items: center;
  1749. justify-content: flex-start;
  1750. gap: 6px;
  1751. border: 1px solid var(--portal-line);
  1752. border-radius: 10px;
  1753. padding: 0 8px;
  1754. background: var(--portal-card-3);
  1755. font-size: 14px;
  1756. cursor: pointer;
  1757. transition:
  1758. transform 0.2s ease,
  1759. box-shadow 0.2s ease,
  1760. background 0.2s ease;
  1761. }
  1762. .platform-app:hover {
  1763. transform: translateY(-2px);
  1764. border-color: rgba(133, 163, 255, 0.45);
  1765. box-shadow: 0 10px 26px rgba(34, 54, 110, 0.18);
  1766. }
  1767. .platform-app--active {
  1768. background: linear-gradient(
  1769. 90deg,
  1770. rgba(76, 103, 224, 0.5),
  1771. var(--portal-card)
  1772. );
  1773. color: var(--portal-title);
  1774. border-color: rgba(129, 145, 255, 0.44);
  1775. box-shadow: inset 0 0 0 1px rgba(159, 173, 255, 0.08);
  1776. }
  1777. .platform-app--ghost {
  1778. background: var(--portal-card-4);
  1779. color: var(--portal-text);
  1780. }
  1781. .platform-app__icon {
  1782. display: inline-flex;
  1783. align-items: center;
  1784. justify-content: center;
  1785. flex: 0 0 28px;
  1786. color: var(--portal-accent-2);
  1787. filter: drop-shadow(0 0 8px rgba(90, 122, 255, 0.24));
  1788. }
  1789. .platform-app__label {
  1790. min-width: auto;
  1791. flex: 1;
  1792. white-space: nowrap;
  1793. overflow: visible;
  1794. text-overflow: clip;
  1795. font-size: 13px;
  1796. line-height: 1;
  1797. color: var(--portal-text);
  1798. white-space: nowrap;
  1799. overflow: hidden;
  1800. text-overflow: ellipsis;
  1801. }
  1802. .platform-block > .grid {
  1803. flex: 1;
  1804. align-content: start;
  1805. }
  1806. .portal-mobile-shortcuts {
  1807. display: grid;
  1808. grid-template-columns: repeat(3, minmax(0, 1fr));
  1809. gap: 8px;
  1810. }
  1811. .portal-mobile-shortcut {
  1812. min-width: 0;
  1813. border: 1px solid var(--portal-line);
  1814. border-radius: 12px;
  1815. padding: 10px 8px;
  1816. background: linear-gradient(180deg, var(--portal-card), var(--portal-card-2));
  1817. color: var(--portal-text);
  1818. font-size: 13px;
  1819. font-weight: 600;
  1820. box-shadow: var(--portal-shadow);
  1821. }
  1822. .side-card {
  1823. overflow: hidden;
  1824. border: 1px solid var(--portal-line);
  1825. border-radius: 14px;
  1826. background: linear-gradient(180deg, var(--portal-card), var(--portal-card-2));
  1827. box-shadow:
  1828. var(--portal-shadow),
  1829. inset 0 1px 0 rgba(255, 255, 255, 0.04);
  1830. }
  1831. .side-card--content {
  1832. padding-top: 10px;
  1833. }
  1834. .side-card__header {
  1835. display: flex;
  1836. align-items: center;
  1837. justify-content: space-between;
  1838. min-height: 20px;
  1839. padding-right: 10px;
  1840. /* border-radius: 999px; */
  1841. /* background: rgba(28, 39, 72, 0.86); */
  1842. border-bottom: 1px solid var(--portal-line);
  1843. }
  1844. .notice-badge {
  1845. position: relative;
  1846. display: inline-flex;
  1847. align-items: center;
  1848. justify-content: center;
  1849. height: 25px;
  1850. min-width: 70px;
  1851. color: var(--portal-title);
  1852. font-size: 14px;
  1853. }
  1854. .news.notice-badge {
  1855. position: relative;
  1856. display: inline-flex;
  1857. align-items: center;
  1858. justify-content: center;
  1859. height: 25px;
  1860. min-width: 70px;
  1861. color: var(--portal-title);
  1862. font-size: 14px;
  1863. border-bottom: 2px solid var(--portal-accent);
  1864. }
  1865. .notice-tabs {
  1866. display: inline-flex;
  1867. align-items: center;
  1868. gap: 8px;
  1869. }
  1870. .notice-tab {
  1871. height: 25px;
  1872. min-width: 68px;
  1873. padding: 0 14px;
  1874. border: 0;
  1875. border-radius: 999px;
  1876. background: transparent;
  1877. color: var(--portal-subtitle);
  1878. font-size: 14px;
  1879. cursor: pointer;
  1880. transition:
  1881. background 0.2s ease,
  1882. color 0.2s ease;
  1883. }
  1884. .notice-tab--active {
  1885. position: relative;
  1886. color: var(--portal-title);
  1887. }
  1888. .notice-tab--active::before {
  1889. content: "";
  1890. position: absolute;
  1891. left: 50%;
  1892. bottom: -2px;
  1893. width: 90%;
  1894. height: 2px;
  1895. transform: translateX(-50%);
  1896. border-radius: 999px;
  1897. background: linear-gradient(
  1898. to right,
  1899. #5f3abf 0%,
  1900. #7044c5 30%,
  1901. #7044c5 40%,
  1902. rgba(255, 255, 255, 0.7) 50%,
  1903. #7044c5 60%,
  1904. #7044c5 70%,
  1905. #5f3abf 100%
  1906. );
  1907. box-shadow: 0 0 12px rgba(112, 120, 255, 0.95);
  1908. }
  1909. .notice-badge::after {
  1910. content: "";
  1911. position: absolute;
  1912. top: 0;
  1913. right: -15px;
  1914. }
  1915. .side-card__more {
  1916. display: inline-flex;
  1917. align-items: center;
  1918. justify-content: center;
  1919. border: 0;
  1920. background: transparent;
  1921. color: var(--portal-subtitle);
  1922. cursor: pointer;
  1923. font-size: 12px;
  1924. }
  1925. .notice-item {
  1926. /* background: rgba(14, 22, 42, 0.88); */
  1927. padding: 8px 14px;
  1928. box-shadow: 0 10px 22px rgba(23, 52, 95, 0.08);
  1929. border-radius: 12px;
  1930. }
  1931. .notice-item:hover {
  1932. box-shadow: 0 16px 30px rgba(0, 0, 0, 0.22);
  1933. transition: all 0.2s ease;
  1934. transform: translateY(-1px);
  1935. }
  1936. .notice-item__title,
  1937. .notice-item__desc {
  1938. color: var(--portal-text);
  1939. font-size: 13px;
  1940. line-height: 1.5;
  1941. display: -webkit-box;
  1942. -webkit-box-orient: vertical;
  1943. -webkit-line-clamp: 1;
  1944. overflow: hidden;
  1945. }
  1946. .panel-title {
  1947. padding: 0 16px;
  1948. color: var(--portal-title);
  1949. font-size: 15px;
  1950. font-weight: 700;
  1951. }
  1952. .todo-item {
  1953. border: 1px solid var(--portal-line);
  1954. background: var(--portal-card-4);
  1955. padding: 8px 14px;
  1956. box-shadow: 0 10px 22px rgba(23, 52, 95, 0.08);
  1957. }
  1958. .todo-item__tag {
  1959. display: inline-flex;
  1960. align-items: center;
  1961. border-radius: 999px;
  1962. padding: 2px 8px;
  1963. font-size: 11px;
  1964. white-space: nowrap;
  1965. }
  1966. .news-mini {
  1967. display: flex;
  1968. align-items: center;
  1969. gap: 12px;
  1970. border: 1px solid var(--portal-line);
  1971. background: var(--portal-card-4);
  1972. padding: 5px 12px;
  1973. box-shadow: 0 10px 22px rgba(23, 52, 95, 0.08);
  1974. border-radius: 12px;
  1975. }
  1976. .news-mini:hover {
  1977. box-shadow: 0 16px 30px rgba(0, 0, 0, 0.22);
  1978. transition: all 0.2s ease;
  1979. transform: translateY(-1px);
  1980. }
  1981. .placeholder-panel {
  1982. display: flex;
  1983. height: 100%;
  1984. padding: 16px;
  1985. justify-content: start;
  1986. color: var(--portal-title);
  1987. font-size: 16px;
  1988. font-weight: 600;
  1989. letter-spacing: 0.08em;
  1990. }
  1991. @media (max-width: 1279px) {
  1992. .hero-text {
  1993. font-size: 34px;
  1994. max-width: 420px;
  1995. }
  1996. .hero-visual {
  1997. min-height: 260px;
  1998. }
  1999. }
  2000. @media (max-width: 767px) {
  2001. .platform-block {
  2002. display: block;
  2003. }
  2004. .platform-block__header {
  2005. width: 100%;
  2006. min-width: 0;
  2007. padding: 0 16px 0 18px;
  2008. flex: none;
  2009. justify-content: flex-start;
  2010. }
  2011. .platform-block__title-wrap {
  2012. gap: 12px;
  2013. flex-wrap: wrap;
  2014. }
  2015. .platform-block__title {
  2016. font-size: 16px;
  2017. }
  2018. .platform-block__subtitle {
  2019. font-size: 12px;
  2020. }
  2021. .platform-block__watermark {
  2022. font-size: 34px;
  2023. }
  2024. .hero-banner {
  2025. min-height: 240px;
  2026. }
  2027. .carousel-container {
  2028. height: 240px;
  2029. }
  2030. .carousel-caption {
  2031. left: 22px;
  2032. right: 22px;
  2033. top: 50%;
  2034. transform: translateY(-50%);
  2035. }
  2036. .hero-text {
  2037. font-size: 24px;
  2038. max-width: 100%;
  2039. }
  2040. }
  2041. .carousel-indicators {
  2042. display: flex;
  2043. gap: 10px;
  2044. z-index: 30;
  2045. }
  2046. .carousel-indicators span {
  2047. width: 36px;
  2048. height: 4px;
  2049. border-radius: 999px;
  2050. cursor: pointer;
  2051. transition: all 0.25s ease;
  2052. box-shadow: 0 0 16px rgba(112, 103, 255, 0.35);
  2053. }
  2054. .carousel-slide {
  2055. position: absolute;
  2056. inset: 0;
  2057. width: 100%;
  2058. height: 100%;
  2059. }
  2060. .hero-visual img {
  2061. width: 100%;
  2062. height: 100%;
  2063. object-fit: cover;
  2064. display: block;
  2065. filter: saturate(1.12) contrast(1.06);
  2066. }
  2067. .carousel-caption {
  2068. position: absolute;
  2069. left: 80px;
  2070. top: 44%;
  2071. z-index: 2;
  2072. transform: translateY(-50%);
  2073. text-align: left;
  2074. }
  2075. .carousel-container::before {
  2076. content: "";
  2077. position: absolute;
  2078. inset: auto 0 0;
  2079. height: 120px;
  2080. background: linear-gradient(180deg, transparent, rgba(5, 11, 26, 0.2));
  2081. z-index: 1;
  2082. pointer-events: none;
  2083. }
  2084. :deep(.portal-home .el-dialog) {
  2085. border-radius: 16px;
  2086. }
  2087. .top-nav-bar {
  2088. display: flex;
  2089. align-items: center;
  2090. justify-content: space-between;
  2091. padding: 5px 24px;
  2092. background: var(--portal-nav-bg);
  2093. backdrop-filter: blur(10px);
  2094. -webkit-backdrop-filter: blur(10px);
  2095. border: 1px solid var(--portal-line);
  2096. border-radius: 8px;
  2097. margin-bottom: 10px;
  2098. box-shadow: var(--portal-shadow);
  2099. }
  2100. .nav-title {
  2101. color: var(--portal-title);
  2102. font-size: 14px;
  2103. font-weight: 600;
  2104. letter-spacing: 0.08em;
  2105. }
  2106. .search-container {
  2107. display: flex;
  2108. align-items: center;
  2109. gap: 8px;
  2110. position: relative;
  2111. width: 320px;
  2112. height: 36px;
  2113. border-radius: 20px;
  2114. background: var(--portal-input-bg);
  2115. border: 1px solid var(--portal-line);
  2116. overflow: hidden;
  2117. transition: all 0.2s ease;
  2118. }
  2119. .search-container:hover {
  2120. background: var(--portal-input-hover);
  2121. border-color: rgba(109, 137, 213, 0.4);
  2122. }
  2123. .search-icon {
  2124. flex-shrink: 0;
  2125. font-size: 18px;
  2126. color: var(--portal-text-soft);
  2127. margin-left: 12px;
  2128. }
  2129. .search-input {
  2130. flex: 1;
  2131. border: none;
  2132. background: transparent;
  2133. outline: none;
  2134. font-size: 14px;
  2135. color: var(--portal-text);
  2136. padding: 0;
  2137. margin: 0;
  2138. }
  2139. .shortcut-key {
  2140. position: absolute;
  2141. right: 12px;
  2142. top: 50%;
  2143. transform: translateY(-50%);
  2144. font-size: 12px;
  2145. color: var(--portal-text-soft);
  2146. font-family: "Courier New", monospace;
  2147. }
  2148. .quick-access {
  2149. display: flex;
  2150. gap: 16px;
  2151. }
  2152. .quick-btn {
  2153. display: flex;
  2154. align-items: center;
  2155. gap: 8px;
  2156. padding: 8px 12px;
  2157. /* border: 1px solid rgba(109, 137, 213, 0.16); */
  2158. border-radius: 12px;
  2159. /* background: rgba(17, 25, 48, 0.8); */
  2160. color: var(--portal-text);
  2161. font-size: 13px;
  2162. cursor: pointer;
  2163. transition:
  2164. background 0.2s ease,
  2165. border-color 0.2s ease,
  2166. transform 0.2s ease;
  2167. }
  2168. .quick-btn--dropdown {
  2169. border: none;
  2170. background: transparent;
  2171. outline: none;
  2172. }
  2173. .quick-btn--dropdown:focus-visible {
  2174. outline: none;
  2175. }
  2176. .quick-btn:hover {
  2177. background: var(--portal-nav-hover);
  2178. border-color: rgba(109, 137, 213, 0.4);
  2179. transform: translateY(-1px);
  2180. }
  2181. .btn-icon {
  2182. font-size: 16px;
  2183. color: var(--portal-accent-2);
  2184. }
  2185. .btn-label {
  2186. white-space: nowrap;
  2187. }
  2188. .quick-btn__arrow {
  2189. font-size: 16px;
  2190. color: var(--portal-text-soft);
  2191. }
  2192. .quick-btn__meta {
  2193. color: var(--portal-text-soft);
  2194. }
  2195. .quick-btn__count {
  2196. font-weight: 700;
  2197. }
  2198. .quick-btn__count--todo {
  2199. color: var(--portal-number-todo);
  2200. }
  2201. .quick-btn__count--done {
  2202. color: var(--portal-number-done);
  2203. }
  2204. .aside-btn {
  2205. color: var(--portal-text-soft);
  2206. }
  2207. .badge {
  2208. display: inline-flex;
  2209. align-items: center;
  2210. justify-content: center;
  2211. width: 18px;
  2212. height: 18px;
  2213. border-radius: 50%;
  2214. background: #f56c6c;
  2215. color: white;
  2216. font-size: 10px;
  2217. font-weight: 600;
  2218. }
  2219. .quick-todo-bar {
  2220. display: flex;
  2221. gap: 16px;
  2222. padding: 8px 16px;
  2223. background: var(--portal-todo-bg);
  2224. backdrop-filter: blur(10px);
  2225. border: 1px solid var(--portal-line);
  2226. -webkit-backdrop-filter: blur(10px);
  2227. border-radius: 8px;
  2228. box-shadow: var(--portal-shadow);
  2229. /* margin-bottom: 16px; */
  2230. }
  2231. .quick-todo-btn {
  2232. display: flex;
  2233. flex: 1;
  2234. flex-direction: column;
  2235. align-items: flex-start;
  2236. justify-content: center;
  2237. gap: 8px;
  2238. border: none;
  2239. background: transparent;
  2240. padding: 0;
  2241. color: inherit;
  2242. cursor: pointer;
  2243. outline: none;
  2244. }
  2245. .quick-todo-btn--center {
  2246. align-items: center;
  2247. }
  2248. .quick-todo-btn:hover {
  2249. transform: translateY(-1px);
  2250. }
  2251. .todo-item {
  2252. display: flex;
  2253. align-items: center;
  2254. gap: 8px;
  2255. flex: 1;
  2256. padding: 8px 12px;
  2257. border-radius: 8px;
  2258. transition: all 0.2s ease;
  2259. cursor: pointer;
  2260. }
  2261. .todo-item:hover {
  2262. background: var(--portal-todo-hover);
  2263. transform: translateY(-1px);
  2264. border-color: rgba(109, 137, 213, 0.2);
  2265. }
  2266. .number {
  2267. font-size: 18px;
  2268. font-weight: 700;
  2269. color: var(--portal-number-todo);
  2270. min-width: 24px;
  2271. text-align: center;
  2272. }
  2273. .label {
  2274. font-size: 12px;
  2275. color: var(--portal-text);
  2276. white-space: nowrap;
  2277. }
  2278. .icon {
  2279. font-size: 14px;
  2280. color: var(--portal-accent-2);
  2281. }
  2282. :global(.quick-access-dropdown) {
  2283. padding: 0 !important;
  2284. border: 1px solid var(--portal-line) !important;
  2285. border-radius: 10px !important;
  2286. background: var(--dropdown-menu) !important;
  2287. box-shadow: var(--portal-shadow) !important;
  2288. overflow: hidden !important;
  2289. }
  2290. :global(.quick-access-dropdown .el-dropdown-menu) {
  2291. padding: 0px !important;
  2292. border: none !important;
  2293. background: var(--dropdown-menu) !important;
  2294. }
  2295. :global(.quick-access-dropdown .el-dropdown-menu__item) {
  2296. padding: 0px !important;
  2297. padding-left: 10px !important;
  2298. padding-right: 10px !important;
  2299. margin: 0 !important;
  2300. border: none !important;
  2301. background: var(--dropdown-menu) !important;
  2302. }
  2303. /* :global(.quick-access-dropdown .el-dropdown-menu__item):hover {
  2304. background: var(--dropdown-menu-hover) !important;
  2305. } */
  2306. :global(.quick-access-dropdown .el-popper__arrow::before) {
  2307. background: var(--dropdown-menu) !important;
  2308. border-color: var(--portal-line) !important;
  2309. }
  2310. :deep(.el-dropdown-menu__item):hover {
  2311. background: var(--dropdown-menu-hover) !important;
  2312. color: #fff !important;
  2313. }
  2314. </style>