wangxx 4 mesi fa
parent
commit
8655a94cff

+ 65 - 0
src/directive/common/copyText.js

@@ -0,0 +1,65 @@
1
+/**
2
+* v-copyText 复制文本内容
3
+* Copyright (c) 2022 ruoyi
4
+*/
5
+export default {
6
+  beforeMount(el, { value, arg }) {
7
+    if (arg === "callback") {
8
+      el.$copyCallback = value
9
+    } else {
10
+      el.$copyValue = value
11
+      const handler = () => {
12
+        copyTextToClipboard(el.$copyValue)
13
+        if (el.$copyCallback) {
14
+          el.$copyCallback(el.$copyValue)
15
+        }
16
+      }
17
+      el.addEventListener("click", handler)
18
+      el.$destroyCopy = () => el.removeEventListener("click", handler)
19
+    }
20
+  }
21
+}
22
+
23
+function copyTextToClipboard(input, { target = document.body } = {}) {
24
+  const element = document.createElement('textarea')
25
+  const previouslyFocusedElement = document.activeElement
26
+
27
+  element.value = input
28
+
29
+  // Prevent keyboard from showing on mobile
30
+  element.setAttribute('readonly', '')
31
+
32
+  element.style.contain = 'strict'
33
+  element.style.position = 'absolute'
34
+  element.style.left = '-9999px'
35
+  element.style.fontSize = '12pt' // Prevent zooming on iOS
36
+
37
+  const selection = document.getSelection()
38
+  const originalRange = selection.rangeCount > 0 && selection.getRangeAt(0)
39
+
40
+  target.append(element)
41
+  element.select()
42
+
43
+  // Explicit selection workaround for iOS
44
+  element.selectionStart = 0
45
+  element.selectionEnd = input.length
46
+
47
+  let isSuccess = false
48
+  try {
49
+    isSuccess = document.execCommand('copy')
50
+  } catch { }
51
+
52
+  element.remove()
53
+
54
+  if (originalRange) {
55
+    selection.removeAllRanges()
56
+    selection.addRange(originalRange)
57
+  }
58
+
59
+  // Get the focus back on the previously focused element, if any
60
+  if (previouslyFocusedElement) {
61
+    previouslyFocusedElement.focus()
62
+  }
63
+
64
+  return isSuccess
65
+}

+ 11 - 0
src/directive/index.js

@@ -0,0 +1,11 @@
1
+import hasRole from './permission/hasRole'
2
+import hasPermi from './permission/hasPermi'
3
+import copyText from './common/copyText'
4
+import { vCardExpand } from './view/index'
5
+
6
+export default function directive(app){
7
+  app.directive('hasRole', hasRole)
8
+  app.directive('hasPermi', hasPermi)
9
+  app.directive('copyText', copyText)
10
+  app.directive('card-expand', vCardExpand)
11
+}

+ 27 - 0
src/directive/permission/hasPermi.js

@@ -0,0 +1,27 @@
1
+ /**
2
+ * v-hasPermi 操作权限处理
3
+ * Copyright (c) 2019 ruoyi
4
+ */
5
+import useUserStore from '@/store/modules/user'
6
+
7
+export default {
8
+  mounted(el, binding, vnode) {
9
+    const { value } = binding
10
+    const all_permission = "*:*:*"
11
+    const permissions = useUserStore().permissions
12
+
13
+    if (value && value instanceof Array && value.length > 0) {
14
+      const permissionFlag = value
15
+
16
+      const hasPermissions = permissions.some(permission => {
17
+        return all_permission === permission || permissionFlag.includes(permission)
18
+      })
19
+
20
+      if (!hasPermissions) {
21
+        el.parentNode && el.parentNode.removeChild(el)
22
+      }
23
+    } else {
24
+      throw new Error(`请设置操作权限标签值`)
25
+    }
26
+  }
27
+}

+ 27 - 0
src/directive/permission/hasRole.js

@@ -0,0 +1,27 @@
1
+ /**
2
+ * v-hasRole 角色权限处理
3
+ * Copyright (c) 2019 ruoyi
4
+ */
5
+import useUserStore from '@/store/modules/user'
6
+
7
+export default {
8
+  mounted(el, binding, vnode) {
9
+    const { value } = binding
10
+    const super_admin = "admin"
11
+    const roles = useUserStore().roles
12
+
13
+    if (value && value instanceof Array && value.length > 0) {
14
+      const roleFlag = value
15
+
16
+      const hasRole = roles.some(role => {
17
+        return super_admin === role || roleFlag.includes(role)
18
+      })
19
+
20
+      if (!hasRole) {
21
+        el.parentNode && el.parentNode.removeChild(el)
22
+      }
23
+    } else {
24
+      throw new Error(`请设置角色权限标签值`)
25
+    }
26
+  }
27
+}

