@yqg/permission 1.1.3-beta.0 → 1.2.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.
@@ -1,24 +1,36 @@
1
1
  <template>
2
2
  <ConfigProvider
3
- v-if="allPermissions.length"
4
- prefixCls="yqg-permission"
5
- :theme="{
6
- token: {
7
- colorPrimary: props.color,
8
- }}">
3
+ prefixCls="yqg-permission"
4
+ :theme="{
5
+ token: {
6
+ colorPrimary: props.color,
7
+ }
8
+ }"
9
+ >
9
10
  <div class="crane-wraper">
10
- <template v-if="type==='text'">
11
- <TypographyLink @click="showModal">{{t('permissionApply')}}</TypographyLink>
11
+ <template v-if="[COM_TYPE.FLOATBUTTON, COM_TYPE.TEXT].includes(type)">
12
+ <FloatButton
13
+ ref="dragElement"
14
+ type="primary"
15
+ :tooltip="t('clickToApply')"
16
+ :style="{
17
+ right: RIGHT_DEFAULT,
18
+ top: currentTop,
19
+ }"
20
+ >
21
+ <template #icon>
22
+ <img :src="applyIconUrl" height="20" width="20"/>
23
+ </template>
24
+ </FloatButton>
12
25
  </template>
13
- <template v-else-if="type==='custom'">
26
+ <template v-else-if="type===COM_TYPE.CUSTOM">
14
27
  <div @click="showModal">
15
- <slot name="custom"></slot>
28
+ <slot name="custom"/>
16
29
  </div>
17
30
  </template>
18
31
  <template v-else>
19
-
20
32
  <!-- 可申请 -->
21
- <template v-if="curStatus.status === statusMap.DEFAULT">
33
+ <template v-if="curStatus.status === STATUS_MAP.DEFAULT">
22
34
  <img
23
35
  :src="curStatus.imageUrl"
24
36
  height="200"
@@ -32,7 +44,7 @@
32
44
  </div>
33
45
  </template>
34
46
  <!-- 审批中 -->
35
- <template v-if="curStatus.status === statusMap.PENDING">
47
+ <template v-if="curStatus.status === STATUS_MAP.PENDING">
36
48
  <img
37
49
  :src="curStatus.imageUrl"
38
50
  height="200"
@@ -52,7 +64,7 @@
52
64
  </div>
53
65
  </template>
54
66
  <!-- 不可申请 -->
55
- <div v-if="curStatus.status === statusMap.NO" class="crane-wraper">
67
+ <div v-if="curStatus.status === STATUS_MAP.NO" class="crane-wraper">
56
68
  <img
57
69
  :src="curStatus.imageUrl"
58
70
  height="200"
@@ -76,27 +88,42 @@
76
88
  :permissionList="permissionList"
77
89
  :workNumber="workNumber"
78
90
  :businessCode="businessCode"
79
- @onSuccess="getPermissions">
91
+ @onSuccess="() => emit('onSuccess')"
92
+ @onSubmit="getPermissions">
80
93
  </ApplyModal>
81
94
 
82
95
  </ConfigProvider>
83
96
  </template>
84
97
  <script lang="ts" setup>
85
- import { ref, defineAsyncComponent, computed, watchEffect, watch} from 'vue';
86
- import { Button, ConfigProvider, TypographyLink, Popover , message} from 'ant-design-vue';
98
+ import { ref, defineAsyncComponent, computed, watchEffect, watch } from 'vue';
99
+ import { Button, ConfigProvider, Popover , message, FloatButton} from 'ant-design-vue';
87
100
  import applyUrl from '@/assets/applying.png';
101
+ import applyIconUrl from '@/assets/applyicon.png';
88
102
  import noauthority from '@/assets/noauthority.png';
89
103
  import Http from '../axios/index';
90
104
  import t from '../utils';
105
+ import useDraggable from '../hooks/useDragable';
91
106
 
