index.tsx 9.8 KB

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