Layout.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. <template>
  2. <el-container class="layout-container">
  3. <el-aside width="200px">
  4. <div class="logo">浠艾福</div>
  5. <el-scrollbar wrap-class="sidebar-scrollbar-wrap">
  6. <el-menu
  7. :default-active="activeMenu"
  8. class="sidebar-menu"
  9. background-color="transparent"
  10. text-color="#64748B"
  11. active-text-color="#6366F1"
  12. @select="handleMenuSelect"
  13. >
  14. <template v-for="item in menuItems">
  15. <el-submenu v-if="item.children && hasPerm(item.perm)" :key="item.title" :index="item.title">
  16. <template slot="title">
  17. <i :class="item.icon"></i>
  18. <span>{{ item.label || item.title }}</span>
  19. </template>
  20. <el-menu-item
  21. v-for="child in item.children"
  22. :key="child.path"
  23. :index="child.path"
  24. >
  25. <i :class="child.icon"></i>
  26. <span slot="title">{{ child.label }}</span>
  27. </el-menu-item>
  28. </el-submenu>
  29. <el-menu-item v-else-if="hasPerm(item.perm)" :key="item.path" :index="item.path">
  30. <i :class="item.icon"></i>
  31. <span slot="title">{{ item.label }}</span>
  32. </el-menu-item>
  33. </template>
  34. </el-menu>
  35. </el-scrollbar>
  36. </el-aside>
  37. <el-container>
  38. <el-header>
  39. <div class="header-right">
  40. <el-button v-if="hasPerm('service:*')" type="text" class="invite-code-btn" @click="showInviteCode" icon="el-icon-share">
  41. 邀请码
  42. </el-button>
  43. <el-dropdown trigger="click" @command="handleProfileCommand">
  44. <span class="el-dropdown-link username">
  45. {{ displayUserLabel }}<i class="el-icon-arrow-down el-icon--right"></i>
  46. </span>
  47. <el-dropdown-menu slot="dropdown">
  48. <el-dropdown-item command="profile" icon="el-icon-user">个人信息</el-dropdown-item>
  49. <el-dropdown-item command="password" icon="el-icon-key">修改密码</el-dropdown-item>
  50. <el-dropdown-item command="logout" icon="el-icon-switch-button" divided>退出登录</el-dropdown-item>
  51. </el-dropdown-menu>
  52. </el-dropdown>
  53. </div>
  54. <!-- 邀请码 dialog -->
  55. <el-dialog title="我的邀请码" :visible.sync="inviteCodeVisible" width="500px" :close-on-click-modal="false">
  56. <div v-loading="inviteCodeLoading">
  57. <div v-if="inviteCodeData" class="invite-code-content">
  58. <div class="invite-code-section">
  59. <div class="invite-code-label">团队邀请码</div>
  60. <div class="invite-code-value">
  61. <span class="code-text">{{ inviteCodeData.code || '无' }}</span>
  62. <el-button size="mini" type="primary" @click="copyCode(inviteCodeData.code)" plain>复制</el-button>
  63. </div>
  64. <div class="invite-code-desc">
  65. 家庭用户通过此码加入您的团队
  66. </div>
  67. </div>
  68. </div>
  69. <div v-else-if="!inviteCodeLoading" class="no-invite-code">
  70. <i class="el-icon-warning-outline"></i>
  71. <p>暂无邀请码,请先生成</p>
  72. <el-button type="primary" @click="generateInviteCode">生成邀请码</el-button>
  73. </div>
  74. </div>
  75. </el-dialog>
  76. </el-header>
  77. <!-- 个人信息 dialog -->
  78. <el-dialog title="个人信息" :visible.sync="profileDialogVisible" width="400px">
  79. <el-form :model="profileForm" label-width="80px">
  80. <el-form-item label="用户名">
  81. <el-input v-model="profileForm.username" disabled></el-input>
  82. </el-form-item>
  83. <el-form-item label="真实姓名">
  84. <el-input v-model="profileForm.realName" disabled></el-input>
  85. </el-form-item>
  86. <el-form-item label="昵称">
  87. <el-input v-model="profileForm.nickname"></el-input>
  88. </el-form-item>
  89. <el-form-item label="手机号">
  90. <el-input v-model="profileForm.phone"></el-input>
  91. </el-form-item>
  92. <el-form-item label="邮箱">
  93. <el-input v-model="profileForm.email"></el-input>
  94. </el-form-item>
  95. </el-form>
  96. <span slot="footer">
  97. <el-button @click="profileDialogVisible = false">取消</el-button>
  98. <el-button type="primary" @click="confirmUpdateInfo" :loading="submitting">保存</el-button>
  99. </span>
  100. </el-dialog>
  101. <!-- 修改密码 dialog -->
  102. <el-dialog title="修改密码" :visible.sync="passwordDialogVisible" width="400px">
  103. <el-form :model="passwordForm" :rules="passwordRules" ref="passwordForm" label-width="100px">
  104. <el-form-item label="原密码" prop="oldPassword">
  105. <el-input v-model="passwordForm.oldPassword" type="password" show-password></el-input>
  106. </el-form-item>
  107. <el-form-item label="新密码" prop="newPassword">
  108. <el-input v-model="passwordForm.newPassword" type="password" show-password></el-input>
  109. </el-form-item>
  110. <el-form-item label="确认密码" prop="confirmPassword">
  111. <el-input v-model="passwordForm.confirmPassword" type="password" show-password></el-input>
  112. </el-form-item>
  113. </el-form>
  114. <span slot="footer">
  115. <el-button @click="passwordDialogVisible = false">取消</el-button>
  116. <el-button type="primary" @click="confirmChangePassword" :loading="submitting">确定</el-button>
  117. </span>
  118. </el-dialog>
  119. <el-main>
  120. <router-view />
  121. </el-main>
  122. </el-container>
  123. </el-container>
  124. </template>
  125. <script>
  126. import { getAdminInfo, changePassword } from '@/api/auth'
  127. import { updateAdminInfo } from '@/api/admin'
  128. import { getMyInviteCode } from '@/api/guide'
  129. import { getEffectivePermissions, hasPermission, getRoleLabel } from '@/utils/permissions'
  130. export default {
  131. data() {
  132. return {
  133. menuItems: [
  134. // ===== 1. 首页 (ALL) =====
  135. { path: '/dashboard', label: '首页', icon: 'el-icon-s-home', perm: 'dashboard' },
  136. // ===== 2. 家庭运营 (admin) =====
  137. { title: '家庭运营', icon: 'el-icon-s-custom', perm: 'operation',
  138. children: [
  139. { path: '/families', label: '家庭列表', icon: 'el-icon-s-custom', perm: 'family:list' },
  140. { path: '/children', label: '孩子管理', icon: 'el-icon-user-solid', perm: 'family:children' },
  141. { path: '/points', label: '积分管理', icon: 'el-icon-s-finance', perm: 'family:points' },
  142. { path: '/wishes', label: '心愿管理', icon: 'el-icon-star-off', perm: 'family:wishes' },
  143. { path: '/rewards', label: '奖励管理', icon: 'el-icon-s-goods', perm: 'reward:list' },
  144. ]},
  145. // ===== 3. 任务中心 (admin) =====
  146. { title: '任务中心', icon: 'el-icon-s-order', perm: 'task:*',
  147. children: [
  148. { path: '/tasks', label: '任务列表', icon: 'el-icon-s-order', perm: 'task:list' },
  149. { path: '/task-templates', label: '任务模板', icon: 'el-icon-document', perm: 'task:templates' },
  150. { path: '/growth-task', label: '成长任务管理', icon: 'el-icon-s-management', perm: 'task:list' },
  151. ]},
  152. // ===== 4. 审核中心 (admin) =====
  153. { title: '审核中心', icon: 'el-icon-s-check', perm: 'audit',
  154. children: [
  155. { path: '/package-audit', label: '套餐审核', icon: 'el-icon-document-checked', perm: 'audit:package' },
  156. { path: '/guide-audit', label: '规划师审核', icon: 'el-icon-document-checked', perm: 'audit:guide' },
  157. { path: '/vendor-review', label: '服务商审核', icon: 'el-icon-document-checked', perm: 'audit:vendor' },
  158. { path: '/withdraw-audit', label: '提现审核', icon: 'el-icon-document-checked', perm: 'audit:withdraw' },
  159. ]},
  160. // ===== 5. 商城营销 (admin) =====
  161. { title: '商城营销', icon: 'el-icon-s-goods', perm: 'commerce',
  162. children: [
  163. { path: '/product-manage', label: '商品管理', icon: 'el-icon-s-goods', perm: 'commerce:products' },
  164. { path: '/order-manage', label: '订单管理', icon: 'el-icon-s-order', perm: 'commerce:orders' },
  165. { path: '/product-profit-rate', label: '产品利润率', icon: 'el-icon-data-line', perm: 'commerce:profit' },
  166. { path: '/coupon', label: '优惠券管理', icon: 'el-icon-ticket', perm: 'marketing:coupon' },
  167. { path: '/promotion', label: '推广管理', icon: 'el-icon-s-marketing', perm: 'marketing:promotion' },
  168. { path: '/danshop-category', label: 'DanShop分类', icon: 'el-icon-menu', perm: 'danshop:category' },
  169. { path: '/danshop-profit-rule', label: 'DanShop分润', icon: 'el-icon-s-finance', perm: 'danshop:profit' },
  170. { path: '/danshop-logistics', label: 'DanShop物流', icon: 'el-icon-truck', perm: 'danshop:logistics' },
  171. ]},
  172. // ===== 6. 内容中心 (admin + article_manager + activity_manager) =====
  173. { title: '内容中心', icon: 'el-icon-document', perm: 'content',
  174. children: [
  175. { path: '/article-categories', label: '文章分类', icon: 'el-icon-folder', perm: 'articles:categories' },
  176. { path: '/article-manage', label: '文章管理', icon: 'el-icon-document', perm: 'articles:manage' },
  177. { path: '/activities', label: '活动列表', icon: 'el-icon-date', perm: 'activity:list' },
  178. { path: '/activity-review', label: '活动审核', icon: 'el-icon-document-checked', perm: 'activity:review' },
  179. ]},
  180. // ===== 7. 健康饮食 (admin + nutritionist) =====
  181. { title: '健康饮食', icon: 'el-icon-first-aid-kit', perm: 'health',
  182. children: [
  183. { path: '/health-reports', label: '健康报告', icon: 'el-icon-document', perm: 'health:reports' },
  184. { path: '/health-indicators', label: '健康指标', icon: 'el-icon-data-line', perm: 'health:indicators' },
  185. { path: '/health-checkins', label: '健康打卡', icon: 'el-icon-s-order', perm: 'health:checkins' },
  186. { path: '/nutrition-mappings', label: '营养素映射', icon: 'el-icon-connection', perm: 'health:mappings' },
  187. { path: '/energy-sandbox', label: '五维能量', icon: 'el-icon-data-line', perm: 'energy' },
  188. { path: '/foods', label: '食材管理', icon: 'el-icon-apple', perm: 'diet:foods' },
  189. { path: '/recipes', label: '食谱管理', icon: 'el-icon-dish', perm: 'diet:recipes' },
  190. { path: '/seasonal-foods', label: '应季食材', icon: 'el-icon-sunny', perm: 'diet:seasonal' },
  191. ]},
  192. // ===== 8. 规划师工作台 (teacher) =====
  193. { path: '/teacher-dashboard', label: '工作台', icon: 'el-icon-s-home', perm: 'service:*' },
  194. { title: '家庭服务', icon: 'el-icon-s-custom', perm: 'service:*',
  195. children: [
  196. { path: '/my-families', label: '我的家庭', icon: 'el-icon-s-custom', perm: 'service:family' },
  197. { path: '/teacher-team', label: '我的团队', icon: 'el-icon-s-custom', perm: 'service:team' },
  198. ]},
  199. { title: '业务管理', icon: 'el-icon-s-marketing', perm: 'biz:*',
  200. children: [
  201. { path: '/teacher-packages', label: '套餐管理', icon: 'el-icon-s-goods', perm: 'biz:packages' },
  202. { path: '/teacher-orders', label: '订单佣金', icon: 'el-icon-s-order', perm: 'biz:orders' },
  203. ]},
  204. { title: '测评咨询', icon: 'el-icon-edit', perm: 'assessment:*',
  205. children: [
  206. { path: '/teacher-assessment', label: 'DAN测评', icon: 'el-icon-edit', perm: 'assessment:dan' },
  207. { path: '/teacher-consult', label: '家长咨询', icon: 'el-icon-chat-dot-round', perm: 'assessment:consult' },
  208. ]},
  209. { path: '/growth-records', label: '成长记录', icon: 'el-icon-document', perm: 'growth:records' },
  210. { path: '/growth-plans', label: '成长计划', icon: 'el-icon-document', perm: 'growth:plans' },
  211. { path: '/teacher-messages', label: '消息中心', icon: 'el-icon-chat-dot-round', perm: 'messages' },
  212. // ===== 9. 系统配置 (admin) =====
  213. { title: '系统配置', icon: 'el-icon-s-tools', perm: 'system:*',
  214. children: [
  215. { path: '/sys-config', label: '系统配置', icon: 'el-icon-s-tools', perm: 'system:config' },
  216. { path: '/users', label: '用户管理', icon: 'el-icon-user', perm: 'system:users' },
  217. { path: '/operation-logs', label: '操作日志', icon: 'el-icon-s-order', perm: 'system:logs' },
  218. { path: '/dimension-config', label: '维度配置', icon: 'el-icon-data-line', perm: 'system:config' },
  219. { path: '/knowledge-base', label: '知识库管理', icon: 'el-icon-reading', perm: 'system:config' },
  220. { path: '/relationship-types', label: '关系类型', icon: 'el-icon-share', perm: 'system:config' },
  221. { path: '/education-systems', label: '教育体系', icon: 'el-icon-s-management', perm: 'system:education' },
  222. { path: '/service-types', label: '服务类型', icon: 'el-icon-s-management', perm: 'system:config' },
  223. { path: '/service-contents', label: '服务内容', icon: 'el-icon-s-management', perm: 'system:config' },
  224. { path: '/package-templates', label: '套餐模板', icon: 'el-icon-s-management', perm: 'system:config' },
  225. { path: '/assessment-admin', label: '测评管理', icon: 'el-icon-edit', perm: 'assessment:dan' },
  226. { path: '/health-norm-config', label: '健康常模', icon: 'el-icon-data-line', perm: 'system:config' },
  227. { path: '/health-data-source', label: '健康数据源', icon: 'el-icon-data-line', perm: 'system:config' },
  228. { path: '/zodiac-configs', label: '星座配置', icon: 'el-icon-s-management', perm: 'system:config' },
  229. { path: '/bazi-configs', label: '八字配置', icon: 'el-icon-s-management', perm: 'system:config' },
  230. { path: '/blood-type-configs', label: '血型配置', icon: 'el-icon-s-management', perm: 'system:config' },
  231. { path: '/virtual-teams', label: '虚拟团队', icon: 'el-icon-s-custom', perm: 'system:config' },
  232. ]},
  233. ],
  234. profileDialogVisible: false,
  235. passwordDialogVisible: false,
  236. submitting: false,
  237. profileForm: {
  238. nickname: '',
  239. phone: '',
  240. email: '',
  241. username: '',
  242. realName: ''
  243. },
  244. passwordForm: {
  245. oldPassword: '',
  246. newPassword: '',
  247. confirmPassword: ''
  248. },
  249. inviteCodeVisible: false,
  250. inviteCodeLoading: false,
  251. inviteCodeData: null,
  252. passwordRules: {
  253. oldPassword: [
  254. { required: true, message: '请输入原密码', trigger: 'blur' }
  255. ],
  256. newPassword: [
  257. { required: true, message: '请输入新密码', trigger: 'blur' },
  258. { min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
  259. ],
  260. confirmPassword: [
  261. { required: true, message: '请确认新密码', trigger: 'blur' },
  262. {
  263. validator: (rule, value, callback) => {
  264. if (value !== this.passwordForm.newPassword) {
  265. callback(new Error('两次输入密码不一致'))
  266. } else {
  267. callback()
  268. }
  269. },
  270. trigger: 'blur'
  271. }
  272. ]
  273. }
  274. }
  275. },
  276. computed: {
  277. activeMenu() {
  278. return this.$route.path
  279. },
  280. currentRole() {
  281. return localStorage.getItem('role') || 'admin'
  282. },
  283. vendorType() {
  284. return localStorage.getItem('vendorType') || ''
  285. },
  286. effectivePerms() {
  287. const rolesStr = localStorage.getItem('roles')
  288. const roles = rolesStr ? JSON.parse(rolesStr) : [this.currentRole]
  289. return getEffectivePermissions(roles)
  290. },
  291. displayRoles() {
  292. const rolesStr = localStorage.getItem('roles')
  293. const roles = rolesStr ? JSON.parse(rolesStr) : [this.currentRole]
  294. return roles.map(function(r) { return getRoleLabel(r) }).join(' / ')
  295. },
  296. displayUserLabel() {
  297. const adminName = localStorage.getItem('adminName')
  298. if (adminName) {
  299. return adminName + ' (' + this.displayRoles + ')'
  300. }
  301. return this.displayRoles
  302. }
  303. },
  304. methods: {
  305. hasPerm(required) {
  306. return hasPermission(this.effectivePerms, required)
  307. },
  308. handleMenuSelect(index) {
  309. if (index && index.startsWith('/')) {
  310. this.$router.push(index)
  311. }
  312. },
  313. handleProfileCommand(command) {
  314. if (command === 'profile') {
  315. this.loadAdminInfo()
  316. this.profileDialogVisible = true
  317. } else if (command === 'password') {
  318. this.passwordForm = { oldPassword: '', newPassword: '', confirmPassword: '' }
  319. this.passwordDialogVisible = true
  320. } else if (command === 'logout') {
  321. this.handleLogout()
  322. }
  323. },
  324. async loadAdminInfo() {
  325. try {
  326. const res = await getAdminInfo()
  327. this.profileForm = {
  328. nickname: res.data.nickname || '',
  329. phone: res.data.phone || '',
  330. email: res.data.email || '',
  331. username: res.data.username || '',
  332. realName: res.data.realName || ''
  333. }
  334. } catch (e) {
  335. this.$message.error('加载个人信息失败')
  336. }
  337. },
  338. async confirmUpdateInfo() {
  339. this.submitting = true
  340. try {
  341. await updateAdminInfo({
  342. nickname: this.profileForm.nickname,
  343. phone: this.profileForm.phone,
  344. email: this.profileForm.email
  345. })
  346. if (this.profileForm.nickname) {
  347. localStorage.setItem('adminName', this.profileForm.nickname)
  348. }
  349. this.$message.success('保存成功')
  350. this.profileDialogVisible = false
  351. } catch (e) {
  352. this.$message.error(e.message || '保存失败')
  353. } finally {
  354. this.submitting = false
  355. }
  356. },
  357. async confirmChangePassword() {
  358. this.$refs.passwordForm.validate(async (valid) => {
  359. if (!valid) return
  360. this.submitting = true
  361. try {
  362. await changePassword({
  363. oldPassword: this.passwordForm.oldPassword,
  364. newPassword: this.passwordForm.newPassword
  365. })
  366. this.$message.success('密码修改成功')
  367. this.passwordDialogVisible = false
  368. } catch (e) {
  369. this.$message.error(e.message || '密码修改失败')
  370. } finally {
  371. this.submitting = false
  372. }
  373. })
  374. },
  375. async showInviteCode() {
  376. this.inviteCodeVisible = true
  377. this.inviteCodeLoading = true
  378. this.inviteCodeData = null
  379. try {
  380. const res = await getMyInviteCode()
  381. if (res.data) {
  382. this.inviteCodeData = res.data
  383. }
  384. } catch (e) {
  385. this.$message.error('获取邀请码失败')
  386. } finally {
  387. this.inviteCodeLoading = false
  388. }
  389. },
  390. async generateInviteCode() {
  391. this.inviteCodeLoading = true
  392. try {
  393. const res = await getMyInviteCode()
  394. if (res.data) {
  395. this.inviteCodeData = res.data
  396. this.$message.success('邀请码已生成')
  397. }
  398. } catch (e) {
  399. this.$message.error('生成邀请码失败')
  400. } finally {
  401. this.inviteCodeLoading = false
  402. }
  403. },
  404. copyCode(code) {
  405. if (!code) return
  406. const input = document.createElement('input')
  407. input.value = code
  408. document.body.appendChild(input)
  409. input.select()
  410. document.execCommand('copy')
  411. document.body.removeChild(input)
  412. this.$message.success('已复制邀请码')
  413. },
  414. handleLogout() {
  415. this.$confirm('确定要退出登录吗?', '提示', {
  416. confirmButtonText: '确定',
  417. cancelButtonText: '取消',
  418. type: 'warning'
  419. }).then(() => {
  420. localStorage.removeItem('token')
  421. localStorage.removeItem('role')
  422. localStorage.removeItem('roles')
  423. localStorage.removeItem('adminId')
  424. localStorage.removeItem('adminName')
  425. this.$router.push('/login')
  426. })
  427. }
  428. }
  429. }
  430. </script>
  431. <style>
  432. /* 全局防御:防止 Element UI 弹窗或组件残留 DOM 产生遮罩覆盖 */
  433. .v-modal {
  434. display: none !important;
  435. }
  436. .layout-container {
  437. height: 100vh;
  438. }
  439. .el-aside {
  440. background-color: #304156;
  441. overflow: visible;
  442. display: flex;
  443. flex-direction: column;
  444. }
  445. .logo {
  446. height: 60px;
  447. line-height: 60px;
  448. text-align: center;
  449. color: #fff;
  450. font-size: 20px;
  451. font-weight: bold;
  452. background-color: #2b3a4a;
  453. flex-shrink: 0;
  454. }
  455. /* 侧边栏滚动条样式 */
  456. .sidebar-scrollbar-wrap {
  457. height: calc(100vh - 60px) !important;
  458. overflow-x: hidden !important;
  459. overflow-y: auto !important;
  460. }
  461. .sidebar-scrollbar-wrap .el-scrollbar__view {
  462. height: 100%;
  463. }
  464. .sidebar-scrollbar-wrap::-webkit-scrollbar {
  465. width: 6px;
  466. }
  467. .sidebar-scrollbar-wrap::-webkit-scrollbar-track {
  468. background: #304156;
  469. }
  470. .sidebar-scrollbar-wrap::-webkit-scrollbar-thumb {
  471. background: #4a5568;
  472. border-radius: 3px;
  473. }
  474. .sidebar-scrollbar-wrap::-webkit-scrollbar-thumb:hover {
  475. background: #5a6578;
  476. }
  477. .el-header {
  478. background-color: #fff;
  479. box-shadow: 0 1px 4px rgba(0,21,41,.08);
  480. display: flex;
  481. justify-content: flex-end;
  482. align-items: center;
  483. padding: 0 20px;
  484. }
  485. .header-right {
  486. display: flex;
  487. align-items: center;
  488. gap: 12px;
  489. }
  490. .username {
  491. margin-right: 20px;
  492. color: #606266;
  493. }
  494. .invite-code-btn {
  495. color: #6366F1;
  496. font-size: 13px;
  497. }
  498. .invite-code-btn:hover {
  499. color: #4F46E5;
  500. }
  501. .invite-code-content {
  502. padding: 8px 0;
  503. }
  504. .invite-code-section {
  505. background: #F8FAFC;
  506. border: 1px solid #E2E8F0;
  507. border-radius: 8px;
  508. padding: 20px;
  509. text-align: center;
  510. }
  511. .invite-code-label {
  512. font-size: 14px;
  513. color: #64748B;
  514. margin-bottom: 12px;
  515. }
  516. .invite-code-value {
  517. display: flex;
  518. align-items: center;
  519. justify-content: center;
  520. gap: 12px;
  521. margin-bottom: 8px;
  522. }
  523. .code-text {
  524. font-size: 28px;
  525. font-weight: 700;
  526. letter-spacing: 6px;
  527. color: #1E293B;
  528. font-family: monospace;
  529. }
  530. .no-invite-code {
  531. text-align: center;
  532. padding: 24px;
  533. color: #94A3B8;
  534. }
  535. .no-invite-code i {
  536. font-size: 48px;
  537. margin-bottom: 12px;
  538. }
  539. .no-invite-code p {
  540. margin-bottom: 16px;
  541. }
  542. .el-main {
  543. background-color: #f0f2f5;
  544. padding: 20px;
  545. position: relative;
  546. z-index: 1;
  547. }
  548. </style>