index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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. console.log('list');
  174. initFileList();
  175. }
  176. );
  177. const handleClearFile = () => {
  178. uploadRef.value?.clear();
  179. console.log('清空', uploadRef.value);
  180. };
  181. expose({
  182. handleClearFile
  183. });
  184. const CropperModal = ref();
  185. const onBeforeUpload = async (options: any) => {
  186. const file = options.file;
  187. // 文件大小
  188. let isLt2M = true;
  189. if (props.size) {
  190. isLt2M = file.file.size / 1024 / 1024 < props.size;
  191. if (!isLt2M) {
  192. message.error(`文件大小不能超过${props.size}M`);
  193. return false;
  194. }
  195. }
  196. if (!isLt2M) {
  197. return isLt2M;
  198. }
  199. // 是否裁切
  200. if (props.cropper) {
  201. getBase64(file.file, (imageUrl: any) => {
  202. const target = Object.assign({}, props.options, {
  203. img: imageUrl,
  204. name: file.file.name // 上传文件名
  205. });
  206. visiable.value = true;
  207. setTimeout(() => {
  208. CropperModal.value?.edit(target);
  209. console.log(CropperModal.value, 'cropper');
  210. }, 100);
  211. });
  212. return false;
  213. }
  214. try {
  215. btnLoading.value = true;
  216. console.log(props.path, file.file);
  217. const name = file.file.name;
  218. const suffix = name.slice(name.lastIndexOf('.'));
  219. // const months = dayjs().format('MM')
  220. const fileName = `${props.path}${
  221. props.fileName || Date.now() + suffix
  222. }`;
  223. const obj = {
  224. filename: fileName,
  225. bucketName: props.bucketName,
  226. postData: {
  227. filename: fileName,
  228. acl: 'public-read',
  229. key: fileName,
  230. unknowValueField: []
  231. }
  232. };
  233. // const { data } = await policy(obj);
  234. // state.policy = data.policy;
  235. // state.signature = data.signature;
  236. // state.key = fileName;
  237. // state.KSSAccessKeyId = data.kssAccessKeyId;
  238. // state.name = fileName;
  239. // tempFiileBuffer.value = file.file;
  240. const { data } = await getUploadSign(obj);
  241. state.push({
  242. id: file.id,
  243. tempFiileBuffer: file.file,
  244. policy: data.policy,
  245. signature: data.signature,
  246. acl: 'public-read',
  247. key: fileName,
  248. KSSAccessKeyId: data.kssAccessKeyId,
  249. name: fileName
  250. });
  251. } catch {
  252. //
  253. // message.error('上传失败')
  254. btnLoading.value = false;
  255. return false;
  256. }
  257. return true;
  258. };
  259. const getBase64 = async (img: any, callback: any) => {
  260. const reader = new FileReader();
  261. reader.addEventListener('load', () => callback(reader.result));
  262. reader.readAsDataURL(img);
  263. };
  264. const onFinish = (options: any) => {
  265. const item = state.find((c: any) => c.id == options.file.id);
  266. // const url = ossUploadUrl + state.key;
  267. emit('update:fileList', options.file.url);
  268. emit('readFileInputEventAsArrayBuffer', item.tempFiileBuffer);
  269. // options.file.url = url;
  270. visiable.value = false;
  271. btnLoading.value = false;
  272. };
  273. const onRemove = async (options: any) => {
  274. console.log('🚀 ~ options', options);
  275. emit('update:fileList', '');
  276. emit('remove');
  277. btnLoading.value = false;
  278. };
  279. const onCustomRequest = ({
  280. file,
  281. // data,
  282. // headers,
  283. // withCredentials,
  284. action,
  285. onFinish,
  286. onError,
  287. onProgress
  288. }: UploadCustomRequestOptions) => {
  289. const item = state.find((c: any) => {
  290. return c.id == file.id;
  291. });
  292. item.file = file;
  293. onFileUpload({ file, action, data: item, onProgress, onFinish, onError });
  294. };
  295. // 裁切失败
  296. // const cropperNo = () => {}
  297. // 裁切成功
  298. const cropperOk = async (blob: any) => {
  299. try {
  300. // const months = dayjs().format('MM')
  301. const fileName = `${props.path}${
  302. props.fileName || new Date().getTime() + '.png'
  303. }`;
  304. const obj = {
  305. filename: fileName,
  306. bucketName: props.bucketName,
  307. postData: {
  308. filename: fileName,
  309. acl: 'public-read',
  310. key: fileName,
  311. unknowValueField: []
  312. }
  313. };
  314. // const { data } = await policy(obj);
  315. const { data } = await getUploadSign(obj);
  316. const formData = {
  317. policy: data.policy,
  318. signature: data.signature,
  319. acl: 'public-read',
  320. key: fileName,
  321. KSSAccessKeyId: data.kssAccessKeyId,
  322. name: fileName,
  323. file: blob
  324. };
  325. const res = await onOnlyFileUpload(ossUploadUrl, formData);
  326. console.log(res, 'upload');
  327. emit('update:fileList', res);
  328. visiable.value = false;
  329. } catch {
  330. //
  331. // message.error('上传失败');
  332. return false;
  333. }
  334. };
  335. return () => (
  336. <div>
  337. <NUpload
  338. ref={uploadRef}
  339. action={ossUploadUrl}
  340. // data={state}
  341. customRequest={onCustomRequest}
  342. v-model:fileList={fileListRef.value}
  343. listType={props.listType}
  344. accept={props.accept}
  345. multiple={props.multiple}
  346. max={props.max}
  347. disabled={props.disabled}
  348. showFileList={props.showFileList}
  349. showPreviewButton
  350. onBeforeUpload={(options: any) => onBeforeUpload(options)}
  351. onFinish={(options: any) => onFinish(options)}
  352. onRemove={(options: any) => onRemove(options)}>
  353. {props.showType === 'default' && props.listType === 'image' && (
  354. <NButton loading={btnLoading.value} type="primary">
  355. {props.text}
  356. </NButton>
  357. )}
  358. {props.showType === 'custom' && slots.custom && slots.custom()}
  359. </NUpload>
  360. {props.tips && (
  361. <p style="font-size: 13px; color: #666; padding-top: 4px;">
  362. {props.tips}
  363. </p>
  364. )}
  365. <NModal
  366. v-model:show={visiable.value}
  367. preset="dialog"
  368. showIcon={false}
  369. class={['modalTitle background']}
  370. title="上传图片"
  371. style={{ width: '800px' }}>
  372. {/* @cropper-no="error" @cropper-ok="success" */}
  373. <Copper
  374. // ref="CropperModal"
  375. ref={CropperModal}
  376. onClose={() => (visiable.value = false)}
  377. onCropperOk={cropperOk}
  378. />
  379. </NModal>
  380. </div>
  381. );
  382. }
  383. });