index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. import {
  2. NButton,
  3. NModal,
  4. NUpload,
  5. UploadCustomRequestOptions,
  6. UploadFileInfo,
  7. useMessage
  8. } from 'naive-ui';
  9. import { defineComponent, watch, PropType, reactive, ref } from 'vue';
  10. import { policy } from './api';
  11. import Copper from './copper';
  12. import axios from 'axios';
  13. import {
  14. getUploadSign,
  15. onFileUpload,
  16. onOnlyFileUpload
  17. } from '/src/helpers/oss-file-upload';
  18. export default defineComponent({
  19. name: 'upload-file',
  20. props: {
  21. fileList: {
  22. type: String,
  23. default: ''
  24. },
  25. imageList: {
  26. type: Array,
  27. default: () => []
  28. },
  29. accept: {
  30. // 支持类型
  31. type: String,
  32. default: '.jpg,.png,.jpeg,.gif'
  33. },
  34. listType: {
  35. type: String as PropType<'image' | 'image-card'>,
  36. default: 'image-card'
  37. },
  38. showType: {
  39. type: String as PropType<'default' | 'custom'>,
  40. default: 'default'
  41. },
  42. showFileList: {
  43. type: Boolean,
  44. default: true
  45. },
  46. // width: {
  47. // type: Number,
  48. // default: 96
  49. // },
  50. // height: {
  51. // type: Number,
  52. // default: 96
  53. // },
  54. text: {
  55. type: String as PropType<string>,
  56. default: '上传文件'
  57. },
  58. size: {
  59. // 文件大小
  60. type: Number as PropType<number>,
  61. default: 5
  62. },
  63. max: {
  64. type: Number as PropType<number>,
  65. default: 1
  66. },
  67. multiple: {
  68. type: Boolean as PropType<boolean>,
  69. default: false
  70. },
  71. disabled: {
  72. type: Boolean as PropType<boolean>,
  73. default: false
  74. },
  75. tips: {
  76. type: String as PropType<string>,
  77. default: ''
  78. },
  79. bucketName: {
  80. type: String,
  81. default: 'gyt'
  82. },
  83. path: {
  84. type: String,
  85. default: ''
  86. },
  87. fileName: {
  88. type: String,
  89. default: ''
  90. },
  91. cropper: {
  92. // 是否裁切, 只有图片才支持
  93. type: Boolean as PropType<boolean>,
  94. default: false
  95. },
  96. options: {
  97. type: Object,
  98. default: () => {
  99. return {
  100. viewMode: 0,
  101. autoCrop: true, //是否默认生成截图框
  102. enlarge: 1, // 图片放大倍数
  103. autoCropWidth: 200, //默认生成截图框宽度
  104. autoCropHeight: 200, //默认生成截图框高度
  105. fixedBox: false, //是否固定截图框大小 不允许改变
  106. previewsCircle: true, //预览图是否是原图形
  107. title: '上传图片'
  108. };
  109. }
  110. }
  111. },
  112. // readFileInputEventAsArrayBuffer 只会在文件的时间回调
  113. emits: [
  114. 'update:fileList',
  115. 'close',
  116. 'readFileInputEventAsArrayBuffer',
  117. 'remove'
  118. ],
  119. setup(props, { emit, expose, slots }) {
  120. const ossUploadUrl = `https://${props.bucketName}.ks3-cn-beijing.ksyuncs.com/`;
  121. const message = useMessage();
  122. const visiable = ref<boolean>(false);
  123. const btnLoading = ref<boolean>(false);
  124. const tempFiileBuffer = ref();
  125. const uploadRef = ref();
  126. // const state = reactive({
  127. // policy: '',
  128. // signature: '',
  129. // key: '',
  130. // KSSAccessKeyId: '',
  131. // acl: 'public-read',
  132. // name: ''
  133. // }) as any;
  134. const state = reactive([]) as any;
  135. const fileListRef = ref<UploadFileInfo[]>([]);
  136. const initFileList = () => {
  137. if (props.fileList) {
  138. const splitName = props.fileList.split('/');
  139. fileListRef.value = [
  140. {
  141. id: new Date().getTime().toString(),
  142. name: splitName[splitName.length - 1],
  143. status: 'finished',
  144. url: props.fileList
  145. }
  146. ];
  147. } else if (Array.isArray(props.imageList)) {
  148. const list: any = [];
  149. props.imageList.forEach((n: any) => {
  150. const splitName = n.split('/');
  151. list.push({
  152. id: Date.now().toString(),
  153. name: splitName[splitName.length - 1],
  154. status: 'finished',
  155. url: n
  156. });
  157. });
  158. fileListRef.value = list;
  159. } else {
  160. fileListRef.value = [];
  161. }
  162. };
  163. initFileList();
  164. watch(
  165. () => props.imageList,
  166. () => {
  167. initFileList();
  168. }
  169. );
  170. watch(
  171. () => props.fileList,
  172. () => {
  173. initFileList();
  174. }
  175. );
  176. const handleClearFile = () => {
  177. uploadRef.value?.clear();
  178. console.log('清空', uploadRef.value);
  179. };
  180. expose({
  181. handleClearFile
  182. });
  183. const CropperModal = ref();
  184. const onBeforeUpload = async (options: any) => {
  185. const file = options.file;
  186. // 文件大小
  187. let isLt2M = true;
  188. if (props.size) {
  189. isLt2M = file.file.size / 1024 / 1024 < props.size;
  190. if (!isLt2M) {
  191. message.error(`文件大小不能超过${props.size}M`);
  192. return false;
  193. }
  194. }
  195. if (!isLt2M) {
  196. return isLt2M;
  197. }
  198. // 是否裁切
  199. if (props.cropper) {
  200. getBase64(file.file, (imageUrl: any) => {
  201. const target = Object.assign({}, props.options, {
  202. img: imageUrl,
  203. name: file.file.name // 上传文件名
  204. });
  205. visiable.value = true;
  206. setTimeout(() => {
  207. CropperModal.value?.edit(target);
  208. console.log(CropperModal.value, 'cropper');
  209. }, 100);
  210. });
  211. return false;
  212. }
  213. try {
  214. btnLoading.value = true;
  215. console.log(props.path, file.file);
  216. const name = file.file.name;
  217. const suffix = name.slice(name.lastIndexOf('.'));
  218. // const months = dayjs().format('MM')
  219. const fileName = `${props.path}${
  220. props.fileName || Date.now() + suffix
  221. }`;
  222. const obj = {
  223. filename: fileName,
  224. bucketName: props.bucketName,
  225. postData: {
  226. filename: fileName,
  227. acl: 'public-read',
  228. key: fileName,
  229. unknowValueField: []
  230. }
  231. };
  232. // const { data } = await policy(obj);
  233. // state.policy = data.policy;
  234. // state.signature = data.signature;
  235. // state.key = fileName;
  236. // state.KSSAccessKeyId = data.kssAccessKeyId;
  237. // state.name = fileName;
  238. // tempFiileBuffer.value = file.file;
  239. const { data } = await getUploadSign(obj);
  240. state.push({
  241. id: file.id,
  242. tempFiileBuffer: file.file,
  243. policy: data.policy,
  244. signature: data.signature,
  245. acl: 'public-read',
  246. key: fileName,
  247. KSSAccessKeyId: data.kssAccessKeyId,
  248. name: fileName
  249. });
  250. } catch {
  251. //
  252. // message.error('上传失败')
  253. btnLoading.value = false;
  254. return false;
  255. }
  256. return true;
  257. };
  258. const getBase64 = async (img: any, callback: any) => {
  259. const reader = new FileReader();
  260. reader.addEventListener('load', () => callback(reader.result));
  261. reader.readAsDataURL(img);
  262. };
  263. const onFinish = (options: any) => {
  264. const item = state.find((c: any) => c.id == options.file.id);
  265. // const url = ossUploadUrl + state.key;
  266. emit('update:fileList', options.file.url);
  267. emit('readFileInputEventAsArrayBuffer', item.tempFiileBuffer);
  268. // options.file.url = url;
  269. visiable.value = false;
  270. btnLoading.value = false;
  271. };
  272. const onRemove = async (options: any) => {
  273. console.log('🚀 ~ options', options);
  274. emit('update:fileList', '');
  275. emit('remove');
  276. btnLoading.value = false;
  277. };
  278. const onCustomRequest = ({
  279. file,
  280. // data,
  281. // headers,
  282. // withCredentials,
  283. action,
  284. onFinish,
  285. onError,
  286. onProgress
  287. }: UploadCustomRequestOptions) => {
  288. const item = state.find((c: any) => {
  289. return c.id == file.id;
  290. });
  291. item.file = file;
  292. onFileUpload({ file, action, data: item, onProgress, onFinish, onError });
  293. };
  294. // 裁切失败
  295. // const cropperNo = () => {}
  296. // 裁切成功
  297. const cropperOk = async (blob: any) => {
  298. try {
  299. // const months = dayjs().format('MM')
  300. const fileName = `${props.path}${
  301. props.fileName || new Date().getTime() + '.png'
  302. }`;
  303. const obj = {
  304. filename: fileName,
  305. bucketName: props.bucketName,
  306. postData: {
  307. filename: fileName,
  308. acl: 'public-read',
  309. key: fileName,
  310. unknowValueField: []
  311. }
  312. };
  313. // const { data } = await policy(obj);
  314. const { data } = await getUploadSign(obj);
  315. const formData = {
  316. policy: data.policy,
  317. signature: data.signature,
  318. acl: 'public-read',
  319. key: fileName,
  320. KSSAccessKeyId: data.kssAccessKeyId,
  321. name: fileName,
  322. file: blob
  323. };
  324. const res = await onOnlyFileUpload(ossUploadUrl, formData);
  325. console.log(res, 'upload');
  326. emit('update:fileList', res);
  327. visiable.value = false;
  328. } catch {
  329. //
  330. // message.error('上传失败');
  331. return false;
  332. }
  333. };
  334. return () => (
  335. <div>
  336. <NUpload
  337. ref={uploadRef}
  338. action={ossUploadUrl}
  339. // data={state}
  340. customRequest={onCustomRequest}
  341. v-model:fileList={fileListRef.value}
  342. listType={props.listType}
  343. accept={props.accept}
  344. multiple={props.multiple}
  345. max={props.max}
  346. disabled={props.disabled}
  347. showFileList={props.showFileList}
  348. showPreviewButton
  349. onBeforeUpload={(options: any) => onBeforeUpload(options)}
  350. onFinish={(options: any) => onFinish(options)}
  351. onRemove={(options: any) => onRemove(options)}>
  352. {props.showType === 'default' && props.listType === 'image' && (
  353. <NButton loading={btnLoading.value} type="primary">
  354. {props.text}
  355. </NButton>
  356. )}
  357. {props.showType === 'custom' && slots.custom && slots.custom()}
  358. </NUpload>
  359. {props.tips && (
  360. <p style="font-size: 13px; color: #666; padding-top: 4px;">
  361. {props.tips}
  362. </p>
  363. )}
  364. <NModal
  365. v-model:show={visiable.value}
  366. preset="dialog"
  367. showIcon={false}
  368. class={['modalTitle background']}
  369. title="上传图片"
  370. style={{ width: '800px' }}>
  371. {/* @cropper-no="error" @cropper-ok="success" */}
  372. <Copper
  373. // ref="CropperModal"
  374. ref={CropperModal}
  375. onClose={() => (visiable.value = false)}
  376. onCropperOk={cropperOk}
  377. />
  378. </NModal>
  379. </div>
  380. );
  381. }
  382. });