├── .babelrc
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── README.md
├── build
├── build.js
├── check-versions.js
├── logo.png
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── config
├── dev.env.js
├── index.js
└── prod.env.js
├── index.html
├── package.json
├── php
├── Uploader.php
├── hw_file.sql
└── up.php
├── src
├── App.vue
├── assets
│ ├── bg.png
│ ├── body_bg.gif
│ ├── css
│ │ ├── iconfont.css
│ │ ├── iconfont.eot
│ │ ├── iconfont.js
│ │ ├── iconfont.json
│ │ ├── iconfont.svg
│ │ ├── iconfont.ttf
│ │ ├── iconfont.woff
│ │ └── iconfont.woff2
│ └── logo.png
├── components
│ ├── Breakpoint.vue
│ ├── Chunk.vue
│ ├── Md5.vue
│ ├── Skip.vue
│ └── Uploader.vue
├── main.js
└── router
│ └── index.js
└── static
└── .gitkeep
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | package-lock.json
8 |
9 | # Editor directories and files
10 | .idea
11 | .vscode
12 | *.suo
13 | *.ntvs*
14 | *.njsproj
15 | *.sln
16 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fast-uploader
2 |
3 | > Demo source for a fast uploader dependens on vue-simple-uploader.
4 |
5 | ## 文件上传系列文章
6 |
7 | - [01.使用vue-simple-uploader上传文件和文件夹](https://www.helloweba.net/javascript/632.html)
8 | - [02.文件分片上传之前端文件分片](https://www.helloweba.net/javascript/633.html)
9 | - [03.文件分片上传之后端PHP合成文件](https://www.helloweba.net/php/634.html)
10 | - [04.超大文件上传之计算文件MD5值](https://www.helloweba.net/javascript/635.html)
11 | - [05.文件上传之秒传文件](https://www.helloweba.net/php/636.html)
12 | - 06.文件上传之断点续传和跨端续传
13 |
14 | ----
15 |
16 | ## Build Setup
17 |
18 | ``` bash
19 | # install dependencies
20 | npm install
21 |
22 | # serve with hot reload at localhost:8080
23 | npm run dev
24 |
25 | # build for production with minification
26 | npm run build
27 |
28 | # build for production and view the bundle analyzer report
29 | npm run build --report
30 | ```
31 | ----
32 | ``` bash
33 | # run php server
34 | cd php
35 | php -S localhost:9999
36 |
37 | ```
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | require('./check-versions')()
3 |
4 | process.env.NODE_ENV = 'production'
5 |
6 | const ora = require('ora')
7 | const rm = require('rimraf')
8 | const path = require('path')
9 | const chalk = require('chalk')
10 | const webpack = require('webpack')
11 | const config = require('../config')
12 | const webpackConfig = require('./webpack.prod.conf')
13 |
14 | const spinner = ora('building for production...')
15 | spinner.start()
16 |
17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18 | if (err) throw err
19 | webpack(webpackConfig, (err, stats) => {
20 | spinner.stop()
21 | if (err) throw err
22 | process.stdout.write(stats.toString({
23 | colors: true,
24 | modules: false,
25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
26 | chunks: false,
27 | chunkModules: false
28 | }) + '\n\n')
29 |
30 | if (stats.hasErrors()) {
31 | console.log(chalk.red(' Build failed with errors.\n'))
32 | process.exit(1)
33 | }
34 |
35 | console.log(chalk.cyan(' Build complete.\n'))
36 | console.log(chalk.yellow(
37 | ' Tip: built files are meant to be served over an HTTP server.\n' +
38 | ' Opening index.html over file:// won\'t work.\n'
39 | ))
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 |
7 | function exec (cmd) {
8 | return require('child_process').execSync(cmd).toString().trim()
9 | }
10 |
11 | const versionRequirements = [
12 | {
13 | name: 'node',
14 | currentVersion: semver.clean(process.version),
15 | versionRequirement: packageConfig.engines.node
16 | }
17 | ]
18 |
19 | if (shell.which('npm')) {
20 | versionRequirements.push({
21 | name: 'npm',
22 | currentVersion: exec('npm --version'),
23 | versionRequirement: packageConfig.engines.npm
24 | })
25 | }
26 |
27 | module.exports = function () {
28 | const warnings = []
29 |
30 | for (let i = 0; i < versionRequirements.length; i++) {
31 | const mod = versionRequirements[i]
32 |
33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34 | warnings.push(mod.name + ': ' +
35 | chalk.red(mod.currentVersion) + ' should be ' +
36 | chalk.green(mod.versionRequirement)
37 | )
38 | }
39 | }
40 |
41 | if (warnings.length) {
42 | console.log('')
43 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
44 | console.log()
45 |
46 | for (let i = 0; i < warnings.length; i++) {
47 | const warning = warnings[i]
48 | console.log(' ' + warning)
49 | }
50 |
51 | console.log()
52 | process.exit(1)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrfbeyond/fast-uploader/35c6a8f963ac2e688d8c52bed4bf9ce09252a4c8/build/logo.png
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const config = require('../config')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 | const packageConfig = require('../package.json')
6 |
7 | exports.assetsPath = function (_path) {
8 | const assetsSubDirectory = process.env.NODE_ENV === 'production'
9 | ? config.build.assetsSubDirectory
10 | : config.dev.assetsSubDirectory
11 |
12 | return path.posix.join(assetsSubDirectory, _path)
13 | }
14 |
15 | exports.cssLoaders = function (options) {
16 | options = options || {}
17 |
18 | const cssLoader = {
19 | loader: 'css-loader',
20 | options: {
21 | sourceMap: options.sourceMap
22 | }
23 | }
24 |
25 | const postcssLoader = {
26 | loader: 'postcss-loader',
27 | options: {
28 | sourceMap: options.sourceMap
29 | }
30 | }
31 |
32 | // generate loader string to be used with extract text plugin
33 | function generateLoaders (loader, loaderOptions) {
34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
35 |
36 | if (loader) {
37 | loaders.push({
38 | loader: loader + '-loader',
39 | options: Object.assign({}, loaderOptions, {
40 | sourceMap: options.sourceMap
41 | })
42 | })
43 | }
44 |
45 | // Extract CSS when that option is specified
46 | // (which is the case during production build)
47 | if (options.extract) {
48 | return ExtractTextPlugin.extract({
49 | use: loaders,
50 | fallback: 'vue-style-loader'
51 | })
52 | } else {
53 | return ['vue-style-loader'].concat(loaders)
54 | }
55 | }
56 |
57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
58 | return {
59 | css: generateLoaders(),
60 | postcss: generateLoaders(),
61 | less: generateLoaders('less'),
62 | sass: generateLoaders('sass', { indentedSyntax: true }),
63 | scss: generateLoaders('sass'),
64 | stylus: generateLoaders('stylus'),
65 | styl: generateLoaders('stylus')
66 | }
67 | }
68 |
69 | // Generate loaders for standalone style files (outside of .vue)
70 | exports.styleLoaders = function (options) {
71 | const output = []
72 | const loaders = exports.cssLoaders(options)
73 |
74 | for (const extension in loaders) {
75 | const loader = loaders[extension]
76 | output.push({
77 | test: new RegExp('\\.' + extension + '$'),
78 | use: loader
79 | })
80 | }
81 |
82 | return output
83 | }
84 |
85 | exports.createNotifierCallback = () => {
86 | const notifier = require('node-notifier')
87 |
88 | return (severity, errors) => {
89 | if (severity !== 'error') return
90 |
91 | const error = errors[0]
92 | const filename = error.file && error.file.split('!').pop()
93 |
94 | notifier.notify({
95 | title: packageConfig.name,
96 | message: severity + ': ' + error.name,
97 | subtitle: filename || '',
98 | icon: path.join(__dirname, 'logo.png')
99 | })
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const config = require('../config')
4 | const isProduction = process.env.NODE_ENV === 'production'
5 | const sourceMapEnabled = isProduction
6 | ? config.build.productionSourceMap
7 | : config.dev.cssSourceMap
8 |
9 | module.exports = {
10 | loaders: utils.cssLoaders({
11 | sourceMap: sourceMapEnabled,
12 | extract: isProduction
13 | }),
14 | cssSourceMap: sourceMapEnabled,
15 | cacheBusting: config.dev.cacheBusting,
16 | transformToRequire: {
17 | video: ['src', 'poster'],
18 | source: 'src',
19 | img: 'src',
20 | image: 'xlink:href'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const config = require('../config')
5 | const vueLoaderConfig = require('./vue-loader.conf')
6 |
7 | function resolve (dir) {
8 | return path.join(__dirname, '..', dir)
9 | }
10 |
11 |
12 |
13 | module.exports = {
14 | context: path.resolve(__dirname, '../'),
15 | entry: {
16 | app: './src/main.js'
17 | },
18 | output: {
19 | path: config.build.assetsRoot,
20 | filename: '[name].js',
21 | publicPath: process.env.NODE_ENV === 'production'
22 | ? config.build.assetsPublicPath
23 | : config.dev.assetsPublicPath
24 | },
25 | resolve: {
26 | extensions: ['.js', '.vue', '.json'],
27 | alias: {
28 | 'vue$': 'vue/dist/vue.esm.js',
29 | '@': resolve('src'),
30 | }
31 | },
32 | module: {
33 | rules: [
34 | {
35 | test: /\.vue$/,
36 | loader: 'vue-loader',
37 | options: vueLoaderConfig
38 | },
39 | {
40 | test: /\.js$/,
41 | loader: 'babel-loader',
42 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
43 | },
44 | {
45 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
46 | loader: 'url-loader',
47 | options: {
48 | limit: 10000,
49 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
50 | }
51 | },
52 | {
53 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
54 | loader: 'url-loader',
55 | options: {
56 | limit: 10000,
57 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
58 | }
59 | },
60 | {
61 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
62 | loader: 'url-loader',
63 | options: {
64 | limit: 10000,
65 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
66 | }
67 | }
68 | ]
69 | },
70 | node: {
71 | // prevent webpack from injecting useless setImmediate polyfill because Vue
72 | // source contains it (although only uses it if it's native).
73 | setImmediate: false,
74 | // prevent webpack from injecting mocks to Node native modules
75 | // that does not make sense for the client
76 | dgram: 'empty',
77 | fs: 'empty',
78 | net: 'empty',
79 | tls: 'empty',
80 | child_process: 'empty'
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const webpack = require('webpack')
4 | const config = require('../config')
5 | const merge = require('webpack-merge')
6 | const path = require('path')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
11 | const portfinder = require('portfinder')
12 |
13 | const HOST = process.env.HOST
14 | const PORT = process.env.PORT && Number(process.env.PORT)
15 |
16 | const devWebpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
19 | },
20 | // cheap-module-eval-source-map is faster for development
21 | devtool: config.dev.devtool,
22 |
23 | // these devServer options should be customized in /config/index.js
24 | devServer: {
25 | clientLogLevel: 'warning',
26 | historyApiFallback: {
27 | rewrites: [
28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
29 | ],
30 | },
31 | hot: true,
32 | contentBase: false, // since we use CopyWebpackPlugin.
33 | compress: true,
34 | host: HOST || config.dev.host,
35 | port: PORT || config.dev.port,
36 | open: config.dev.autoOpenBrowser,
37 | overlay: config.dev.errorOverlay
38 | ? { warnings: false, errors: true }
39 | : false,
40 | publicPath: config.dev.assetsPublicPath,
41 | proxy: config.dev.proxyTable,
42 | quiet: true, // necessary for FriendlyErrorsPlugin
43 | watchOptions: {
44 | poll: config.dev.poll,
45 | }
46 | },
47 | plugins: [
48 | new webpack.DefinePlugin({
49 | 'process.env': require('../config/dev.env')
50 | }),
51 | new webpack.HotModuleReplacementPlugin(),
52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
53 | new webpack.NoEmitOnErrorsPlugin(),
54 | // https://github.com/ampedandwired/html-webpack-plugin
55 | new HtmlWebpackPlugin({
56 | filename: 'index.html',
57 | template: 'index.html',
58 | inject: true
59 | }),
60 | // copy custom static assets
61 | new CopyWebpackPlugin([
62 | {
63 | from: path.resolve(__dirname, '../static'),
64 | to: config.dev.assetsSubDirectory,
65 | ignore: ['.*']
66 | }
67 | ])
68 | ]
69 | })
70 |
71 | module.exports = new Promise((resolve, reject) => {
72 | portfinder.basePort = process.env.PORT || config.dev.port
73 | portfinder.getPort((err, port) => {
74 | if (err) {
75 | reject(err)
76 | } else {
77 | // publish the new Port, necessary for e2e tests
78 | process.env.PORT = port
79 | // add port to devServer config
80 | devWebpackConfig.devServer.port = port
81 |
82 | // Add FriendlyErrorsPlugin
83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
84 | compilationSuccessInfo: {
85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
86 | },
87 | onErrors: config.dev.notifyOnErrors
88 | ? utils.createNotifierCallback()
89 | : undefined
90 | }))
91 |
92 | resolve(devWebpackConfig)
93 | }
94 | })
95 | })
96 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const webpack = require('webpack')
5 | const config = require('../config')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
13 |
14 | const env = require('../config/prod.env')
15 |
16 | const webpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({
19 | sourceMap: config.build.productionSourceMap,
20 | extract: true,
21 | usePostCSS: true
22 | })
23 | },
24 | devtool: config.build.productionSourceMap ? config.build.devtool : false,
25 | output: {
26 | path: config.build.assetsRoot,
27 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
29 | },
30 | plugins: [
31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
32 | new webpack.DefinePlugin({
33 | 'process.env': env
34 | }),
35 | new UglifyJsPlugin({
36 | uglifyOptions: {
37 | compress: {
38 | warnings: false
39 | }
40 | },
41 | sourceMap: config.build.productionSourceMap,
42 | parallel: true
43 | }),
44 | // extract css into its own file
45 | new ExtractTextPlugin({
46 | filename: utils.assetsPath('css/[name].[contenthash].css'),
47 | // Setting the following option to `false` will not extract CSS from codesplit chunks.
48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
51 | allChunks: true,
52 | }),
53 | // Compress extracted CSS. We are using this plugin so that possible
54 | // duplicated CSS from different components can be deduped.
55 | new OptimizeCSSPlugin({
56 | cssProcessorOptions: config.build.productionSourceMap
57 | ? { safe: true, map: { inline: false } }
58 | : { safe: true }
59 | }),
60 | // generate dist index.html with correct asset hash for caching.
61 | // you can customize output by editing /index.html
62 | // see https://github.com/ampedandwired/html-webpack-plugin
63 | new HtmlWebpackPlugin({
64 | filename: config.build.index,
65 | template: 'index.html',
66 | inject: true,
67 | minify: {
68 | removeComments: true,
69 | collapseWhitespace: true,
70 | removeAttributeQuotes: true
71 | // more options:
72 | // https://github.com/kangax/html-minifier#options-quick-reference
73 | },
74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
75 | chunksSortMode: 'dependency'
76 | }),
77 | // keep module.id stable when vendor modules does not change
78 | new webpack.HashedModuleIdsPlugin(),
79 | // enable scope hoisting
80 | new webpack.optimize.ModuleConcatenationPlugin(),
81 | // split vendor js into its own file
82 | new webpack.optimize.CommonsChunkPlugin({
83 | name: 'vendor',
84 | minChunks (module) {
85 | // any required modules inside node_modules are extracted to vendor
86 | return (
87 | module.resource &&
88 | /\.js$/.test(module.resource) &&
89 | module.resource.indexOf(
90 | path.join(__dirname, '../node_modules')
91 | ) === 0
92 | )
93 | }
94 | }),
95 | // extract webpack runtime and module manifest to its own file in order to
96 | // prevent vendor hash from being updated whenever app bundle is updated
97 | new webpack.optimize.CommonsChunkPlugin({
98 | name: 'manifest',
99 | minChunks: Infinity
100 | }),
101 | // This instance extracts shared chunks from code splitted chunks and bundles them
102 | // in a separate chunk, similar to the vendor chunk
103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
104 | new webpack.optimize.CommonsChunkPlugin({
105 | name: 'app',
106 | async: 'vendor-async',
107 | children: true,
108 | minChunks: 3
109 | }),
110 |
111 | // copy custom static assets
112 | new CopyWebpackPlugin([
113 | {
114 | from: path.resolve(__dirname, '../static'),
115 | to: config.build.assetsSubDirectory,
116 | ignore: ['.*']
117 | }
118 | ])
119 | ]
120 | })
121 |
122 | if (config.build.productionGzip) {
123 | const CompressionWebpackPlugin = require('compression-webpack-plugin')
124 |
125 | webpackConfig.plugins.push(
126 | new CompressionWebpackPlugin({
127 | asset: '[path].gz[query]',
128 | algorithm: 'gzip',
129 | test: new RegExp(
130 | '\\.(' +
131 | config.build.productionGzipExtensions.join('|') +
132 | ')$'
133 | ),
134 | threshold: 10240,
135 | minRatio: 0.8
136 | })
137 | )
138 | }
139 |
140 | if (config.build.bundleAnalyzerReport) {
141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
143 | }
144 |
145 | module.exports = webpackConfig
146 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 |
24 | /**
25 | * Source Maps
26 | */
27 |
28 | // https://webpack.js.org/configuration/devtool/#development
29 | devtool: 'cheap-module-eval-source-map',
30 |
31 | // If you have problems debugging vue-files in devtools,
32 | // set this to false - it *may* help
33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
34 | cacheBusting: true,
35 |
36 | cssSourceMap: true
37 | },
38 |
39 | build: {
40 | // Template for index.html
41 | index: path.resolve(__dirname, '../dist/index.html'),
42 |
43 | // Paths
44 | assetsRoot: path.resolve(__dirname, '../dist'),
45 | assetsSubDirectory: 'static',
46 | assetsPublicPath: '',
47 |
48 | /**
49 | * Source Maps
50 | */
51 |
52 | productionSourceMap: false,
53 | // https://webpack.js.org/configuration/devtool/#production
54 | devtool: '#source-map',
55 |
56 | // Gzip off by default as many popular static hosts such as
57 | // Surge or Netlify already gzip all static assets for you.
58 | // Before setting to `true`, make sure to:
59 | // npm install --save-dev compression-webpack-plugin
60 | productionGzip: false,
61 | productionGzipExtensions: ['js', 'css'],
62 |
63 | // Run the build command with an extra argument to
64 | // View the bundle analyzer report after build finishes:
65 | // `npm run build --report`
66 | // Set to `true` or `false` to always turn it on or off
67 | bundleAnalyzerReport: process.env.npm_config_report
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 文件上传-Helloweba
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fast-uploader",
3 | "version": "1.0.0",
4 | "description": "Demo source for a fast uploader dependens on vue-simple-uploader.",
5 | "author": "lrfbeyond ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "build": "node build/build.js"
11 | },
12 | "dependencies": {
13 | "axios": "^0.19.2",
14 | "element-ui": "^2.13.0",
15 | "spark-md5": "^3.0.1",
16 | "vue": "^2.5.2",
17 | "vue-router": "^3.0.1",
18 | "vue-simple-uploader": "^0.7.4"
19 | },
20 | "devDependencies": {
21 | "autoprefixer": "^7.1.2",
22 | "babel-core": "^6.22.1",
23 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
24 | "babel-loader": "^7.1.1",
25 | "babel-plugin-syntax-jsx": "^6.18.0",
26 | "babel-plugin-transform-runtime": "^6.22.0",
27 | "babel-plugin-transform-vue-jsx": "^3.5.0",
28 | "babel-preset-env": "^1.3.2",
29 | "babel-preset-stage-2": "^6.22.0",
30 | "chalk": "^2.0.1",
31 | "copy-webpack-plugin": "^4.0.1",
32 | "css-loader": "^0.28.0",
33 | "extract-text-webpack-plugin": "^3.0.0",
34 | "file-loader": "^1.1.4",
35 | "friendly-errors-webpack-plugin": "^1.6.1",
36 | "html-webpack-plugin": "^2.30.1",
37 | "less": "^3.11.1",
38 | "less-loader": "^5.0.0",
39 | "node-notifier": "^5.1.2",
40 | "optimize-css-assets-webpack-plugin": "^3.2.0",
41 | "ora": "^1.2.0",
42 | "portfinder": "^1.0.13",
43 | "postcss-import": "^11.0.0",
44 | "postcss-loader": "^2.0.8",
45 | "postcss-url": "^7.2.1",
46 | "rimraf": "^2.6.0",
47 | "semver": "^5.3.0",
48 | "shelljs": "^0.7.6",
49 | "uglifyjs-webpack-plugin": "^1.1.1",
50 | "url-loader": "^0.5.8",
51 | "vue-loader": "^13.3.0",
52 | "vue-style-loader": "^3.0.1",
53 | "vue-template-compiler": "^2.5.2",
54 | "webpack": "^3.6.0",
55 | "webpack-bundle-analyzer": "^2.9.0",
56 | "webpack-dev-server": "^2.9.1",
57 | "webpack-merge": "^4.1.0"
58 | },
59 | "engines": {
60 | "node": ">= 6.0.0",
61 | "npm": ">= 3.0.0"
62 | },
63 | "browserslist": [
64 | "> 1%",
65 | "last 2 versions",
66 | "not ie <= 8"
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/php/Uploader.php:
--------------------------------------------------------------------------------
1 | '', //文件的唯一标识
17 | 'chunkNumber' => 1, //当前是第几个分片
18 | 'totalChunks' => 1, //总分片数
19 | 'filename' => '', //文件名称
20 | 'totalSize' => 0 //文件总大小
21 | ];
22 |
23 | public static function mysql()
24 | {
25 | $conn = [
26 | 'dbhost' => '127.0.0.1',//数据库服务器
27 | 'dbport' => 3306,//端口
28 | 'dbname' => 'demo',//数据库名称
29 | 'dbuser' => 'root',//用户名
30 | 'dbpass' => ''//密码
31 | ];
32 |
33 | // 连接
34 | try {
35 | $db = new PDO('mysql:host='.$conn['dbhost'].';port='.$conn['dbport'].';dbname='.$conn['dbname'], $conn['dbuser'], $conn['dbpass']);
36 | $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //设置错误模式
37 | $db->query('SET NAMES utf8;');
38 | self::$mysql = $db;
39 | } catch (PDOException $e) {
40 | echo '连接数据库失败!'.PHP_EOL;
41 | exit;
42 | }
43 | return self::$mysql;
44 | }
45 |
46 | //检测md5表是否已存在该文件
47 | private function checkMd5($md5, $filesize)
48 | {
49 | $db = self::mysql();
50 | $sql = "SELECT count(*) as t,filepath FROM `hw_file` WHERE md5=:md5 AND filesize=:filesize";
51 | $stmt = $db->prepare($sql);
52 | $stmt->execute([
53 | ':md5' => $md5,
54 | ':filesize' => $filesize
55 | ]);
56 | $row = $stmt->fetch(PDO::FETCH_ASSOC);
57 | $count = $row['t'];
58 | if ($count > 0) {
59 | $res['isExist'] = true;
60 | $res['filepath'] = $row['filepath'];
61 | } else {
62 | $res['isExist'] = false;
63 | }
64 | return $res;
65 | }
66 |
67 | //检测断点和md5
68 | public function checkFile()
69 | {
70 | $identifier = $this->fileInfo['identifier'];
71 | $filePath = self::$tmpDir. DIRECTORY_SEPARATOR . $identifier; //临时分片文件路径
72 | $totalChunks = $this->fileInfo['totalChunks'];
73 |
74 | //检测文件md5是否已经存在
75 | $rs = $this->checkMd5($identifier, $this->fileInfo['totalSize']);
76 | if ($rs['isExist'] === true) {
77 | return $rs;
78 | }
79 |
80 | //检查分片是否存在
81 | $chunkExists = [];
82 | for ($index = 1; $index <= $totalChunks; $index++ ) {
83 | if (file_exists("{$filePath}_{$index}")) {
84 | array_push($chunkExists, $index);
85 | }
86 | }
87 | if (count($chunkExists) == $totalChunks) { //全部分片存在,则直接合成
88 | $this->merge();
89 | } else {
90 | $res['uploaded'] = $chunkExists;
91 | return $res;
92 | }
93 | }
94 |
95 | //上传分片
96 | public function upload()
97 | {
98 | if (!empty($_FILES)) {
99 | $in = @fopen($_FILES["file"]["tmp_name"], "rb");
100 | if (!$in = @fopen($_FILES["file"]["tmp_name"], "rb")) {
101 | return $this->message(1002, '打开临时文件失败');
102 | }
103 | } else {
104 | if (!$in = @fopen("php://input", "rb")) {
105 | return $this->message(1003, '打开输入流失败');
106 | }
107 | }
108 |
109 | if ($this->fileInfo['totalChunks'] === 1) {
110 | //如果只有1片,则不需要合并,直接将临时文件转存到保存目录下
111 | $filename = $this->fileInfo['filename'];
112 | $saveDir = self::$saveDir . DIRECTORY_SEPARATOR . date('Y-m-d');
113 | if (!is_dir($saveDir)) {
114 | @mkdir($saveDir);
115 | }
116 |
117 | $uploadPath = $saveDir . DIRECTORY_SEPARATOR .$filename;
118 | $res['merge'] = false;
119 | } else { //需要合并
120 | $filePath = self::$tmpDir. DIRECTORY_SEPARATOR . $this->fileInfo['identifier']; //临时分片文件路径
121 | $uploadPath = $filePath . '_' . $this->fileInfo['chunkNumber']; //临时分片文件名
122 | $res['merge'] = true;
123 | }
124 | if (!$out = @fopen($uploadPath, "wb")) {
125 | return $this->message(1004, '文件不可写');
126 | }
127 | while ($buff = fread($in, 4096)) {
128 | fwrite($out, $buff);
129 | }
130 | @fclose($in);
131 | @fclose($out);
132 |
133 | if ($this->fileInfo['totalChunks'] === 1) { //数据入库
134 | $fileData = [
135 | 'filename' => $filename,
136 | 'filesize' => $this->fileInfo['totalSize'],
137 | 'md5' => $this->fileInfo['identifier'],
138 | 'filepath' => date('Y-m-d') . '/' . $filename
139 | ];
140 | $url = $this->insertData($fileData);
141 | $res['url'] = $url;
142 | }
143 |
144 | $res['code'] = 0;
145 | return $res;
146 | }
147 |
148 | //合并文件
149 | public function merge()
150 | {
151 | $filePath = self::$tmpDir. DIRECTORY_SEPARATOR . $this->fileInfo['identifier'];
152 |
153 | $totalChunks = $this->fileInfo['totalChunks'];
154 | $filename = $this->fileInfo['filename'];
155 |
156 | $done = true;
157 | //检查所有分片是否都存在
158 | for ($index = 1; $index <= $totalChunks; $index++ ) {
159 | if (!file_exists("{$filePath}_{$index}")) {
160 | $done = false;
161 | break;
162 | }
163 | }
164 | if ($done === false) {
165 | return $this->message(1005, '分片信息错误');
166 | }
167 | //如果所有文件分片都上传完毕,开始合并
168 | $timeStart = $this->getmicrotime(); //合并开始时间
169 | $saveDir = self::$saveDir . DIRECTORY_SEPARATOR . date('Y-m-d');
170 | if (!is_dir($saveDir)) {
171 | @mkdir($saveDir);
172 | }
173 |
174 | $uploadPath = $saveDir . DIRECTORY_SEPARATOR .$filename;
175 |
176 | if (!$out = @fopen($uploadPath, "wb")) {
177 | return $this->message(1004, '文件不可写');
178 | }
179 | if (flock($out, LOCK_EX) ) { // 进行排他型锁定
180 | for($index = 1; $index <= $totalChunks; $index++ ) {
181 | if (!$in = @fopen("{$filePath}_{$index}", "rb")) {
182 | break;
183 | }
184 | while ($buff = fread($in, 4096)) {
185 | fwrite($out, $buff);
186 | }
187 | @fclose($in);
188 | @unlink("{$filePath}_{$index}"); //删除分片
189 | }
190 |
191 | flock($out, LOCK_UN); // 释放锁定
192 | }
193 | @fclose($out);
194 | $timeEnd = $this->getmicrotime(); //合并完成时间
195 |
196 | $fileData = [
197 | 'filename' => $filename,
198 | 'filesize' => $this->fileInfo['totalSize'],
199 | 'md5' => $this->fileInfo['identifier'],
200 | 'filepath' => date('Y-m-d') . '/' . $filename
201 | ];
202 | $url = $this->insertData($fileData);
203 |
204 | $res['code'] = 0;
205 | $res['url'] = $url;
206 | $res['time'] = $timeEnd - $timeStart; //合并总耗时
207 |
208 | return $res;
209 | }
210 |
211 | //将文件信息写入数据表
212 | private function insertData($data)
213 | {
214 | if (empty($data['filename'])) {
215 | return false;
216 | }
217 | $pathInfo = pathinfo($data['filename']);
218 | $ext = '0';
219 | if (array_key_exists('extension', $pathInfo)) {
220 | $ext = $pathInfo['extension'];
221 | }
222 | $db = self::mysql();
223 | $sql = "INSERT INTO `hw_file` (filename,filesize,md5,type,filepath,created_at) VALUES (:filename,:filesize,:md5,:type,:filepath,:created_at)";
224 | $stmt = $db->prepare($sql);
225 | $stmt->execute([
226 | ':filename' => $data['filename'],
227 | ':filesize' => $data['filesize'],
228 | ':md5' => $data['md5'],
229 | ':type' => $ext,
230 | ':filepath' => $data['filepath'],
231 | ':created_at' => date('Y-m-d H:i:s'),
232 | ]);
233 |
234 | $insertId = $db->lastInsertId(); //返回插入成功后的id
235 | if ($insertId > 0) {
236 | return $data['filepath']; //返回文件路径
237 | } else {
238 | return $this->message(1008, '数据写入出错了');
239 | }
240 | }
241 |
242 | private function getmicrotime()
243 | {
244 | list($usec, $sec) = explode(" ",microtime());
245 | return ((float)$usec + (float)$sec);
246 | }
247 |
248 | private function message($code, $msg)
249 | {
250 | $res = [
251 | 'code' => $code,
252 | 'message' => $msg
253 | ];
254 | return $res;
255 | }
256 | }
--------------------------------------------------------------------------------
/php/hw_file.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Navicat MySQL Data Transfer
3 |
4 | Source Server : localhost
5 | Source Server Version : 50724
6 | Source Host : localhost:3306
7 | Source Database : lrfbeyond_demo
8 |
9 | Target Server Type : MYSQL
10 | Target Server Version : 50724
11 | File Encoding : 65001
12 |
13 | Date: 2020-04-23 17:30:15
14 | */
15 |
16 | SET FOREIGN_KEY_CHECKS=0;
17 |
18 | -- ----------------------------
19 | -- Table structure for hw_file
20 | -- ----------------------------
21 | DROP TABLE IF EXISTS `hw_file`;
22 | CREATE TABLE `hw_file` (
23 | `id` int(11) NOT NULL AUTO_INCREMENT,
24 | `filename` varchar(255) NOT NULL COMMENT '文件名',
25 | `filesize` int(11) NOT NULL DEFAULT '0' COMMENT '文件大小',
26 | `md5` varchar(32) NOT NULL COMMENT '文件md5',
27 | `type` varchar(10) NOT NULL COMMENT '文件类型',
28 | `filepath` varchar(128) NOT NULL COMMENT '文件保存路径',
29 | `created_at` datetime NOT NULL COMMENT '上传时间',
30 | PRIMARY KEY (`id`)
31 | ) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4;
32 |
--------------------------------------------------------------------------------
/php/up.php:
--------------------------------------------------------------------------------
1 | fileInfo = [
19 | 'filename' => htmlentities($data['filename']), //文件名称
20 | 'identifier' => htmlentities($data['identifier']), //文件唯一标识
21 | 'totalSize' => intval($data['totalSize']), //文件总大小
22 | 'totalChunks' => intval($data['totalChunks']) //总分片数
23 | ];
24 | $res = $up->merge();
25 | } else {
26 | $method = $_SERVER['REQUEST_METHOD'];
27 | if ($method === 'POST') { //上传
28 | $up->fileInfo = [
29 | 'identifier' => htmlentities($_POST['identifier']), //每个文件的唯一标识
30 | 'filename' => htmlentities($_POST['filename']), //文件名称
31 | 'totalSize' => intval($_POST['totalSize']), //文件总大小
32 | 'chunkNumber' => intval($_POST['chunkNumber']), //当前是第几个分片
33 | 'totalChunks' => intval($_POST['totalChunks']) //总分片数
34 | ];
35 | $res = $up->upload();
36 | } else { //上传前检测文件md5和分片
37 | $up->fileInfo = [
38 | 'identifier' => htmlentities($_GET['identifier']), //每个文件的唯一标识
39 | 'filename' => htmlentities($_GET['filename']), //文件名称
40 | 'totalSize' => intval($_GET['totalSize']), //文件总大小
41 | 'totalChunks' => intval($_GET['totalChunks']) //总分片数
42 | ];
43 | $res = $up->checkFile();
44 | }
45 | }
46 |
47 | echo json_encode($res);
48 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
36 |
37 |
81 |
--------------------------------------------------------------------------------
/src/assets/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrfbeyond/fast-uploader/35c6a8f963ac2e688d8c52bed4bf9ce09252a4c8/src/assets/bg.png
--------------------------------------------------------------------------------
/src/assets/body_bg.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrfbeyond/fast-uploader/35c6a8f963ac2e688d8c52bed4bf9ce09252a4c8/src/assets/body_bg.gif
--------------------------------------------------------------------------------
/src/assets/css/iconfont.css:
--------------------------------------------------------------------------------
1 | @font-face {font-family: "iconfont";
2 | src: url('iconfont.eot?t=1585640036938'); /* IE9 */
3 | src: url('iconfont.eot?t=1585640036938#iefix') format('embedded-opentype'), /* IE6-IE8 */
4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
5 | url('iconfont.woff?t=1585640036938') format('woff'),
6 | url('iconfont.ttf?t=1585640036938') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
7 | url('iconfont.svg?t=1585640036938#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family: "iconfont" !important;
12 | font-size: 16px;
13 | font-style: normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-zip:before {
19 | content: "\e626";
20 | }
21 |
22 | .icon-unknown:before {
23 | content: "\e613";
24 | }
25 |
26 | .icon-audio:before {
27 | content: "\e645";
28 | }
29 |
30 | .icon-image:before {
31 | content: "\e656";
32 | }
33 |
34 | .icon-video:before {
35 | content: "\e617";
36 | }
37 |
38 | .icon-selimage:before {
39 | content: "\e724";
40 | }
41 |
42 | .icon-dir:before {
43 | content: "\e761";
44 | }
45 |
46 | .icon-tianjia:before {
47 | content: "\e61f";
48 | }
49 |
50 | .icon-mkdir:before {
51 | content: "\e607";
52 | }
53 |
54 | .icon-upload:before {
55 | content: "\e614";
56 | }
57 |
58 | .icon-document:before {
59 | content: "\e760";
60 | }
61 |
62 | .icon-folder:before {
63 | content: "\e686";
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/src/assets/css/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrfbeyond/fast-uploader/35c6a8f963ac2e688d8c52bed4bf9ce09252a4c8/src/assets/css/iconfont.eot
--------------------------------------------------------------------------------
/src/assets/css/iconfont.js:
--------------------------------------------------------------------------------
1 | !function(n){var c,a='',t=(c=document.getElementsByTagName("script"))[c.length-1].getAttribute("data-injectcss");if(t&&!n.__iconfont__svg__cssinject__){n.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}!function(c){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(c,0);else{var t=function(){document.removeEventListener("DOMContentLoaded",t,!1),c()};document.addEventListener("DOMContentLoaded",t,!1)}else document.attachEvent&&(l=c,o=n.document,i=!1,(a=function(){try{o.documentElement.doScroll("left")}catch(c){return void setTimeout(a,50)}e()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,e())});function e(){i||(i=!0,l())}var l,o,i,a}(function(){var c,t,e,l,o,i;(c=document.createElement("div")).innerHTML=a,a=null,(t=c.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",e=t,(l=document.body).firstChild?(o=e,(i=l.firstChild).parentNode.insertBefore(o,i)):l.appendChild(e))})}(window);
--------------------------------------------------------------------------------
/src/assets/css/iconfont.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "1720184",
3 | "name": "upload",
4 | "font_family": "iconfont",
5 | "css_prefix_text": "icon-",
6 | "description": "",
7 | "glyphs": [
8 | {
9 | "icon_id": "6049876",
10 | "name": "压缩包",
11 | "font_class": "zip",
12 | "unicode": "e626",
13 | "unicode_decimal": 58918
14 | },
15 | {
16 | "icon_id": "553902",
17 | "name": "文档",
18 | "font_class": "unknown",
19 | "unicode": "e613",
20 | "unicode_decimal": 58899
21 | },
22 | {
23 | "icon_id": "1004653",
24 | "name": "文件-音频",
25 | "font_class": "audio",
26 | "unicode": "e645",
27 | "unicode_decimal": 58949
28 | },
29 | {
30 | "icon_id": "1071023",
31 | "name": "图像1",
32 | "font_class": "image",
33 | "unicode": "e656",
34 | "unicode_decimal": 58966
35 | },
36 | {
37 | "icon_id": "1638517",
38 | "name": "视频",
39 | "font_class": "video",
40 | "unicode": "e617",
41 | "unicode_decimal": 58903
42 | },
43 | {
44 | "icon_id": "13137449",
45 | "name": "图像_未选择",
46 | "font_class": "selimage",
47 | "unicode": "e724",
48 | "unicode_decimal": 59172
49 | },
50 | {
51 | "icon_id": "4933421",
52 | "name": "文件夹",
53 | "font_class": "dir",
54 | "unicode": "e761",
55 | "unicode_decimal": 59233
56 | },
57 | {
58 | "icon_id": "521050",
59 | "name": "添加",
60 | "font_class": "tianjia",
61 | "unicode": "e61f",
62 | "unicode_decimal": 58911
63 | },
64 | {
65 | "icon_id": "602556",
66 | "name": "网盘-新建文件夹",
67 | "font_class": "mkdir",
68 | "unicode": "e607",
69 | "unicode_decimal": 58887
70 | },
71 | {
72 | "icon_id": "2305613",
73 | "name": "上传文件",
74 | "font_class": "upload",
75 | "unicode": "e614",
76 | "unicode_decimal": 58900
77 | },
78 | {
79 | "icon_id": "4933419",
80 | "name": "文件",
81 | "font_class": "document",
82 | "unicode": "e760",
83 | "unicode_decimal": 59232
84 | },
85 | {
86 | "icon_id": "6583375",
87 | "name": "文件",
88 | "font_class": "folder",
89 | "unicode": "e686",
90 | "unicode_decimal": 59014
91 | }
92 | ]
93 | }
94 |
--------------------------------------------------------------------------------
/src/assets/css/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
63 |
--------------------------------------------------------------------------------
/src/assets/css/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrfbeyond/fast-uploader/35c6a8f963ac2e688d8c52bed4bf9ce09252a4c8/src/assets/css/iconfont.ttf
--------------------------------------------------------------------------------
/src/assets/css/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrfbeyond/fast-uploader/35c6a8f963ac2e688d8c52bed4bf9ce09252a4c8/src/assets/css/iconfont.woff
--------------------------------------------------------------------------------
/src/assets/css/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrfbeyond/fast-uploader/35c6a8f963ac2e688d8c52bed4bf9ce09252a4c8/src/assets/css/iconfont.woff2
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrfbeyond/fast-uploader/35c6a8f963ac2e688d8c52bed4bf9ce09252a4c8/src/assets/logo.png
--------------------------------------------------------------------------------
/src/components/Breakpoint.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
19 |
20 |
21 | 上传文件
22 | 上传文件夹
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
173 |
174 |
--------------------------------------------------------------------------------
/src/components/Chunk.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
17 |
18 |
19 | 上传文件
20 | 上传文件夹
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
109 |
110 |
--------------------------------------------------------------------------------
/src/components/Md5.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
19 |
20 |
21 | 上传文件
22 | 上传文件夹
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
181 |
182 |
--------------------------------------------------------------------------------
/src/components/Skip.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
19 |
20 |
21 | 上传文件
22 | 上传文件夹
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
176 |
177 |
--------------------------------------------------------------------------------
/src/components/Uploader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
温馨提示:可将文件拖动到灰色区域上传
10 |
11 |
12 |
13 | 上传文件
14 | 上传文件夹
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
60 |
61 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import router from './router'
6 |
7 | import './assets/css/iconfont.css'
8 |
9 | Vue.config.productionTip = false
10 |
11 |
12 | import uploader from 'vue-simple-uploader'
13 | Vue.use(uploader);
14 |
15 | /* eslint-disable no-new */
16 | new Vue({
17 | el: '#app',
18 | router,
19 | components: { App },
20 | template: ''
21 | })
22 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Uploader from '@/components/Uploader'
4 | import Chunk from '@/components/Chunk'
5 | import Md5 from '@/components/Md5'
6 | import Skip from '@/components/Skip'
7 | import Breakpoint from '@/components/Breakpoint'
8 |
9 | Vue.use(Router)
10 |
11 | export default new Router({
12 | routes: [
13 | {
14 | path: '/',
15 | name: 'uploader',
16 | component: Uploader
17 | },
18 | {
19 | path: '/chunk',
20 | name: 'chunk',
21 | component: Chunk
22 | },
23 | {
24 | path: '/skip',
25 | name: 'skip',
26 | component: Skip
27 | },
28 | {
29 | path: '/md5',
30 | name: 'md5',
31 | component: Md5
32 | },
33 | {
34 | path: '/breakpoint',
35 | name: 'breakpoint',
36 | component: Breakpoint
37 | },
38 | ]
39 | })
40 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrfbeyond/fast-uploader/35c6a8f963ac2e688d8c52bed4bf9ce09252a4c8/static/.gitkeep
--------------------------------------------------------------------------------