@befly-addon/admin 1.0.55 → 1.0.57

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 (61) hide show
  1. package/apis/admin/ins.ts +11 -15
  2. package/apis/admin/upd.ts +1 -13
  3. package/apis/auth/login.ts +49 -5
  4. package/apis/email/config.ts +16 -0
  5. package/apis/email/logList.ts +13 -0
  6. package/apis/email/send.ts +42 -0
  7. package/apis/email/verify.ts +12 -0
  8. package/apis/loginLog/list.ts +13 -0
  9. package/apis/operateLog/list.ts +13 -0
  10. package/apis/sysConfig/all.ts +12 -0
  11. package/apis/sysConfig/del.ts +30 -0
  12. package/apis/sysConfig/get.ts +31 -0
  13. package/apis/sysConfig/ins.ts +38 -0
  14. package/apis/sysConfig/list.ts +14 -0
  15. package/apis/sysConfig/upd.ts +50 -0
  16. package/package.json +14 -3
  17. package/plugins/email.ts +206 -0
  18. package/styles/variables.scss +121 -60
  19. package/tables/admin.json +12 -15
  20. package/tables/emailLog.json +69 -0
  21. package/tables/loginLog.json +96 -0
  22. package/tables/operateLog.json +82 -0
  23. package/tables/role.json +1 -1
  24. package/tables/sysConfig.json +53 -0
  25. package/views/403_1/meta.json +4 -0
  26. package/views/{dict → config/dict}/index.vue +27 -38
  27. package/views/config/dict/meta.json +4 -0
  28. package/views/config/meta.json +4 -0
  29. package/views/config/system/components/edit.vue +179 -0
  30. package/views/config/system/index.vue +256 -0
  31. package/views/config/system/meta.json +4 -0
  32. package/views/index/index.vue +46 -9
  33. package/views/index/meta.json +4 -0
  34. package/views/log/email/index.vue +285 -0
  35. package/views/log/email/meta.json +4 -0
  36. package/views/log/login/index.vue +180 -0
  37. package/views/log/login/meta.json +4 -0
  38. package/views/log/meta.json +4 -0
  39. package/views/log/operate/index.vue +242 -0
  40. package/views/log/operate/meta.json +4 -0
  41. package/views/login_1/meta.json +4 -0
  42. package/views/{admin → people/admin}/components/edit.vue +22 -24
  43. package/views/{admin → people/admin}/index.vue +21 -61
  44. package/views/people/admin/meta.json +4 -0
  45. package/views/people/meta.json +4 -0
  46. package/views/{api → permission/api}/index.vue +17 -55
  47. package/views/permission/api/meta.json +4 -0
  48. package/views/{menu → permission/menu}/index.vue +17 -56
  49. package/views/permission/menu/meta.json +4 -0
  50. package/views/permission/meta.json +4 -0
  51. package/views/{role → permission/role}/components/api.vue +13 -38
  52. package/views/{role → permission/role}/components/edit.vue +9 -8
  53. package/views/{role → permission/role}/components/menu.vue +2 -25
  54. package/views/{role → permission/role}/index.vue +27 -36
  55. package/views/permission/role/meta.json +4 -0
  56. /package/views/{403 → 403_1}/index.vue +0 -0
  57. /package/views/{dict → config/dict}/components/edit.vue +0 -0
  58. /package/views/{login → login_1}/components/emailLoginForm.vue +0 -0
  59. /package/views/{login → login_1}/components/registerForm.vue +0 -0
  60. /package/views/{login → login_1}/components/welcomePanel.vue +0 -0
  61. /package/views/{login/index_1.vue → login_1/index.vue} +0 -0