92
- // 重置 message 类名,避免被全局样式覆盖
93
- message.config({prefixCls: 'yqg-permission-message'});
94
- const ApplyModal = defineAsyncComponent(() =>import('./apply-modal.vue'));
95
- const statusMap = {
107
+ const STATUS_MAP = {
96
108
  DEFAULT: 'DEFAULT',
97
109
  PENDING: 'PENDING',
98
110
  NO: 'NO',
99
- }
111
+ } as const;
112
+
113
+ const COM_TYPE = {
114
+ FLOATBUTTON: 'floatButton',
115
+ DEFAULT: 'default',
116
+ TEXT: 'text',
117
+ CUSTOM: 'custom',
118
+ };
119
+ const RIGHT_DEFAULT = '10px';
120
+
121
+ // 重置 message 类名,避免被全局样式覆盖
122
+ message.config({prefixCls: 'yqg-permission-message'});
123
+
124
+ const ApplyModal = defineAsyncComponent(() =>import('./apply-modal.vue'));
125
+
126
+ const emit = defineEmits(['onSuccess']);
100
127
 
101
128
  const props = defineProps({
102
129
  workNumber: {
@@ -107,10 +134,6 @@
107
134
  type: [String, Array<String>],
108
135
  default: []
109
136
  },
110
- businessCode: {
111
- type: String,
112
- default: ''
113
- },
114
137
  locale: {
115
138
  type: String,
116
139
  default: 'zh-CN'
@@ -123,9 +146,12 @@
123
146
  type: String,
124
147
  default: 'default'
125
148
  },
149
+ top: {
150
+ type: String,
151
+ default: '100px'
152
+ }
126
153
  });
127
- const emit = defineEmits(['onSuccess']);
128
-
154
+
129
155
  const open = ref(false);
130
156
  const curApproving = ref<PermissionType>();
131
157
  let permissionList = ref<PermissionListType>([]);
@@ -133,18 +159,27 @@
133
159
  imageUrl: noauthority,
134
160
  status: '',
135
161
  })
162
+
136
163
  const allPermissions = computed(() => {
164
+ permissionList.value = [];
137
165
  if (Array.isArray(props.permissions)) {
138
- return props.permissions;
139
- } else {
140
- return props.permissions.split(',');
166
+ return props.permissions.filter((item) => item.trim());
141
167
  }
168
+ return props.permissions?.split(',')?.filter((item) => item.trim()) || [];
142
169
  });
143
170
 
