gaoluyang
5 天以前 c7566bba45e6f90594d36ec4b80ac4ff89f577ee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
<template>
  <div :style="{ minWidth: minWidth }">
    <template v-for="(o, key) in operations" :key="key">
      <!-- 上传:与原实现一致,走 el-upload 自身的 action 自动上传 -->
      <el-upload
        v-if="o.type === 'upload'"
        v-show="isVisible(o)"
        :key="uploadKey"
        :ref="el => setUploadRef(key, el)"
        :action="uploadAction(o)"
        :multiple="!!o.multiple"
        :limit="1"
        :disabled="isDisabled(o)"
        :accept="o.accept || DEFAULT_ACCEPT"
        :headers="currentUploadHeader()"
        :before-upload="beforeUpload"
        :on-change="(file, fileList) => onChange(file, fileList, key)"
        :on-error="() => onError(key)"
        :on-success="response => onSuccess(response, key)"
        :on-exceed="onExceed"
        :show-file-list="false"
        style="display: inline-block; width: 50px"
      >
        <el-button link :size="normalizeSize(o.size) || 'small'" :disabled="isDisabled(o)">{{ o.name }}</el-button>
      </el-upload>
 
      <el-button
        v-else
        v-show="isVisible(o)"
        size="small"
        :disabled="isDisabled(o)"
        :icon="$icon(iconName(o))"
        :plain="o.plain"
        :link="isTextButton(o)"
        :style="{ color: o.color }"
        :type="buttonType(o)"
        @click="onClick(o)"
      >
        {{ o.name }}
      </el-button>
    </template>
  </div>
</template>
 
<script setup>
import { ElMessage } from 'element-plus'
import { actionWidth } from './action-width'
 
const DEFAULT_ACCEPT = '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'
 
const props = defineProps({
  operations: {
    type: Array,
    default: () => []
  },
  row: {
    type: Object,
    default: () => ({})
  }
})
 
const { proxy } = getCurrentInstance()
 
// 每个实例只服务一行,用 Map 收函数 ref,避免原实现「以 $index 为 key 的全局 record」带来的串行
const uploadRefs = new Map()
const uploadKey = ref(Date.now())
 
const minWidth = computed(() => `${actionWidth(props.operations, props.row)}px`)
 
function setUploadRef(key, el) {
  if (el) {
    uploadRefs.set(key, el)
  } else {
    uploadRefs.delete(key)
  }
}
 
function isVisible(o) {
  return o.showHide ? o.showHide(props.row) : true
}
 
function isDisabled(o) {
  return o.disabled ? o.disabled(props.row) : false
}
 
// 操作按钮回调:页面在 operation 项里用 clickFun(row) 声明点击行为
function onClick(o) {
  if (typeof o.clickFun === 'function') {
    o.clickFun(props.row)
  }
}
 
// o.type 可能是函数,取值与原实现的 typeFn 过滤器一致
function resolveButtonType(o) {
  return typeof o.type === 'function' ? o.type(props.row) : o.type
}
 
// element-ui 的 type="text" 在 element-plus 里已废弃,等价形态是 link 布尔属性
function isTextButton(o) {
  return resolveButtonType(o) === 'text'
}
 
function buttonType(o) {
  // 删除类操作统一走 danger 语义色,避免内联 #f56c6c 绕过 token(暗色模式会失效)
  if (o.name === '删除') return 'danger'
  const t = resolveButtonType(o)
  // element-ui 的 type="text" 等价于 element-plus 的 link 按钮;显式给 primary,
  // 与各页面手写的 <el-button link type="primary"> 保持一致(否则默认是灰色文字)
  return t === 'text' ? 'primary' : t
}
 
function iconName(o) {
  if (Object.prototype.hasOwnProperty.call(o, 'icon')) return o.icon
  if (o.name === '编辑' || o.name === '修改') return 'el-icon-edit'
  if (o.name === '查看') return 'el-icon-view'
  return o.icon
}
 
// element-plus 只认 large/default/small,旧值 mini/medium 会触发 prop 校验告警
function normalizeSize(size) {
  if (size === 'mini') return 'small'
  if (size === 'medium') return 'default'
  return size
}
 
function uploadAction(o) {
  const id = o.uploadIdFun ? o.uploadIdFun(props.row) : props.row.id
  return proxy.javaApi + o.url + '?id=' + id
}
 
function currentUploadHeader() {
  return proxy.uploadHeader
}
 
function beforeUpload(file) {
  if (file.size > 1024 * 1024 * 10) {
    ElMessage.error('上传文件不超过10M')
    return false
  }
  return true
}
 
function onChange(file, fileList, key) {
  // 超出限制时移除最早的文件
  if (fileList.length > 1) {
    uploadRefs.get(key)?.handleRemove(fileList[0])
  }
}
 
function onSuccess(response, key) {
  if (response.code == 200) {
    uploadRefs.get(key)?.clearFiles()
    ElMessage.success('上传成功')
    // 换 key 强制重建组件,清掉内部文件列表
    uploadKey.value = Date.now()
  } else {
    ElMessage.error(response.message)
  }
}
 
// 必须清空内部文件列表,否则 limit=1 会让下一次选择直接触发 on-exceed
function onError(key) {
  ElMessage.error('文件上传失败,请重试')
  uploadRefs.get(key)?.clearFiles()
}
 
function onExceed() {
  ElMessage.warning('超出文件个数')
}
</script>