@@ -0,0 +1,256 @@
1
+ <template>
2
+ <div class="page-sys-config page-table">
3
+ <div class="main-tool">
4
+ <div class="left">
5
+ <TButton theme="primary" @click="$Method.onAction('add', {})">
6
+ <template #icon>
7
+ <ILucidePlus />
8
+ </template>
9
+ 新增配置
10
+ </TButton>
11
+ <TSelect v-model="$Data.filter.group" placeholder="配置分组" clearable style="width: 150px" @change="$Method.handleFilter">
12
+ <TOption v-for="item in $Data.groupOptions" :key="item" :label="item" :value="item" />
13
+ </TSelect>
14
+ </div>
15
+ <div class="right">
16
+ <TButton shape="circle" @click="$Method.handleRefresh">
17
+ <template #icon>
18
+ <ILucideRotateCw />
19
+ </template>
20
+ </TButton>
21
+ </div>
22
+ </div>
23
+
24
+ <div class="main-content">
25
+ <div class="main-table">
26
+ <TTable :data="$Data.tableData" :columns="$Data.columns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" row-key="id" height="100%" active-row-type="single" @active-change="$Method.onActiveChange">
27
+ <template #isSystem="{ row }">
28
+ <TTag v-if="row.isSystem === 1" shape="round" theme="warning" variant="light-outline">系统</TTag>
29
+ <TTag v-else shape="round" variant="light-outline">自定义</TTag>
30
+ </template>
31
+ <template #valueType="{ row }">
32
+ <TTag shape="round" variant="light-outline">{{ row.valueType }}</TTag>
33
+ </template>
34
+ <template #state="{ row }">
35
+ <TTag v-if="row.state === 1" shape="round" theme="success" variant="light-outline">正常</TTag>
36
+ <TTag v-else-if="row.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
37
+ </template>
38
+ <template #operation="{ row }">
39
+ <TDropdown trigger="click" placement="bottom-right" @click="(data) => $Method.onAction(data.value, row)">
40
+ <TButton theme="primary" size="small">
41
+ 操作
42
+ <template #suffix><t-icon name="chevron-down" size="16" /></template>
43
+ </TButton>
44
+ <TDropdownMenu slot="dropdown">
45
+ <TDropdownItem value="upd">
46
+ <ILucidePencil />
47
+ 编辑
48
+ </TDropdownItem>
49
+ <TDropdownItem v-if="row.isSystem !== 1" value="del" :divider="true">
50
+ <ILucideTrash2 style="width: 14px; height: 14px; margin-right: 6px" />
51
+ 删除
52
+ </TDropdownItem>
53
+ </TDropdownMenu>
54
+ </TDropdown>
55
+ </template>
56
+ </TTable>
57
+ </div>
58
+
59
+ <div class="main-detail">
60
+ <DetailPanel :data="$Data.currentRow" :fields="$Data.detailFields">
61
+ <template #isSystem="{ value }">
62
+ <TTag v-if="value === 1" shape="round" theme="warning" variant="light-outline">系统配置</TTag>
63
+ <TTag v-else shape="round" variant="light-outline">自定义配置</TTag>
64
+ </template>
65
+ <template #valueType="{ value }">
66
+ <TTag shape="round" variant="light-outline">{{ value }}</TTag>
67
+ </template>
68
+ <template #value="{ value }">
69
+ <pre class="config-value">{{ value }}</pre>
70
+ </template>
71
+ </DetailPanel>
72
+ </div>
73
+ </div>
74
+
75
+ <div class="main-page">
76
+ <TPagination :current-page="$Data.pagerConfig.currentPage" :page-size="$Data.pagerConfig.limit" :total="$Data.pagerConfig.total" @current-change="$Method.onPageChange" @page-size-change="$Method.handleSizeChange" />
77
+ </div>
78
+
79
+ <!-- 编辑对话框 -->
80
+ <EditDialog v-if="$Data.editVisible" v-model="$Data.editVisible" :action-type="$Data.actionType" :row-data="$Data.rowData" @success="$Method.apiConfigList" />
81
+ </div>
82
+ </template>
83
+
84
+ <script setup>
85
+ import { Button as TButton, Table as TTable, Tag as TTag, Select as TSelect, Option as TOption, Dropdown as TDropdown, DropdownMenu as TDropdownMenu, DropdownItem as TDropdownItem, Pagination as TPagination, MessagePlugin, DialogPlugin } from 'tdesign-vue-next';
86
+ import ILucidePlus from '~icons/lucide/plus';
87
+ import ILucideRotateCw from '~icons/lucide/rotate-cw';
88
+ import ILucidePencil from '~icons/lucide/pencil';
89
+ import ILucideTrash2 from '~icons/lucide/trash-2';
90
+ import EditDialog from './components/edit.vue';
91
+ import DetailPanel from '@/components/DetailPanel.vue';
92
+ import { $Http } from '@/plugins/http';
93
+ import { withDefaultColumns } from '@/utils';
94
+
95
+ // 响应式数据
96
+ const $Data = $ref({
97
+ tableData: [],
98
+ loading: false,
99
+ columns: withDefaultColumns([
100
+ { colKey: 'name', title: '配置名称', fixed: 'left', width: 150 },
101
+ { colKey: 'id', title: '序号', width: 80 },
102
+ { colKey: 'code', title: '配置代码', ellipsis: true },
103
+ { colKey: 'value', title: '配置值', ellipsis: true, width: 200 },
104
+ { colKey: 'valueType', title: '值类型', width: 100 },
105
+ { colKey: 'group', title: '分组', width: 100 },
106
+ { colKey: 'sort', title: '排序', width: 80 },
107
+ { colKey: 'isSystem', title: '类型', width: 80 },
108
+ { colKey: 'state', title: '状态', width: 80 },
109
+ { colKey: 'operation', title: '操作', width: 100 }
110
+ ]),
111
+ detailFields: [
112
+ { colKey: 'name', title: '配置名称' },
113
+ { colKey: 'code', title: '配置代码' },
114
+ { colKey: 'value', title: '配置值' },
115
+ { colKey: 'valueType', title: '值类型' },
116
+ { colKey: 'group', title: '配置分组' },
117
+ { colKey: 'sort', title: '排序' },
118
+ { colKey: 'isSystem', title: '配置类型' },
119
+ { colKey: 'description', title: '描述说明' }
120
+ ],
121
+ pagerConfig: {
122
+ currentPage: 1,
123
+ limit: 30,
124
+ total: 0
125
+ },
126
+ currentRow: null,
127
+ activeRowKeys: [],
128
+ editVisible: false,
129
+ actionType: 'add',
130
+ rowData: {},
131
+ filter: {
132
+ group: ''
133
+ },
134
+ groupOptions: ['基础配置', '邮件配置', '存储配置', '安全配置', '其他']
135
+ });
136
+
137
+ // 方法
138
+ const $Method = {
139
+ async initData() {
140
+ await $Method.apiConfigList();
141
+ },
142
+
143
+ // 加载配置列表
144
+ async apiConfigList() {
145
+ $Data.loading = true;
146
+ try {
147
+ const res = await $Http('/addon/admin/sysConfig/list', {
148
+ page: $Data.pagerConfig.currentPage,
149
+ limit: $Data.pagerConfig.limit
150
+ });
151
+ $Data.tableData = res.data.lists || [];
152
+ $Data.pagerConfig.total = res.data.total || 0;
153
+
154
+ if ($Data.tableData.length > 0) {
155
+ $Data.currentRow = $Data.tableData[0];
156
+ $Data.activeRowKeys = [$Data.tableData[0].id];
157
+ } else {
158
+ $Data.currentRow = null;
159
+ $Data.activeRowKeys = [];
160
+ }
161
+ } catch (error) {
162
+ MessagePlugin.error('加载数据失败');
163
+ } finally {
164
+ $Data.loading = false;
165
+ }
166
+ },
167
+
168
+ // 删除配置
169
+ async apiConfigDel(row) {
170
+ if (row.isSystem === 1) {
171
+ MessagePlugin.warning('系统配置不允许删除');
172
+ return;
173
+ }
174
+
175
+ DialogPlugin.confirm({
176
+ header: '确认删除',
177
+ body: `确定要删除配置"${row.name}"吗?`,
178
+ status: 'warning'
179
+ }).then(async () => {
180
+ try {
181
+ const res = await $Http('/addon/admin/sysConfig/del', { id: row.id });
182
+ if (res.code === 0) {
183
+ MessagePlugin.success('删除成功');
184
+ $Method.apiConfigList();
185
+ } else {
186
+ MessagePlugin.error(res.msg || '删除失败');
187
+ }
188
+ } catch (error) {
189
+ MessagePlugin.error('删除失败');
190
+ }
191
+ });
192
+ },
193
+
194
+ // 筛选
195
+ handleFilter() {
196
+ $Data.pagerConfig.currentPage = 1;
197
+ $Method.apiConfigList();
198
+ },
199
+
200
+ // 刷新
201
+ handleRefresh() {
202
+ $Method.apiConfigList();
203
+ },
204
+
205
+ // 分页改变
206
+ onPageChange(currentPage) {
207
+ $Data.pagerConfig.currentPage = currentPage;
208
+ $Method.apiConfigList();
209
+ },
210
+
211
+ // 每页条数改变
212
+ handleSizeChange(pageSize) {
213
+ $Data.pagerConfig.limit = pageSize;
214
+ $Data.pagerConfig.currentPage = 1;
215
+ $Method.apiConfigList();
216
+ },
217
+
218
+ // 高亮行变化
219
+ onActiveChange(value, context) {
220
+ if (value.length === 0 && $Data.activeRowKeys.length > 0) {
221
+ return;
222
+ }
223
+ $Data.activeRowKeys = value;
224
+ if (context.activeRowList && context.activeRowList.length > 0) {
225
+ $Data.currentRow = context.activeRowList[0].row;
226
+ }
227
+ },
228
+
229
+ // 操作菜单点击
230
+ onAction(command, rowData) {
231
+ $Data.actionType = command;
232
+ $Data.rowData = rowData;
233
+ if (command === 'add' || command === 'upd') {
234
+ $Data.editVisible = true;
235
+ } else if (command === 'del') {
236
+ $Method.apiConfigDel(rowData);
237
+ }
238
+ }
239
+ };
240
+
241
+ $Method.initData();
242
+ </script>
243
+
244
+ <style scoped lang="scss">
245
+ .config-value {
246
+ margin: 0;
247
+ padding: 8px;
248
+ background: var(--td-bg-color-container);
249
+ border-radius: 4px;
250
+ font-size: 12px;
251
+ max-height: 150px;
252
+ overflow: auto;
253
+ white-space: pre-wrap;
254
+ word-break: break-all;
255
+ }
256
+ </style>
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "系统配置",
3
+ "order": 2
4
+ }
@@ -1,10 +1,21 @@
1
1
  <template>