171
+ const businessCode = computed(() => {
172
+ const code = allPermissions.value[0] || '';
173
+ if (!code) return '';
174
+
175
+ return code.split('.')[0];
176
+ })
177
+
144
178
  const formatPermissionsData = (data: PermissionListType) => {
145
179
  const arr:PermissionListType = [];
146
180
  const flattenData = (list: PermissionListType) => {
147
181
  list.forEach((item) => {
182
+ item.name = item.name.replace(/^[^.]+\./, '');
148
183
  if (item.children) {
149
184
  flattenData(item.children);
150
185
  } else {
@@ -153,13 +188,19 @@
153
188
  })
154
189
  };
155
190
  flattenData(data);
191
+
156
192
  // 需要排序,规则:businessApplyType 为 null 在前面, PENDING. OWNER 在中间, NO 在后面
157
193
  // 然后再根据 L1, L2, L3 排序
158
- const sort = [ null, 'PENDING', 'OWNER', 'NO'];
194
+ const sort = [ null, 'TEMP_OWNER', 'PENDING', 'OWNER', 'NO'];
159
195
  const levelSort = ['L1', 'L2', 'L3'];
196
+ const sortMap = new Map(sort.map((value, index) => [value, index]));
197
+ const levelSortMap = new Map(levelSort.map((value, index) => [value, index]));
198
+
160
199
  arr.sort((a, b) => {
161
- return sort.indexOf(a.businessApplyType) - sort.indexOf(b.businessApplyType) || levelSort.indexOf(a.securityLevel) - levelSort.indexOf(b.securityLevel);
200
+ return (sortMap.get(a.businessApplyType) ?? 0) - (sortMap.get(b.businessApplyType) ?? 0)
201
+ || (levelSortMap.get(a.securityLevel) ?? 0) - (levelSortMap.get(b.securityLevel) ?? 0);
162
202
  });
203
+
163
204
  return arr;
164
205
  }
165
206
 
@@ -167,16 +208,18 @@
167
208
  if (!data.length) {
168
209
  return {
169
210
  imageUrl: noauthority,
170
- status: '',
211
+ status: ''
171
212
  };
172
213
  }
173
- const current = data.find((per) => per.businessApplyType === statusMap.PENDING);
174
- const cannotApply = data.every((per) => per.businessApplyType === statusMap.NO);
214
+
215
+ const current = data.find((per) => per.businessApplyType === STATUS_MAP.PENDING);
216
+ const cannotApply = data.every((per) => per.businessApplyType === STATUS_MAP.NO);
217
+
175
218
  if (current) {
176
219
  curApproving.value = current;
177
220
  return {
178
221
  imageUrl: applyUrl,
179
- status: statusMap.PENDING,
222
+ status: STATUS_MAP.PENDING,
180
223
  tips: t('status.PENDING'),
181
224
  url: current.oaFlowUrl,
182
225
  };
@@ -185,48 +228,55 @@
185
228
  if (cannotApply) {
186
229
  return {
187
230
  imageUrl: noauthority,
188
- status: statusMap.NO,
231
+ status: STATUS_MAP.NO,
189
232
  tips: data[0].admin?.map((item) => `${item.name}(${item.departmentName})`)?.join('、'),
190
233
  };
191
234
  };
235
+
192
236
  return {
193
237
  imageUrl: noauthority,
194
- status: statusMap.DEFAULT,
238
+ status: STATUS_MAP.DEFAULT,
195
239
  }
196
240
  };
197
241
 
198
242
  const goViewApproval = () => {
199
- if (curApproving.value?.oaFlowUrl) {
200
- window.open(curApproving.value.oaFlowUrl);
201
- }
243
+ const url = curApproving.value?.oaFlowUrl;
244
+ if (!url) return;
245
+
246
+ window.open(url, '_blank');
202
247
  };
203
248
 
204
249
  const getPermissions = async () => {
205
- if (!allPermissions.value.length || !props.businessCode || !props.workNumber) return;
250
+ const { workNumber } = props;
251
+ const permissions = allPermissions.value;
252
+ if (!permissions?.length || !workNumber) return;
206
253
 
207
254
  const params = {
208
- businessCode: props.businessCode,
209
- features: allPermissions.value.toString(),
210
- workNumber: props.workNumber,
255
+ workNumber,
256
+ features: permissions.toString(),
211
257
  };
212
258
 
213
- let res = await Http.getPermissions(params);
259
+ const res = await Http.getPermissions(params);
214
260
  permissionList.value = formatPermissionsData(res.body || []);
261
+
215
262
  curStatus.value = getStatus(permissionList.value);
216
263
  };
217
264
 
218
265
  const showModal = () => {
266
+ getPermissions();
219
267
  open.value = !open.value;
220
268
  };
221
269
 
270
+ const { currentTop, dragElement } = useDraggable(props, showModal);
271
+
222
272
  watchEffect(getPermissions);
223
273
 
224
274
  watch(() => props.locale, (cur, pre) => {
225
275
  if (cur === pre) return;
226
276
  localStorage.setItem('permission_locale', props.locale);
227
277
  }, {immediate: true})
228
-
229
278
  </script>
279
+
230
280
  <style scoped>
231
281
  .crane-wraper {
232
282
  display: flex;
@@ -243,3 +293,4 @@
243
293
  }
244
294
  </style>
245
295
 
296
+ ../hooks/useDragable
@@ -0,0 +1,136 @@
1
+ // useDraggable.js
2
+
3
+ import { ref, onBeforeUnmount, watch, onMounted } from 'vue';
4
+
5
+ export default function useDraggable(props: { top: any }, showModal: () => void) {
6
+ const currentTop = ref(0);
7
+ const isDragging = ref(false); // 是否正在拖拽
8
+ const startX = ref(0); // 鼠标按下时的X坐标
9
+ const initialX = ref(0); // 元素初始X坐标
10
+ const initialY = ref(0);
11
+ const dragElement = ref<any>(null);
12
+
13
+ watch(() => props.top, (newVal) => {
14
+ console.log('newVal', newVal);
15
+ currentTop.value = newVal;
16
+ }, { immediate: true });
17
+
18
+ // 获取元素的初始位置
19
+ const getPosition = (el: HTMLElement) => {
20
+ const rect = el.getBoundingClientRect();
21
+ return {
22
+ x: rect.left,
23
+ y: rect.top
24
+ };
25
+ };
26
+
27
+ // 限制拖拽元素在屏幕内
28
+ const constrainToScreen = (clientX: number, clientY: number, el: HTMLElement) => {
29
+ const { innerWidth, innerHeight } = window;
30
+ const elRect = el.getBoundingClientRect();
31
+
32
+ const constrainedX = Math.min(Math.max(clientX, 0), innerWidth - elRect.width);
33
+ const constrainedY = Math.min(Math.max(clientY, 0), innerHeight - elRect.height);
34
+
35
+ return { x: constrainedX, y: constrainedY };
36
+ };
37
+
38
+ // 鼠标移动时的处理函数
39
+ const onMouseMove = (e: MouseEvent, el: HTMLElement) => {
40
+ if (!isDragging.value) return;
41
+
42
+ // 移动的时候yqg-permission-tooltip隐藏
43
+ const tooltip = document.querySelector('.yqg-permission-tooltip') as HTMLElement;
44
+ if (tooltip) {
45
+ tooltip.style.display = 'none';
46
+ }
47
+
48
+ const { clientX, clientY } = e;
49
+ const { x, y } = constrainToScreen(clientX - startX.value + initialX.value, clientY - currentTop.value + initialY.value, el);
50
+
51
+ el.style.left = `${x}px`;
52
+ el.style.top = `${y}px`;
53
+
54
+ };
55
+
56
+ // 鼠标松开时的处理函数
57
+ const onMouseUp = (e: MouseEvent, el: HTMLElement) => {
58
+ if (!isDragging.value) return;
59
+
60
+ // 阻止点击事件触发
61
+ e.preventDefault(); // 阻止默认行为
62
+ e.stopPropagation(); // 阻止事件冒泡
63
+
64
+ // // 获取元素当前的位置
65
+ const { x: currentX, y: currentY } = getPosition(el);
66
+ if ( Math.abs(currentY - initialY.value) < 10 && Math.abs(currentX - initialX.value) < 10) {
67
+ showModal();
68
+ // 卸载拖拽
69
+ isDragging.value = false;
70
+
71
+ return;
72
+ }
73
+
74
+
75
+ isDragging.value = false;
76
+ const { clientY } = e;
77
+
78
+ // 在鼠标松开时设置x为初始位置,y为鼠标当前位置
79
+ const { x, y } = constrainToScreen(initialX.value, clientY - currentTop.value + initialY.value, el);
80
+ el.style.transition = 'all 0.3s';
81
+ el.style.left = `${x}px`;
82
+ el.style.top = `${y}px`;
83
+
84
+ // 清除事件监听
85
+ document.removeEventListener('mousemove', (e) => onMouseMove(e, el));
86
+ document.removeEventListener('mouseup', (e) => onMouseUp(e, el));
87
+ };
88
+
89
+ // 鼠标按下时的处理函数
90
+ const onMouseDown = (e: MouseEvent, el: HTMLElement) => {
91
+ isDragging.value = true;
92
+
93
+ // 阻止点击事件触发
94
+ e.preventDefault(); // 阻止默认行为
95
+ e.stopPropagation();
96
+
97
+ // 获取鼠标按下时的位置
98
+ startX.value = e.clientX;
99
+ currentTop.value = e.clientY;
100
+
101
+
102
+ // 获取元素的初始位置
103
+ const { x, y } = getPosition(el);
104
+ initialX.value = x;
105
+ initialY.value = y;
106
+ el.style.transition = 'none';
107
+
108
+ // 添加鼠标移动和鼠标松开事件监听
109
+ document.addEventListener('mousemove', (e) => onMouseMove(e, el));
110
+ document.addEventListener('mouseup', (e) => onMouseUp(e, el));
111
+ };
112
+
113
+ // 返回拖拽的绑定方法
114
+ const bindDraggable = () => {
115
+ onMounted(() => {
116
+ const el = dragElement.value?.$el;
117
+ if (el) {
118
+ el.addEventListener('mousedown', (e: MouseEvent) => onMouseDown(e, el));
119
+ }
120
+ });
121
+
122
+ onBeforeUnmount(() => {
123
+ const el = dragElement.value?.$el;
124
+ if (el) {
125
+ el.removeEventListener('mousedown', (e: MouseEvent) => onMouseDown(e, el));
126
+ }
127
+ });
128
+ };
129
+
130
+ bindDraggable();
131
+
132
+ return {
133
+ currentTop,
134
+ dragElement,
135
+ };
136
+ }
package/src/i18n/en-US.ts CHANGED
@@ -3,31 +3,35 @@ export default {
3
3
  applyPermission: '申请权限',
4
4
  applyReason: '申请理由',
5
5
  approvalProcess: '审批流程',
6
- applyReason1: '新入职员工申请',
7
- applyReason2: '临时项目',
8
- applyReason3: '数据查询与分析',
9
- applyReasonPlaceholder: '请尽可能详细描述申请理由',
6
+ applyReasonPlaceholder: '请尽可能详细说明申请原因和使用场景,不要只填写“工作需要”之类的理由,以免影响你获取权限的审批时间。',
7
+ applyReasonTips: '示例:由于XX项目需要,需要查看/操作XXXX场景/问题,涉及到XXX权限的使用,因此提交申请!',
10
8
  cancel: '取消',
11
9
  submit: '确定',
12
10
  close: '关闭',
13
11
  viewApprovalDetail: '查看审批详情',
14
- applyMore: '继续申请其他权限',
12
+ applyMore: '继续申请其他权限',
15
13
  successTips: '已提交申请,审批通过后可拥有相关权限',
16
14
  resoultTitle: '操作结果反馈',
17
15
  start: '发起',
18
16
  end: '结束',
19
- noNeed: '无需审批',
17
+ noNeed: '无需审批',
20
18
  adaptDepartment: '适用部门',
21
- noApprovalProcess: '无审批流程',
19
+ noApprovalProcess: '无审批流程',
22
20
  excessTips: '一次最多可申请{number}个权限',
23
- unavailableTips: '您暂无权限查看/操作该页面,请点击按钮进行申请',
21
+ unavailableTips: '您暂无权限查看/操作该页面,请点击下方按钮进行申请',
24
22
  appliedTips: '权限已申请,正在{status}...',
25
23
  unapplyTips: '您暂无权限查看/操作该页面,且该页面中没有您可以申请的权限,',
26
- callManager: '如有需要请联系系统管理员',
27
- manager: '系统管理员',
24
+ callManager: '如有需要请联系系统管理员',
25
+ manager: '系统管理员',
28
26
  availableTime: '有效期',
29
- selectPlaceholder: '请选择权限点',
30
- reasonPlaceholder: '请输入申请理由',
27
+ selectPlaceholder: '请选择权限点',
28
+ reasonPlaceholder: '请输入申请理由',
29
+ maxCountTips: '一次最多只可申请{count}个权限',
30
+ maxLengthTips: '最多{length}个字符',
31
+ lastDays: '{count}天后到期',
32
+ taday: '今天到期',
33
+ clickToApply: '点击申请权限',
34
+ noPermissionTips: '该菜单下暂无权限点',
31
35
  availiables: {
32
36
  SEVEN_DAYS: '7天',
33
37
  THIRTY_DAYS: '30天',
@@ -43,10 +47,11 @@ export default {
43
47
  status: {
44
48
  PENDING: '审批中',
45
49
  NO: '不可申请',
46
- OWNER: '已拥有',
50
+ OWNER: '永久拥有',
51
+ TEMP_OWNER: '临时拥有',
47
52
  },
48
- operationType: {
49
- QUERY: '查询',
50
- MANAGE: '操作',
51
- }
53
+ operationType: {
54
+ QUERY: '查询',
55
+ MANAGE: '操作',
56
+ }
52
57
  };
package/src/i18n/zh-CH.ts CHANGED
@@ -3,10 +3,8 @@ export default {
3
3
  applyPermission: '申请权限',
4
4
  applyReason: '申请理由',
5
5
  approvalProcess: '审批流程',
6
- applyReason1: '新入职员工申请',
7
- applyReason2: '临时项目',
8
- applyReason3: '数据查询与分析',
9
- applyReasonPlaceholder: '请尽可能详细描述申请理由',
6
+ applyReasonPlaceholder: '请尽可能详细说明申请原因和使用场景,不要只填写“工作需要”之类的理由,以免影响你获取权限的审批时间。',
7
+ applyReasonTips: '示例:由于XX项目需要,需要查看/操作XXXX场景/问题,涉及到XXX权限的使用,因此提交申请!',
10
8
  cancel: '取消',
11
9
  submit: '确定',
12
10
  close: '关闭',
@@ -20,7 +18,7 @@ export default {
20
18
  adaptDepartment: '适用部门',
21
19
  noApprovalProcess: '无审批流程',
22
20
  excessTips: '一次最多可申请{number}个权限',
23
- unavailableTips: '您暂无权限查看/操作该页面,请点击按钮进行申请',
21
+ unavailableTips: '您暂无权限查看/操作该页面,请点击下方按钮进行申请',
24
22
  appliedTips: '权限已申请,正在{status}...',
25
23
  unapplyTips: '您暂无权限查看/操作该页面,且该页面中没有您可以申请的权限,',
26
24
  callManager: '如有需要请联系系统管理员',
@@ -30,6 +28,10 @@ export default {
30
28
  reasonPlaceholder: '请输入申请理由',
31
29
  maxCountTips: '一次最多只可申请{count}个权限',
32
30
  maxLengthTips: '最多{length}个字符',
31
+ lastDays: '{count}天后到期',
32
+ taday: '今天到期',
33
+ clickToApply: '点击申请权限',
34
+ noPermissionTips: '该菜单下暂无权限点',
33
35
  availiables: {
34
36
  SEVEN_DAYS: '7天',
35
37
  THIRTY_DAYS: '30天',
@@ -15,17 +15,19 @@ declare type PermissionType = {
15
15
  name: string;
16
16
  roleId: number;
17
17
  desc: string;
18
- businessCode: string;
19
- feature: string;
18
+ businessCode: string;
19
+ feature: string;
20
20
  validTime?: string;
21
- operationType: string;
22
- securityLevel: LevelType;
23
- relatedDepartments?: any[];
21
+ operationType: string;
22
+ securityLevel: LevelType;
23
+ relatedDepartments?: any[];
24
+ relatedCompleteNames: string[];
24
25
  relatedDepartmentIds?: number[];
25
26
  curDepartmentId?: number;
26
27
  businessApplyType: StatusType;
27
28
  oaFlowUrl?: string;
28
29
  children?: PermissionListType;
30
+ ownStatusVO: any;
29
31
  admin?: any[];
30
32
  };
31
33
  declare type PermissionListType = PermissionType[];
@@ -34,6 +36,7 @@ declare type LevelMapType = {
34
36
  [key in LevelType]: {
35
37
  color: string;
36
38
  text: string;
39
+ background: string;
37
40
  };
38
41
  };
39
42
 
@@ -52,3 +55,6 @@ declare type formStateType = {
52
55
  submitWorkNumber: string,
53
56
 
54
57
  };
58
+ declare interface Window {
59
+ YQG_PERMISSION_CALLBACK?: () => void;
60
+ }
package/src/vite-env.d.ts CHANGED
@@ -1 +1 @@
1
- /// <reference types="vite/client" />
1
+ /// <reference types="vite/client" />
package/vite.config.ts CHANGED
@@ -13,7 +13,7 @@ export default defineConfig({
13
13
  },
14
14
  server: {
15
15
  proxy: {
16
- '/admin': {
16
+ '/crane': {
17
17
  target: 'https://crane-test.yangqianguan.com',
18
18
  changeOrigin: true,
19
19
  },