├── .all-contributorsrc
├── .babelrc.js
├── .editorconfig
├── .eslintrc.js
├── .github
└── badge.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── .travis.yml
├── LICENSE
├── README-zh.md
├── README.md
├── build.sh
├── build
└── rollup.config.js
├── docs
├── accept.md
├── basic.md
├── before-upload.md
├── draggable.md
├── faq.md
├── file.md
├── guide-ali-oss.md
├── guide-global-register.md
├── loaded.md
├── max.md
├── multiple.md
├── on-click.md
├── on-oversize.md
├── request.md
├── size.md
├── slot-default.md
├── slot-placeholder-spinner.md
└── tip.md
├── netlify.sh
├── notify.sh
├── package.json
├── src
├── components
│ ├── draggable-list.vue
│ └── upload-item.vue
├── index.js
├── upload-to-ali.d.ts
├── upload-to-ali.vue
└── utils.js
├── styleguide.config.js
├── test
└── utils.test.js
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "upload-to-ali",
3 | "projectOwner": "FEMessage",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": false,
11 | "commitConvention": "angular",
12 | "contributors": [
13 | {
14 | "login": "levy9527",
15 | "name": "levy",
16 | "avatar_url": "https://avatars3.githubusercontent.com/u/9384365?v=4",
17 | "profile": "http://levy.work",
18 | "contributions": [
19 | "code",
20 | "review",
21 | "infra",
22 | "blog",
23 | "ideas"
24 | ]
25 | },
26 | {
27 | "login": "Alvin-Liu",
28 | "name": "Alvin",
29 | "avatar_url": "https://avatars0.githubusercontent.com/u/11909145?v=4",
30 | "profile": "https://github.com/Alvin-Liu",
31 | "contributions": [
32 | "code",
33 | "review"
34 | ]
35 | },
36 | {
37 | "login": "listars",
38 | "name": "listars",
39 | "avatar_url": "https://avatars2.githubusercontent.com/u/20613509?v=4",
40 | "profile": "https://github.com/listars",
41 | "contributions": [
42 | "code",
43 | "review",
44 | "doc"
45 | ]
46 | },
47 | {
48 | "login": "evillt",
49 | "name": "EVILLT",
50 | "avatar_url": "https://avatars3.githubusercontent.com/u/19513289?v=4",
51 | "profile": "https://evila.me",
52 | "contributions": [
53 | "code",
54 | "doc"
55 | ]
56 | },
57 | {
58 | "login": "donaldshen",
59 | "name": "Donald Shen",
60 | "avatar_url": "https://avatars3.githubusercontent.com/u/19591950?v=4",
61 | "profile": "https://donaldshen.github.io/portfolio",
62 | "contributions": [
63 | "code",
64 | "doc",
65 | "test"
66 | ]
67 | },
68 | {
69 | "login": "OuZuYu",
70 | "name": "OuZuYu",
71 | "avatar_url": "https://avatars3.githubusercontent.com/u/26338853?v=4",
72 | "profile": "http://67.216.223.155/resume/",
73 | "contributions": [
74 | "bug"
75 | ]
76 | },
77 | {
78 | "login": "xrr2016",
79 | "name": "轻剑快马",
80 | "avatar_url": "https://avatars1.githubusercontent.com/u/18013127?v=4",
81 | "profile": "https://justcodeit.fun",
82 | "contributions": [
83 | "doc"
84 | ]
85 | },
86 | {
87 | "login": "colmugx",
88 | "name": "ColMugX",
89 | "avatar_url": "https://avatars1.githubusercontent.com/u/21327913?v=4",
90 | "profile": "https://colmugx.github.io",
91 | "contributions": [
92 | "bug"
93 | ]
94 | },
95 | {
96 | "login": "rexerwang",
97 | "name": "Rexer Wang",
98 | "avatar_url": "https://avatars2.githubusercontent.com/u/15629940?v=4",
99 | "profile": "https://rexer.wang",
100 | "contributions": [
101 | "bug"
102 | ]
103 | },
104 | {
105 | "login": "cjfff",
106 | "name": "cjf",
107 | "avatar_url": "https://avatars1.githubusercontent.com/u/20502762?v=4",
108 | "profile": "http://www.ccc1996.cn",
109 | "contributions": [
110 | "code",
111 | "doc"
112 | ]
113 | },
114 | {
115 | "login": "Django-Tung",
116 | "name": "Django.Tung",
117 | "avatar_url": "https://avatars.githubusercontent.com/u/23208972?v=4",
118 | "profile": "https://github.com/Django-Tung",
119 | "contributions": [
120 | "bug"
121 | ]
122 | }
123 | ],
124 | "contributorsPerLine": 7,
125 | "skipCi": true
126 | }
127 |
--------------------------------------------------------------------------------
/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = api => {
2 | return {
3 | presets: [['@babel/env', {modules: api.env('test') ? 'commonjs' : false}]],
4 | plugins: [
5 | [
6 | '@babel/transform-runtime',
7 | {
8 | regenerator: true
9 | }
10 | ]
11 | ]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_style = space
8 | indent_size = 2
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | node: true
6 | },
7 | parserOptions: {
8 | parser: 'babel-eslint'
9 | },
10 | extends: [
11 | 'eslint:recommended',
12 | 'plugin:jest/recommended',
13 | 'plugin:vue/recommended',
14 | 'plugin:prettier/recommended',
15 | 'prettier/vue'
16 | ],
17 | plugins: ['vue', 'prettier', 'jest'],
18 | rules: {
19 | 'no-console': [
20 | 'error',
21 | {
22 | allow: ['warn', 'error']
23 | }
24 | ],
25 | 'no-debugger': 'error',
26 | 'prettier/prettier': 'error'
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.github/badge.yml:
--------------------------------------------------------------------------------
1 | types:
2 | feat: 'enhancement'
3 | fix:
4 | hack: 'hack'
5 | default: 'bug'
6 | hack: 'hack'
7 | docs: 'documentation'
8 | refactor: 'refactor'
9 | style: 'style'
10 | test: 'test'
11 | perf: 'performance'
12 | chore:
13 | deps: 'dependencies'
14 | default: 'chore'
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 | dist
7 | docs/build
8 | docs/index.html
9 |
10 | # Editor directories and files
11 | .idea
12 | .vscode
13 | *.suo
14 | *.ntvs*
15 | *.njsproj
16 | *.sln
17 | .env
18 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | docs
2 | dist
3 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | semi: false
2 | singleQuote: true
3 | bracketSpacing: false
4 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "stylelint-config-standard",
3 | "rules": {
4 | "no-empty-source": null
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | branches:
2 | only:
3 | - master
4 | language: node_js
5 | node_js:
6 | - lts/*
7 | git:
8 | depth: 30
9 | install:
10 | - yarn --frozen-lockfile
11 | - yarn test
12 | script:
13 | - ./build.sh
14 | after_script:
15 | - ./notify.sh
16 | cache: yarn
17 | deploy:
18 | - provider: pages
19 | local-dir: docs
20 | github-token: $GITHUB_TOKEN
21 | skip-cleanup: true
22 | keep-history: true
23 | - provider: npm
24 | email: levy9527@qq.com
25 | api_key: $NPM_TOKEN
26 | skip-cleanup: true
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 FEMessage
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 | # upload-to-ali
2 |
3 | [](https://travis-ci.com/FEMessage/upload-to-ali)
4 | [](https://www.npmjs.com/package/@femessage/upload-to-ali)
5 | [](https://www.npmjs.com/package/@femessage/upload-to-ali)
6 | [](https://github.com/FEMessage/upload-to-ali/blob/master/LICENSE)
7 | [](https://github.com/FEMessage/upload-to-ali/pulls)
8 | [](https://github-tools.github.io/github-release-notes/)
9 |
10 | 可通过环境变量配置上传信息,可自定义域名,支持多选、限制文件大小、删除、粘贴上传功能,拖拽上传功能,让上传功能更加简单。
11 | > 虽然最初的设计是上传至阿里云,但现在已可通过设置环境变量 `UPLOAD_ACTION` 把数据传送至自定义的后端接口,因此,理论上可以上传至任何云!考虑到有许多项目在用,就暂时不改包名了。
12 |
13 | 
14 |
15 | ## Table of Contents
16 |
17 | - [Feature](#feature)
18 | - [Links](#links)
19 | - [Install](#install)
20 | - [Config](#config)
21 | - [Dotenv](#dotenv)
22 | - [Contributing](#contributing)
23 | - [Contributors](#contributors)
24 | - [License](#license)
25 |
26 | ## Feature
27 |
28 | - 只需配置少量上传信息,即可实现上传功能
29 | - 上传前自动压缩图片,上传过程中有 loading 提示,支持图片显示及删除
30 | - 可拓展自定义 loading 和默认上传样式
31 | - 可限制上传文件大小和上传文件数量
32 | - 可截图粘贴上传
33 | - 可拖拽上传
34 | - 可预览图片
35 | - 支持 v-model
36 |
37 | 可以只设置 `action` props, 指向上传地址,组件内部默认实现了一套 post 方法,向上传地址传递数据
38 |
39 | 可以设置 `request`,实现自定义上传函数,覆盖原有默认的上传行为
40 |
41 | [⬆ Back to Top](#table-of-contents)
42 |
43 | ## Links
44 |
45 | - [docs](https://FEMessage.github.io/upload-to-ali/)
46 | - [ali oss guide](docs/guide-ali-oss.md)
47 |
48 | [⬆ Back to Top](#table-of-contents)
49 |
50 | ## Install
51 |
52 | ```sh
53 | yarn add @femessage/upload-to-ali
54 | ```
55 |
56 | [⬆ Back to Top](#table-of-contents)
57 |
58 | ## Dotenv
59 |
60 | 推荐使用环境变量配置上传参数
61 |
62 | 使用 dotenv,我们只需要将环境变量配置写在`.env`文件中,配合 CI 工具,可满足同一套代码在不同环境对接不同上传配置的需求
63 |
64 | 以下是所有可传入的环境变量
65 |
66 | ```sh
67 | # .env文件
68 | # 以下所有参数都是可选的
69 | UPLOAD_ACTION=upload-url
70 |
71 | OSS_BUCKET=your-bucket
72 | OSS_REGION=oss-cn-beijing
73 | OSS_DIR=oss-dir
74 | OSS_CUSTOM_DOMAIN=cdn.xxx.com
75 | ```
76 |
77 | `dotenv` 文档参考 https://www.npmjs.com/package/dotenv
78 |
79 | ### vue-cli3
80 |
81 | vue-cli3 提供了简便的方案替换[环境变量](https://cli.vuejs.org/zh/guide/mode-and-env.html#%E6%A8%A1%E5%BC%8F),但无法在客户端注入。这个场景需要结合`dotenv-webpack`插件。
82 |
83 | ```js
84 | // vue.config.js
85 | const Dotenv = require('dotenv-webpack')
86 | module.exports = {
87 | configureWebpack: {
88 | plugins: [new Dotenv()]
89 | }
90 | }
91 | ```
92 |
93 | [⬆ Back to Top](#table-of-contents)
94 |
95 | ## Contributing
96 |
97 | For those who are interested in contributing to this project, such as:
98 |
99 | - report a bug
100 | - request new feature
101 | - fix a bug
102 | - implement a new feature
103 |
104 | Please refer to our [contributing guide](https://github.com/FEMessage/.github/blob/master/CONTRIBUTING.md).
105 |
106 | [⬆ Back to Top](#table-of-contents)
107 |
108 | ## Contributors
109 |
110 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
120 |
121 | ## License
122 |
123 | [MIT](./LICENSE)
124 |
125 | [⬆ Back to Top](#table-of-contents)
126 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # upload-to-ali
2 |
3 | [](https://travis-ci.com/FEMessage/upload-to-ali)
4 | [](https://www.npmjs.com/package/@femessage/upload-to-ali)
5 | [](https://www.npmjs.com/package/@femessage/upload-to-ali)
6 | [](https://github.com/FEMessage/upload-to-ali/blob/master/LICENSE)
7 | [](https://github.com/FEMessage/upload-to-ali/pulls)
8 | [](https://github-tools.github.io/github-release-notes/)
9 |
10 | In the beginning, this component is designed to upload file to Aliyun-OSS easily,but now it can upload to any oss as you wish.
11 |
12 | You can configure OSS information through environmental variables, customize domain, support multi-selection, limit file size, delete, paste to upload, drag and drop to upload, make files upload simpler.
13 |
14 | 
15 |
16 | [中文文档](./README-zh.md)
17 |
18 | ## Table of Contents
19 |
20 | - [Feature](#feature)
21 | - [Links](#links)
22 | - [Install](#install)
23 | - [Config](#config)
24 | - [Dotenv](#dotenv)
25 | - [Contributing](#contributing)
26 | - [Contributors](#contributors)
27 | - [License](#license)
28 |
29 | ## Feature
30 |
31 | - The upload function can be done with little oss configuration
32 | - Automatically compress pictures before uploading, and loading prompts during uploading, support picture display and deletion
33 | - With default styles and support customize
34 | - Can limit the size or the number of files to upload
35 | - Support paste screenshot to upload
36 | - Can drag and drop to upload
37 | - Can preivew img
38 | - support v-model
39 |
40 | You can only set `action` props, that refers to upload url, the component has a default implement to post data to the url.
41 |
42 | You can set `request` props to customize own upload function.
43 |
44 | [⬆Back to Top](#table-of-contents)
45 |
46 | ## Links
47 |
48 | - [docs](https://FEMessage.github.io/upload-to-ali/)
49 | - [ali oss guide](docs/guide-ali-oss.md)
50 |
51 | [⬆ Back to Top](#table-of-contents)
52 |
53 | ## Install
54 |
55 | ```bash
56 | yarn add @femessage/upload-to-ali
57 | ```
58 |
59 | [⬆Back to Top](#table-of-contents)
60 |
61 | ## Dotenv
62 |
63 | Recommend using environment variables to configure upload parameters With dotenv, we just need to write the environment variable in `.env`. With CI tools, this can meet the needs of using different configuration in different environments without change the source code. Here are all can passed-in environment variables
64 |
65 | ```sh
66 | # .env file
67 | # these params are all optional
68 | UPLOAD_ACTION=upload-url
69 |
70 | OSS_BUCKET=your-bucket
71 | OSS_REGION=oss-cn-beijing
72 | OSS_DIR=oss-dir
73 | OSS_CUSTOM_DOMAIN=cdn.xxx.com
74 | ```
75 |
76 | `dotenv` document reference [https://www.npmjs.com/package/dotenv](https://www.npmjs.com/package/dotenv)
77 |
78 | ### vue-cli3
79 |
80 | vue-cli3 offers an easy solution to replace [process.env](https://cli.vuejs.org/zh/guide/mode-and-env.html#%E6%A8%A1%E5%BC%8F), but it requires a pattern(VUE*APP*\*) to inject in client side. So we need to use `dotenv-webpack`'s solution.
81 |
82 | ```js
83 | // vue.config.js
84 | const Dotenv = require('dotenv-webpack')
85 | module.exports = {
86 | configureWebpack: {
87 | plugins: [new Dotenv()]
88 | }
89 | }
90 | ```
91 |
92 | [⬆Back to Top](#table-of-contents)
93 |
94 | ## Contributing
95 |
96 | For those who are interested in contributing to this project, such as:
97 |
98 | - report a bug
99 | - request new feature
100 | - fix a bug
101 | - implement a new feature
102 |
103 | Please refer to our [contributing guide](https://github.com/FEMessage/.github/blob/master/CONTRIBUTING.md).
104 |
105 | [⬆ Back to Top](#table-of-contents)
106 |
107 | ## Contributors
108 |
109 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
110 |
111 |
112 |
113 |
114 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
138 |
139 | ## License
140 |
141 | [MIT](./LICENSE)
142 |
143 | [⬆ Back to Top](#table-of-contents)
144 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | yarn stdver
3 |
4 | yarn build
5 |
--------------------------------------------------------------------------------
/build/rollup.config.js:
--------------------------------------------------------------------------------
1 | // rollup.config.js
2 | import vue from 'rollup-plugin-vue'
3 | import babel from 'rollup-plugin-babel'
4 | import commonjs from 'rollup-plugin-commonjs'
5 | import {terser} from 'rollup-plugin-terser'
6 | import minimist from 'minimist'
7 |
8 | const argv = minimist(process.argv.slice(2))
9 |
10 | const config = {
11 | input: 'src/index.js',
12 | output: {
13 | name: 'UploadToAli',
14 | exports: 'named'
15 | },
16 | plugins: [
17 | commonjs(),
18 | vue({
19 | css: true,
20 | compileTemplate: true
21 | }),
22 | babel({
23 | runtimeHelpers: true,
24 | exclude: 'node_modules/**'
25 | })
26 | ]
27 | }
28 |
29 | // Only minify browser (iife) version
30 | if (argv.format === 'iife') {
31 | config.plugins.push(terser())
32 | }
33 |
34 | export default config
35 |
--------------------------------------------------------------------------------
/docs/accept.md:
--------------------------------------------------------------------------------
1 | 自定义上传文件类型
2 |
3 | ```vue
4 |
5 |
6 |
7 |
16 | ```
17 |
--------------------------------------------------------------------------------
/docs/basic.md:
--------------------------------------------------------------------------------
1 | 基本用法。内部使用`img-preview`组件进行图片预览
2 |
3 | ```vue
4 |
5 |
6 |
7 |
16 | ```
17 |
--------------------------------------------------------------------------------
/docs/before-upload.md:
--------------------------------------------------------------------------------
1 | 使用 beforeUpload 自定义上传前检查。比如检查图片的尺寸。
2 | Promise.reject()阻止上传,返回Promise.resolve()则可以上传
3 |
4 | ```vue
5 |
6 |
7 |
8 |
34 | ```
35 |
--------------------------------------------------------------------------------
/docs/draggable.md:
--------------------------------------------------------------------------------
1 | 可以拖拽排序
2 |
3 | ```vue
4 |
5 |
6 |
7 |
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/faq.md:
--------------------------------------------------------------------------------
1 | ## 在 TypeScript 中指定组件的类型
2 |
3 | ```html
4 |
5 |
6 |
7 |
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/file.md:
--------------------------------------------------------------------------------
1 | 上传文件
2 |
3 | ```vue
4 |
5 |
6 |
7 |
18 | ```
19 |
--------------------------------------------------------------------------------
/docs/guide-ali-oss.md:
--------------------------------------------------------------------------------
1 | # 阿里云OSS配置指南
2 |
3 | 对于新建的bucket,需要做一些设置
4 |
9 |
10 | ### 绑定域名
11 |
12 |
13 | 假设用户域名为:static.deepexi.top
14 |
15 |
16 | 则使用自定义域名访问,可以解决html变成下载的问题
17 |
18 | ### CNAME设置
19 | 如果绑定的是同一个阿里云账号下的域名,则可以自动添加CNAME记录。否则需要手动添加。
20 |
21 | 查看bucket外网地址:deepexi-serverless.oss-cn-shenzhen.aliyuncs.com
22 |
23 |
24 | 则去域名解析供应商设置:
25 |
26 | static.deepexi.top -> CNAME -> deepexi-serverless.oss-cn-shenzhen.aliyuncs.com
27 |
28 |
29 | ### 证书托管
30 |
31 | 上传HTTPS证书,开启HTTPS
32 |
33 |
34 |
35 |
36 |
37 | 如果没有证书,查看教程获取:[🔒免费开启HTTPS](https://github.com/levy9527/blog/issues/5)
38 |
39 | ### 公共读
40 |
41 | 点击基础设置,设置读写权限为公共读
42 |
43 |
44 | 这样可以解决访问链接超时的问题
45 |
46 | ### 跨域设置
47 | 在基础设置下,找到跨域设置
48 |
49 |
50 |
51 | 在来源中设置域名,或ip地址。下面给出最简单的示例为 `*`,实际可以根据需要填写允许的域名,一行一个。
52 |
53 | - 将allowed origins设置成 `*`
54 | - 将allowed methods设置成`GET, POST, PUT, DELETE, HEAD`
55 | - 将allowed headers设置成 `*`
56 | - 将expose headers设置成
57 | - `etag`
58 | - `x-oss-request-id`
59 |
60 | 这样可以解决字体无法显示的问题
61 |
--------------------------------------------------------------------------------
/docs/guide-global-register.md:
--------------------------------------------------------------------------------
1 | 如果组件在项目中多个地方被使用的话,每次指定 `request` 方法是比较烦琐的一件事情。
2 |
3 | 可以用两种方式去进行全局注册 `request` 方法
4 |
5 | 1.全局注册组件
6 |
7 | ```md
8 | import Vue from 'vue';
9 | import UploadToAli from 'upload-to-ali'
10 |
11 | Vue.use(UploadToAli, {
12 | // 回调的文件
13 | async request(file) {
14 | const url = await customRequest(file)
15 | return url;
16 | }
17 | })
18 | ```
19 |
20 | 2. 在 Vue 的原型链上添加 `$uploadRequest` 方法
21 |
22 | ```md
23 | Vue.prototype.$uploadRequest = async request(file) {
24 | const url = await customRequest(file)
25 | return url;
26 | }
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/loaded.md:
--------------------------------------------------------------------------------
1 | 上传回调
2 |
3 | 上传后的文件 url 在`@loaded`事件会返回
4 |
5 | ```vue
6 |
7 |
8 |
9 |
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/max.md:
--------------------------------------------------------------------------------
1 | 限制文件数量
2 |
3 | ```vue
4 |
5 |
6 |
7 |
16 | ```
17 |
--------------------------------------------------------------------------------
/docs/multiple.md:
--------------------------------------------------------------------------------
1 | 上传多张操作
2 |
3 | ```vue
4 |
5 |
6 |
7 |
16 | ```
17 |
--------------------------------------------------------------------------------
/docs/on-click.md:
--------------------------------------------------------------------------------
1 | 自定义事件
2 |
3 | ```vue
4 |
5 |
6 |
7 |
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/on-oversize.md:
--------------------------------------------------------------------------------
1 | 可以自定义如何处理文件大小超出的情况(默认是alert警告)
2 |
3 | ```vue
4 |
5 |
6 |
7 |
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/request.md:
--------------------------------------------------------------------------------
1 | 覆盖默认的上传行为,可以自定义上传的实现
2 |
3 | ```vue
4 |
5 |
6 |
7 |
8 |
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/size.md:
--------------------------------------------------------------------------------
1 | 限制文件大小,size单位为kb
2 |
3 | ```vue
4 |
5 |
6 |
7 |
16 | ```
17 |
--------------------------------------------------------------------------------
/docs/slot-default.md:
--------------------------------------------------------------------------------
1 | 自定义上传内容
2 |
3 | 下面是一个真实遇到过的例子:上传组件被包在 form 元素里,上传组件自定义上传内容为 button, 此时需要设置 button 的 type="button",否则点击按钮后会触发表单的提交。
4 |
5 | ```vue
6 |
7 |
12 |
13 |
28 | ```
29 |
--------------------------------------------------------------------------------
/docs/slot-placeholder-spinner.md:
--------------------------------------------------------------------------------
1 | 自定义上传占位符和 loading 图标
2 |
3 | ```vue
4 |
5 |
6 |
7 | 点击上传
8 |
9 | 上传中
10 |
11 |
12 |
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/tip.md:
--------------------------------------------------------------------------------
1 | 自定义上传提示内容
2 |
3 | ```vue
4 |
5 |
6 |
7 |
16 | ```
17 |
--------------------------------------------------------------------------------
/netlify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | echo "is netlify: $NETLIFY"
3 | echo "in branch: $BRANCH"
4 | echo "head: $HEAD"
5 |
6 | if [ "$NETLIFY" != "true" ]
7 | then
8 | echo "this script only runs in netlify, bye"
9 | exit 1
10 | fi
11 |
12 | if [ "$BRANCH" != "dev" ] && [ "$HEAD" != "dev" ]
13 | then
14 | yarn doc
15 | else
16 | echo "this script only runs in targeting dev's PR deploy preview, bye"
17 | fi
18 |
--------------------------------------------------------------------------------
/notify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # https://stackoverflow.com/questions/13872048/bash-script-what-does-bin-bash-mean
3 | echo "1/5: checking TRAVIS_TEST_RESULT"
4 | if [ "$TRAVIS_TEST_RESULT" != "0" ]
5 | then
6 | echo "build not success, bye"
7 | exit 1
8 | fi
9 |
10 | ORG_NAME=$(echo "$TRAVIS_REPO_SLUG" | cut -d '/' -f 1)
11 | REPO_NAME=$(echo "$TRAVIS_REPO_SLUG" | cut -d '/' -f 2)
12 |
13 | echo "2/5: pushing commit and tag to github"
14 | # 该命令很可能报错,但不影响实际进行,因而不能简单地在脚本开头 set -e
15 | git remote add github https://$GITHUB_TOKEN@github.com/$TRAVIS_REPO_SLUG.git > /dev/null 2>&1
16 | git push github HEAD:master --follow-tags
17 |
18 | echo "3/5: generating github release notes"
19 | GREN_GITHUB_TOKEN=$GITHUB_TOKEN yarn release
20 |
21 | # 避免发送错误信息
22 | if [ $? -ne 0 ]
23 | then
24 | echo "gren fails, bye"
25 | exit 1
26 | fi
27 |
28 | echo "4/5: downloading github release info"
29 | url=https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases/latest
30 | resp_tmp_file=resp.tmp
31 |
32 | curl -H "Authorization: token $GITHUB_TOKEN" $url > $resp_tmp_file
33 |
34 | html_url=$(sed -n 5p $resp_tmp_file | sed 's/\"html_url\"://g' | awk -F '"' '{print $2}')
35 | body=$(grep body < $resp_tmp_file | sed 's/\"body\"://g;s/\"//g')
36 | version=$(echo $html_url | awk -F '/' '{print $NF}')
37 |
38 | echo "5/5: notifying with dingtalk bot"
39 | msg='{"msgtype": "markdown", "markdown": {"title": "'$REPO_NAME'更新", "text": "@所有人\n# ['$REPO_NAME'('$version')]('$html_url')\n'$body'"}}'
40 |
41 | curl -X POST https://oapi.dingtalk.com/robot/send\?access_token\=$DINGTALK_ROBOT_TOKEN -H 'Content-Type: application/json' -d "$msg"
42 |
43 | rm $resp_tmp_file
44 |
45 | echo "executing notify.sh successfully"
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@femessage/upload-to-ali",
3 | "version": "1.5.0",
4 | "description": "",
5 | "author": "https://github.com/FEMessage",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/FEMessage/upload-to-ali.git"
10 | },
11 | "keywords": [
12 | "vue",
13 | "upload",
14 | "component"
15 | ],
16 | "files": [
17 | "src",
18 | "dist"
19 | ],
20 | "main": "dist/upload-to-ali.umd.js",
21 | "module": "dist/upload-to-ali.esm.js",
22 | "unpkg": "dist/upload-to-ali.min.js",
23 | "browser": {
24 | "./sfc": "src/upload-to-ali.vue"
25 | },
26 | "types": "src/upload-to-ali.d.ts",
27 | "scripts": {
28 | "dev": "vue-styleguidist server",
29 | "test": "jest",
30 | "doc": "vue-styleguidist build",
31 | "build": "npm run build:unpkg & npm run build:es & npm run build:umd & npm run doc",
32 | "build:umd": "rollup --config build/rollup.config.js --format umd --file dist/upload-to-ali.umd.js",
33 | "build:es": "rollup --config build/rollup.config.js --format es --file dist/upload-to-ali.esm.js",
34 | "build:unpkg": "rollup --config build/rollup.config.js --format iife --file dist/upload-to-ali.min.js",
35 | "stdver": "standard-version -m '[skip ci] chore(release): v%s'",
36 | "release": "gren release --override"
37 | },
38 | "dependencies": {
39 | "@femessage/img-preview": "^1.2.0",
40 | "compressorjs": "^1.0.6",
41 | "crypto-browserify": "^3.12.0"
42 | },
43 | "devDependencies": {
44 | "@babel/core": "^7.4.3",
45 | "@babel/plugin-transform-runtime": "^7.4.3",
46 | "@babel/preset-env": "^7.4.3",
47 | "@femessage/github-release-notes": "latest",
48 | "babel-eslint": "^10.0.3",
49 | "babel-loader": "^8.0.5",
50 | "dotenv": "^7.0.0",
51 | "eslint": "^6.6.0",
52 | "eslint-config-prettier": "^6.5.0",
53 | "eslint-plugin-jest": "^23.1.1",
54 | "eslint-plugin-prettier": "^3.1.1",
55 | "eslint-plugin-vue": "^5.2.3",
56 | "file-loader": "^3.0.1",
57 | "glob": "^7.1.3",
58 | "husky": "1.3.1",
59 | "jest": "^24.8.0",
60 | "less": "^3.9.0",
61 | "less-loader": "^5.0.0",
62 | "lint-staged": "^8.1.0",
63 | "minimist": "^1.2.0",
64 | "prettier": "1.18.2",
65 | "rollup": "^1.9.0",
66 | "rollup-plugin-babel": "^4.3.2",
67 | "rollup-plugin-commonjs": "^9.3.4",
68 | "rollup-plugin-terser": "^4.0.4",
69 | "rollup-plugin-vue": "^4.7.2",
70 | "standard-version": "^6.0.1",
71 | "stylelint": "^9.10.0",
72 | "stylelint-config-standard": "^18.2.0",
73 | "vue": "^2.6.10",
74 | "vue-loader": "^15.7.1",
75 | "vue-styleguidist": "^3.16.3",
76 | "vue-template-compiler": "^2.5.16",
77 | "webpack": "^4.29.6"
78 | },
79 | "publishConfig": {
80 | "access": "public"
81 | },
82 | "vue-sfc-cli": "1.12.0",
83 | "engines": {
84 | "node": ">= 4.0.0",
85 | "npm": ">= 3.0.0"
86 | },
87 | "husky": {
88 | "hooks": {
89 | "pre-commit": "lint-staged",
90 | "post-commit": "git update-index --again",
91 | "pre-push": "yarn test"
92 | }
93 | },
94 | "lint-staged": {
95 | "*.@(md|json)": [
96 | "prettier --write",
97 | "git add"
98 | ],
99 | "*.js": [
100 | "eslint --fix",
101 | "prettier --write",
102 | "git add"
103 | ],
104 | "*.vue": [
105 | "eslint --fix",
106 | "prettier --write",
107 | "stylelint --fix",
108 | "git add"
109 | ]
110 | },
111 | "gren": "@femessage/grenrc"
112 | }
113 |
--------------------------------------------------------------------------------
/src/components/draggable-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
62 |
76 |
--------------------------------------------------------------------------------
/src/components/upload-item.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ fileName }}
4 |
5 |
6 |
7 |
8 |
38 |
39 |
64 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // Import vue component
2 | import Component from './upload-to-ali.vue'
3 |
4 | // `Vue.use` automatically prevents you from using
5 | // the same plugin more than once,
6 | // so calling it multiple times on the same plugin
7 | // will install the plugin only once
8 | Component.install = (Vue, opts = {}) => {
9 | if (opts.request) {
10 | Vue.prototype.$uploadRequest = opts.request
11 | }
12 | Vue.component(Component.name, Component)
13 | }
14 |
15 | // To auto-install when vue is found
16 | let GlobalVue = null
17 | if (typeof window !== 'undefined') {
18 | GlobalVue = window.Vue
19 | } else if (typeof global !== 'undefined') {
20 | GlobalVue = global.Vue
21 | }
22 | if (GlobalVue) {
23 | GlobalVue.use(Component)
24 | }
25 |
26 | // To allow use as module (npm/webpack/etc.) export component
27 | export default Component
28 |
29 | // It's possible to expose named exports when writing components that can
30 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo';
31 | // export const RollupDemoDirective = component;
32 |
--------------------------------------------------------------------------------
/src/upload-to-ali.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, {VueConstructor} from 'vue'
2 |
3 | declare module '@femessage/upload-to-ali' {
4 | class FemessageComponent extends Vue {
5 | static install(vue: typeof Vue): void
6 | }
7 |
8 | type CombinedVueInstance<
9 | Instance extends Vue,
10 | Data,
11 | Methods,
12 | Computed,
13 | Props
14 | > = Data & Methods & Computed & Props & Instance
15 |
16 | type ExtendedVue<
17 | Instance extends Vue,
18 | Data,
19 | Methods,
20 | Computed,
21 | Props
22 | > = VueConstructor<
23 | CombinedVueInstance & Vue
24 | >
25 |
26 | type Combined = Data &
27 | Methods &
28 | Computed &
29 | Props
30 |
31 | type UploadToAliData = {
32 | previewUrl: string
33 |
34 | uploading: boolean
35 |
36 | isHighlight: boolean
37 | }
38 |
39 | type UploadToAliMethods = {
40 | selectFiles: () => void
41 | }
42 |
43 | type UploadToAliComputed = {
44 | uploadList: any[]
45 | canUpload: boolean
46 | uploadRequest: (file: any) => Promise
47 | }
48 |
49 | type UploadToAliProps = {
50 | action: string
51 | bucket: string
52 | region: string
53 | dir: string
54 | customDomain: string
55 | value: string | any[]
56 | multiple: boolean
57 | size: number
58 | accept: string
59 | timeout: number
60 | disabled: boolean
61 | max: number
62 | compressOptions: {[key: string]: any}
63 | uploadOptions: {[key: string]: any}
64 | preview: boolean
65 | tip: string
66 | onClick: (url: string, isFile: boolean) => void
67 | beforeUpload: (files: any[]) => Promise
68 | onOversize: (fileOvesize: any) => void
69 | request: (file: any) => Promise
70 | }
71 |
72 | type UploadToAli = Combined<
73 | UploadToAliData,
74 | UploadToAliMethods,
75 | UploadToAliComputed,
76 | UploadToAliProps
77 | >
78 |
79 | export interface UploadToAliType extends FemessageComponent, UploadToAli {}
80 |
81 | const UploadToAliConstruction: ExtendedVue<
82 | Vue,
83 | UploadToAliData,
84 | UploadToAliMethods,
85 | UploadToAliComputed,
86 | UploadToAliProps
87 | >
88 |
89 | export default UploadToAliConstruction
90 | }
91 |
--------------------------------------------------------------------------------
/src/upload-to-ali.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
78 |
79 |
80 |
81 |
82 | {{ tip }}
83 |
84 |
85 |
86 |
484 |
653 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto-browserify'
2 |
3 | /**
4 | * 签名函数参考
5 | * https://help.aliyun.com/document_detail/29442.html?spm=a2c4g.11186623.2.13.2c541605Ky1bxj
6 | */
7 | export function getSignature(origin, timestamp) {
8 | const param = {
9 | origin,
10 | timestamp,
11 | signatureMethod: 'HMAC-SHA1'
12 | }
13 | const paramStr = Object.keys(param)
14 | .sort()
15 | .map(k => `${k}=${encodeURIComponent(param[k])}`)
16 | .join('&')
17 | const signStr = 'POST&%2F&' + encodeURIComponent(paramStr)
18 |
19 | return crypto
20 | .createHmac('sha1', 'nonce')
21 | .update(signStr)
22 | .digest('base64')
23 | }
24 |
25 | export function getBasename(url = '') {
26 | const filename = decodeURIComponent(url.split('/').pop())
27 | return filename.length > 40 ? `${filename.slice(0, 40)}...` : filename
28 | }
29 | /**
30 | * 默认上传方法
31 | * @param {*} file
32 | */
33 | export function defaultRequest(file) {
34 | const formData = new FormData()
35 | ;['bucket', 'region', 'customDomain', 'dir']
36 | .filter(key => this[key])
37 | .forEach(key => formData.append(key, this[key]))
38 | formData.append('file', file)
39 |
40 | return new Promise((resolve, reject) => {
41 | if (!this.action) {
42 | return reject(new Error('missing UPLOAD_ACTION'))
43 | }
44 | const xhr = new XMLHttpRequest()
45 | xhr.responseType = 'json'
46 | xhr.onload = () => {
47 | if (xhr.status === 200) {
48 | resolve(xhr.response.payload.url)
49 | } else {
50 | reject(xhr.response)
51 | }
52 | }
53 | xhr.onerror = reject
54 | const timestamp = Date.now()
55 | const sep = this.action.indexOf('?') > -1 ? '&' : '?'
56 | const url = `${this.action}${sep}_=${timestamp}`
57 | xhr.open('POST', url, true)
58 |
59 | const signature = getSignature(location.origin, timestamp)
60 | xhr.setRequestHeader('x-upload-timestamp', timestamp)
61 | xhr.setRequestHeader('x-upload-signature', signature)
62 |
63 | xhr.send(formData)
64 | })
65 | }
66 |
--------------------------------------------------------------------------------
/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const {VueLoaderPlugin} = require('vue-loader')
2 | const path = require('path')
3 | const glob = require('glob')
4 | const env = Object.assign({}, require('dotenv').config().parsed, {
5 | UPLOAD_ACTION: process.env.UPLOAD_ACTION,
6 | OSS_BUCKET: process.env.OSS_BUCKET,
7 | OSS_REGION: process.env.OSS_REGION
8 | })
9 |
10 | const sections = (() => {
11 | const docs = glob
12 | .sync('docs/*.md')
13 | .map(p => ({name: path.basename(p, '.md'), content: p}))
14 | const demos = []
15 | let faq = '' // 约定至多只有一个faq.md
16 | const guides = []
17 | docs.forEach(d => {
18 | if (/^faq$/i.test(d.name)) {
19 | d.name = d.name.toUpperCase()
20 | faq = d
21 | } else if (/^guide-/.test(d.name)) {
22 | guides.push(d)
23 | } else {
24 | demos.push(d)
25 | }
26 | })
27 | return [
28 | {
29 | name: 'Components',
30 | components: 'src/*.vue',
31 | usageMode: 'expand'
32 | },
33 | {
34 | name: 'Demo',
35 | sections: demos,
36 | sectionDepth: 2
37 | },
38 | ...(faq ? [faq] : []),
39 | ...(guides.length ? [{name: 'Guide', sections: guides}] : [])
40 | ]
41 | })()
42 |
43 | module.exports = {
44 | styleguideDir: 'docs',
45 | pagePerSection: true,
46 | ribbon: {
47 | url: 'https://github.com/FEMessage/upload-to-ali'
48 | },
49 | sections,
50 | editorConfig: {
51 | readOnly: process.env.NODE_ENV === 'development' ? false : 'nocursor'
52 | },
53 | webpackConfig: {
54 | module: {
55 | rules: [
56 | {
57 | test: /\.vue$/,
58 | loader: 'vue-loader'
59 | },
60 | {
61 | test: /\.js?$/,
62 | exclude: /node_modules/,
63 | loader: 'babel-loader'
64 | },
65 | {
66 | test: /\.css$/,
67 | loaders: ['style-loader', 'css-loader']
68 | },
69 | {
70 | test: /\.less$/,
71 | loaders: ['vue-style-loader', 'css-loader', 'less-loader']
72 | },
73 | {
74 | test: /\.(woff2?|eot|[ot]tf)(\?.*)?$/,
75 | loader: 'file-loader'
76 | }
77 | ]
78 | },
79 | plugins: [
80 | new VueLoaderPlugin(),
81 | new (require('webpack')).DefinePlugin({
82 | 'process.env': JSON.stringify(env)
83 | })
84 | ]
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/test/utils.test.js:
--------------------------------------------------------------------------------
1 | import {getBasename, getSignature} from '../src/utils'
2 |
3 | describe('getBasename', () => {
4 | test('仅存在文件名', () => {
5 | const str = 'README.md'
6 | expect(getBasename(str)).toBe('README.md')
7 | })
8 |
9 | test('小于 40 字符文件地址', () => {
10 | const str = '//localhost/all-contributorsrc'
11 | expect(getBasename(str)).toBe('all-contributorsrc')
12 | })
13 |
14 | test('大于 40 字符中英文混合文件地址', () => {
15 | const fileName = '苹果apple香蕉banana西瓜watermelon桃子peach柠檬lemon.md'
16 | const str = `//localhost/${fileName}`
17 | expect(getBasename(str)).toBe(`${fileName.slice(0, 40)}...`)
18 | })
19 |
20 | test('文件名被 encode 的文件地址', () => {
21 | const str2 = '//localhost/example%2B1.png'
22 | expect(getBasename(str2)).toBe('example+1.png')
23 | })
24 | })
25 |
26 | describe('getSignature', () => {
27 | test('用例一', () => {
28 | const origin = 'http://localhost:6060'
29 | const timestamp = 1575362612539
30 | expect(getSignature(origin, timestamp)).toBe('VBrn5MJFxMj5OPExLx8eXq1DCCc=')
31 | })
32 | test('用例二', () => {
33 | const origin = 'https://example.com'
34 | const timestamp = 1575362612539
35 | expect(getSignature(origin, timestamp)).toBe('iZj/mMZghtdi1jyk9zqG8mYiKPo=')
36 | })
37 | })
38 |
--------------------------------------------------------------------------------