2
2
  <div class="dashboard-container">
3
- <SystemOverview />
4
- <ServiceStatus />
5
- <SystemResources />
6
- <PerformanceMetrics />
7
- <EnvironmentInfo />
3
+ <!-- 第一行:系统概览(全宽) -->
4
+ <div class="dashboard-row full-width">
5
+ <SystemOverview />
6
+ </div>
7
+
8
+ <!-- 第二行:服务状态 + 系统资源 -->
9
+ <div class="dashboard-row two-columns">
10
+ <ServiceStatus />
11
+ <SystemResources />
12
+ </div>
13
+
14
+ <!-- 第三行:性能指标 + 环境信息 -->
15
+ <div class="dashboard-row two-columns">
16
+ <PerformanceMetrics />
17
+ <EnvironmentInfo />
18
+ </div>
8
19
  </div>
9
20
  </template>
10
21
 
@@ -20,11 +31,37 @@ import EnvironmentInfo from './components/environmentInfo.vue';
20
31
  .dashboard-container {
21
32
  display: flex;
22
33
  flex-direction: column;
23
- gap: 12px;
34
+ gap: var(--layout-gap);
24
35
  overflow-y: auto;
25
36
  height: 100%;
26
- background-color: #fff;
27
- padding: 15px;
28
- border: 1px solid #e8eaed;
37
+ padding: 0;
38
+
39
+ .dashboard-row {
40
+ display: flex;
41
+ gap: var(--layout-gap);
42
+
43
+ &.full-width {
44
+ > * {
45
+ flex: 1;
46
+ }
47
+ }
48
+
49
+ &.two-columns {
50
+ > * {
51
+ flex: 1;
52
+ min-width: 0;
53
+ }
54
+ }
55
+ }
56
+
57
+ // 每个组件都是独立的卡片
58
+ :deep(.section-block) {
59
+ background: var(--bg-color-container);
60
+ border-radius: var(--card-radius);
61
+ box-shadow: var(--shadow-1);
62
+ padding: var(--spacing-md);
63
+ border: none;
64
+ height: 100%;
65
+ }
29
66
  }
