Sidebar.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. <template>
  2. <aside class="sidebar">
  3. <!-- Logo -->
  4. <div class="sidebar-header">
  5. <div class="logo">
  6. <div class="logo-icon">🧠</div>
  7. <div>
  8. <div class="logo-text">领存智贸</div>
  9. <div class="logo-subtitle">AI原生外贸CRM</div>
  10. </div>
  11. </div>
  12. </div>
  13. <!-- Navigation -->
  14. <nav class="nav-menu">
  15. <!-- AI核心 -->
  16. <div class="nav-section">
  17. <div class="nav-section-title">AI 核心</div>
  18. <!-- <router-link to="/" class="nav-item" :class="{ active: $route.path === '/' }">
  19. <i class="fas fa-brain nav-icon"></i>
  20. <span class="nav-text">企业大脑</span>
  21. </router-link> -->
  22. <!-- <router-link to="/prospect" class="nav-item" :class="{ active: $route.path === '/prospect' }">
  23. <i class="fas fa-crosshairs nav-icon"></i>
  24. <span class="nav-text">自动寻客</span>
  25. <span class="nav-badge">新</span>
  26. </router-link> -->
  27. <router-link to="/marketing" class="nav-item" :class="{ active: $route.path === '/marketing' || $route.path === '/' }">
  28. <i class="fas fa-paper-plane nav-icon"></i>
  29. <span class="nav-text">自动营销</span>
  30. </router-link>
  31. <!-- <router-link to="/strategy" class="nav-item" :class="{ active: $route.path === '/strategy' }">
  32. <i class="fas fa-lightbulb nav-icon"></i>
  33. <span class="nav-text">AI策略中心</span>
  34. </router-link> -->
  35. </div>
  36. <!-- 客户管理 -->
  37. <div class="nav-section">
  38. <div class="nav-section-title">客户管理</div>
  39. <router-link to="/customers" class="nav-item" :class="{ active: $route.path === '/customers' }">
  40. <i class="fas fa-users nav-icon"></i>
  41. <span class="nav-text">客户池</span>
  42. <span class="nav-badge">847</span>
  43. </router-link>
  44. <!-- <router-link to="/opportunities" class="nav-item" :class="{ active: $route.path === '/opportunities' }">
  45. <i class="fas fa-briefcase nav-icon"></i>
  46. <span class="nav-text">商机管理</span>
  47. </router-link>
  48. <router-link to="/orders" class="nav-item" :class="{ active: $route.path === '/orders' }">
  49. <i class="fas fa-box nav-icon"></i>
  50. <span class="nav-text">订单跟进</span>
  51. </router-link> -->
  52. </div>
  53. <!-- 数据洞察 -->
  54. <!-- <div class="nav-section">
  55. <div class="nav-section-title">数据洞察</div>
  56. <router-link to="/analysis" class="nav-item" :class="{ active: $route.path === '/analysis' }">
  57. <i class="fas fa-chart-line nav-icon"></i>
  58. <span class="nav-text">经营分析</span>
  59. </router-link>
  60. <router-link to="/market" class="nav-item" :class="{ active: $route.path === '/market' }">
  61. <i class="fas fa-globe nav-icon"></i>
  62. <span class="nav-text">市场情报</span>
  63. </router-link>
  64. <router-link to="/risk" class="nav-item" :class="{ active: $route.path === '/risk' }">
  65. <i class="fas fa-shield-alt nav-icon"></i>
  66. <span class="nav-text">风险风控</span>
  67. <span class="nav-badge" style="background: var(--warning);">3</span>
  68. </router-link>
  69. </div> -->
  70. <!-- 系统设置 -->
  71. <div class="nav-section">
  72. <div class="nav-section-title">系统</div>
  73. <router-link to="/knowledge" class="nav-item" :class="{ active: $route.path === '/knowledge' }">
  74. <i class="fas fa-book nav-icon"></i>
  75. <span class="nav-text">知识库</span>
  76. </router-link>
  77. <router-link to="/settings" class="nav-item" :class="{ active: $route.path === '/settings' }">
  78. <i class="fas fa-cog nav-icon"></i>
  79. <span class="nav-text">设置</span>
  80. </router-link>
  81. </div>
  82. </nav>
  83. <!-- User Profile -->
  84. <div class="sidebar-footer">
  85. <div class="user-card" @click="toggleUserMenu">
  86. <div class="user-avatar-wrapper">
  87. <div class="user-avatar">{{ userData?.realName?.charAt(0) }}</div>
  88. <div class="online-indicator"></div>
  89. </div>
  90. <div class="user-info">
  91. <div class="user-name">{{ userData?.realName }}</div>
  92. <div class="user-status">
  93. <span class="status-dot"></span>
  94. 在线
  95. </div>
  96. </div>
  97. <i class="fas fa-chevron-up user-arrow" :class="{ rotated: showUserMenu }"></i>
  98. </div>
  99. <!-- User Dropdown Menu -->
  100. <transition name="dropdown">
  101. <div v-if="showUserMenu" class="user-menu">
  102. <div class="menu-header">
  103. <div class="menu-avatar">{{ userData?.realName?.charAt(0) }}</div>
  104. <div class="menu-user-info">
  105. <div class="menu-user-name">{{ userData?.realName }}</div>
  106. <div class="menu-user-email">{{ userData?.email }}</div>
  107. </div>
  108. </div>
  109. <div class="menu-divider"></div>
  110. <div class="menu-items">
  111. <div class="menu-item" @click="showProfileModal = true">
  112. <div class="menu-item-icon" style="background: rgba(99, 102, 241, 0.2); color: var(--primary);">
  113. <i class="fas fa-user"></i>
  114. </div>
  115. <div class="menu-item-content">
  116. <span class="menu-item-title">个人资料</span>
  117. <span class="menu-item-desc">编辑头像、姓名等信息</span>
  118. </div>
  119. </div>
  120. </div>
  121. <div class="menu-divider"></div>
  122. <div class="menu-items">
  123. <div class="menu-item" @click="openPwdModal">
  124. <div class="menu-item-icon" style="background: rgba(245, 158, 11, 0.2); color: var(--warning);">
  125. <i :class="isDarkMode ? 'fas fa-sun' : 'fas fa-moon'"></i>
  126. </div>
  127. <div class="menu-item-content">
  128. <span class="menu-item-title">修改密码</span>
  129. </div>
  130. </div>
  131. <div class="menu-item" @click="openHistoryModal">
  132. <div class="menu-item-icon" style="background: rgba(6, 182, 212, 0.2); color: var(--info);">
  133. <i class="fas fa-question-circle"></i>
  134. </div>
  135. <div class="menu-item-content">
  136. <span class="menu-item-title">登录历史</span>
  137. </div>
  138. </div>
  139. </div>
  140. <div class="menu-divider"></div>
  141. <div class="menu-footer">
  142. <div class="menu-item danger" @click="handleLogout">
  143. <div class="menu-item-icon" style="background: rgba(239, 68, 68, 0.2); color: var(--danger);">
  144. <i class="fas fa-sign-out-alt"></i>
  145. </div>
  146. <div class="menu-item-content">
  147. <span class="menu-item-title">退出登录</span>
  148. </div>
  149. </div>
  150. </div>
  151. </div>
  152. </transition>
  153. </div>
  154. <!-- Help Modal -->
  155. <div v-if="showHelpModal" class="modal-overlay" @click.self="showHelpModal = false">
  156. <div class="modal">
  157. <div class="modal-header">
  158. <div class="modal-title"><i class="fas fa-question-circle"></i> 帮助中心</div>
  159. <button class="modal-close" @click="showHelpModal = false">
  160. <i class="fas fa-times"></i>
  161. </button>
  162. </div>
  163. <div class="modal-body">
  164. <div class="help-section">
  165. <h4>🚀 快速入门</h4>
  166. <div class="help-list">
  167. <div class="help-item">
  168. <i class="fas fa-brain"></i>
  169. <span>企业大脑 - 与AI对话,快速执行任务</span>
  170. </div>
  171. <div class="help-item">
  172. <i class="fas fa-crosshairs"></i>
  173. <span>自动寻客 - AI全网挖掘高匹配潜客</span>
  174. </div>
  175. <div class="help-item">
  176. <i class="fas fa-paper-plane"></i>
  177. <span>自动营销 - 多渠道自动化客户触达</span>
  178. </div>
  179. </div>
  180. </div>
  181. <div class="help-section">
  182. <h4>📞 联系支持</h4>
  183. <div class="contact-cards">
  184. <div class="contact-card">
  185. <i class="fas fa-envelope"></i>
  186. <div>
  187. <div class="contact-label">邮箱</div>
  188. <div class="contact-value">support@lingcunai.com</div>
  189. </div>
  190. </div>
  191. <div class="contact-card">
  192. <i class="fab fa-weixin"></i>
  193. <div>
  194. <div class="contact-label">微信</div>
  195. <div class="contact-value">领存智贸</div>
  196. </div>
  197. </div>
  198. </div>
  199. </div>
  200. </div>
  201. <div class="modal-footer">
  202. <button class="btn btn-secondary" @click="showHelpModal = false">关闭</button>
  203. <button class="btn btn-primary">
  204. <i class="fas fa-book"></i> 查看完整文档
  205. </button>
  206. </div>
  207. </div>
  208. </div>
  209. <!-- 个人信息弹窗 -->
  210. <div v-if="showProfileModal" class="modal-overlay" @click.self="showProfileModal = false">
  211. <div class="modal settings-modal">
  212. <div class="modal-header">
  213. <div class="modal-title"><i class="fas fa-user-edit"></i> 编辑个人信息</div>
  214. <button class="modal-close" @click="showProfileModal = false"><i class="fas fa-times"></i></button>
  215. </div>
  216. <div class="modal-body">
  217. <el-form ref="settingsFormRef" label-width="100px">
  218. <el-form-item label="姓名">
  219. <el-input readonly v-model="userData.realName" autocomplete="off" />
  220. </el-form-item>
  221. <el-form-item label="职位">
  222. <el-input readonly v-model="userData.jobName" autocomplete="off" />
  223. </el-form-item>
  224. <el-form-item label="邮箱">
  225. <el-input readonly v-model="userData.email" autocomplete="off" />
  226. </el-form-item>
  227. <el-form-item label="电话">
  228. <el-input readonly v-model="userData.userName" autocomplete="off" />
  229. </el-form-item>
  230. <el-form-item label="所属部门">
  231. <el-input readonly v-model="userData.deptName" autocomplete="off" />
  232. </el-form-item>
  233. <el-form-item label="个人简介">
  234. <el-input type="textarea" v-model="userData.remark" :rows="3" autocomplete="off" placeholder="请输入个人简介" />
  235. </el-form-item>
  236. </el-form>
  237. </div>
  238. <div class="modal-footer">
  239. <button class="btn btn-secondary" @click="showProfileModal = false">取消</button>
  240. <button class="btn btn-primary" @click="saveRemark">保存</button>
  241. </div>
  242. </div>
  243. </div>
  244. <!-- 修改密码弹窗 -->
  245. <div v-if="showPasswordModal" class="modal-overlay" @click.self="showPasswordModal = false">
  246. <div class="modal settings-modal">
  247. <div class="modal-header">
  248. <div class="modal-title"><i class="fas fa-key"></i> 修改密码</div>
  249. <button class="modal-close" @click="showPasswordModal = false"><i class="fas fa-times"></i></button>
  250. </div>
  251. <div class="modal-body">
  252. <el-form ref="resetPwdformRef" :rules="rules" :model="pwdForm" label-width="120px">
  253. <el-form-item label="当前密码" prop="password">
  254. <el-input type='password' v-model="pwdForm.password" placeholder="请输入当前密码" @input="getPassword"></el-input>
  255. </el-form-item>
  256. <el-form-item label="新密码" prop="newPass">
  257. <el-input type='password' v-model="pwdForm.newPass" placeholder="请输入新密码"
  258. @input="getNPassword"></el-input>
  259. </el-form-item>
  260. <el-form-item label="确认新密码" prop="newPassAgain">
  261. <el-input type='password' v-model="pwdForm.newPassAgain" placeholder="请再次输入新密码"
  262. @input="getNPasswordA"></el-input>
  263. </el-form-item>
  264. <div class="password-hint">
  265. <i class="fas fa-info-circle"></i> 密码要求:至少8位,包含字母和数字
  266. </div>
  267. </el-form>
  268. </div>
  269. <div class="modal-footer">
  270. <button class="btn btn-secondary" @click="showPasswordModal = false">取消</button>
  271. <button class="btn btn-primary" @click="submitForm">确认修改</button>
  272. </div>
  273. </div>
  274. </div>
  275. <!-- 登录历史弹窗 -->
  276. <div v-if="showLoginHistoryModal" class="modal-overlay" @click.self="showLoginHistoryModal = false">
  277. <div class="modal settings-modal wide" style="max-width: 520px;">
  278. <div class="modal-header">
  279. <div class="modal-title"><i class="fas fa-history"></i> 登录历史记录</div>
  280. <button class="modal-close" @click="showLoginHistoryModal = false"><i class="fas fa-times"></i></button>
  281. </div>
  282. <div class="modal-body">
  283. <div class="login-history-list">
  284. <div v-for="login in loginHistory" :key="login.id" class="login-history-item">
  285. <div class="login-icon" :class="login.status == 1 ? 'success' : 'error'">
  286. <i :class="login.status === 1 ? 'fas fa-check-circle' : 'fas fa-times-circle'"></i>
  287. </div>
  288. <div class="login-info">
  289. <div class="login-time">{{ login.createTime }}</div>
  290. <div class="login-details">
  291. <span><i class="fas fa-map-marker-alt"></i> {{ login.city }}</span>
  292. <span><i class="fas fa-desktop"></i> {{ login.browser }} / {{ login.os.split(' ')[0] }}</span>
  293. <span><i class="fas fa-network-wired"></i> {{ login.ip }}</span>
  294. </div>
  295. </div>
  296. <div class="login-status" :class="login.status == 1 ? 'success' : 'error'">{{ login.status == 1 ? '成功' : '失败' }}</div>
  297. </div>
  298. </div>
  299. </div>
  300. <div class="modal-footer">
  301. <button class="btn btn-secondary" @click="showLoginHistoryModal = false">关闭</button>
  302. </div>
  303. </div>
  304. </div>
  305. <!-- Click outside to close menu -->
  306. <div v-if="showUserMenu" class="menu-backdrop" @click="showUserMenu = false"></div>
  307. </aside>
  308. </template>
  309. <script setup>
  310. import { ref, onMounted } from 'vue'
  311. import { useRoute, useRouter } from 'vue-router'
  312. import { getUserInfo, updateUserPwd, getLoginData, updateRemark } from '@/api/layout'
  313. import { ElMessageBox } from 'element-plus'
  314. const route = useRoute()
  315. const router = useRouter()
  316. const showProfileModal = ref(false)
  317. const showPasswordModal = ref(false)
  318. const showLoginHistoryModal = ref(false)
  319. // 登录历史数据
  320. const loginHistory = ref([])
  321. const openHistoryModal = () => {
  322. getHistoryData()
  323. showLoginHistoryModal.value = true
  324. }
  325. const getHistoryData = () => {
  326. getLoginData({ pageIndex: 1, pageSize: 10000 }).then(res => {
  327. if(res.code === 200){
  328. loginHistory.value = res.result.records
  329. }
  330. })
  331. }
  332. const saveRemark = () => {
  333. updateRemark({ remark: userData.value.remark, id: userData.value.id }).then(res => {
  334. ElMessage.success("个人信息已更新");
  335. showProfileModal.value = false
  336. }).catch(err => {
  337. ElMessage.error(err.message);
  338. })
  339. }
  340. const showUserMenu = ref(false)
  341. const showHelpModal = ref(false)
  342. const isDarkMode = ref(true)
  343. const toggleUserMenu = () => {
  344. showUserMenu.value = !showUserMenu.value
  345. }
  346. const goToSettings = (section) => {
  347. showUserMenu.value = false
  348. router.push('/settings')
  349. }
  350. const toggleTheme = () => {
  351. isDarkMode.value = !isDarkMode.value
  352. showUserMenu.value = false
  353. }
  354. const handleLogout = () => {
  355. ElMessageBox.confirm(
  356. '确定要退出登录吗?',
  357. '提示',
  358. {
  359. confirmButtonText: '确认',
  360. cancelButtonText: '取消',
  361. type: 'warning',
  362. }
  363. ).then(() => {
  364. localStorage.clear()
  365. router.push({ path: '/login' })
  366. })
  367. }
  368. const pwdForm = ref({
  369. password: '',
  370. newPass: '',
  371. newPassAgain: ''
  372. });
  373. const resetPwdformRef = ref();
  374. const getPassword = (data) => {
  375. pwdForm.value.password = data.replace(/\s+/g, '')
  376. }
  377. const getNPassword = (data) => {
  378. pwdForm.value.newPass = data.replace(/\s+/g, '')
  379. }
  380. const getNPasswordA = (data) => {
  381. pwdForm.value.newPassAgain = data.replace(/\s+/g, '')
  382. }
  383. const openPwdModal = () => {
  384. showPasswordModal.value = true
  385. resetPwdformRef.value?.resetFields();
  386. pwdForm.value.password = ''
  387. pwdForm.value.newPass = ''
  388. pwdForm.value.newPassAgain = ''
  389. }
  390. var validatePass1 = (rule, value, callback) => {
  391. const reg = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\W]{8,}$/;
  392. if (value === '') {
  393. callback(new Error('请输入新密码'));
  394. } else if (!reg.test(value)) {
  395. callback(new Error('密码须包含字母+数字且长度大于8位!'));
  396. } else {
  397. callback();
  398. }
  399. };
  400. var validatePass2 = (rule, value, callback) => {
  401. if (value === '') {
  402. callback(new Error('请再次输入新密码'));
  403. } else if (value !== pwdForm.value.newPass) {
  404. callback(new Error('两次输入密码不一致!'));
  405. } else {
  406. callback();
  407. }
  408. };
  409. const rules = ref({
  410. password: [
  411. { required: true, message: '请输入现在使用的密码', trigger: 'blur' },
  412. { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
  413. ],
  414. newPass: [
  415. { required: true, validator: validatePass1, trigger: 'blur' },
  416. ],
  417. newPassAgain: [
  418. { required: true, validator: validatePass2, trigger: 'blur' },
  419. ],
  420. });
  421. const submitForm = () => {
  422. resetPwdformRef.value?.validate((valid) => {
  423. if (valid) {
  424. updateUserPwd(pwdForm.value).then(res => {
  425. localStorage.clear()
  426. ElMessage.success("修改成功");
  427. router.push({ path: '/login' })
  428. }).catch(err => {
  429. ElMessage.error(err.message);
  430. })
  431. } else {
  432. }
  433. });
  434. }
  435. const userData = ref()
  436. onMounted(() => {
  437. getUserInfo().then((res) => {
  438. if(res.code == 200) {
  439. userData.value = res.result
  440. }
  441. })
  442. })
  443. </script>
  444. <style scoped>
  445. .sidebar {
  446. width: 260px;
  447. background: var(--bg-card);
  448. border-right: 1px solid var(--border);
  449. display: flex;
  450. flex-direction: column;
  451. position: fixed;
  452. top: 0;
  453. left: 0;
  454. bottom: 0;
  455. z-index: 100;
  456. }
  457. .sidebar-header {
  458. padding: 20px;
  459. border-bottom: 1px solid var(--border);
  460. }
  461. .logo {
  462. display: flex;
  463. align-items: center;
  464. gap: 12px;
  465. }
  466. .logo-icon {
  467. width: 40px;
  468. height: 40px;
  469. background: var(--gradient-1);
  470. border-radius: 10px;
  471. display: flex;
  472. align-items: center;
  473. justify-content: center;
  474. font-size: 20px;
  475. }
  476. .logo-text {
  477. font-size: 18px;
  478. font-weight: 700;
  479. color: var(--text-primary);
  480. }
  481. .logo-subtitle {
  482. font-size: 11px;
  483. color: var(--text-secondary);
  484. }
  485. /* Navigation Menu */
  486. .nav-menu {
  487. flex: 1;
  488. overflow-y: auto;
  489. padding: 16px 12px;
  490. }
  491. .nav-section {
  492. margin-bottom: 20px;
  493. }
  494. .nav-section-title {
  495. font-size: 11px;
  496. font-weight: 600;
  497. color: var(--text-muted);
  498. text-transform: uppercase;
  499. letter-spacing: 0.5px;
  500. padding: 0 12px;
  501. margin-bottom: 8px;
  502. }
  503. .nav-item {
  504. display: flex;
  505. align-items: center;
  506. gap: 12px;
  507. padding: 10px 12px;
  508. border-radius: 8px;
  509. cursor: pointer;
  510. transition: all 0.2s;
  511. margin-bottom: 4px;
  512. color: var(--text-secondary);
  513. text-decoration: none;
  514. }
  515. .nav-item:hover {
  516. background: var(--bg-hover);
  517. color: var(--text-primary);
  518. }
  519. .nav-item.active {
  520. background: rgba(99, 102, 241, 0.2);
  521. color: var(--primary);
  522. }
  523. .nav-icon {
  524. width: 20px;
  525. font-size: 16px;
  526. }
  527. .nav-text {
  528. flex: 1;
  529. font-size: 13px;
  530. }
  531. .nav-badge {
  532. background: var(--danger);
  533. color: white;
  534. font-size: 10px;
  535. padding: 2px 6px;
  536. border-radius: 10px;
  537. font-weight: 600;
  538. }
  539. /* User Card */
  540. .sidebar-footer {
  541. padding: 12px;
  542. border-top: 1px solid var(--border);
  543. position: relative;
  544. }
  545. .user-card {
  546. display: flex;
  547. align-items: center;
  548. gap: 12px;
  549. padding: 12px;
  550. background: var(--bg-hover);
  551. border-radius: 12px;
  552. cursor: pointer;
  553. transition: all 0.2s;
  554. }
  555. .user-card:hover {
  556. background: var(--border);
  557. }
  558. .user-avatar-wrapper {
  559. position: relative;
  560. }
  561. .user-avatar {
  562. width: 40px;
  563. height: 40px;
  564. background: var(--gradient-1);
  565. border-radius: 50%;
  566. display: flex;
  567. align-items: center;
  568. justify-content: center;
  569. font-size: 16px;
  570. font-weight: 600;
  571. color: white;
  572. }
  573. .online-indicator {
  574. position: absolute;
  575. bottom: 0;
  576. right: 0;
  577. width: 12px;
  578. height: 12px;
  579. background: var(--success);
  580. border: 2px solid var(--bg-card);
  581. border-radius: 50%;
  582. }
  583. .user-info {
  584. flex: 1;
  585. }
  586. .user-name {
  587. font-size: 14px;
  588. font-weight: 600;
  589. }
  590. .user-status {
  591. display: flex;
  592. align-items: center;
  593. gap: 6px;
  594. font-size: 12px;
  595. color: var(--text-secondary);
  596. }
  597. .status-dot {
  598. width: 6px;
  599. height: 6px;
  600. background: var(--success);
  601. border-radius: 50%;
  602. }
  603. .user-arrow {
  604. color: var(--text-muted);
  605. font-size: 12px;
  606. transition: transform 0.2s;
  607. }
  608. .user-arrow.rotated {
  609. transform: rotate(180deg);
  610. }
  611. /* User Menu Dropdown */
  612. .menu-backdrop {
  613. position: fixed;
  614. top: 0;
  615. left: 0;
  616. right: 0;
  617. bottom: 0;
  618. z-index: 98;
  619. }
  620. .user-menu {
  621. position: absolute;
  622. bottom: 100%;
  623. left: 12px;
  624. right: 12px;
  625. margin-bottom: 8px;
  626. background: var(--bg-card);
  627. border: 1px solid var(--border);
  628. border-radius: 12px;
  629. box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3);
  630. z-index: 99;
  631. overflow: hidden;
  632. }
  633. .dropdown-enter-active,
  634. .dropdown-leave-active {
  635. transition: all 0.2s ease;
  636. }
  637. .dropdown-enter-from,
  638. .dropdown-leave-to {
  639. opacity: 0;
  640. transform: translateY(10px);
  641. }
  642. .menu-header {
  643. display: flex;
  644. align-items: center;
  645. gap: 12px;
  646. padding: 16px;
  647. background: var(--bg-hover);
  648. }
  649. .menu-avatar {
  650. width: 44px;
  651. height: 44px;
  652. background: var(--gradient-1);
  653. border-radius: 50%;
  654. display: flex;
  655. align-items: center;
  656. justify-content: center;
  657. font-size: 18px;
  658. font-weight: 600;
  659. color: white;
  660. }
  661. .menu-user-info {
  662. flex: 1;
  663. }
  664. .menu-user-name {
  665. font-size: 14px;
  666. font-weight: 600;
  667. }
  668. .menu-user-email {
  669. font-size: 12px;
  670. color: var(--text-secondary);
  671. }
  672. .menu-divider {
  673. height: 1px;
  674. background: var(--border);
  675. margin: 4px 0;
  676. }
  677. .menu-items {
  678. padding: 8px;
  679. }
  680. .menu-item {
  681. display: flex;
  682. align-items: center;
  683. gap: 12px;
  684. padding: 10px;
  685. border-radius: 8px;
  686. cursor: pointer;
  687. transition: background 0.2s;
  688. text-decoration: none;
  689. color: inherit;
  690. }
  691. .menu-item:hover {
  692. background: var(--bg-hover);
  693. }
  694. .menu-item.danger:hover {
  695. background: rgba(239, 68, 68, 0.1);
  696. }
  697. .menu-item.danger .menu-item-title {
  698. color: var(--danger);
  699. }
  700. .menu-item-icon {
  701. width: 36px;
  702. height: 36px;
  703. border-radius: 8px;
  704. display: flex;
  705. align-items: center;
  706. justify-content: center;
  707. font-size: 14px;
  708. }
  709. .menu-item-content {
  710. flex: 1;
  711. }
  712. .menu-item-title {
  713. display: block;
  714. font-size: 13px;
  715. font-weight: 500;
  716. }
  717. .menu-item-desc {
  718. display: block;
  719. font-size: 11px;
  720. color: var(--text-secondary);
  721. margin-top: 2px;
  722. }
  723. .menu-footer {
  724. padding: 8px;
  725. padding-top: 0;
  726. }
  727. /* Modal */
  728. .modal-overlay {
  729. position: fixed;
  730. top: 0;
  731. left: 0;
  732. right: 0;
  733. bottom: 0;
  734. background: rgba(0, 0, 0, 0.7);
  735. display: flex;
  736. align-items: center;
  737. justify-content: center;
  738. z-index: 1000;
  739. padding: 20px;
  740. }
  741. .modal {
  742. background: var(--bg-card);
  743. border-radius: 16px;
  744. width: 100%;
  745. max-width: 500px;
  746. overflow: hidden;
  747. }
  748. .modal-header {
  749. display: flex;
  750. justify-content: space-between;
  751. align-items: center;
  752. padding: 20px;
  753. border-bottom: 1px solid var(--border);
  754. }
  755. .modal-title {
  756. font-size: 16px;
  757. font-weight: 600;
  758. display: flex;
  759. align-items: center;
  760. gap: 10px;
  761. }
  762. .modal-title i {
  763. color: var(--primary);
  764. }
  765. .modal-close {
  766. width: 32px;
  767. height: 32px;
  768. background: var(--bg-hover);
  769. border: none;
  770. border-radius: 8px;
  771. color: var(--text-secondary);
  772. cursor: pointer;
  773. display: flex;
  774. align-items: center;
  775. justify-content: center;
  776. }
  777. .modal-close:hover {
  778. background: var(--border);
  779. color: var(--text-primary);
  780. }
  781. .modal-body {
  782. padding: 20px;
  783. }
  784. .modal-footer {
  785. display: flex;
  786. justify-content: flex-end;
  787. gap: 12px;
  788. padding: 20px;
  789. border-top: 1px solid var(--border);
  790. }
  791. /* Help Section */
  792. .help-section {
  793. margin-bottom: 20px;
  794. }
  795. .help-section:last-child {
  796. margin-bottom: 0;
  797. }
  798. .help-section h4 {
  799. font-size: 14px;
  800. font-weight: 600;
  801. margin-bottom: 12px;
  802. }
  803. .help-list {
  804. display: flex;
  805. flex-direction: column;
  806. gap: 8px;
  807. }
  808. .help-item {
  809. display: flex;
  810. align-items: center;
  811. gap: 10px;
  812. padding: 12px;
  813. background: var(--bg-hover);
  814. border-radius: 8px;
  815. font-size: 13px;
  816. }
  817. .help-item i {
  818. color: var(--primary);
  819. width: 20px;
  820. }
  821. .contact-cards {
  822. display: grid;
  823. grid-template-columns: repeat(2, 1fr);
  824. gap: 12px;
  825. }
  826. .contact-card {
  827. display: flex;
  828. align-items: center;
  829. gap: 12px;
  830. padding: 14px;
  831. background: var(--bg-hover);
  832. border-radius: 10px;
  833. }
  834. .contact-card i {
  835. font-size: 20px;
  836. color: var(--primary);
  837. }
  838. .contact-label {
  839. font-size: 11px;
  840. color: var(--text-muted);
  841. }
  842. .contact-value {
  843. font-size: 13px;
  844. font-weight: 500;
  845. }
  846. /* Login History Modal */
  847. .login-history-list { display: flex; flex-direction: column; gap: 12px; }
  848. .login-history-item { display: flex; align-items: center; gap: 14px; padding: 14px; background: var(--bg-hover); border-radius: 10px; }
  849. .login-icon { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 16px; }
  850. .login-icon.success { background: rgba(16, 185, 129, 0.2); color: var(--success); }
  851. .login-icon.error { background: rgba(239, 68, 68, 0.2); color: var(--danger); }
  852. .login-info { flex: 1; }
  853. .login-time { font-size: 14px; font-weight: 500; margin-bottom: 4px; }
  854. .login-details { display: flex; gap: 16px; font-size: 12px; color: var(--text-muted); }
  855. .login-details span { display: flex; align-items: center; gap: 4px; }
  856. .login-status { font-size: 12px; padding: 4px 10px; border-radius: 10px; }
  857. .login-status.success { background: rgba(16, 185, 129, 0.2); color: var(--success); }
  858. .login-status.error { background: rgba(239, 68, 68, 0.2); color: var(--danger); }
  859. :deep(.el-form-item){
  860. display: flex;
  861. flex-direction: column;
  862. }
  863. :deep(.el-form-item .el-form-item__label){
  864. justify-content: flex-start;
  865. }
  866. :deep(.el-form-item__label), :deep(.el-radio__label), :deep(.el-checkbox__label) {
  867. color: var(--text-secondary);
  868. }
  869. :deep(.el-input__inner) {
  870. color: var(--text-primary);
  871. }
  872. :deep(.el-input__wrapper), :deep(.el-textarea__inner), :deep(.el-select__wrapper){
  873. background: var(--bg-dark);
  874. border: 1px solid var(--border);
  875. box-shadow: none;
  876. border-radius: 8px;
  877. color: var(--text-primary);
  878. }
  879. </style>