@liujitcn/kratos-admin-system 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +137 -0
  2. package/dist/package/declarations/index.d.ts +1 -0
  3. package/dist/package/declarations/src/ai.d.ts +10 -0
  4. package/dist/package/declarations/src/api/base/ai_message.d.ts +27 -0
  5. package/dist/package/declarations/src/api/base/ai_session.d.ts +17 -0
  6. package/dist/package/declarations/src/api/base/ai_tool.d.ts +7 -0
  7. package/dist/package/declarations/src/api/base/oauth.d.ts +13 -0
  8. package/dist/package/declarations/src/api/base/sse.d.ts +31 -0
  9. package/dist/package/declarations/src/api/system/auth.d.ts +17 -0
  10. package/dist/package/declarations/src/api/system/base_api.d.ts +22 -0
  11. package/dist/package/declarations/src/api/system/base_area.d.ts +19 -0
  12. package/dist/package/declarations/src/api/system/base_config.d.ts +20 -0
  13. package/dist/package/declarations/src/api/system/base_dept.d.ts +21 -0
  14. package/dist/package/declarations/src/api/system/base_dict.d.ts +32 -0
  15. package/dist/package/declarations/src/api/system/base_job.d.ts +28 -0
  16. package/dist/package/declarations/src/api/system/base_log.d.ts +9 -0
  17. package/dist/package/declarations/src/api/system/base_menu.d.ts +21 -0
  18. package/dist/package/declarations/src/api/system/base_migration.d.ts +10 -0
  19. package/dist/package/declarations/src/api/system/base_post.d.ts +21 -0
  20. package/dist/package/declarations/src/api/system/base_role.d.ts +23 -0
  21. package/dist/package/declarations/src/api/system/base_tenant.d.ts +21 -0
  22. package/dist/package/declarations/src/api/system/base_user.d.ts +23 -0
  23. package/dist/package/declarations/src/api/system/code_gen.d.ts +14 -0
  24. package/dist/package/declarations/src/api/system/code_gen_column.d.ts +12 -0
  25. package/dist/package/declarations/src/api/system/code_gen_proto.d.ts +10 -0
  26. package/dist/package/declarations/src/api/system/code_gen_sse.d.ts +8 -0
  27. package/dist/package/declarations/src/api/system/code_gen_table.d.ts +18 -0
  28. package/dist/package/declarations/src/api/system/project_document.d.ts +9 -0
  29. package/dist/package/declarations/src/components/Ai.vue.d.ts +3 -0
  30. package/dist/package/declarations/src/index.d.ts +3 -0
  31. package/dist/package/declarations/src/module.d.ts +2 -0
  32. package/dist/package/declarations/src/rpc/base/v1/ai_message.d.ts +82 -0
  33. package/dist/package/declarations/src/rpc/base/v1/ai_session.d.ts +176 -0
  34. package/dist/package/declarations/src/rpc/base/v1/ai_tool.d.ts +59 -0
  35. package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +167 -0
  36. package/dist/package/declarations/src/rpc/base/v1/sse.d.ts +13 -0
  37. package/dist/package/declarations/src/rpc/common/v1/common.d.ts +57 -0
  38. package/dist/package/declarations/src/rpc/common/v1/enum.d.ts +58 -0
  39. package/dist/package/declarations/src/rpc/common/v1/types.d.ts +60 -0
  40. package/dist/package/declarations/src/rpc/google/protobuf/empty.d.ts +11 -0
  41. package/dist/package/declarations/src/rpc/google/protobuf/timestamp.d.ts +107 -0
  42. package/dist/package/declarations/src/rpc/system/admin/v1/auth.d.ts +156 -0
  43. package/dist/package/declarations/src/rpc/system/admin/v1/base_api.d.ts +193 -0
  44. package/dist/package/declarations/src/rpc/system/admin/v1/base_area.d.ts +82 -0
  45. package/dist/package/declarations/src/rpc/system/admin/v1/base_config.d.ts +112 -0
  46. package/dist/package/declarations/src/rpc/system/admin/v1/base_dept.d.ts +112 -0
  47. package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +215 -0
  48. package/dist/package/declarations/src/rpc/system/admin/v1/base_job.d.ts +177 -0
  49. package/dist/package/declarations/src/rpc/system/admin/v1/base_log.d.ts +87 -0
  50. package/dist/package/declarations/src/rpc/system/admin/v1/base_menu.d.ts +151 -0
  51. package/dist/package/declarations/src/rpc/system/admin/v1/base_migration.d.ts +64 -0
  52. package/dist/package/declarations/src/rpc/system/admin/v1/base_post.d.ts +112 -0
  53. package/dist/package/declarations/src/rpc/system/admin/v1/base_role.d.ts +128 -0
  54. package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant.d.ts +112 -0
  55. package/dist/package/declarations/src/rpc/system/admin/v1/base_user.d.ts +161 -0
  56. package/dist/package/declarations/src/rpc/system/admin/v1/code_gen.d.ts +155 -0
  57. package/dist/package/declarations/src/rpc/system/admin/v1/code_gen_column.d.ts +143 -0
  58. package/dist/package/declarations/src/rpc/system/admin/v1/code_gen_proto.d.ts +86 -0
  59. package/dist/package/declarations/src/rpc/system/admin/v1/code_gen_table.d.ts +149 -0
  60. package/dist/package/declarations/src/rpc/system/admin/v1/project_document.d.ts +64 -0
  61. package/dist/package/declarations/src/rpc/system/common/v1/enum.d.ts +67 -0
  62. package/dist/package/declarations/src/views/ai/chat/attachment.d.ts +14 -0
  63. package/dist/package/declarations/src/views/ai/chat/components/AiMarkdown.vue.d.ts +13 -0
  64. package/dist/package/declarations/src/views/ai/chat/components/ChatPanel.vue.d.ts +45 -0
  65. package/dist/package/declarations/src/views/ai/chat/components/SessionPanel.vue.d.ts +34 -0
  66. package/dist/package/declarations/src/views/ai/chat/components/XSender.vue.d.ts +12 -0
  67. package/dist/package/declarations/src/views/ai/chat/index.vue.d.ts +4 -0
  68. package/dist/package/declarations/src/views/ai/chat/message.d.ts +59 -0
  69. package/dist/package/declarations/src/views/ai/chat/stream.d.ts +10 -0
  70. package/dist/package/declarations/src/views/ai/chat/types.d.ts +115 -0
  71. package/dist/package/declarations/src/views/base/api/index.vue.d.ts +3 -0
  72. package/dist/package/declarations/src/views/base/area/index.vue.d.ts +3 -0
  73. package/dist/package/declarations/src/views/base/config/index.vue.d.ts +3 -0
  74. package/dist/package/declarations/src/views/base/dept/index.vue.d.ts +3 -0
  75. package/dist/package/declarations/src/views/base/dict/index.vue.d.ts +3 -0
  76. package/dist/package/declarations/src/views/base/dict/item.vue.d.ts +3 -0
  77. package/dist/package/declarations/src/views/base/job/index.vue.d.ts +3 -0
  78. package/dist/package/declarations/src/views/base/job/log.vue.d.ts +3 -0
  79. package/dist/package/declarations/src/views/base/log/index.vue.d.ts +3 -0
  80. package/dist/package/declarations/src/views/base/menu/index.vue.d.ts +3 -0
  81. package/dist/package/declarations/src/views/base/migration/index.vue.d.ts +4 -0
  82. package/dist/package/declarations/src/views/base/post/index.vue.d.ts +3 -0
  83. package/dist/package/declarations/src/views/base/role/index.vue.d.ts +3 -0
  84. package/dist/package/declarations/src/views/base/tenant/index.vue.d.ts +3 -0
  85. package/dist/package/declarations/src/views/base/user/components/dept-tree.vue.d.ts +19 -0
  86. package/dist/package/declarations/src/views/base/user/index.vue.d.ts +3 -0
  87. package/dist/package/declarations/src/views/profile/components/base.vue.d.ts +13 -0
  88. package/dist/package/declarations/src/views/profile/components/password.vue.d.ts +3 -0
  89. package/dist/package/declarations/src/views/profile/components/security.vue.d.ts +15 -0
  90. package/dist/package/declarations/src/views/profile/index.vue.d.ts +3 -0
  91. package/dist/package/declarations/src/views/tool/api-doc/index.vue.d.ts +4 -0
  92. package/dist/package/declarations/src/views/tool/code-gen/code-preview/index.vue.d.ts +3 -0
  93. package/dist/package/declarations/src/views/tool/code-gen/columns/index.vue.d.ts +3 -0
  94. package/dist/package/declarations/src/views/tool/code-gen/columns/option-copy.d.ts +25 -0
  95. package/dist/package/declarations/src/views/tool/code-gen/components/CodeGenProgressDialog.vue.d.ts +16 -0
  96. package/dist/package/declarations/src/views/tool/code-gen/components/CodePreviewPane.vue.d.ts +9 -0
  97. package/dist/package/declarations/src/views/tool/code-gen/config.d.ts +32 -0
  98. package/dist/package/declarations/src/views/tool/code-gen/preview/capabilities.d.ts +20 -0
  99. package/dist/package/declarations/src/views/tool/code-gen/preview/data.d.ts +42 -0
  100. package/dist/package/declarations/src/views/tool/code-gen/preview/index.vue.d.ts +3 -0
  101. package/dist/package/declarations/src/views/tool/code-gen/proto/index.vue.d.ts +3 -0
  102. package/dist/package/declarations/src/views/tool/code-gen/table/index.vue.d.ts +3 -0
  103. package/dist/package/declarations/src/views/tool/project-doc/index.vue.d.ts +4 -0
  104. package/dist/package/src/ai.ts +16 -0
  105. package/dist/package/src/api/base/ai_message.ts +139 -0
  106. package/dist/package/src/api/base/ai_session.ts +77 -0
  107. package/dist/package/src/api/base/ai_tool.ts +22 -0
  108. package/dist/package/src/api/base/oauth.ts +47 -0
  109. package/dist/package/src/api/base/sse.ts +192 -0
  110. package/dist/package/src/api/system/auth.ts +65 -0
  111. package/dist/package/src/api/system/base_api.ts +95 -0
  112. package/dist/package/src/api/system/base_area.ts +73 -0
  113. package/dist/package/src/api/system/base_config.ts +82 -0
  114. package/dist/package/src/api/system/base_dept.ts +83 -0
  115. package/dist/package/src/api/system/base_dict.ts +144 -0
  116. package/dist/package/src/api/system/base_job.ts +125 -0
  117. package/dist/package/src/api/system/base_log.ts +32 -0
  118. package/dist/package/src/api/system/base_menu.ts +83 -0
  119. package/dist/package/src/api/system/base_migration.ts +35 -0
  120. package/dist/package/src/api/system/base_post.ts +83 -0
  121. package/dist/package/src/api/system/base_role.ts +93 -0
  122. package/dist/package/src/api/system/base_tenant.ts +83 -0
  123. package/dist/package/src/api/system/base_user.ts +93 -0
  124. package/dist/package/src/api/system/code_gen.ts +56 -0
  125. package/dist/package/src/api/system/code_gen_column.ts +43 -0
  126. package/dist/package/src/api/system/code_gen_proto.ts +32 -0
  127. package/dist/package/src/api/system/code_gen_sse.ts +33 -0
  128. package/dist/package/src/api/system/code_gen_table.ts +74 -0
  129. package/dist/package/src/api/system/project_document.ts +33 -0
  130. package/dist/package/src/components/Ai.vue +57 -0
  131. package/dist/package/src/index.ts +3 -0
  132. package/dist/package/src/module.ts +17 -0
  133. package/dist/package/src/rpc/base/v1/ai_message.ts +98 -0
  134. package/dist/package/src/rpc/base/v1/ai_session.ts +214 -0
  135. package/dist/package/src/rpc/base/v1/ai_tool.ts +74 -0
  136. package/dist/package/src/rpc/base/v1/oauth.ts +199 -0
  137. package/dist/package/src/rpc/base/v1/sse.ts +22 -0
  138. package/dist/package/src/rpc/common/v1/common.ts +71 -0
  139. package/dist/package/src/rpc/common/v1/enum.ts +71 -0
  140. package/dist/package/src/rpc/common/v1/types.ts +77 -0
  141. package/dist/package/src/rpc/google/protobuf/empty.ts +19 -0
  142. package/dist/package/src/rpc/google/protobuf/timestamp.ts +115 -0
  143. package/dist/package/src/rpc/system/admin/v1/auth.ts +208 -0
  144. package/dist/package/src/rpc/system/admin/v1/base_api.ts +241 -0
  145. package/dist/package/src/rpc/system/admin/v1/base_area.ts +105 -0
  146. package/dist/package/src/rpc/system/admin/v1/base_config.ts +140 -0
  147. package/dist/package/src/rpc/system/admin/v1/base_dept.ts +140 -0
  148. package/dist/package/src/rpc/system/admin/v1/base_dict.ts +249 -0
  149. package/dist/package/src/rpc/system/admin/v1/base_job.ts +206 -0
  150. package/dist/package/src/rpc/system/admin/v1/base_log.ts +101 -0
  151. package/dist/package/src/rpc/system/admin/v1/base_menu.ts +195 -0
  152. package/dist/package/src/rpc/system/admin/v1/base_migration.ts +81 -0
  153. package/dist/package/src/rpc/system/admin/v1/base_post.ts +134 -0
  154. package/dist/package/src/rpc/system/admin/v1/base_role.ts +151 -0
  155. package/dist/package/src/rpc/system/admin/v1/base_tenant.ts +134 -0
  156. package/dist/package/src/rpc/system/admin/v1/base_user.ts +190 -0
  157. package/dist/package/src/rpc/system/admin/v1/code_gen.ts +176 -0
  158. package/dist/package/src/rpc/system/admin/v1/code_gen_column.ts +170 -0
  159. package/dist/package/src/rpc/system/admin/v1/code_gen_proto.ts +104 -0
  160. package/dist/package/src/rpc/system/admin/v1/code_gen_table.ts +179 -0
  161. package/dist/package/src/rpc/system/admin/v1/project_document.ts +79 -0
  162. package/dist/package/src/rpc/system/common/v1/enum.ts +80 -0
  163. package/dist/package/src/typings/swagger-ui-dist.d.ts +28 -0
  164. package/dist/package/src/typings/x-markdown-vue.d.ts +1 -0
  165. package/dist/package/src/views/ai/chat/attachment.ts +65 -0
  166. package/dist/package/src/views/ai/chat/components/AiMarkdown.vue +162 -0
  167. package/dist/package/src/views/ai/chat/components/ChatPanel.vue +1861 -0
  168. package/dist/package/src/views/ai/chat/components/SessionPanel.vue +316 -0
  169. package/dist/package/src/views/ai/chat/components/XSender.vue +554 -0
  170. package/dist/package/src/views/ai/chat/index.vue +848 -0
  171. package/dist/package/src/views/ai/chat/message.ts +416 -0
  172. package/dist/package/src/views/ai/chat/stream.ts +115 -0
  173. package/dist/package/src/views/ai/chat/types.ts +126 -0
  174. package/dist/package/src/views/base/api/index.vue +455 -0
  175. package/dist/package/src/views/base/area/index.vue +269 -0
  176. package/dist/package/src/views/base/config/index.vue +530 -0
  177. package/dist/package/src/views/base/dept/index.vue +461 -0
  178. package/dist/package/src/views/base/dict/index.vue +304 -0
  179. package/dist/package/src/views/base/dict/item.vue +373 -0
  180. package/dist/package/src/views/base/job/index.vue +535 -0
  181. package/dist/package/src/views/base/job/log.vue +202 -0
  182. package/dist/package/src/views/base/log/index.vue +317 -0
  183. package/dist/package/src/views/base/menu/index.vue +944 -0
  184. package/dist/package/src/views/base/migration/index.vue +791 -0
  185. package/dist/package/src/views/base/post/index.vue +324 -0
  186. package/dist/package/src/views/base/role/index.vue +684 -0
  187. package/dist/package/src/views/base/tenant/index.vue +328 -0
  188. package/dist/package/src/views/base/user/components/dept-tree.vue +75 -0
  189. package/dist/package/src/views/base/user/index.vue +783 -0
  190. package/dist/package/src/views/profile/components/base.vue +320 -0
  191. package/dist/package/src/views/profile/components/password.vue +269 -0
  192. package/dist/package/src/views/profile/components/security.vue +443 -0
  193. package/dist/package/src/views/profile/index.vue +199 -0
  194. package/dist/package/src/views/tool/api-doc/index.vue +275 -0
  195. package/dist/package/src/views/tool/code-gen/code-preview/index.vue +233 -0
  196. package/dist/package/src/views/tool/code-gen/columns/index.vue +1456 -0
  197. package/dist/package/src/views/tool/code-gen/columns/option-copy.ts +125 -0
  198. package/dist/package/src/views/tool/code-gen/components/CodeGenProgressDialog.vue +328 -0
  199. package/dist/package/src/views/tool/code-gen/components/CodePreviewPane.vue +42 -0
  200. package/dist/package/src/views/tool/code-gen/config.ts +175 -0
  201. package/dist/package/src/views/tool/code-gen/preview/capabilities.ts +34 -0
  202. package/dist/package/src/views/tool/code-gen/preview/data.ts +331 -0
  203. package/dist/package/src/views/tool/code-gen/preview/index.vue +831 -0
  204. package/dist/package/src/views/tool/code-gen/proto/index.vue +703 -0
  205. package/dist/package/src/views/tool/code-gen/table/index.vue +864 -0
  206. package/dist/package/src/views/tool/project-doc/index.vue +561 -0
  207. package/package.json +60 -0
  208. package/tsconfig.json +13 -0
