@ysgroup/ui 0.1.8 → 0.1.9

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.
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@ysgroup/ui",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
7
7
  "dependencies": {
8
+ "@element-plus/icons-vue": "^2.3.1",
8
9
  "@ysgroup/contracts": "^0.1.1",
9
10
  "@ysgroup/theme": "^0.1.0"
10
11
  },
@@ -3,10 +3,10 @@
3
3
  超过 20 条自动显示分页器(20 / 50 / 100 档,默认每页 20 条)。 -->
4
4
  <script setup lang="ts">
5
5
  import { computed, ref, watch } from "vue";
6
- import { ElMessageBox } from "element-plus";
7
6
  import { FIELD_SPECS, ENUM_META } from "@ysgroup/contracts";
8
7
  import { enumLabel } from "../tokens-runtime";
9
8
  import YsFormDialog from "./YsFormDialog.vue";
9
+ import YsRowActionMenu from "./YsRowActionMenu.vue";
10
10
 
11
11
  interface FieldSpec {
12
12
  key: string;
@@ -172,13 +172,17 @@ function onSubmit(payload: Record<string, unknown>) {
172
172
  <el-table-column v-for="c in displayColumns" :key="c.key" :label="c.title" :width="columnWidth(c)" show-overflow-tooltip>
173
173
  <template #default="{ row }">{{ display(row, c) }}</template>
174
174
  </el-table-column>
175
- <el-table-column v-if="!readonly" label="操作" :width="actionWidth ?? 260" fixed="right">
175
+ <el-table-column v-if="!readonly" label="操作栏" :width="actionWidth ?? 76" fixed="right" align="center">
176
176
  <template #default="{ row }">
177
- <div class="ys-crud__actions">
178
- <el-button size="small" @click="onEdit(row)">编辑</el-button>
179
- <slot name="row-actions" :row="row" />
180
- <el-button size="small" type="danger" plain @click="onRemove(row)">删除</el-button>
181
- </div>
177
+ <YsRowActionMenu
178
+ :actions="[
179
+ { key: 'edit', label: '编辑', tone: 'primary' },
180
+ { key: 'delete', label: '删除', tone: 'danger', divided: true },
181
+ ]"
182
+ @action="$event === 'edit' ? onEdit(row) : onRemove(row)"
183
+ >
184
+ <template #extra="{ close }"><slot name="row-actions" :row="row" :close="close" /></template>
185
+ </YsRowActionMenu>
182
186
  </template>
183
187
  </el-table-column>
184
188
  </el-table>
@@ -211,13 +215,4 @@ function onSubmit(payload: Record<string, unknown>) {
211
215
  margin-top: 12px;
212
216
  justify-content: flex-end;
213
217
  }
214
- .ys-crud__actions {
215
- display: flex;
216
- align-items: center;
217
- gap: 8px;
218
- white-space: nowrap;
219
- }
220
- .ys-crud__actions :deep(.el-button + .el-button) {
221
- margin-left: 0;
222
- }
223
218
  </style>
@@ -37,7 +37,7 @@ defineProps<{
37
37
  color: var(--ys-color-primary);
38
38
  font-size: 10px;
39
39
  font-weight: 700;
40
- letter-spacing: .08em;
40
+ letter-spacing: 0.08em;
41
41
  text-transform: uppercase;
42
42
  }
43
43
  .ys-page-header h1 {
@@ -77,4 +77,4 @@ defineProps<{
77
77
  flex-wrap: wrap;
78
78
  }
79
79
  }
80
- </style>
80
+ </style>
@@ -0,0 +1,148 @@
1
+ <script setup lang="ts">
2
+ import { ref } from "vue";
3
+ import { Setting } from "@element-plus/icons-vue";
4
+
5
+ export interface YsRowAction {
6
+ key: string;
7
+ label: string;
8
+ tone?: "primary" | "accent" | "warning" | "success" | "neutral" | "danger";
9
+ disabled?: boolean;
10
+ divided?: boolean;
11
+ }
12
+
13
+ defineProps<{ actions: YsRowAction[]; ariaLabel?: string }>();
14
+ const emit = defineEmits<{ action: [key: string] }>();
15
+ const visible = ref(false);
16
+
17
+ function select(action: YsRowAction) {
18
+ if (action.disabled) return;
19
+ visible.value = false;
20
+ emit("action", action.key);
21
+ }
22
+ </script>
23
+
24
+ <template>
25
+ <el-popover
26
+ :visible="visible"
27
+ placement="right"
28
+ :fallback-placements="['right']"
29
+ :width="150"
30
+ :offset="12"
31
+ trigger="click"
32
+ popper-class="ys-action-popover"
33
+ @update:visible="visible = $event"
34
+ >
35
+ <template #reference
36
+ ><button class="ys-row-action__trigger" type="button" :aria-label="ariaLabel || '操作'">
37
+ <el-icon :size="17"><Setting /></el-icon></button
38
+ ></template>
39
+ <nav class="ys-row-action__menu" :aria-label="ariaLabel || '操作菜单'">
40
+ <template v-for="action in actions.filter((item) => !item.divided)" :key="action.key">
41
+ <button class="ys-row-action__item" :class="`is-${action.tone || 'neutral'}`" type="button" :disabled="action.disabled" @click="select(action)">{{ action.label }}</button>
42
+ </template>
43
+ <slot name="extra" :close="() => (visible = false)" />
44
+ <template v-for="action in actions.filter((item) => item.divided)" :key="action.key">
45
+ <div class="ys-row-action__divider" />
46
+ <button class="ys-row-action__item" :class="`is-${action.tone || 'neutral'}`" type="button" :disabled="action.disabled" @click="select(action)">{{ action.label }}</button>
47
+ </template>
48
+ </nav>
49
+ </el-popover>
50
+ </template>
51
+
52
+ <style scoped>
53
+ .ys-row-action__trigger {
54
+ display: inline-grid;
55
+ place-items: center;
56
+ width: 32px;
57
+ height: var(--el-component-size-small);
58
+ padding: 0;
59
+ border: 0;
60
+ border-radius: 4px;
61
+ background: transparent;
62
+ color: var(--ys-color-text-3);
63
+ line-height: 1;
64
+ vertical-align: middle;
65
+ cursor: pointer;
66
+ }
67
+ .ys-row-action__trigger:hover,
68
+ .ys-row-action__trigger:focus-visible {
69
+ outline: none;
70
+ color: var(--ys-color-primary);
71
+ background: color-mix(in srgb, var(--ys-color-primary) 9%, transparent);
72
+ }
73
+ .ys-row-action__menu {
74
+ display: grid;
75
+ gap: 3px;
76
+ }
77
+ .ys-row-action__item {
78
+ width: 100%;
79
+ padding: 8px 10px;
80
+ border: 0;
81
+ border-radius: 5px;
82
+ background: transparent;
83
+ color: var(--ys-color-text-2);
84
+ font: inherit;
85
+ font-size: 12px;
86
+ line-height: 1.2;
87
+ text-align: left;
88
+ cursor: pointer;
89
+ transition:
90
+ background-color 0.15s,
91
+ color 0.15s;
92
+ }
93
+ .ys-row-action__item:hover,
94
+ .ys-row-action__item:focus-visible {
95
+ outline: none;
96
+ background: color-mix(in srgb, var(--item-color) 10%, transparent);
97
+ color: var(--item-color);
98
+ }
99
+ .ys-row-action__item:disabled {
100
+ opacity: 0.45;
101
+ cursor: not-allowed;
102
+ }
103
+ .ys-row-action__item.is-primary {
104
+ --item-color: var(--ys-color-primary);
105
+ }
106
+ .ys-row-action__item.is-accent {
107
+ --item-color: #0f8f83;
108
+ }
109
+ .ys-row-action__item.is-warning {
110
+ --item-color: #b7791f;
111
+ }
112
+ .ys-row-action__item.is-success {
113
+ --item-color: var(--ys-color-success);
114
+ }
115
+ .ys-row-action__item.is-neutral {
116
+ --item-color: #58677f;
117
+ }
118
+ .ys-row-action__item.is-danger {
119
+ --item-color: var(--ys-color-danger);
120
+ color: var(--ys-color-danger);
121
+ }
122
+ .ys-row-action__menu :deep(.ys-row-action-menu__item) {
123
+ width: 100%;
124
+ padding: 8px 10px;
125
+ border: 0;
126
+ border-radius: 5px;
127
+ background: transparent;
128
+ color: var(--item-color, var(--ys-color-text-2));
129
+ font: inherit;
130
+ font-size: 12px;
131
+ line-height: 1.2;
132
+ text-align: left;
133
+ cursor: pointer;
134
+ }
135
+ .ys-row-action__divider {
136
+ height: 1px;
137
+ margin: 3px 4px;
138
+ background: var(--ys-color-border);
139
+ }
140
+ :global(.ys-action-popover.el-popover.el-popper) {
141
+ padding: 7px;
142
+ border-color: color-mix(in srgb, var(--ys-color-primary) 18%, var(--ys-color-border));
143
+ border-radius: 7px;
144
+ box-shadow:
145
+ 0 10px 28px color-mix(in srgb, #0f172a 16%, transparent),
146
+ 0 2px 6px color-mix(in srgb, #0f172a 8%, transparent);
147
+ }
148
+ </style>
@@ -6,6 +6,7 @@ const props = defineProps<{
6
6
  modelValue: boolean;
7
7
  role?: string;
8
8
  username?: string;
9
+ passwordManagementUrl?: string;
9
10
  }>();
10
11
 
11
12
  const emit = defineEmits<{
@@ -18,9 +19,7 @@ const form = reactive({ current: "", next: "", confirm: "" });
18
19
  const error = ref("");
19
20
  const submitting = ref(false);
20
21
 
21
- const canChangePassword = computed(
22
- () => props.role !== "Root" && props.username?.trim().toLowerCase() !== "root",
23
- );
22
+ const canChangePassword = computed(() => props.role !== "Root" && props.username?.trim().toLowerCase() !== "root");
24
23
 
25
24
  function reset() {
26
25
  form.current = "";
@@ -30,9 +29,12 @@ function reset() {
30
29
  submitting.value = false;
31
30
  }
32
31
 
33
- watch(() => props.modelValue, (open) => {
34
- if (!open) reset();
35
- });
32
+ watch(
33
+ () => props.modelValue,
34
+ (open) => {
35
+ if (!open) reset();
36
+ },
37
+ );
36
38
 
37
39
  function close() {
38
40
  if (submitting.value) return;
@@ -79,15 +81,20 @@ defineExpose({
79
81
  >
80
82
  <el-tabs v-model="active" tab-position="left" class="ys-settings">
81
83
  <el-tab-pane label="账号安全" name="security">
82
- <template v-if="canChangePassword">
83
- <h3 class="ys-settings__title">修改登录密码</h3>
84
+ <template v-if="canChangePassword && passwordManagementUrl">
85
+ <h3 class="ys-settings__title">登录密码管理</h3>
84
86
  <el-alert
85
- title="修改成功后,当前账号的所有登录会话都会退出,请使用新密码重新登录。"
87
+ title="登录密码由Auth统一认证中心管理。修改密码后,当前账号在所有业务系统中的登录会话都将失效,需要使用新密码重新登录。"
86
88
  type="info"
87
89
  :closable="false"
88
90
  show-icon
89
91
  class="ys-settings__notice"
90
92
  />
93
+ <el-button tag="a" type="primary" :href="passwordManagementUrl" target="_blank" rel="noopener noreferrer"> 前往 Auth系统 修改密码 </el-button>
94
+ </template>
95
+ <template v-else-if="canChangePassword">
96
+ <h3 class="ys-settings__title">修改登录密码</h3>
97
+ <el-alert title="修改成功后,当前账号的所有登录会话都会退出,请使用新密码重新登录。" type="info" :closable="false" show-icon class="ys-settings__notice" />
91
98
  <el-form label-width="110px" @submit.prevent="submit">
92
99
  <el-form-item label="当前密码" required>
93
100
  <el-input v-model="form.current" type="password" show-password autocomplete="current-password" />
@@ -109,14 +116,7 @@ defineExpose({
109
116
  </el-tabs>
110
117
  <template #footer>
111
118
  <el-button @click="close">关闭</el-button>
112
- <el-button
113
- v-if="canChangePassword && active === 'security'"
114
- type="primary"
115
- :loading="submitting"
116
- @click="submit"
117
- >
118
- 修改并退出
119
- </el-button>
119
+ <el-button v-if="canChangePassword && !passwordManagementUrl && active === 'security'" type="primary" :loading="submitting" @click="submit"> 修改并退出 </el-button>
120
120
  </template>
121
121
  </el-dialog>
122
122
  </template>
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  /* @ysgroup/ui —— 平台组件库(基于 Element Plus 封装,令牌来自 @ysgroup/theme)。 */
2
2
 
3
3
  export { default as YsCrudTable } from "./components/YsCrudTable.vue";
4
+ export { default as YsRowActionMenu } from "./components/YsRowActionMenu.vue";
5
+ export type { YsRowAction } from "./components/YsRowActionMenu.vue";
4
6
  export { default as YsFormDialog } from "./components/YsFormDialog.vue";
5
7
  export { default as YsPageLayout } from "./components/YsPageLayout.vue";
6
8
  export { default as YsPageHeader } from "./components/YsPageHeader.vue";