+ 241 - 0
src/directive/view/index.js

@@ -0,0 +1,241 @@
1
+/**
2
+ * 卡片展开收起指令
3
+ * 完全独立的自定义指令,内置控制按钮和状态管理
4
+ */
5
+
6
+import { nextTick } from 'vue'
7
+
8
+// 存储所有卡片的状态
9
+const cardStates = new Map()
10
+
11
+/**
12
+ * 获取卡片内容元素
13
+ * @param {HTMLElement} cardEl - a-card 元素
14
+ * @returns {HTMLElement|null} 卡片内容元素
15
+ */
16
+function getCardBody (cardEl) {
17
+  return cardEl.querySelector('.el-card__body')
18
+}
19
+
20
+/**
21
+ * 设置卡片高度
22
+ * @param {HTMLElement} cardEl - a-card 元素
23
+ * @param {boolean} isExpanded - 是否展开
24
+ * @param {number} collapsedHeight - 收起时的高度
25
+ */
26
+function setCardHeight (cardEl, isExpanded, collapsedHeight = 60) {
27
+  const cardBody = getCardBody(cardEl)
28
+  if (!cardBody) return
29
+
30
+  // 获取原始 padding 值(如果还没有保存的话)
31
+  if (!cardEl._originalPadding) {
32
+    const computedStyle = window.getComputedStyle(cardBody)
33
+    cardEl._originalPadding = computedStyle.padding
34
+  }
35
+
36
+  if (isExpanded) {
37
+    cardBody.style.height = 'auto'
38
+    cardBody.style.overflow = 'visible'
39
+    cardBody.style.padding = cardEl._originalPadding // 恢复原始 padding
40
+  } else {
41
+    cardBody.style.height = `${collapsedHeight}px`
42
+    cardBody.style.overflow = 'hidden'
43
+    // 当收起高度为0时,同时设置padding为0
44
+    if (collapsedHeight === 0) {
45
+      cardBody.style.padding = '0'
46
+    } else {
47
+      cardBody.style.padding = cardEl._originalPadding // 保持原始 padding
48
+    }
49
+  }
50
+}
51
+
52
+/**
53
+ * 创建控制按钮
54
+ * @param {HTMLElement} cardEl - a-card 元素
55
+ * @param {Object} options - 配置选项
56
+ */
57
+function createControlButton (cardEl, options) {
58
+  const { expandText = '展开', collapseText = '收起' } = options
59
+
60
+  // 创建按钮容器
61
+  const buttonContainer = document.createElement('div')
62
+  buttonContainer.className = 'card-expand-control'
63
+  buttonContainer.style.cssText = `
64
+    position: absolute;
65
+    top: 14px;
66
+    right: 15px;
67
+    z-index: 10;
68
+    cursor: pointer;
69
+    color: #1677ff;
70
+    font-size: 14px;
71
+    user-select: none;
72
+    transition: color 0.3s;
73
+  `
74
+
75
+  // 创建按钮文本
76
+  const buttonText = document.createElement('span')
77
+  buttonText.textContent = expandText
78
+  buttonContainer.appendChild(buttonText)
79
+
80
+  // 添加悬停效果
81
+  buttonContainer.addEventListener('mouseenter', () => {
82
+    buttonContainer.style.color = '#4096ff'
83
+  })
84
+
85
+  buttonContainer.addEventListener('mouseleave', () => {
86
+    buttonContainer.style.color = '#1677ff'
87
+  })
88
+
89
+  // 点击事件
90
+  buttonContainer.addEventListener('click', (e) => {
91
+    e.preventDefault()
92
+    e.stopPropagation()
93
+    toggleCard(cardEl._cardExpandId)
94
+  })
95
+
96
+  // 将按钮添加到卡片
97
+  cardEl.style.position = 'relative'
98
+  cardEl.appendChild(buttonContainer)
99
+
100
+  // 存储按钮引用
101
+  cardEl._controlButton = buttonContainer
102
+  cardEl._buttonText = buttonText
103
+  cardEl._expandText = expandText
104
+  cardEl._collapseText = collapseText
105
+}
106
+
107
+/**
108
+ * 更新按钮文本
109
+ * @param {HTMLElement} cardEl - a-card 元素
110
+ * @param {boolean} isExpanded - 是否展开
111
+ */
112
+function updateButtonText (cardEl, isExpanded) {
113
+  if (cardEl._buttonText) {
114
+    cardEl._buttonText.textContent = isExpanded ? cardEl._collapseText : cardEl._expandText
115
+  }
116
+}
117
+
118
+/**
119
+ * 初始化卡片
120
+ * @param {HTMLElement} cardEl - a-card 元素
121
+ * @param {Object} options - 配置选项
122
+ */
123
+function initCard (cardEl, options = {}) {
124
+  const {
125
+    isExpanded = false,
126
+    expandText = '展开',
127
+    collapseText = '收起',
128
+    collapsedHeight = 0
129
+  } = options
130
+
131
+  const cardId = cardEl.getAttribute('data-card-id') || `card-${Date.now()}-${Math.random()}`
132
+  cardEl.setAttribute('data-card-id', cardId)
133
+
134
+  // 存储卡片状态
135
+  cardStates.set(cardId, {
136
+    isExpanded: isExpanded,
137
+    element: cardEl,
138
+    options,
139
+    collapsedHeight
140
+  })
141
+
142
+  // 设置初始状态
143
+  setCardHeight(cardEl, isExpanded, collapsedHeight)
144
+
145
+  // 创建控制按钮
146
+  createControlButton(cardEl, { expandText, collapseText })
147
+
148
+  // 更新按钮文本
149
+  updateButtonText(cardEl, isExpanded)
150
+
151
+  return cardId
152
+}
153
+
154
+/**
155
+ * 切换卡片状态
156
+ * @param {string} cardId - 卡片ID
157
+ */
158
+function toggleCard (cardId) {
159
+  const state = cardStates.get(cardId)
160
+  if (!state) return
161
+
162
+  state.isExpanded = !state.isExpanded
163
+  setCardHeight(state.element, state.isExpanded, state.collapsedHeight)
164
+  updateButtonText(state.element, state.isExpanded)
165
+
166
+  return state.isExpanded
167
+}
168
+
169
+/**
170
+ * 设置卡片状态
171
+ * @param {string} cardId - 卡片ID
172
+ * @param {boolean} isExpanded - 是否展开
173
+ */
174
+function setCardState (cardId, isExpanded) {
175
+  const state = cardStates.get(cardId)
176
+  if (!state) return
177
+
178
+  state.isExpanded = isExpanded
179
+  setCardHeight(state.element, isExpanded, state.collapsedHeight)
180
+  updateButtonText(state.element, isExpanded)
181
+}
182
+
183
+/**
184
+ * 获取卡片状态
185
+ * @param {string} cardId - 卡片ID
186
+ * @returns {boolean} 是否展开
187
+ */
188
+function getCardState (cardId) {
189
+  const state = cardStates.get(cardId)
190
+  return state ? state.isExpanded : false
191
+}
192
+
193
+/**
194
+ * 卡片展开收起指令
195
+ * @param {HTMLElement} el - a-card 元素
196
+ * @param {Object} binding - 绑定对象
197
+ * @param {Object} value - 配置选项
198
+ * @param {boolean} value.isExpanded - 是否展开
199
+ * @param {string} value.expandText - 展开文本
200
+ * @param {string} value.collapseText - 收起文本
201
+ * @param {number} value.collapsedHeight - 收起时的高度
202
+ * @returns {void}
203
+ * @example
204
+ * <a-card v-card-expand="{ isExpanded: true, expandText: '展开', collapseText: '收起', collapsedHeight: 0 }">
205
+ *   <div>卡片内容</div>
206
+ * </a-card>
207
+ */
208
+export const vCardExpand = {
209
+  mounted (el, binding) {
210
+    const { value = {} } = binding
211
+
212
+    nextTick(() => {
213
+      const cardId = initCard(el, value)
214
+      el._cardExpandId = cardId
215
+    })
216
+  },
217
+
218
+  updated (el, binding) {
219
+    const { value = {} } = binding
220
+    const { isExpanded } = value
221
+
222
+    // 通过 isExpanded 控制状态
223
+    if (typeof isExpanded === 'boolean' && el._cardExpandId) {
224
+      setCardState(el._cardExpandId, isExpanded)
225
+    }
226
+  },
227
+
228
+  unmounted (el) {
229
+    // 清理资源
230
+    if (el._cardExpandId) {
231
+      cardStates.delete(el._cardExpandId)
232
+    }
233
+  }
234
+}
235
+
236
+// 导出工具函数供外部使用
237
+export {
238
+  toggleCard,
239
+  setCardState,
240
+  getCardState
241
+}