@@ -0,0 +1,443 @@
1
+ <template>
2
+ <div class="profile-security">
3
+ <el-card class="security-card" shadow="never">
4
+ <div class="security-intro">
5
+ <div>
6
+ <h3>安全设置</h3>
7
+ <p>管理登录密码、手机验证和账号完整度。</p>
8
+ </div>
9
+ <el-tag effect="plain" type="success">账号状态正常</el-tag>
10
+ </div>
11
+ <div class="security-list">
12
+ <div class="security-item">
13
+ <div class="security-item__content">
14
+ <strong>登录密码</strong>
15
+ <p>建议定期更新,避免长期使用同一密码。</p>
16
+ </div>
17
+ <el-button type="primary" plain @click="emit('switchTab', 'password')">前往修改</el-button>
18
+ </div>
19
+ <div class="security-item">
20
+ <div class="security-item__content">
21
+ <strong>绑定手机</strong>
22
+ <p>{{ mobileTip }}</p>
23
+ </div>
24
+ <el-button plain @click="openPhoneDialog">{{ profile.phone ? "更换手机号" : "立即绑定" }}</el-button>
25
+ </div>
26
+ <div v-for="item in oauthBindings" :key="item.provider" class="security-item">
27
+ <div class="security-item__content security-item__content--oauth">
28
+ <el-tooltip :content="item.name" placement="top" :trigger="['hover', 'focus']">
29
+ <span class="oauth-icon" :aria-label="item.name" :title="item.name">
30
+ <component :is="getOauthProviderIcon(item)" />
31
+ </span>
32
+ </el-tooltip>
33
+ <div>
34
+ <strong>{{ item.name }}</strong>
35
+ <p>{{ item.bound ? "已绑定,可用于登录管理后台。" : "未绑定,绑定成功后可用于登录管理后台。" }}</p>
36
+ </div>
37
+ </div>
38
+ <el-button
39
+ v-if="item.bound"
40
+ plain
41
+ type="danger"
42
+ :loading="oauthLoadingProvider === item.provider"
43
+ @click="handleUnbindOauth(item)"
44
+ >
45
+ 解绑
46
+ </el-button>
47
+ <el-button v-else plain :loading="oauthLoadingProvider === item.provider" @click="handleBindOauth(item)"
48
+ >绑定</el-button
49
+ >
50
+ </div>
51
+ </div>
52
+ </el-card>
53
+
54
+ <el-card class="status-card" shadow="never">
55
+ <template #header>
56
+ <div class="status-header">
57
+ <div>
58
+ <h3>账号状态</h3>
59
+ <p>查看当前账号验证状态与资料完整度。</p>
60
+ </div>
61
+ </div>
62
+ </template>
63
+ <div class="status-grid">
64
+ <div class="status-item">
65
+ <span>手机验证</span>
66
+ <strong>{{ profile.phone ? "已启用" : "未启用" }}</strong>
67
+ </div>
68
+ <div class="status-item">
69
+ <span>资料完整度</span>
70
+ <strong>{{ profileCompletion }}</strong>
71
+ </div>
72
+ </div>
73
+ </el-card>
74
+
75
+ <ProDialog v-model="phoneDialogVisible" title="绑定手机" :width="520" @closed="handleDialogClosed">
76
+ <ProForm ref="phoneFormRef" :model="phoneForm" :fields="phoneFormFields" :rules="phoneFormRules" label-width="96px">
77
+ <template #mobileCodeInput>
78
+ <el-input v-model="phoneForm.code" placeholder="请输入验证码">
79
+ <template #append>
80
+ <el-button :disabled="countdown > 0" @click="handleSendCode">
81
+ {{ countdown > 0 ? `${countdown}s后重试` : "发送验证码" }}
82
+ </el-button>
83
+ </template>
84
+ </el-input>
85
+ </template>
86
+ </ProForm>
87
+ <template #footer>
88
+ <div class="dialog-footer">
89
+ <el-button @click="phoneDialogVisible = false">取消</el-button>
90
+ <el-button type="primary" :loading="submitLoading" @click="handleSubmitPhone">保存</el-button>
91
+ </div>
92
+ </template>
93
+ </ProDialog>
94
+ </div>
95
+ </template>
96
+
97
+ <script setup lang="ts">
98
+ import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
99
+ import { useRoute, useRouter } from "vue-router";
100
+ import { defProfileAuthService } from "@liujitcn/kratos-admin-system/api/system/auth";
101
+ import { defProfileOauthService } from "@liujitcn/kratos-admin-system/api/base/oauth";
102
+ import type { OauthBinding } from "@liujitcn/kratos-admin-system/rpc/base/v1/oauth";
103
+ import type {
104
+ SendPhoneCodeRequest,
105
+ UserPhoneForm,
106
+ UserProfileForm
107
+ } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/auth";
108
+ import ProDialog from "@liujitcn/kratos-admin-core/components/Dialog/ProDialog.vue";
109
+ import ProForm from "@liujitcn/kratos-admin-core/components/ProForm/index.vue";
110
+ import type { ProFormField, ProFormInstance } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
111
+ import {
112
+ getOauthProviderIcon,
113
+ withOauthProviderDisplay,
114
+ type OauthProviderDisplay
115
+ } from "@liujitcn/kratos-admin-core/security";
116
+ import { resolveFrontendRouteURL } from "@liujitcn/kratos-admin-core/navigation";
117
+ import { ElMessage, ElMessageBox } from "element-plus";
118
+
119
+ /** 安全中心组件属性。 */
120
+ interface ProfileSecurityProps {
121
+ /** 当前用户资料。 */
122
+ profile: UserProfileForm;
123
+ }
124
+
125
+ const props = defineProps<ProfileSecurityProps>();
126
+
127
+ const emit = defineEmits<{
128
+ refreshed: [];
129
+ switchTab: [tab: "account" | "security" | "password"];
130
+ }>();
131
+
132
+ const route = useRoute();
133
+ const router = useRouter();
134
+ const phoneFormRef = ref<ProFormInstance>();
135
+ const phoneDialogVisible = ref(false);
136
+ const submitLoading = ref(false);
137
+ /** 安全中心三方账号绑定展示项。 */
138
+ type SecurityOauthBinding = OauthBinding & OauthProviderDisplay;
139
+
140
+ const oauthBindings = ref<SecurityOauthBinding[]>([]);
141
+ const oauthLoadingProvider = ref("");
142
+ const countdown = ref(0);
143
+ const phoneTimer = ref<number | null>(null);
144
+ const phoneForm = reactive<UserPhoneForm>({
145
+ phone: "",
146
+ code: ""
147
+ });
148
+ const sendPhoneCodeForm = reactive<SendPhoneCodeRequest>({
149
+ phone: ""
150
+ });
151
+
152
+ const phoneFormFields: ProFormField[] = [
153
+ { prop: "phone", label: "手机号码", component: "input", props: { placeholder: "请输入手机号" } },
154
+ { prop: "code", label: "验证码", component: "slot", slotName: "mobileCodeInput" }
155
+ ];
156
+
157
+ const phoneFormRules = {
158
+ phone: [
159
+ { required: true, message: "请输入手机号", trigger: "blur" },
160
+ { pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号码", trigger: "blur" }
161
+ ],
162
+ code: [{ required: true, message: "请输入验证码", trigger: "blur" }]
163
+ };
164
+
165
+ /** 根据当前绑定状态输出手机号说明文案。 */
166
+ const mobileTip = computed(() => {
167
+ return props.profile.phone ? `已绑定:${props.profile.phone}` : "当前未绑定手机。";
168
+ });
169
+
170
+ /** 根据关键资料估算当前资料完成度。 */
171
+ const profileCompletion = computed(() => {
172
+ const fieldList = [props.profile.nick_name, props.profile.phone, props.profile.role_name, props.profile.dept_name];
173
+ const completedCount = fieldList.filter(item => Boolean(item)).length;
174
+ return `${Math.round((completedCount / fieldList.length) * 100)}%`;
175
+ });
176
+
177
+ /** 获取当前安全设置页绝对地址,供 OAuth 绑定完成后回跳到前端页面。 */
178
+ function getCurrentSecurityPath() {
179
+ const query = { ...route.query };
180
+ delete query.oauth_bind_provider;
181
+ delete query.oauth_bind_success;
182
+ delete query.oauth_bind_error;
183
+ return resolveFrontendRouteURL(router, { path: route.path, query });
184
+ }
185
+
186
+ /** 拉取当前用户三方账号绑定状态。 */
187
+ async function loadOauthBindings() {
188
+ const result = await defProfileOauthService.ListOauthBinding({});
189
+ oauthBindings.value = result.bindings.map(withOauthProviderDisplay);
190
+ }
191
+
192
+ /** 处理 OAuth 绑定回跳结果。 */
193
+ async function consumeOauthBindingResult() {
194
+ const bindError = route.query.oauth_bind_error;
195
+ const bindSuccess = route.query.oauth_bind_success;
196
+ if (typeof bindError === "string" && bindError) {
197
+ ElMessage.error(bindError);
198
+ } else if (bindSuccess === "1") {
199
+ ElMessage.success("三方账号绑定成功");
200
+ } else {
201
+ return;
202
+ }
203
+ await router.replace({
204
+ path: route.path,
205
+ query: {
206
+ ...route.query,
207
+ oauth_bind_provider: undefined,
208
+ oauth_bind_success: undefined,
209
+ oauth_bind_error: undefined
210
+ }
211
+ });
212
+ }
213
+
214
+ /** 发起三方账号绑定授权。 */
215
+ async function handleBindOauth(binding: SecurityOauthBinding) {
216
+ if (oauthLoadingProvider.value) return;
217
+ oauthLoadingProvider.value = binding.provider;
218
+ try {
219
+ const result = await defProfileOauthService.CreateOauthBindingAuthorization({
220
+ provider: binding.provider,
221
+ redirect_url: getCurrentSecurityPath()
222
+ });
223
+ if (result.authorization_url) {
224
+ window.location.href = result.authorization_url;
225
+ }
226
+ } finally {
227
+ oauthLoadingProvider.value = "";
228
+ }
229
+ }
230
+
231
+ /** 解绑三方账号并刷新绑定状态。 */
232
+ async function handleUnbindOauth(binding: SecurityOauthBinding) {
233
+ await ElMessageBox.confirm(`是否确定解绑该登录方式?\n登录方式:${binding.name}`, "提示", {
234
+ type: "warning",
235
+ confirmButtonText: "解绑",
236
+ cancelButtonText: "取消"
237
+ });
238
+ oauthLoadingProvider.value = binding.provider;
239
+ try {
240
+ await defProfileOauthService.UnbindOauthAccount({ provider: binding.provider });
241
+ ElMessage.success("三方账号已解绑");
242
+ await loadOauthBindings();
243
+ } finally {
244
+ oauthLoadingProvider.value = "";
245
+ }
246
+ }
247
+
248
+ /** 打开手机号绑定弹窗,并回填当前手机号。 */
249
+ function openPhoneDialog() {
250
+ phoneForm.phone = props.profile.phone;
251
+ phoneForm.code = "";
252
+ phoneDialogVisible.value = true;
253
+ }
254
+
255
+ /** 发送手机验证码并启动倒计时。 */
256
+ async function handleSendCode() {
257
+ if (!phoneForm.phone) {
258
+ ElMessage.error("请输入手机号");
259
+ return;
260
+ }
261
+ if (!/^1[3-9]\d{9}$/.test(phoneForm.phone)) {
262
+ ElMessage.error("手机号格式不正确");
263
+ return;
264
+ }
265
+
266
+ sendPhoneCodeForm.phone = phoneForm.phone;
267
+ await defProfileAuthService.SendPhoneCode(sendPhoneCodeForm);
268
+ ElMessage.success("验证码已发送");
269
+ startCountdown();
270
+ }
271
+
272
+ /** 提交绑定手机号请求。 */
273
+ async function handleSubmitPhone() {
274
+ if (!(await phoneFormRef.value?.validate())) return;
275
+
276
+ submitLoading.value = true;
277
+ try {
278
+ await defProfileAuthService.UpdateUserPhone({ user_phone: phoneForm });
279
+ ElMessage.success("手机号更新成功");
280
+ phoneDialogVisible.value = false;
281
+ emit("refreshed");
282
+ } finally {
283
+ submitLoading.value = false;
284
+ }
285
+ }
286
+
287
+ /** 启动验证码倒计时,并在重复发送前进行限制。 */
288
+ function startCountdown() {
289
+ clearCountdown();
290
+ countdown.value = 60;
291
+ phoneTimer.value = window.setInterval(() => {
292
+ if (countdown.value <= 1) {
293
+ clearCountdown();
294
+ return;
295
+ }
296
+ countdown.value -= 1;
297
+ }, 1000);
298
+ }
299
+
300
+ /** 清理验证码倒计时。 */
301
+ function clearCountdown() {
302
+ if (phoneTimer.value !== null) {
303
+ window.clearInterval(phoneTimer.value);
304
+ phoneTimer.value = null;
305
+ }
306
+ countdown.value = 0;
307
+ }
308
+
309
+ /** 弹窗关闭后重置临时表单状态。 */
310
+ function handleDialogClosed() {
311
+ phoneFormRef.value?.resetFields();
312
+ phoneFormRef.value?.clearValidate();
313
+ phoneForm.phone = props.profile.phone;
314
+ phoneForm.code = "";
315
+ }
316
+
317
+ onBeforeUnmount(() => {
318
+ clearCountdown();
319
+ });
320
+
321
+ onMounted(async () => {
322
+ await consumeOauthBindingResult();
323
+ await loadOauthBindings();
324
+ });
325
+ </script>
326
+
327
+ <style scoped lang="scss">
328
+ .profile-security {
329
+ display: flex;
330
+ flex-direction: column;
331
+ gap: 20px;
332
+ }
333
+ .security-card,
334
+ .status-card {
335
+ border: 1px solid #ebeef5;
336
+ border-radius: 12px;
337
+ }
338
+ :deep(.security-card .el-card__body),
339
+ :deep(.status-card .el-card__body) {
340
+ padding: 20px;
341
+ }
342
+ :deep(.status-card .el-card__header) {
343
+ padding: 18px 20px;
344
+ border-bottom: 1px solid #f0f2f5;
345
+ }
346
+ .security-intro,
347
+ .status-header {
348
+ display: flex;
349
+ gap: 16px;
350
+ align-items: flex-start;
351
+ justify-content: space-between;
352
+ }
353
+ .security-intro h3,
354
+ .status-header h3 {
355
+ margin: 0;
356
+ font-size: 18px;
357
+ color: #303133;
358
+ }
359
+ .security-intro p,
360
+ .status-header p {
361
+ margin: 6px 0 0;
362
+ font-size: 13px;
363
+ color: #909399;
364
+ }
365
+ .security-list {
366
+ display: flex;
367
+ flex-direction: column;
368
+ gap: 12px;
369
+ margin-top: 16px;
370
+ }
371
+ .security-item,
372
+ .status-item {
373
+ display: flex;
374
+ gap: 18px;
375
+ align-items: center;
376
+ justify-content: space-between;
377
+ padding: 16px;
378
+ background: #ffffff;
379
+ border: 1px solid #f0f2f5;
380
+ border-radius: 10px;
381
+ }
382
+ .security-item__content {
383
+ min-width: 0;
384
+ }
385
+ .security-item__content--oauth {
386
+ display: flex;
387
+ gap: 12px;
388
+ align-items: center;
389
+ }
390
+ .oauth-icon {
391
+ display: inline-flex;
392
+ flex: 0 0 36px;
393
+ align-items: center;
394
+ justify-content: center;
395
+ width: 36px;
396
+ height: 36px;
397
+ background: transparent;
398
+ border-radius: 50%;
399
+
400
+ svg,
401
+ img {
402
+ width: 28px;
403
+ height: 28px;
404
+ object-fit: contain;
405
+ }
406
+ }
407
+ .security-item strong,
408
+ .status-item strong {
409
+ display: block;
410
+ margin-bottom: 6px;
411
+ font-size: 15px;
412
+ color: #303133;
413
+ }
414
+ .security-item p,
415
+ .status-item span {
416
+ margin: 0;
417
+ font-size: 13px;
418
+ line-height: 1.6;
419
+ color: #909399;
420
+ }
421
+ .status-grid {
422
+ display: grid;
423
+ grid-template-columns: repeat(2, minmax(0, 1fr));
424
+ gap: 12px;
425
+ }
426
+ .dialog-footer {
427
+ display: flex;
428
+ justify-content: flex-end;
429
+ }
430
+
431
+ @media screen and (width <= 768px) {
432
+ .security-intro,
433
+ .status-header,
434
+ .security-item,
435
+ .status-item {
436
+ flex-direction: column;
437
+ align-items: flex-start;
438
+ }
439
+ .status-grid {
440
+ grid-template-columns: 1fr;
441
+ }
442
+ }
443
+ </style>
@@ -0,0 +1,199 @@
1
+ <template>
2
+ <div class="profile-page">
3
+ <section class="profile-shell">
4
+ <aside class="profile-nav">
5
+ <button
6
+ v-for="tab in profileTabs"
7
+ :key="tab.value"
8
+ type="button"
9
+ class="nav-item"
10
+ :class="{ 'nav-item--active': activeTab === tab.value }"
11
+ @click="handleTabChange(tab.value)"
12
+ >
13
+ <div class="nav-item__content">
14
+ <strong>{{ tab.label }}</strong>
15
+ <span>{{ tab.description }}</span>
16
+ </div>
17
+ <el-icon><ArrowRight /></el-icon>
18
+ </button>
19
+ </aside>
20
+
21
+ <main class="profile-content">
22
+ <ProfileBase v-if="activeTab === 'account'" :profile="userProfileForm" @refreshed="loadUserProfile" />
23
+ <ProfileSecurity
24
+ v-else-if="activeTab === 'security'"
25
+ :profile="userProfileForm"
26
+ @refreshed="loadUserProfile"
27
+ @switch-tab="handleTabChange"
28
+ />
29
+ <ProfilePassword v-else />
30
+ </main>
31
+ </section>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup lang="ts">
36
+ defineOptions({
37
+ name: "Profile",
38
+ inheritAttrs: false
39
+ });
40
+
41
+ import { onMounted, reactive, ref } from "vue";
42
+ import { useRoute } from "vue-router";
43
+ import { defProfileAuthService } from "@liujitcn/kratos-admin-system/api/system/auth";
44
+ import type { UserProfileForm } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/auth";
45
+ import { useUserStore } from "@liujitcn/kratos-admin-core/stores/runtime";
46
+ import ProfileBase from "./components/base.vue";
47
+ import ProfileSecurity from "./components/security.vue";
48
+ import ProfilePassword from "./components/password.vue";
49
+ import { ArrowRight } from "@element-plus/icons-vue";
50
+
51
+ /** 个人中心标签页。 */
52
+ type ProfileTab = "account" | "security" | "password";
53
+
54
+ /** 左侧导航项结构。 */
55
+ interface ProfileTabOption {
56
+ /** 标签值。 */
57
+ value: ProfileTab;
58
+ /** 导航标题。 */
59
+ label: string;
60
+ /** 导航描述。 */
61
+ description: string;
62
+ }
63
+
64
+ const userStore = useUserStore();
65
+ const route = useRoute();
66
+ const activeTab = ref<ProfileTab>("account");
67
+ const profileTabs: ProfileTabOption[] = [
68
+ {
69
+ value: "account",
70
+ label: "账号信息",
71
+ description: "维护头像和资料"
72
+ },
73
+ {
74
+ value: "security",
75
+ label: "安全设置",
76
+ description: "管理验证与安全"
77
+ },
78
+ {
79
+ value: "password",
80
+ label: "修改密码",
81
+ description: "更新登录密码"
82
+ }
83
+ ];
84
+ const userProfileForm = reactive<UserProfileForm>({
85
+ user_name: "",
86
+ nick_name: "",
87
+ avatar: "",
88
+ gender: 3,
89
+ phone: "",
90
+ role_name: "",
91
+ dept_name: "",
92
+ created_at: ""
93
+ });
94
+
95
+ /** 切换当前显示标签,仅更新本地视图状态,不触发路由变化。 */
96
+ function handleTabChange(tab: ProfileTab) {
97
+ activeTab.value = tab;
98
+ }
99
+
100
+ /** 拉取当前登录用户的个人中心资料。 */
101
+ async function loadUserProfile() {
102
+ const profile = await defProfileAuthService.GetUserProfile({});
103
+ Object.assign(userProfileForm, profile);
104
+ // 个人中心资料更新后,同步刷新头部头像和昵称展示,避免页面内外信息不一致。
105
+ userStore.setUserInfo({
106
+ ...userStore.userInfo,
107
+ user_name: profile.user_name,
108
+ nick_name: profile.nick_name,
109
+ phone: profile.phone,
110
+ avatar: profile.avatar,
111
+ role_name: profile.role_name,
112
+ dept_name: profile.dept_name
113
+ });
114
+ }
115
+
116
+ onMounted(async () => {
117
+ // OAuth 绑定回跳时优先打开安全设置,让子组件消费绑定结果并刷新状态。
118
+ if (route.query.oauth_bind_success || route.query.oauth_bind_error) {
119
+ activeTab.value = "security";
120
+ }
121
+ await loadUserProfile();
122
+ });
123
+ </script>
124
+
125
+ <style scoped lang="scss">
126
+ .profile-page {
127
+ padding: 20px;
128
+ }
129
+ .profile-shell {
130
+ display: grid;
131
+ grid-template-columns: 260px minmax(0, 1fr);
132
+ gap: 16px;
133
+ align-items: start;
134
+ }
135
+ .profile-nav {
136
+ position: sticky;
137
+ top: 20px;
138
+ padding: 16px;
139
+ background: #ffffff;
140
+ border: 1px solid #ebeef5;
141
+ border-radius: 12px;
142
+ box-shadow: 0 2px 12px rgb(0 0 0 / 4%);
143
+ }
144
+ .nav-item {
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: space-between;
148
+ width: 100%;
149
+ padding: 14px 12px;
150
+ color: #606266;
151
+ text-align: left;
152
+ cursor: pointer;
153
+ background: #ffffff;
154
+ border: 1px solid transparent;
155
+ border-radius: 10px;
156
+ transition: all 0.2s ease;
157
+ }
158
+ .nav-item + .nav-item {
159
+ margin-top: 8px;
160
+ }
161
+ .nav-item__content {
162
+ display: flex;
163
+ flex-direction: column;
164
+ gap: 4px;
165
+ }
166
+ .nav-item strong {
167
+ display: block;
168
+ font-size: 14px;
169
+ font-weight: 600;
170
+ }
171
+ .nav-item span {
172
+ font-size: 12px;
173
+ color: #909399;
174
+ }
175
+ .nav-item:hover,
176
+ .nav-item--active {
177
+ color: #409eff;
178
+ background: #f5f9ff;
179
+ border-color: #d9ecff;
180
+ }
181
+ .profile-content {
182
+ min-width: 0;
183
+ }
184
+
185
+ @media screen and (width <= 1080px) {
186
+ .profile-shell {
187
+ grid-template-columns: 1fr;
188
+ }
189
+ .profile-nav {
190
+ position: static;
191
+ }
192
+ }
193
+
194
+ @media screen and (width <= 640px) {
195
+ .profile-page {
196
+ padding: 16px;
197
+ }
198
+ }
199
+ </style>