30
67
  </style>
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "首页",
3
+ "order": 1
4
+ }
@@ -0,0 +1,285 @@
1
+ <template>
2
+ <div class="page-email page-table">
3
+ <div class="main-tool">
4
+ <div class="left">
5
+ <TButton theme="primary" @click="$Method.openSendDialog">
6
+ <template #icon>
7
+ <ILucideSend />
8
+ </template>
9
+ 发送邮件
10
+ </TButton>
11
+ <TButton variant="outline" @click="$Method.onVerify">
12
+ <template #icon>
13
+ <ILucideCheckCircle />
14
+ </template>
15
+ 验证配置
16
+ </TButton>
17
+ </div>
18
+ <div class="right">
19
+ <TButton shape="circle" @click="$Method.handleRefresh">
20
+ <template #icon>
21
+ <ILucideRotateCw />
22
+ </template>
23
+ </TButton>
24
+ </div>
25
+ </div>
26
+
27
+ <div class="main-content">
28
+ <div class="main-table">
29
+ <TTable :data="$Data.tableData" :columns="$Data.columns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" row-key="id" height="100%" active-row-type="single" @active-change="$Method.onActiveChange">
30
+ <template #sendResult="{ row }">
31
+ <TTag v-if="row.sendResult === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
32
+ <TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
33
+ </template>
34
+ <template #sendTime="{ row }">
35
+ {{ $Method.formatTime(row.sendTime) }}
36
+ </template>
37
+ </TTable>
38
+ </div>
39
+
40
+ <div class="main-detail">
41
+ <DetailPanel :data="$Data.currentRow" :fields="$Data.detailFields">
42
+ <template #sendResult="{ value }">
43
+ <TTag v-if="value === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
44
+ <TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
45
+ </template>
46
+ <template #sendTime="{ value }">
47
+ {{ $Method.formatTime(value) }}
48
+ </template>
49
+ <template #content="{ value }">
50
+ <div class="email-content" v-html="value"></div>
51
+ </template>
52
+ </DetailPanel>
53
+ </div>
54
+ </div>
55
+
56
+ <div class="main-page">
57
+ <TPagination :current-page="$Data.pagerConfig.currentPage" :page-size="$Data.pagerConfig.limit" :total="$Data.pagerConfig.total" @current-change="$Method.onPageChange" @page-size-change="$Method.handleSizeChange" />
58
+ </div>
59
+
60
+ <!-- 发送邮件弹框 -->
61
+ <TDialog v-model:visible="$Data.sendDialogVisible" header="发送邮件" :footer="false" width="600px">
62
+ <TForm ref="sendFormRef" :data="$Data.sendForm" :rules="$Data.sendRules" label-width="80px">
63
+ <TFormItem label="收件人" name="to">
64
+ <TInput v-model="$Data.sendForm.to" placeholder="请输入收件人邮箱" />
65
+ </TFormItem>
66
+ <TFormItem label="抄送" name="cc">
67
+ <TInput v-model="$Data.sendForm.cc" placeholder="多个邮箱用逗号分隔(可选)" />
68
+ </TFormItem>
69
+ <TFormItem label="主题" name="subject">
70
+ <TInput v-model="$Data.sendForm.subject" placeholder="请输入邮件主题" />
71
+ </TFormItem>
72
+ <TFormItem label="内容" name="content">
73
+ <TTextarea v-model="$Data.sendForm.content" placeholder="请输入邮件内容(支持HTML)" :autosize="{ minRows: 6, maxRows: 12 }" />
74
+ </TFormItem>
75
+ <TFormItem>
76
+ <TSpace>
77
+ <TButton theme="primary" :loading="$Data.sending" @click="$Method.onSend">发送</TButton>
78
+ <TButton variant="outline" @click="$Data.sendDialogVisible = false">取消</TButton>
79
+ </TSpace>
80
+ </TFormItem>
81
+ </TForm>
82
+ </TDialog>
83
+ </div>
84
+ </template>
85
+
86
+ <script setup>
87
+ import { Button as TButton, Table as TTable, Tag as TTag, Pagination as TPagination, Dialog as TDialog, Form as TForm, FormItem as TFormItem, Input as TInput, Textarea as TTextarea, Space as TSpace, MessagePlugin } from 'tdesign-vue-next';
88
+ import ILucideRotateCw from '~icons/lucide/rotate-cw';
89
+ import ILucideSend from '~icons/lucide/send';
90
+ import ILucideCheckCircle from '~icons/lucide/check-circle';
91
+ import DetailPanel from '@/components/DetailPanel.vue';
92
+ import { $Http } from '@/plugins/http';
93
+ import { withDefaultColumns } from '@/utils';
94
+
95
+ const sendFormRef = $ref(null);
96
+
97
+ // 响应式数据
98
+ const $Data = $ref({
99
+ tableData: [],
100
+ loading: false,
101
+ columns: withDefaultColumns([
102
+ { colKey: 'username', title: '发送人', fixed: 'left' },
103
+ { colKey: 'id', title: '序号' },
104
+ { colKey: 'toEmail', title: '收件人' },
105
+ { colKey: 'subject', title: '主题' },
106
+ { colKey: 'sendTime', title: '发送时间' },
107
+ { colKey: 'sendResult', title: '发送结果' }
108
+ ]),
109
+ detailFields: [
110
+ { colKey: 'username', title: '发送人账号' },
111
+ { colKey: 'nickname', title: '发送人昵称' },
112
+ { colKey: 'toEmail', title: '收件人' },
113
+ { colKey: 'ccEmail', title: '抄送' },
114
+ { colKey: 'bccEmail', title: '密送' },
115
+ { colKey: 'subject', title: '主题' },
116
+ { colKey: 'content', title: '内容' },
117
+ { colKey: 'sendTime', title: '发送时间' },
118
+ { colKey: 'sendResult', title: '发送结果' },
119
+ { colKey: 'messageId', title: '消息ID' },
120
+ { colKey: 'failReason', title: '失败原因' }
121
+ ],
122
+ pagerConfig: {
123
+ currentPage: 1,
124
+ limit: 30,
125
+ total: 0
126
+ },
127
+ currentRow: null,
128
+ activeRowKeys: [],
129
+ sendDialogVisible: false,
130
+ sending: false,
131
+ sendForm: {
132
+ to: '',
133
+ cc: '',
134
+ subject: '',
135
+ content: ''
136
+ },
137
+ sendRules: {
138
+ to: [{ required: true, message: '请输入收件人邮箱', trigger: 'blur' }],
139
+ subject: [{ required: true, message: '请输入邮件主题', trigger: 'blur' }],
140
+ content: [{ required: true, message: '请输入邮件内容', trigger: 'blur' }]
141
+ }
142
+ });
143
+
144
+ // 方法
145
+ const $Method = {
146
+ async initData() {
147
+ await $Method.apiEmailLogList();
148
+ },
149
+
150
+ // 加载邮件日志列表
151
+ async apiEmailLogList() {
152
+ $Data.loading = true;
153
+ try {
154
+ const res = await $Http('/addon/admin/email/logList', {
155
+ page: $Data.pagerConfig.currentPage,
156
+ limit: $Data.pagerConfig.limit
157
+ });
158
+ $Data.tableData = res.data.lists || [];
159
+ $Data.pagerConfig.total = res.data.total || 0;
160
+
161
+ if ($Data.tableData.length > 0) {
162
+ $Data.currentRow = $Data.tableData[0];
163
+ $Data.activeRowKeys = [$Data.tableData[0].id];
164
+ } else {
165
+ $Data.currentRow = null;
166
+ $Data.activeRowKeys = [];
167
+ }
168
+ } catch (error) {
169
+ MessagePlugin.error('加载数据失败');
170
+ } finally {
171
+ $Data.loading = false;
172
+ }
173
+ },
174
+
175
+ // 打开发送弹框
176
+ openSendDialog() {
177
+ $Data.sendForm = {
178
+ to: '',
179
+ cc: '',
180
+ subject: '',
181
+ content: ''
182
+ };
183
+ $Data.sendDialogVisible = true;
184
+ },
185
+
186
+ // 发送邮件
187
+ async onSend() {
188
+ const valid = await sendFormRef?.validate();
189
+ if (valid !== true) return;
190
+
191
+ $Data.sending = true;
192
+ try {
193
+ const res = await $Http('/addon/admin/email/send', {
194
+ to: $Data.sendForm.to,
195
+ subject: $Data.sendForm.subject,
196
+ content: $Data.sendForm.content,
197
+ cc: $Data.sendForm.cc || undefined,
198
+ isHtml: true
199
+ });
200
+
201
+ if (res.code === 0) {
202
+ MessagePlugin.success('发送成功');
203
+ $Data.sendDialogVisible = false;
204
+ $Method.apiEmailLogList();
205
+ } else {
206
+ MessagePlugin.error(res.msg || '发送失败');
207
+ }
208
+ } catch (error) {
209
+ MessagePlugin.error('发送失败');
210
+ } finally {
211
+ $Data.sending = false;
212
+ }
213
+ },
214
+
215
+ // 验证配置
216
+ async onVerify() {
217
+ try {
218
+ const res = await $Http('/addon/admin/email/verify');
219
+ if (res.code === 0) {
220
+ MessagePlugin.success('邮件服务配置正常');
221
+ } else {
222
+ MessagePlugin.error(res.msg || '配置异常');
223
+ }
224
+ } catch (error) {
225
+ MessagePlugin.error('验证失败');
226
+ }
227
+ },
228
+
229
+ // 刷新
230
+ handleRefresh() {
231
+ $Method.apiEmailLogList();
232
+ },
233
+
234
+ // 分页改变
235
+ onPageChange(currentPage) {
236
+ $Data.pagerConfig.currentPage = currentPage;
237
+ $Method.apiEmailLogList();
238
+ },
239
+
240
+ // 每页条数改变
241
+ handleSizeChange(pageSize) {
242
+ $Data.pagerConfig.limit = pageSize;
243
+ $Data.pagerConfig.currentPage = 1;
244
+ $Method.apiEmailLogList();
245
+ },
246
+
247
+ // 高亮行变化
248
+ onActiveChange(value, context) {
249
+ if (value.length === 0 && $Data.activeRowKeys.length > 0) {
250
+ return;
251
+ }
252
+ $Data.activeRowKeys = value;
253
+ if (context.activeRowList && context.activeRowList.length > 0) {
254
+ $Data.currentRow = context.activeRowList[0].row;
255
+ }
256
+ },
257
+
258
+ // 格式化时间
259
+ formatTime(timestamp) {
260
+ if (!timestamp) return '-';
261
+ const date = new Date(timestamp);
262
+ const year = date.getFullYear();
263
+ const month = String(date.getMonth() + 1).padStart(2, '0');
264
+ const day = String(date.getDate()).padStart(2, '0');
265
+ const hours = String(date.getHours()).padStart(2, '0');
266
+ const minutes = String(date.getMinutes()).padStart(2, '0');
267
+ const seconds = String(date.getSeconds()).padStart(2, '0');
268
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
269
+ }
270
+ };
271
+
272
+ $Method.initData();
273
+ </script>
274
+
275
+ <style scoped lang="scss">
276
+ .email-content {
277
+ max-height: 300px;
278
+ overflow-y: auto;
279
+ padding: 8px;
280
+ background: #f5f5f5;
281
+ border-radius: 4px;
282
+ font-size: 13px;
283
+ line-height: 1.6;
284
+ }
285
+ </style>
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "邮件日志",
3
+ "order": 2
4
+ }