wangqing 5 years ago
parent
commit
be2e5db84f
  1. 12
      .editorconfig
  2. 3
      .env.development
  3. 3
      .env.production
  4. 5
      .huskyrc
  5. 6
      .lintstagedrc
  6. 6
      .stylelintignore
  7. 18
      .stylelintrc
  8. 9
      babel.config.js
  9. 56
      dependencies.cdn.js
  10. 0
      docs/.nojekyll
  11. 30
      docs/README.md
  12. 7
      docs/_coverpage.md
  13. 2
      docs/_navbar.md
  14. 14
      docs/_sidebar.md
  15. 11
      docs/axios.md
  16. 27
      docs/cdn.md
  17. 77
      docs/coding-standard.md
  18. 5
      docs/configure.md
  19. 11
      docs/global-component.md
  20. 39
      docs/index.html
  21. 39
      docs/mobile-support.md
  22. 13
      docs/plop.md
  23. 9
      docs/sass-resources.md
  24. 59
      docs/sprite.md
  25. 35
      docs/start.md
  26. 11
      docs/svg.md
  27. 5
      docs/vue-router.md
  28. 31
      docs/vuex.md
  29. 142
      package.json
  30. 23
      plop-templates/component/index.hbs
  31. 63
      plop-templates/component/prompt.js
  32. 20
      plop-templates/page/index.hbs
  33. 53
      plop-templates/page/prompt.js
  34. 15
      plop-templates/store/index.hbs
  35. 27
      plop-templates/store/prompt.js
  36. 6
      plopfile.js
  37. 66
      scss.template.hbs
  38. BIN
      src/assets/images/official/use-commony.png
  39. BIN
      src/assets/images/old-logo-blue.png
  40. BIN
      src/assets/images/tducklogo-v2-blue.png
  41. BIN
      src/assets/sprites/example/address.png
  42. BIN
      src/assets/sprites/example/feedback.png
  43. BIN
      src/assets/sprites/example/payment.png
  44. 9
      src/assets/styles/index.scss
  45. 27
      src/utils/sign.js
  46. 152
      src/views/account/ForgetPwd.vue
  47. 94
      src/views/form/editor/IconsDialog.vue
  48. 111
      src/views/form/index.vue
  49. 85
      src/views/form/oldIndex.vue
  50. 64
      src/views/form/statistics/chart.vue
  51. 149
      src/views/form/statistics/filter.vue
  52. 16
      src/views/form/statistics/index.vue
  53. 88
      src/views/form/statistics/list.vue
  54. 45
      src/views/home/HomButton.vue
  55. 28
      src/views/home/HomeView.vue
  56. 205
      src/views/home/oldIndex.vue
  57. 13
      src/views/official/enterprise.vue
  58. 48
      src/views/official/index.vue
  59. 270
      src/views/official/introduction.vue
  60. 13
      src/views/official/proposal.vue
  61. 13
      src/views/official/sources.vue
  62. 103
      vue.config.js

12
.editorconfig

@ -1,12 +1,22 @@
# 告诉EditorConfig插件,这是根文件,不用继续往上查找
root = true
# 匹配全部文件
[*]
# 设置字符集
charset = utf-8
# 缩进风格,可选space、tab
indent_style = space
indent_size = 4
# 缩进的空格数
indent_size = 2
# 结尾换行符,可选lf、cr、crlf
end_of_line = lf
# 在文件结尾插入新行
insert_final_newline = true
# 删除一行中的前后空格
trim_trailing_whitespace = true
# 匹配md结尾的文件
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

3
.env.development

@ -2,9 +2,6 @@
VUE_APP_TITLE = 填鸭测试环境
# 接口请求地址,会设置到 axios 的 baseURL 参数上
VUE_APP_API_ROOT = /tduck-api
# 是否开启 CDN 支持,开启设置 ON,关闭设置 OFF
# 详情介绍请阅读 http://eoner.gitee.io/vue-automation/#/cdn
VUE_APP_CDN = OFF
# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
VUE_APP_DEBUG_TOOL =
# 高德地图key

3
.env.production

@ -2,9 +2,6 @@
VUE_APP_TITLE = 填鸭
# 接口请求地址,会设置到 axios 的 baseURL 参数上
VUE_APP_API_ROOT = /tduck-api
# 是否开启 CDN 支持,开启设置 ON,关闭设置 OFF
# 详情介绍请阅读 http://eoner.gitee.io/vue-automation/#/cdn
VUE_APP_CDN = OFF
# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
VUE_APP_DEBUG_TOOL =
# 高德地图key

5
.huskyrc

@ -1,5 +0,0 @@
{
"hooks": {
"pre-commit": "lint-staged"
}
}

6
.lintstagedrc

@ -1,6 +0,0 @@
{
"src/**/*.{js,vue}": [
"vue-cli-service lint",
"vue-cli-service lint:style"
]
}

6
.stylelintignore

@ -1,6 +0,0 @@
dist/
node_modules/
src/assets/sprites/
src/theme/
src/components/FontIcon/
src/components/verifition

18
.stylelintrc

@ -1,18 +0,0 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-recommended-scss"
],
"plugins": [
"stylelint-scss"
],
"rules": {
"indentation": 4,
"rule-empty-line-before": "never",
"at-rule-empty-line-before": "never",
"no-descending-specificity": null,
"selector-pseudo-class-no-unknown": null,
"selector-pseudo-element-no-unknown": [true, { "ignorePseudoElements": ["v-deep"] }],
"property-no-unknown": null
}
}

9
babel.config.js

@ -1,10 +1,13 @@
module.exports = {
presets: [
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
'@vue/cli-plugin-babel/preset'
],
env: {
development: {
plugins: ['dynamic-import-node']
'env': {
'development': {
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
'plugins': ['dynamic-import-node']
}
}
}

56
dependencies.cdn.js

@ -1,56 +0,0 @@
module.exports = [
{
name: 'vue',
library: 'Vue',
js: 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js',
css: ''
},
{
name: 'vue-router',
library: 'VueRouter',
js: 'https://cdn.jsdelivr.net/npm/vue-router@3.3.4/dist/vue-router.min.js',
css: ''
},
{
name: 'vuex',
library: 'Vuex',
js: 'https://cdn.jsdelivr.net/npm/vuex@3.5.1/dist/vuex.min.js',
css: ''
},
{
name: 'axios',
library: 'axios',
js: 'https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js',
css: ''
},
{
name: 'qs',
library: 'Qs',
js: 'https://cdn.jsdelivr.net/npm/qs@6.9.3/dist/qs.js',
css: ''
},
{
name: 'nprogress',
library: 'NProgress',
js: 'https://cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.js',
css: 'https://cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.css'
},
{
name: 'vue-meta',
library: 'VueMeta',
js: 'https://cdn.jsdelivr.net/npm/vue-meta@2.4.0/dist/vue-meta.min.js',
css: ''
},
{
name: 'js-cookie',
library: 'Cookies',
js: 'https://cdn.jsdelivr.net/npm/js-cookie@2.2.1/src/js.cookie.min.js',
css: ''
},
{
name: 'dayjs',
library: 'dayjs',
js: 'https://cdn.jsdelivr.net/npm/dayjs@1.8.29/dayjs.min.js',
css: ''
}
]

0
docs/.nojekyll

30
docs/README.md

@ -1,30 +0,0 @@
# 关于 vue-automation
## 这是什么
一款开箱即用的 Vue 项目模版,基于 Vue CLI
## 特点
- 默认集成 vue-router 和 vuex
- 全局 SASS 资源自动引入
- 精灵图自动生成
- 全局组件自动注册
- CDN支持,优化打包体积
- 轻松实现团队代码规范
## 支持
给个小 ❤️ 吧~
[![star](https://img.shields.io/github/stars/hooray/vue-automation?style=social)](https://github.com/hooray/vue-automation/stargazers)
[![star](https://gitee.com/eoner/vue-automation/badge/star.svg?theme=dark)](https://gitee.com/eoner/vue-automation/stargazers)
## 生态
[![](https://hooray.gitee.io/fantastic-admin/logo.png)](https://hooray.gitee.io/fantastic-admin)
[Fantastic-admin](https://hooray.gitee.io/fantastic-admin)
一款开箱即用的 Vue 中后台管理系统框架

7
docs/_coverpage.md

@ -1,7 +0,0 @@
# vue-automation
> 一款开箱即用的 Vue 项目模版
[开始使用](#关于-vue-automation)
[项目 Github 地址](https://github.com/hooray/vue-automation)
[项目 Gitee 地址](https://gitee.com/eoner/vue-automation)

2
docs/_navbar.md

@ -1,2 +0,0 @@
* [Fantastic-admin](https://hooray.gitee.io/fantastic-admin)
* [文档打开慢?试试 Gitee 地址](http://eoner.gitee.io/vue-automation)

14
docs/_sidebar.md

@ -1,14 +0,0 @@
* [使用](start)
* [配置](configure)
* [全局 SASS 资源](sass-resources)
* [精灵图](sprite)
* [SVG图标](svg)
* [全局组件](global-component)
* [Vue-router](vue-router)
* [Vuex](vuex)
* [Axios拦截器](axios)
* [快速创建文件](plop)
* [代码规范](coding-standard.md)
* 扩展
* [CDN支持](cdn.md)
* [移动端支持](mobile-support.md)

11
docs/axios.md

@ -1,11 +0,0 @@
# Axios拦截器
拦截器的用处就是拦截每一次的请求和响应,然后做一些全局的处理。
例如接口响应报错,可以在拦截器里用统一的报错提示来展示,方便业务开发。
本框架提供了一份拦截器参考代码 `src/api/index.js` ,因为每个公司提供的接口标准不同,所以该文件需要开发者根据各自公司的接口去定制对应的拦截器。
代码很简单,首先初始化 `axios` 对象,然后 `axios.interceptors.request.use()``axios.interceptors.response.use()` 就分别是请求和响应的拦截代码了。
参考代码里只做了简单的拦截处理,例如请求的时候会自动带上 `token` ,响应的时候会根据错误信息判断是登录失效还是接口报错。

27
docs/cdn.md

@ -1,27 +0,0 @@
# CDN支持
开启 CDN 的好处在于,项目中引用的一些第三方库不会打包进项目内,从而减小打包出的文件体积,同时借用 CDN 的优势,大大提高项目加载速度。
CDN 支持默认不开启,如果需要开启,则在 `.env.production` 生产环境配置文件中修改:
```
VUE_APP_CDN = ON
```
CDN 配置文件存放在项目根目录下的 `dependencies.cdn.js` 文件里,可按照标准格式自行扩展配置。
```js
{
name: '',
library: '',
js: '',
css: ''
}
```
其中 `name``library` 最终会转成 webpack 中 externals 的配置项, `name` 是引入的包名, `library` 是全局变量。
这里需要注意以下两点:
1. 如果只在生产环境开启 CDN 支持,请确保第三方库的 CDN 版本与本地依赖包的版本一致,以免出现开发环境是正常的,但生产环境缺不行的情况,也就是因为版本不同造成的 bug
2. 开发环境开启 CDN 支持后会导致 [Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) 无法使用,所以不建议开发环境开启

77
docs/coding-standard.md

@ -1,77 +0,0 @@
# 代码规范
## IDE 编辑器
为保证代码风格统一,统一使用 [VS Code](https://code.visualstudio.com/) 做为开发 IDE ,并安装以下扩展:
- [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig)
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur)
- [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
- [stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint)
安装完后在 `settings.json` 中增加如下配置:
```json
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
}
```
最终效果为,在保存时,会自动对当前文件进行代码格式化操作。
## Git 钩子
上述操作仅对代码的写法规范进行格式化,例如缩进、空格、结尾的分号等。
而在提交代码时, Git 的钩子会检查代码中是否有错误,这些错误是 IDE 无法自动修复的,例如出现未使用过的变量。如果有错误,则会取消此次提交,直到开发者修复完所有错误后才允许提交成功,确保仓库里的代码绝对正确。
> 可通过修改 `.eslintignore``.stylelintignore` 忽略无需做代码规范的文件,例如在项目中引用了一些第三方的插件或组件,我们就可以将其忽略
如果 `git init` 仓库初始化是在依赖包安装之后执行的,则无法初始化 Git 钩子,建议在 `git init` 之后再执行一遍 `yarn` 或者 `npm i` ,重新安装一遍依赖包。
## 配置代码规范
配置文件主要有 3 处,分别为 IDE 配置(`.editorconfig`)、ESLint 配置(`.eslintrc.js` 和 `.eslintignore`)、StyleLint 配置(`.stylelintrc` 和 `.stylelintignore`)。
以代码缩进举例,本模版默认是以 4 空格进行缩进,如果要调整为 2 空格,则需要在 `.editorconfig` 里修改:
```
indent_size = 2
```
`.eslintrc.js` 里修改:
```
'indent': [2, 2, {
'SwitchCase': 1
}],
...
'vue/html-indent': [2, 2],
...
'vue/script-indent': [2, 2, {
'switchCase': 1
}]
```
`.stylelintrc` 里修改:
```
"indentation": 2
```
修改完毕后,再分别执行下面两句命令:
```bash
yarn run lint
yarn run stylelint
```
该操作会将代码进行一次格式校验,如果规则支持自动修复,则会将不符合规则的代码自动进行格式化。
以上面的例子,当缩进规则调整后,我们无需手动去每个文件调整,通过命令可以自动应用新的缩进规则。

5
docs/configure.md

@ -1,5 +0,0 @@
# 配置
默认提供开发环境和生产环境两套配置,分别在根目录下 `.env.development``.env.production` 文件里,可配置项有网站标题、接口请求地址和是否开启CDN支持。
开发者可根据实际业务需求进行扩展,如果对这块不熟悉,可阅读 Vue CLI [环境变量和模式](https://cli.vuejs.org/zh/guide/mode-and-env.html) 章节。

11
docs/global-component.md

@ -1,11 +0,0 @@
# 全局组件
全局组件存放在 `components/global/` 目录下,需要注意各个组件按文件夹区分。
每个组件的文件夹内至少保留一个文件名为 `index` 的组件入口,例如 `index.vue`
组件必须设置 `name` 并保证其唯一,自动注册会将组件的 `name` 设为组件名,可参考 SvgIcon 组件写法。
虽然文件夹名称和 `name` 无关联,但建议与 `name` 保持一致。
如果组件是通过 js 进行调用,则确保组件入口文件为 `index.js`,可参考 ExampleNotice 组件。

39
docs/index.html

@ -1,39 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue-automation</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<!-- <link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css"> -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple-dark.css">
</head>
<body>
<div id="app">载入中...</div>
<script>
window.$docsify = {
name: 'vue-automation',
repo: '',
auto2top: true,
loadNavbar: true,
loadSidebar: true,
maxLevel: 4,
subMaxLevel: 2,
coverpage: true,
copyCode: {
buttonText : '点击复制',
errorText : '错误',
successText: '复制成功'
},
disqus: 'vue-automation'
}
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/disqus.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-themeable@0"></script>
</body>
</html>

39
docs/mobile-support.md

@ -1,39 +0,0 @@
# 移动端支持
移动端各司都有自己的解决方案,以下为我司为例,做为参考:
我司统一使用 vw/vh 做为移动端的布局单位,单位转换通过 [postcss-px-to-viewport](https://www.npmjs.com/package/postcss-px-to-viewport) 进行处理。
首先安装依赖:
`yarn add -D postcss-px-to-viewport`
然后在 `vue.config.js` 里进行配置,具体配置信息可根据项目实际调整:
```js
module.exports = {
css: {
loaderOptions: {
postcss: {
plugins: [
require('postcss-px-to-viewport')({
'unitToConvert': 'px',
'viewportWidth': 750,
'unitPrecision': 3,
'viewportUnit': 'vw',
'selectorBlackList': [
'ignore',
'van',
'mescroll'
],
'minPixelValue': 1,
'mediaQuery': false
})
]
}
}
}
}
```
最后在开发中就可以直接使用 px 了,最终输出就是 vw 。

13
docs/plop.md

@ -1,13 +0,0 @@
# 快速创建文件
开发过程中,避免不了手动去频繁创建页面、组件等文件,并且还要在文件里写一些必要的代码,是不是觉得很麻烦?现在你可以用更简洁的方式来处理这一切。
![](https://s1.ax1x.com/2020/06/30/N5jWcV.gif)
> 该功能基于 [plop](https://www.npmjs.com/package/plop) 实现。
模版默认提供了 page(页面/布局) 、component(组件) 、store(全局状态) 三个模版文件,通过 `yarn new` 指令可以自行选择。
在实际项目开发中,建议根据项目定制适合项目的模版文件,可以大大提高开发效率,当多人协作开发时,也能统一部分标准。
模版目录为 `./plop-templates/` ,如果是新建模版,记得在项目根目录 `plopfile.js` 里引用一下。

9
docs/sass-resources.md

@ -1,9 +0,0 @@
# 全局 SASS 资源
> 全局 SASS 资源并不是全局样式,是变量、@mixin 、@function 这些东西
`assets/styles/resources/` 目录下存放全局的 SASS 资源,也就是说在这个目录里的文件,无需在页面上引用即可生效并使用。
项目中默认存放了 `utils.scss` 文件,里面有几个 `@mixin``%` ,你可以尝试在页面中使用它们看看效果。
同样,[精灵图](sprite)目录下生成的 SASS 资源也是全局可调用的。

59
docs/sprite.md

@ -1,59 +0,0 @@
# 精灵图
> 又称雪碧图,原理是将多张小图合并到一张大图上,以便减少 HTTP 请求,提高网站访问速度。
精灵图原始图片的存放位置位于 `assets/sprites/` 目录下,注意按文件夹区分。
项目运行前会根据文件夹生成对应的精灵图文件(精灵图图片和 `.scss` 文件),多个文件夹则会生成多个精灵图文件。需要注意的是,在项目运行时,修改文件夹里的图片,会重新生成相关精灵图文件,但如果新建文件夹,则需要重新运行项目才会生成对应精灵图文件。
`.vue` 文件中可通过 `@include` 直接使用精灵图,无需手动引入 `.scss` 文件:
```scss
// 方法 1
// @include [文件夹名称]-sprite([文件名称]);
.icon {
@include example-sprite(address);
}
// 方法 2
// @include all-[文件夹名称]-sprites;
@include all-example-sprites;
```
最终输出如下:
```css
/* 方法 1 */
.icon {
background-image: url(img/example.326b35aec20837b9c08563c654422fe6.326b35ae.png);
background-position: 0px 0px;
background-size: 210px 210px;
width: 100px;
height: 100px;
}
/* 方法 2 */
.example-address-sprites {
background-image: url(img/example.326b35aec20837b9c08563c654422fe6.326b35ae.png);
background-position: 0 0;
background-size: 210px 210px;
width: 100px;
height: 100px;
}
.example-feedback-sprites {
background-image: url(img/example.326b35aec20837b9c08563c654422fe6.326b35ae.png);
background-position: -110px 0;
background-size: 210px 210px;
width: 100px;
height: 100px;
}
.example-payment-sprites {
background-image: url(img/example.326b35aec20837b9c08563c654422fe6.326b35ae.png);
background-position: 0 -110px;
background-size: 210px 210px;
width: 100px;
height: 100px;
}
```
如果是小型项目,静态图标不多,可全部放在一个文件夹内;如果是中大型项目,文件夹可按模块来划分,这样不同的模块最终会生成各自的精灵图文件。

35
docs/start.md

@ -1,35 +0,0 @@
# 使用
使用前确保本地环境已安装 [Vue CLI](https://cli.vuejs.org/zh/) 。
## 方法 1
> 适用于初学者快速上手,项目里包含演示文件,方便学习
```bash
git clone https://gitee.com/eoner/vue-automation.git
cd vue-automation
yarn install
```
拉取该项目到本地,安装依赖包后即可运行。
运行后,可以看到功能演示,同时项目目录里带有 `example` 的均为演示代码。
## 方法 2
> 适用于已熟练使用的老手,项目里无演代码,方便快速开展工作
安装并使用 [1one-project](https://www.npmjs.com/package/1one-project) 进行项目初始化。
## 注意事项
~~值得一提的是,如果安装过程出现 sass 相关的安装错误,请在安装 [mirror-config-china](https://www.npmjs.com/package/mirror-config-china) 后重试。~~
~~```yarn global add mirror-config-china```~~
大部分安装报错都是因为 `node-sass` 依赖导致,尤其是 Windows 用户,它会强制安装 `python2``Visual Studio` 才能编译成功。
目前本模版已将 `node-sass` 替换为 `sass` ,简化用户安装成本,同时 Sass 官方也已经将 `dart-sass` 作为未来主要的的开发方向了。
参考《[Node Sass to Dart Sass](https://panjiachen.gitee.io/vue-element-admin-site/zh/guide/advanced/sass.html)》

11
docs/svg.md

@ -1,11 +0,0 @@
# SVG图标
现在越来越多项目开始使用 SVG 图标做为精灵图的替代品,本框架也提供了 SVG 图标支持,方便使用。推荐去[阿里巴巴矢量图标库](https://www.iconfont.cn/)下载高质量 SVG 图标
首先将 svg 文件放到 `src/assets/icons/` 目录下,然后在页面中就可以使用了,`name` 就是 svg 文件名
```html
<svg-icon name="example" />
```
> `<svg-icon />` 组件为全局组件,所以无需注册即可使用

5
docs/vue-router.md

@ -1,5 +0,0 @@
# Vue-router
路由也实现了自动注册,但因为有优先级的概念,先定义的会先匹配,所以同一模块下的路由需要放在一个路由配置文件里。
开发者只需关心 `router/modules/` 目录下的文件,一个模块对应一个 `.js` 文件,可参考 `router/modules/example.js` 文件。

31
docs/vuex.md

@ -1,31 +0,0 @@
# Vuex
Vuex 同样实现了自动注册,开发只需关注 `store/modules/` 文件夹里的文件即可,同样也按照模块区分文件。
新建模版:
```js
// example.js
const state = {}
const getters = {}
const actions = {}
const mutations = {}
export default {
namespaced: true,
state,
actions,
getters,
mutations
}
```
文件默认开启命名空间,文件名会默认注册为模块名。
使用方法:
```js
this.$store.state.example.xxx;
this.$store.getters['example/xxx'];
this.$store.dispatch('example/xxx');
this.$store.commit('example/xxx');
```

142
package.json

@ -1,72 +1,76 @@
{
"name": "tduck-front",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vue-cli-service serve",
"serve": "vue-cli-service serve",
"build-dev": "vue-cli-service build --mode development --dest dist-dev",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"stylelint": "vue-cli-service lint:style",
"svgo": "svgo -f src/assets/icons",
"new": "plop"
},
"dependencies": {
"animate.css": "^4.1.1",
"axios": "^0.21.0",
"core-js": "^3.6.4",
"crypto-js": "^4.0.0",
"dayjs": "^1.9.4",
"echarts": "^5.0.0",
"element-ui": "^2.15.2",
"file-saver": "^2.0.2",
"js-cookie": "^2.2.1",
"nprogress": "^0.2.0",
"signature_pad": "^3.0.0-beta.4",
"ua-parser-js": "^0.7.23",
"vue": "^2.6.12",
"vue-clipboard2": "^0.3.1",
"vue-image-crop-upload": "^2.5.0",
"vue-meta": "^2.4.0",
"vue-qr": "^2.3.0",
"vue-router": "^3.4.8",
"vuedraggable": "^2.24.3",
"vuex": "^3.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.8",
"@vue/cli-plugin-eslint": "^4.5.8",
"@vue/cli-service": "^4.5.8",
"@winner-fed/vue-cli-plugin-stylelint": "^1.0.4",
"babel-eslint": "^10.1.0",
"babel-plugin-dynamic-import-node": "^2.3.3",
"eslint": "^7.12.1",
"eslint-plugin-vue": "^7.1.0",
"html-webpack-plugin": "^4.5.0",
"husky": "^4.3.0",
"lint-staged": "^10.5.1",
"plop": "^2.7.4",
"sass": "~1.32.1",
"sass-loader": "^10.0.4",
"sass-resources-loader": "^2.1.1",
"stylelint": "^13.7.2",
"stylelint-config-recommended-scss": "^4.2.0",
"stylelint-config-standard": "^20.0.0",
"stylelint-scss": "^3.18.0",
"svg-sprite-loader": "^5.0.0",
"svgo": "^1.3.0",
"vue-template-compiler": "^2.6.12",
"webpack-spritesmith": "^1.1.0"
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
"name": "tduck-front",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vue-cli-service serve",
"serve": "vue-cli-service serve",
"build-dev": "vue-cli-service build --mode development --dest dist-dev",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"svgo": "svgo -f src/assets/icons",
"new": "plop"
},
"dependencies": {
"animate.css": "^4.1.1",
"axios": "^0.21.0",
"core-js": "^3.6.4",
"crypto-js": "^4.0.0",
"dayjs": "^1.9.4",
"echarts": "^5.0.0",
"element-ui": "^2.15.2",
"file-saver": "^2.0.2",
"js-cookie": "^2.2.1",
"nprogress": "^0.2.0",
"signature_pad": "^3.0.0-beta.4",
"ua-parser-js": "^0.7.23",
"vue": "^2.6.12",
"vue-clipboard2": "^0.3.1",
"vue-image-crop-upload": "^2.5.0",
"vue-meta": "^2.4.0",
"vue-qr": "^2.3.0",
"vue-router": "^3.4.8",
"vuedraggable": "^2.24.3",
"vuex": "^3.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.8",
"@vue/cli-plugin-eslint": "^4.5.8",
"@vue/cli-service": "^4.5.8",
"babel-eslint": "^10.1.0",
"babel-plugin-dynamic-import-node": "^2.3.3",
"eslint": "^7.12.1",
"eslint-plugin-vue": "^7.1.0",
"html-webpack-plugin": "^4.5.0",
"husky": "1.3.1",
"lint-staged": "8.1.5",
"plop": "^2.7.4",
"sass": "~1.32.1",
"sass-loader": "^10.0.4",
"sass-resources-loader": "^2.1.1",
"svg-sprite-loader": "^5.0.0",
"svgo": "^1.3.0",
"vue-template-compiler": "^2.6.12"
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}

23
plop-templates/component/index.hbs

@ -1,23 +0,0 @@
<template>
<div>
<!-- 组件内容区 -->
</div>
</template>
<script>
export default {
{{#if isGlobal}}
name: '{{ properCase name }}',
{{/if}}
props: {},
data() {
return {}
},
mounted() {},
methods: {}
}
</script>
<style lang="scss" scoped>
// scss
</style>

63
plop-templates/component/prompt.js

@ -1,63 +0,0 @@
const fs = require('fs')
function getFolder(path) {
let components = []
const files = fs.readdirSync(path)
files.forEach(function(item) {
let stat = fs.lstatSync(path + '/' + item)
if (stat.isDirectory() === true && item != 'components') {
components.push(path + '/' + item)
components.push.apply(components, getFolder(path + '/' + item))
}
})
return components
}
module.exports = {
description: '创建组件',
prompts: [
{
type: 'confirm',
name: 'isGlobal',
message: '是否为全局组件',
default: false
},
{
type: 'list',
name: 'path',
message: '请选择组件创建目录',
choices: getFolder('src/views'),
when: answers => {
return !answers.isGlobal
}
},
{
type: 'input',
name: 'name',
message: '请输入组件名称',
validate: v => {
if (!v || v.trim === '') {
return '组件名称不能为空'
} else {
return true
}
}
}
],
actions: data => {
let path = ''
if (data.isGlobal) {
path = 'src/components/{{properCase name}}/oldIndex.vue'
} else {
path = `${data.path}/components/{{properCase name}}/index.vue`
}
const actions = [
{
type: 'add',
path: path,
templateFile: 'plop-templates/component/index.hbs'
}
]
return actions
}
}

20
plop-templates/page/index.hbs

@ -1,20 +0,0 @@
<template>
<div>
页面内容区域
</div>
</template>
<script>
export default {
name: '{{ properCase componentName }}',
data() {
return {}
},
mounted() {},
methods: {}
}
</script>
<style lang="scss" scoped>
// scss
</style>

53
plop-templates/page/prompt.js

@ -1,53 +0,0 @@
const path = require('path')
const fs = require('fs')
function getFolder(path) {
let components = []
const files = fs.readdirSync(path)
files.forEach(function(item) {
let stat = fs.lstatSync(path + '/' + item)
if (stat.isDirectory() === true && item != 'components') {
components.push(path + '/' + item)
components.push.apply(components, getFolder(path + '/' + item))
}
})
return components
}
module.exports = {
description: '创建页面',
prompts: [
{
type: 'list',
name: 'path',
message: '请选择页面创建目录',
choices: getFolder('src/views')
},
{
type: 'input',
name: 'name',
message: '请输入文件名',
validate: v => {
if (!v || v.trim === '') {
return '文件名不能为空'
} else {
return true
}
}
}
],
actions: data => {
let relativePath = path.relative('src/views', data.path)
const actions = [
{
type: 'add',
path: `${data.path}/{{dotCase name}}.vue`,
templateFile: 'plop-templates/page/index.hbs',
data: {
componentName: `${relativePath} ${data.name}`
}
}
]
return actions
}
}

15
plop-templates/store/index.hbs

@ -1,15 +0,0 @@
const state = {}
const getters = {}
const actions = {}
const mutations = {}
export default {
namespaced: true,
state,
actions,
getters,
mutations
}

27
plop-templates/store/prompt.js

@ -1,27 +0,0 @@
module.exports = {
description: '创建全局状态',
prompts: [
{
type: 'input',
name: 'name',
message: '请输入模块名称',
validate: v => {
if (!v || v.trim === '') {
return '模块名称不能为空'
} else {
return true
}
}
}
],
actions: data => {
const actions = [
{
type: 'add',
path: `src/store/modules/${data.name}.js`,
templateFile: 'plop-templates/store/index.hbs'
}
]
return actions
}
}

6
plopfile.js

@ -1,6 +0,0 @@
module.exports = function(plop) {
plop.setWelcomeMessage('请选择需要创建的模式:')
plop.setGenerator('page', require('./plop-templates/page/prompt'))
plop.setGenerator('component', require('./plop-templates/component/prompt'))
plop.setGenerator('store', require('./plop-templates/store/prompt'))
}

66
scss.template.hbs

@ -1,66 +0,0 @@
{
// Default options
'functions': true,
'variableNameTransforms': ['dasherize']
}
{{#block "sprites"}}
{{#each sprites}}
${{../spritesheet_info.strings.name}}-sprite-{{strings.name}}: ({{px.x}}, {{px.y}}, {{px.offset_x}}, {{px.offset_y}}, {{px.width}}, {{px.height}}, {{px.total_width}}, {{px.total_height}}, '{{{escaped_image}}}', '{{name}}');
{{/each}}
${{spritesheet_info.strings.name}}-sprites: (
{{#each sprites}}
{{strings.name}}: ${{../spritesheet_info.strings.name}}-sprite-{{strings.name}},
{{/each}}
);
{{/block}}
{{#block "sprite-functions"}}
{{#if options.functions}}
@mixin {{spritesheet_info.strings.name}}-sprite-width($sprite) {
width: nth($sprite, 5);
}
@mixin {{spritesheet_info.strings.name}}-sprite-height($sprite) {
height: nth($sprite, 6);
}
@mixin {{spritesheet_info.strings.name}}-sprite-position($sprite) {
$sprite-offset-x: nth($sprite, 3);
$sprite-offset-y: nth($sprite, 4);
background-position: $sprite-offset-x $sprite-offset-y;
}
@mixin {{spritesheet_info.strings.name}}-sprite-size($sprite) {
background-size: nth($sprite, 7) nth($sprite, 8);
}
@mixin {{spritesheet_info.strings.name}}-sprite-image($sprite) {
$sprite-image: nth($sprite, 9);
background-image: url(#{$sprite-image});
}
@mixin {{spritesheet_info.strings.name}}-sprite($name) {
@include {{spritesheet_info.strings.name}}-sprite-image(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-position(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-size(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-width(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-height(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
background-repeat: no-repeat;
}
{{/if}}
{{/block}}
{{#block "spritesheet-functions"}}
{{#if options.functions}}
@mixin all-{{spritesheet_info.strings.name}}-sprites() {
@each $key, $val in ${{spritesheet_info.strings.name}}-sprites {
$sprite-name: nth($val, 10);
.{{spritesheet_info.strings.name}}-#{$sprite-name}-sprites {
@include {{spritesheet_info.strings.name}}-sprite($key);
}
}
}
{{/if}}
{{/block}}

BIN
src/assets/images/official/use-commony.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

BIN
src/assets/images/old-logo-blue.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

BIN
src/assets/images/tducklogo-v2-blue.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

BIN
src/assets/sprites/example/address.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

BIN
src/assets/sprites/example/feedback.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

BIN
src/assets/sprites/example/payment.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

9
src/assets/styles/index.scss

@ -127,6 +127,7 @@ div:focus {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.clearfix {
&::after {
@ -198,14 +199,6 @@ aside {
color: rgb(32, 160, 255);
}
}
.filter-container {
padding-bottom: 10px;
.filter-item {
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
}
}
//refine vue-multiselect plugin
.multiselect {

27
src/utils/sign.js

@ -5,9 +5,9 @@ import _ from 'lodash'
export default class sign {
/**
* json参数升序
* @param jsonObj 发送参数
*/
* json参数升序
* @param jsonObj 发送参数
*/
static sortAsc(jsonObj) {
let arr = new Array()
@ -25,10 +25,10 @@ export default class sign {
}
/**
* @param url 请求的url,应该包含请求参数(url的?后面的参数)
* @param requestParams 请求参数(POST的JSON参数)
* @returns {string} 获取签名
*/
* @param url 请求的url,应该包含请求参数(url的?后面的参数)
* @param requestParams 请求参数(POST的JSON参数)
* @returns {string} 获取签名
*/
static getSign(url, request) {
let requestParams = {}
if (request.params) {
@ -38,6 +38,9 @@ export default class sign {
if (value == undefined || value == null) {
return undefined
}
if (value instanceof Object) {
return JSON.stringify(value)
}
return '' + value
}
return value
@ -53,9 +56,9 @@ export default class sign {
}
/**
* @param url 请求的url
* @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数)
*/
* @param url 请求的url
* @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数)
*/
static parseQueryString(url) {
let urlReg = /^[^?]+\?([\w\W]+)$/,
paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g,
@ -71,8 +74,8 @@ export default class sign {
}
/**
* @returns {*} 将两个对象合并成一个
*/
* @returns {*} 将两个对象合并成一个
*/
static mergeObject(objectOne, objectTwo) {
if (Object.keys(objectTwo).length > 0) {
for (let key in objectTwo) {

152
src/views/account/ForgetPwd.vue

@ -4,19 +4,25 @@
<div>
<h4 class="title">找回密码</h4>
<el-tabs
v-model="retrieveType" class="login-form"
v-model="retrieveType"
class="login-form"
>
<el-tab-pane label="手机找回" name="phone">
<el-form ref="phoneForm" :model="retrieveAccountForm" :rules="phoneRules" label-width="0px">
<el-form-item prop="phoneNumber">
<el-input v-model="retrieveAccountForm.phoneNumber" autocomplete="off"
placeholder="请输入手机号"
<el-input
v-model="retrieveAccountForm.phoneNumber"
autocomplete="off"
placeholder="请输入手机号"
/>
</el-form-item>
<el-form-item label="" prop="code">
<el-input v-model="retrieveAccountForm.code" class="width50" autocomplete="off" placeholder="请输入验证码" />
<el-button :disabled="emailValidateCodeBtn" class="ml-20" type="primary"
@click="sendPhoneValidateCodeHandle"
<el-button
:disabled="emailValidateCodeBtn"
class="ml-20"
type="primary"
@click="sendPhoneValidateCodeHandle"
>
{{ emailValidateCodeBtnText }}
</el-button>
@ -29,15 +35,18 @@
</el-form>
</el-tab-pane>
<el-tab-pane label="邮箱找回" name="email">
<el-form ref="emailForm" :model="retrieveAccountForm" :rules="emailRules"
label-width="0px"
status-icon
<el-form
ref="emailForm"
:model="retrieveAccountForm"
:rules="emailRules"
label-width="0px"
status-icon
>
<el-form-item label="" prop="email">
<el-input v-model="retrieveAccountForm.email" autocomplete="off" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item>
<el-button class="width-full" type="primary" @click="sendEmailValidateHandle">
<el-button class="width-full" type="primary" @click="sendEmailValidateHandle">
找回密码
</el-button>
</el-form-item>
@ -55,13 +64,19 @@
</div>
<el-form ref="resetPwdForm" :model="resetPwdForm" :rules="pwdRules" label-width="0px">
<el-form-item label="" prop="password">
<el-input v-model="resetPwdForm.password" autocomplete="off" placeholder="请输入密码"
show-password
<el-input
v-model="resetPwdForm.password"
autocomplete="off"
placeholder="请输入密码"
show-password
/>
</el-form-item>
<el-form-item label="" prop="rePassword">
<el-input v-model="resetPwdForm.rePassword" autocomplete="off" placeholder="请再次输入密码"
show-password
<el-input
v-model="resetPwdForm.rePassword"
autocomplete="off"
placeholder="请再次输入密码"
show-password
/>
</el-form-item>
<el-form-item>
@ -87,7 +102,7 @@ import constants from '@/utils/constants'
export default {
name: 'RetrievePwd',
data() {
let validateRePass = (rule, value, callback) => {
const validateRePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== this.resetPwdForm.rePassword) {
@ -115,17 +130,17 @@ export default {
},
phoneRules: {
phoneNumber: [
{required: true, trigger: 'blur', message: '请输入手机号'},
{ required: true, trigger: 'blur', message: '请输入手机号' },
{
pattern: /^(?:0|86|\+86)?1[3456789]\d{9}$/,
message: '请输入正确的手机号'
}
],
code: {required: true, trigger: 'blur', message: '请输入验证码'}
code: { required: true, trigger: 'blur', message: '请输入验证码' }
},
emailRules: {
email: [
{required: true, trigger: 'blur', message: '请输入邮箱'},
{ required: true, trigger: 'blur', message: '请输入邮箱' },
{
pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/,
message: '请输入正确的邮箱'
@ -134,18 +149,18 @@ export default {
},
pwdRules: {
password: [
{required: true, trigger: 'blur', message: '请输入新密码'},
{ required: true, trigger: 'blur', message: '请输入新密码' },
{
pattern: constants.passwordReg,
message: constants.passwordRegDesc
}
],
rePassword: [{required: true, trigger: 'blur', validator: validateRePass}]
rePassword: [{ required: true, trigger: 'blur', validator: validateRePass }]
}
}
},
created() {
let code = this.$route.query.code
const code = this.$route.query.code
if (code) {
this.resetAccount = this.$route.query.email
this.resetPwdForm.code = code
@ -161,19 +176,19 @@ export default {
})
},
sendPhoneValidateCode() {
let phoneNumber = this.retrieveAccountForm.phoneNumber
const phoneNumber = this.retrieveAccountForm.phoneNumber
this.$refs['phoneForm'].validateField('phoneNumber', err => {
if (!err) {
this.emailValidateCodeBtn = true
this.$api.request({
url: '/retrieve/password/phone/code',
method: 'get',
params: {phoneNumber: phoneNumber}
params: { phoneNumber: phoneNumber }
}).then(() => {
this.msgSuccess('验证码发送成功,5分钟内有效')
this.emailValidateCodeBtn = true
let count = 60
let timer = setInterval(() => {
const timer = setInterval(() => {
count--
this.emailValidateCodeBtnText = count + 's后重新发送'
if (count == 0) {
@ -207,7 +222,7 @@ export default {
if (res.data) {
this.msgSuccess('密码重置成功,快去登录吧')
setTimeout(() => {
this.$router.push({path: '/login'})
this.$router.push({ path: '/login' })
}, 2000)
}
})
@ -227,7 +242,7 @@ export default {
this.$api.request({
url: '/retrieve/password/email',
method: 'get',
params: {email: this.retrieveAccountForm.email}
params: { email: this.retrieveAccountForm.email }
}).then(() => {
this.retrieveStep = 3
})
@ -240,55 +255,64 @@ export default {
<style lang="scss" scoped>
@import '@/assets/styles/mixin.scss';
.app-container {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-content: center;
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-content: center;
}
.title {
color: rgba(16, 16, 16, 100);
font-size: 28px;
text-align: center;
color: rgba(16, 16, 16, 100);
font-size: 28px;
text-align: center;
}
.pwd-container {
width: 20%;
height: 50%;
align-content: center;
div {
width: 350px;
}
width: 20%;
height: 50%;
align-content: center;
div {
width: 350px;
}
}
.reset-pwd-view {
div {
width: 350px;
.rest-pwd-user-view {
line-height: 80px;
height: 100px;
padding: 10px;
.el-icon-user {
display: inline-block;
width: 20%;
color: #717171;
text-align: left;
font-size: 50px;
}
span {
color: #4c4c4c;
display: inline-block;
text-align: center;
font-size: 19px;
width: 80%;
}
}
div {
width: 350px;
.rest-pwd-user-view {
line-height: 80px;
height: 100px;
padding: 10px;
.el-icon-user {
display: inline-block;
width: 20%;
color: #717171;
text-align: left;
font-size: 50px;
}
span {
color: #4c4c4c;
display: inline-block;
text-align: center;
font-size: 19px;
width: 80%;
}
}
}
}
.msg-view {
@include position-center(xy);
@include position-center(xy);
text-align: center;
color: #929292;
width: 30%;
text-align: center;
color: #929292;
width: 30%;
}
</style>

94
src/views/form/editor/IconsDialog.vue

@ -62,7 +62,8 @@ export default {
this.active = this.current
this.key = ''
},
onClose() {},
onClose() {
},
onSelect(icon) {
this.active = icon
this.$emit('select', icon)
@ -73,51 +74,58 @@ export default {
</script>
<style lang="scss" scoped>
.icon-ul {
margin: 0;
padding: 0;
font-size: 0;
li {
list-style-type: none;
text-align: center;
font-size: 14px;
display: inline-block;
width: 16.66%;
box-sizing: border-box;
height: 108px;
padding: 15px 6px 6px 6px;
cursor: pointer;
overflow: hidden;
&:hover {
background: #f2f2f2;
}
&.active-item {
background: #e1f3fb;
color: #7a6df0;
}
> i {
font-size: 30px;
line-height: 50px;
}
margin: 0;
padding: 0;
font-size: 0;
li {
list-style-type: none;
text-align: center;
font-size: 14px;
display: inline-block;
width: 16.66%;
box-sizing: border-box;
height: 108px;
padding: 15px 6px 6px 6px;
cursor: pointer;
overflow: hidden;
&:hover {
background: #f2f2f2;
}
&.active-item {
background: #e1f3fb;
color: #7a6df0;
}
> i {
font-size: 30px;
line-height: 50px;
}
}
}
.icon-dialog {
::v-deep .el-dialog {
border-radius: 8px;
margin-bottom: 0;
margin-top: 4vh !important;
display: flex;
flex-direction: column;
max-height: 92vh;
overflow: hidden;
box-sizing: border-box;
.el-dialog__header {
padding-top: 14px;
}
.el-dialog__body {
margin: 0 20px 20px 20px;
padding: 0;
overflow: auto;
}
::v-deep .el-dialog {
border-radius: 8px;
margin-bottom: 0;
margin-top: 4vh !important;
display: flex;
flex-direction: column;
max-height: 92vh;
overflow: hidden;
box-sizing: border-box;
.el-dialog__header {
padding-top: 14px;
}
.el-dialog__body {
margin: 0 20px 20px 20px;
padding: 0;
overflow: auto;
}
}
}
</style>

111
src/views/form/index.vue

@ -56,7 +56,7 @@ export default {
title: '逻辑',
icon: 'el-icon-menu',
route: '/project/form/logic'
}, {
}, {
title: '外观',
icon: 'el-icon-view',
route: '/project/form/theme'
@ -99,67 +99,80 @@ export default {
<style lang="scss" scoped>
.form-index-container {
height: 100%;
width: 100%;
height: 100%;
width: 100%;
}
::v-deep .el-card__body {
padding: 0;
padding: 0;
}
::v-deep .el-menu {
border: none;
background-color: transparent;
border: none;
background-color: transparent;
}
.header-container {
width: 100%;
height: 60px;
.el-icon-back {
font-size: 22px;
font-weight: 550;
cursor: pointer;
color: #000;
margin-left: 40px;
&:hover {
color: rgb(32, 160, 255);
}
}
.header-logo {
height: 60px;
width: 200px;
width: 100%;
height: 60px;
.el-icon-back {
font-size: 22px;
font-weight: 550;
cursor: pointer;
color: #000;
margin-left: 40px;
&:hover {
color: rgb(32, 160, 255);
}
}
.header-logo {
height: 60px;
width: 200px;
}
}
.main-container {
width: 100vw;
width: 100vw;
height: calc(100vh - 60px);
display: flex;
flex-direction: row;
.right-content-container {
width: calc(100vw - 100px);
height: calc(100vh - 60px);
display: flex;
flex-direction: row;
.right-content-container {
width: calc(100vw - 100px);
height: calc(100vh - 60px);
}
overflow-x: hidden;
}
}
.left-menu-container {
max-width: 100px;
text-align: center;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
border-right: solid 1px #e6e6e6;
.el-menu-vertical:not(.el-menu--collapse) {
width: 100px;
min-height: 400px;
}
.el-icon-d-arrow-left,
.el-icon-d-arrow-right {
font-size: 19px;
cursor: pointer;
font-weight: 550;
color: #000;
margin-bottom: 100px;
&:hover {
color: rgb(32, 160, 255);
}
max-width: 100px;
text-align: center;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
border-right: solid 1px #e6e6e6;
.el-menu-vertical:not(.el-menu--collapse) {
width: 100px;
min-height: 400px;
}
.el-icon-d-arrow-left,
.el-icon-d-arrow-right {
font-size: 19px;
cursor: pointer;
font-weight: 550;
color: #000;
margin-bottom: 100px;
&:hover {
color: rgb(32, 160, 255);
}
}
}
</style>

85
src/views/form/oldIndex.vue

@ -1,85 +0,0 @@
<!--<template>-->
<!-- <div class="container" style="overflow-y: hidden !important;">-->
<!-- <el-row type="flex" align="middle" justify="justify">-->
<!-- <el-col :offset="1" :span="4">-->
<!-- <el-button size="mini" round @click="$router.back(-1)">-->
<!-- <i class="el-icon-arrow-left" />-->
<!-- 返回-->
<!-- </el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="10" :offset="3">-->
<!-- <el-menu :default-active="activeTab" style="background-color: transparent;" mode="horizontal"-->
<!-- @select="handleSelect"-->
<!-- >-->
<!-- <el-menu-item index="editor">编辑</el-menu-item>-->
<!-- <el-menu-item index="logic">逻辑</el-menu-item>-->
<!-- <el-menu-item index="theme">外观</el-menu-item>-->
<!-- <el-menu-item index="setting">设置</el-menu-item>-->
<!-- <el-menu-item index="publish">发布</el-menu-item>-->
<!-- <el-menu-item index="statistics">统计</el-menu-item>-->
<!-- </el-menu>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- <div v-if="projectKey">-->
<!-- <editor v-if="activeTab=='editor'" :project-key="projectKey" :is-edit="isEdit" />-->
<!-- <logic v-if="activeTab=='logic'" :project-key="projectKey" />-->
<!-- <theme v-if="activeTab=='theme'" :project-key="projectKey" />-->
<!-- <setting v-if="activeTab=='setting'" :project-key="projectKey" />-->
<!-- <publish v-if="activeTab=='publish'" :project-key="projectKey" />-->
<!-- <statistics v-if="activeTab=='statistics'" :project-key="projectKey" />-->
<!-- </div>-->
<!-- </div>-->
<!--</template>-->
<!--<script>-->
<!--import editor from './editor'-->
<!--import theme from './theme'-->
<!--import setting from './setting'-->
<!--import publish from './publish'-->
<!--import statistics from './statistics'-->
<!--import logic from './logic'-->
<!--export default {-->
<!-- components: {-->
<!-- editor,-->
<!-- theme,-->
<!-- setting,-->
<!-- publish,-->
<!-- statistics,-->
<!-- logic-->
<!-- },-->
<!-- data() {-->
<!-- return {-->
<!-- activeTab: 'editor',-->
<!-- isEdit: false,-->
<!-- projectKey: ''-->
<!-- }-->
<!-- },-->
<!-- computed: {},-->
<!-- watch: {},-->
<!-- mounted() {-->
<!-- this.projectKey = this.$route.query.key-->
<!-- this.isEdit = !!this.$route.query.active-->
<!-- if (this.$route.query.active) {-->
<!-- this.activeTab = this.$route.query.active-->
<!-- }-->
<!-- },-->
<!-- methods: {-->
<!-- handleSelect(type) {-->
<!-- if (type) {-->
<!-- this.activeTab = type-->
<!-- this.$router.replace({path: '/project/form', query: {key: this.projectKey, active: type}})-->
<!-- }-->
<!-- }-->
<!-- }-->
<!--}-->
<!--</script>-->
<!--<style lang='scss'>-->
<!--.container {-->
<!-- position: relative;-->
<!-- width: 100%;-->
<!-- height: 100%;-->
<!-- //overflow-y: hidden;-->
<!--}-->
<!--</style>-->

64
src/views/form/statistics/chart.vue

@ -1,42 +1,38 @@
<template>
<div class="dashboard-container">
<div class="project-select-view">
<div style="width: 140px;">
<p class="tag-title">回收概览</p>
</div>
</div>
<div v-if="false" class="project-collect-view">
<div class="project-index-view">
<p class="tag-title">回收概览</p>
<div class="project-index-view">
<div>
<div>
<div>
<p style="text-align: center;">有效回收量</p>
<count-to :end-val="projectStats.completeCount"
style="font-size: 20px;"
/>
</div>
<div>
<p>总浏览量</p>
<count-to :end-val="projectStats.viewCount" style="font-size: 20px;" />
</div>
<div>
<p>回收率</p>
<count-to :end-val="projectStats.completeRate" style="font-size: 20px;" />
%
</div>
<div>
<p>平均完成时间</p>
<span style="font-size: 20px;">
{{ projectStats.avgCompleteFormatStr }}
</span>
</div>
<p style="text-align: center;">有效回收量</p>
<count-to :end-val="projectStats.completeCount"
style="font-size: 20px;"
/>
</div>
<div>
<p>总浏览量</p>
<count-to :end-val="projectStats.viewCount" style="font-size: 20px;" />
</div>
<div>
<p>回收率</p>
<count-to :end-val="projectStats.completeRate" style="font-size: 20px;" />
%
</div>
<div>
<p>平均完成时间</p>
<span style="font-size: 20px;">
{{ projectStats.avgCompleteFormatStr }}
</span>
</div>
</div>
<div>
<line-chart :chart-option="lineChartOptionData" />
</div>
</div>
<div class="line-chat">
<line-chart :chart-option="lineChartOptionData" />
</div>
<p class="tag-title">表单提交地域分布图</p>
<map-chart v-if="false" :chart-option="mapChartOptionData" :height="'450px'" />
<div style="width: 90%">
<map-chart :chart-option="mapChartOptionData" :height="'450px'" />
</div>
<div style="display: flex; flex-direction: row; justify-content: space-around;">
<div style="width: 50%;">
<p class="tag-title">常用设备</p>
@ -337,14 +333,14 @@ export default {
<style lang="scss" scoped>
.dashboard-container {
width: 100%;
margin: 0;
padding: 0;
overflow-x: hidden!important;
}
.tag-title {
font-size: 20px;
line-height: 25px;
margin: 10px;
margin-left: 20px;
}
.project-index-view {
& > div {

149
src/views/form/statistics/filter.vue

@ -0,0 +1,149 @@
<template>
<el-dialog :visible.sync="dialogVisible" center title="筛选">
<p>点击添加筛选项</p>
<el-row>
<el-col :span="6" class="filter-left">
<p v-for="(field,index) in fields" :key="field.id"
:class="{'selected':field.selected}"
class="filter-item-label"
@click="selectedFieldHandle(index,field)"
>
{{
field.label
}}
</p>
</el-col>
<el-col :span="18" class="filter-right">
<data-empty v-if="!selectedFields||selectedFields.length==0" :desc="'请在左侧选择筛选项'" />
<div v-for="field in selectedFields" :key="field.id">
<div class="filter-item">
<label>{{ field.label }}</label>
<div v-if="['SELECT','RADIO','CHECKBOX','IMAGE_SELECT'].includes(field.type)">
<p class="compare">
选择
</p>
<el-select v-model="filterParams[`filed${field.formItemId}`]">
<el-option
v-for="item in field.expand.options"
:key="item.id"
:label="item.label"
:value="item.id"
/>
</el-select>
</div>
<div v-else>
<p class="compare">
包含
</p>
<el-input v-model="filterParams[`filed${field.formItemId}`]" />
</div>
<i class="el-icon-delete" @click="removeSelectedFieldHandle" />
</div>
<el-divider />
</div>
</el-col>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button :disabled="selectedFields.length==0" type="primary" @click="submitFilterHandle"> </el-button>
</span>
</el-dialog>
</template>
<script>
import DataEmpty from '@/components/DataEmpty'
export default {
name: 'StatisticsFilter',
components: {DataEmpty},
props: {
fields: null
},
data() {
return {
dialogVisible: true,
selectedFields: [],
filterParams: {}
}
},
methods: {
showDialogHandle() {
this.dialogVisible = true
},
removeSelectedFieldHandle(index, item) {
this.selectedFields.splice(index, 1)
item.selected = false
this.$set(this.fields, item.leftIndex, item)
},
submitFilterHandle() {
this.$emit('filter', this.filterParams)
this.dialogVisible = false
},
selectedFieldHandle(index, item) {
if (item.selected) {
return
}
item.selected = true
this.$set(this.fields, index, item)
item.leftIndex = index
this.selectedFields.push(item)
}
}
}
</script>
<style lang="scss" scoped>
.filter-left {
border-right: 1px solid #dcdfe6;
.selected {
color: $--color-primary !important;
&:hover {
cursor: none !important;
}
}
.filter-item-label {
color: #606266;
line-height: 25px;
&:hover {
color: $--color-primary;
cursor: pointer;
}
}
}
.filter-right {
padding: 20px;
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
& :first-child p {
width: 100px;
}
.compare {
width: 100px;
margin: 0px;
}
& .el-icon-delete:hover {
color: $--color-danger;
}
& > div {
display: flex;
flex-direction: row;
align-items: center;
}
}
}
</style>

16
src/views/form/statistics/index.vue

@ -14,6 +14,7 @@
<script>
import list from './list'
import chart from './chart'
export default {
name: 'ProjectStatistics',
components: {
@ -22,7 +23,7 @@ export default {
},
data() {
return {
activeName: 'chart'
activeName: 'list'
}
},
mounted() {
@ -34,11 +35,12 @@ export default {
<style scoped>
.statistics-tabs {
padding: 20px;
width: 100%;
height: 100%;
.el-tab-pane {
height: 100%;
}
padding: 20px;
//width: 99%;
height: 100%;
}
::v-deep .el-tabs__content {
padding: 0;
}
</style>

88
src/views/form/statistics/list.vue

@ -20,7 +20,7 @@
</el-form-item>
<el-form-item>
<el-button type="primary" @click="queryProjectResult">查询</el-button>
<el-button type="primary" @click="customFilterDialogVisible=true">条件</el-button>
<el-button type="primary" @click="conditionFilterHandle">条件</el-button>
<el-button type="success" @click="exportProjectResult">导出</el-button>
<el-button type="success" @click="downloadProjectResultFile">下载附件</el-button>
</el-form-item>
@ -129,30 +129,16 @@
</span>
</el-dialog>
</div>
<el-dialog :visible.sync="customFilterDialogVisible" center title="结构化筛选">
<div class="flex-center">
<el-transfer
v-model="checkedFilterColumns"
filterable
:props="{
key: 'formItemId',
label: 'label'
}"
filter-placeholder="请输入问题名称"
:data="projectItemList"
/>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="customColumnDialogVisible = false"> </el-button>
<el-button type="primary" @click="saveStatisticsCheckedColumns"> </el-button>
</span>
</el-dialog>
<data-filter ref="dataFilter" :fields="projectItemList" @filter="(params)=>
{this.queryConditions.extParams=params;this.queryProjectResult()}"
/>
</div>
</template>
<script>
import _ from 'lodash'
import ResultItem from './item'
import DataFilter from './filter'
import {getCheckedColumn, saveCheckedColumn} from '@/utils/db'
const fixedDefaultFormColumn = ['serialNumber', 'submitAddress', 'createTime']
@ -161,7 +147,8 @@ const fixedDefaultLabelFormColumn = {serialNumber: '提交序号', submitAddress
export default {
name: 'ProjectStatisticsList',
components: {
ResultItem
ResultItem,
DataFilter
},
data() {
return {
@ -190,7 +177,8 @@ export default {
size: 10,
projectKey: '',
beginDateTime: '',
endDateTime: ''
endDateTime: '',
extParams: {}
}
}
},
@ -207,7 +195,6 @@ export default {
onClick={() => this.customColumnDialogVisible = true}></i>
)
},
openDetailDrawerHandle(row) {
this.activeResultRow = row
this.detailDrawer = true
@ -217,6 +204,9 @@ export default {
this.projectData = res.data
})
},
conditionFilterHandle() {
this.$refs.dataFilter.showDialogHandle()
},
queryProjectResult() {
this.$api.get('/user/project/result/page', {params: this.queryConditions}).then(res => {
let {records, total, size} = res.data
@ -301,50 +291,58 @@ export default {
<style scoped>
.statistics-container {
width: 100%;
height: 100%;
margin-top: 20px;
width: 100%;
height: 100%;
}
.custom-col-container >>> .el-checkbox__label {
width: 200px;
min-height: 25px;
line-height: 25px;
vertical-align: middle;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 200px;
min-height: 25px;
line-height: 25px;
vertical-align: middle;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.result-table-view {
width: 80%;
margin: 6px auto;
width: 80%;
min-height: 75vh;
margin: 6px auto;
}
.detail-container {
padding: 20px;
height: 100% !important;
padding: 20px;
height: 100% !important;
}
.filter-table-view {
width: 80%;
margin: 0 auto;
width: 80%;
margin: 0 auto;
}
::v-deep .el-icon-setting {
font-size: 24px;
line-height: 25px;
color: white;
font-size: 24px;
line-height: 25px;
color: white;
}
::v-deep .data-table-header .cell {
text-overflow: ellipsis !important;
white-space: nowrap !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
/* 1.显示滚动条:当内容超出容器的时候,可以拖动: */
::v-deep .el-drawer__body {
overflow: auto;
overflow: auto;
/* overflow-x: auto; */
/* overflow-x: auto; */
}
/* 2.隐藏滚动条,太丑了 */
::v-deep .el-drawer__container ::-webkit-scrollbar {
display: none;
display: none;
}
</style>

45
src/views/home/HomButton.vue

@ -1,45 +0,0 @@
<template>
<el-row>
<el-col :offset="2" :span="5">
<el-button class="home-fun-btn" @click="$router.push({path:'/project/create'})">
新建项目 <i class="el-icon-plus" />
</el-button>
</el-col>
<el-col :span="5">
<el-button class="home-fun-btn" @click="$router.push({path:'/project/my'})">
我的项目<i class="el-icon-s-custom" />
</el-button>
</el-col>
<el-col :span="5">
<el-button class="home-fun-btn" @click="$router.push({path:'/project/create'})">
行业模板<i class="el-icon-s-management" />
</el-button>
</el-col>
<el-col :span="5">
<el-button class="home-fun-btn" @click="$router.push({path:'/project/recycle'})">
回收站<i class="el-icon-delete-solid" />
</el-button>
</el-col>
</el-row>
</template>
<script>
export default {
name: 'HomeFunction'
}
</script>
<style scoped>
.home-fun-btn {
width: 70%;
height: 145px;
line-height: 20px;
border-radius: 15px;
font-weight: 580;
background-color: rgba(255, 255, 255, 100);
color: rgba(16, 16, 16, 100);
font-size: 14px;
text-align: center;
border: 1px solid rgba(187, 187, 187, 100);
}
</style>

28
src/views/home/HomeView.vue

@ -1,28 +0,0 @@
<template>
<el-row>
<!-- 轮播·1-->
<!-- <el-row>-->
<!-- <el-col :span="24">-->
<!-- <el-carousel height="55px">-->
<!-- <el-carousel-item v-for="item in 2" :key="item">-->
<!-- <img style="height: 55px; width: 100%;"-->
<!-- src="https://freebrio.oss-cn-shanghai.aliyuncs.com/t/v2_q2c4aj.png"-->
<!-- >-->
<!-- </el-carousel-item>-->
<!-- </el-carousel>-->
<!-- </el-col>-->
<!-- </el-row>-->
<Dashboard />
<HomeFunction />
</el-row>
</template>
<script>
import HomeFunction from './HomButton'
import Dashboard from '../form/statistics/chart'
export default {
name: 'HomeView',
components: {HomeFunction, Dashboard}
}
</script>

205
src/views/home/oldIndex.vue

@ -1,205 +0,0 @@
<template>
<div>
<el-container>
<el-header height="92" class="home-header-view">
<el-row type="flex" align="middle" justify="center">
<el-col :span="4" :offset="1">
<img src="@/assets/images/indexLogo.png" class="header-logo-img"
@click="$router.push({path:'/'})"
>
</el-col>
<el-col :span="10">
<el-menu :default-active="menuIndex" mode="horizontal"
text-color="#205BB5"
active-text-color="#205BB5"
@select="activeMenuHandle"
>
<el-menu-item v-for="(item, index) in menuRouters"
:key="oldIndex"
:index="item.routerPath"
class="menu-item"
>
{{ item.title }}
</el-menu-item>
</el-menu>
</el-col>
<el-col :span="1">
<!-- <el-button round>升级</el-button>-->
</el-col>
<el-col :span="1">
<div style="display: flex;
align-items: center;
justify-content: center;"
>
<!-- <svg-icon name="loginWx" style="width: 24px; height: 24px;" />-->
</div>
</el-col>
<el-col :span="1">
<el-link href="https://gitee.com/TDuckApp/tduck-platform/wikis/%E6%9C%AC%E5%9C%B0%E8%BF%90%E8%A1%8C?sort_id=3681729" target="_blank">帮助</el-link>
</el-col>
<el-col :span="3">
<el-popover
placement="bottom-end"
width="200"
trigger="click"
>
<div class="user-person-menu">
<div>
<p v-if="getUserInfo" class="nick-name">{{ getUserInfo.name }}</p>
</div>
<div class="person-menu-divider" />
<div>
<p class="person-menu-item" @click="$router.push({path: '/home/member'})">
<font-icon class="fab fa-get-pocket" />
我的账户
</p>
<div class="person-menu-divider" />
<p class="person-menu-item" @click="logoutHandle">
<font-icon class="fas fa-sign-out" />
退出登录
</p>
</div>
</div>
<div slot="reference" style="display: flex; align-items: center; justify-content: center;">
<img v-if="getUserInfo" :src="getUserInfo.avatar" class="user-avatar">
</div>
</el-popover>
</el-col>
</el-row>
</el-header>
<el-main class="home-main-view">
<router-view />
</el-main>
</el-container>
</div>
</template>
<script>
import store from '@/store/index.js'
import FontIcon from '@/components/FontIcon'
import router from '@/router'
import {openUrl, checkIsUrl} from '@/utils/index'
export default {
name: 'Home',
components: {FontIcon},
data() {
return {
menuIndex: null,
menuRouters: [
{
routerPath: 'https://gitee.com/TDuckApp/tduck-platform?time=1',
title: '免费模板'
},
{
routerPath: 'https://gitee.com/TDuckApp/tduck-platform',
title: '开源项目'
},
{
routerPath: 'https://gitee.com/TDuckApp/tduck-platform/issues',
title: '提出建议'
}
]
}
},
computed: {
getStore() {
return store
},
getUserInfo() {
let user = JSON.parse(this.getStore.getters['user/userInfo'])
return user
}
},
mounted() {
this.menuIndex = this.$route.path
},
methods: {
activeMenuHandle(routerPath) {
if (checkIsUrl(routerPath)) {
openUrl(routerPath)
} else {
this.menuIndex = routerPath
}
},
logoutHandle() {
this.$confirm('您确定要退出登录吗?', '退出确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.dispatch('user/logout').then(() => {
router.push({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
})
}).catch(() => {
})
}
}
}
</script>
<style lang="scss" scoped>
.menu-item {
line-height: 80px;
height: 80px;
text-align: left;
font-weight: 550;
color: rgba(32, 91, 181, 100);
font-size: 20px;
&:hover {
cursor: pointer;
}
}
.el-menu.el-menu--horizontal {
border-bottom: none;
}
.home-header-view {
line-height: 92px;
height: 92px;
min-width: 1024px;
background-color: rgba(255, 255, 255, 100);
color: rgba(16, 16, 16, 100);
font-size: 14px;
text-align: center;
.header-logo-img {
width: 90%;
height: 90%;
float: left;
}
}
.user-person-menu {
.nick-name {
height: 16px;
color: rgba(70, 70, 70, 86);
font-size: 14px;
line-height: 16px;
text-align: left;
}
.person-menu-item {
color: rgba(70, 70, 70, 86);
font-size: 14px;
text-align: left;
}
.person-menu-item:hover {
cursor: pointer;
color: rgba(32, 91, 181, 100);
}
}
.user-avatar {
width: 50px;
height: 50px;
border-radius: 100px;
cursor: pointer;
}
.home-main-view {
height: calc(100vh - 92px);
min-width: 1024px;
background-color: #f7f7f7;
padding: 0;
}
</style>

13
src/views/official/enterprise.vue

@ -1,13 +0,0 @@
<template>
<div id="enterprise">
企业部署
</div>
</template>
<script>
export default {
name: 'Enterprise',
data() {
return { }
}
}
</script>

48
src/views/official/index.vue

@ -87,40 +87,7 @@
</div>
<div class="use-company">
<p class="title">他们都在使用</p>
<div>
<img src="https://qiniu.smileyi.top/lanzhouxiandaizhiye.png" style="width: 170px; height: 30px;">
<img src="https://qiniu.smileyi.top/baoluekeji.png" style="width: 117px; height: 33px;">
<img src="https://qiniu.smileyi.top/tongxinraunjian.png" style="width: 100px; height: 29px;">
<img src="https://qiniu.smileyi.top/yunshangshien.png" style="width: 100px; height: 24px;">
</div>
<div>
<img src="https://qiniu.smileyi.top/wanshitong.png" style="width: 24px; height: 24px;">
<img src="https://qiniu.smileyi.top/shengji@2x.png" style="width: 48px; height: 46px;">
<img src="https://qiniu.smileyi.top/hebeijiangong.png" style="width: 46px; height: 48px;">
<img src="https://qiniu.smileyi.top/indexLogo.d128b371@2x.png" style="width: 134px; height: 41px;">
<img src="https://qiniu.smileyi.top/zhongguopingan.png" style="width: 165px; height: 28px;">
</div>
<div>
<img src="https://qiniu.smileyi.top/舒心家园房产@2x.png" style="width: 168px; height: 40px;">
<img src="https://qiniu.smileyi.top/丽珠试剂@2x.png" style="width: 150px; height: 53px;">
<img src="https://qiniu.smileyi.top/申银万国期货@2x.png" style="width: 192px; height: 47px;">
<img src="https://qiniu.smileyi.top/ucloud@2x.png" style="width: 241px; height: 29px;">
<img src="https://qiniu.smileyi.top/H3C@2x.png" style="width: 127px; height: 54px;">
</div>
<div>
<img src="https://qiniu.smileyi.top/中央企业工业互联网融通平台@2x.png" style="width: 169px; height: 47px;">
<img src="https://qiniu.smileyi.top/yifengertiyan.png" style="width: 206px; height: 32px;">
<img src="https://qiniu.smileyi.top/hauxin@2x.png" style="width: 315px; height: 50px;">
<img src="https://qiniu.smileyi.top/17466041@2x.png" style="width: 138px; height: 62px;">
<img src="https://qiniu.smileyi.top/美博会@2x.png" style="width: 118px; height: 67px;">
</div>
<div>
<img src="https://qiniu.smileyi.top/zhangzhutong.png" style="width: 43px; height: 45px;">
<img src="https://qiniu.smileyi.top/泰享健康@2x.png" style="width: 196px; height: 48px;">
<img src="https://qiniu.smileyi.top/renrenyun.png" style="width: 156px; height: 54px;">
<img src="https://qiniu.smileyi.top/xykj@2x.png" style="width: 209px; height: 51px;">
<img src="https://qiniu.smileyi.top/上海市闵行区卫生健康委员会@2x.png" style="width: 47px; height: 49px;">
</div>
<img src="@/assets/images/official/use-commony.png">
</div>
<div class="footer">
<div class="top">
@ -406,18 +373,7 @@ body {
align-items: center;
justify-content: flex-start;
margin-top: 200px;
& > div {
display: flex;
align-content: center;
justify-content: center;
align-items: center;
}
div {
margin: 10px 3px;
}
img {
margin-right: 30px;
}
}
.footer {
margin-top: 227px;

270
src/views/official/introduction.vue

@ -1,270 +0,0 @@
<!--<template>-->
<!-- <div class="introduction">-->
<!-- <div class="introduction-body">-->
<!-- <div class="view-container">-->
<!-- <div class="view-container-content">-->
<!-- <el-row type="flex" justify="center" align="middle">-->
<!-- <el-col :span="10" :offset="1">-->
<!-- <div class="view-container-content">-->
<!-- <p class="body-title">TDuck - 填鸭表单</p>-->
<!-- <p class="body-slogan">有你所想有你所得</p>-->
<!-- <p class="body-sloganEn">TDuck - Have what you want, get you income.</p>-->
<!-- <p class="body-detail">TDuck填鸭是一款在线制作表单的应用工具通过填鸭表单可以收集任何你想得到的信息</p>-->
<!-- <p class="body-detail">简单易用灵活的反馈数据筛选统计图表一目了然</p>-->
<!-- <div>-->
<!-- <el-button class="body-btn primary" type="primary"-->
<!-- @click="$router.push({path:'/login'})"-->
<!-- >-->
<!-- 免费使用-->
<!-- </el-button>-->
<!-- <el-button class="body-btn">在线体验</el-button>-->
<!-- </div>-->
<!-- </div>-->
<!-- </el-col>-->
<!-- <el-col :span="10">-->
<!-- <div>-->
<!-- <img class="body-right-img"-->
<!-- src="@/assets/images/official/v2_q2woer.png"-->
<!-- >-->
<!-- </div>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </div>-->
<!-- </div>-->
<!-- <el-divider />-->
<!-- <div class="view-container">-->
<!-- <div class="view-container-content">-->
<!-- <el-row type="flex" justify="center" align="middle">-->
<!-- <el-col :span="10" :offset="1">-->
<!-- <div>-->
<!-- <img class="body-right-img"-->
<!-- src="@/assets/images/official/v2_q2wstp.png"-->
<!-- >-->
<!-- </div>-->
<!-- </el-col>-->
<!-- <el-col :offset="1" :span="12">-->
<!-- <div>-->
<!-- <p style="font-size: 28px;">杂乱的工作界面也会影响心情鸭</p>-->
<!-- <p class="body-title">TDuck化繁为简给你最好的体验</p>-->
<!-- <p class="body-sloganEn">TDuck -So Easy</p>-->
<!-- <p class="body-detail">·超多表单组件供您选择</p>-->
<!-- <p class="body-detail">·逻辑设置让您的表单更灵活</p>-->
<!-- <p class="body-detail"> ·Element UI饿了么同款UI组建表单也要美美哒</p>-->
<!-- <p class="body-detail"> ·多样化数据统计懂你胜过另一半</p>-->
<!-- <div style="width: 42px;" class="title-divider-line" />-->
<!-- </div>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </div>-->
<!-- </div>-->
<!-- <el-divider />-->
<!-- <div class="view-container">-->
<!-- <div class="view-container-content" style="width: 80%;">-->
<!-- <el-row type="flex" align="middle">-->
<!-- <el-col :span="10" :offset="5">-->
<!-- <p style="font-size: 28px;">TDuck - 填鸭表单</p>-->
<!-- <p class="body-title">社区版 / 开源版 / 企业部署 </p>-->
<!-- <p class="body-detail">Technology changes the world 用技术改变世界</p>-->
<!-- </el-col>-->
<!-- <el-col :span="8">-->
<!-- <p class="body-detail"> ·人人都是我们的用户 </p>-->
<!-- <p class="body-detail">·基于Apache开源协议开发者的福音</p>-->
<!-- <p class="body-detail">·成熟的技术支持企业表单问卷系统解决方案</p>-->
<!-- <p class="body-detail">·更新速度快总为您奉上不一样的惊喜</p>-->
<!-- <div style="width: 112px;" class="title-divider-line" />-->
<!-- <p style="font-size: 20px; font-weight: 550;">联系我们</p>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </div>-->
<!-- <div>-->
<!-- <img style="height: 478px;"-->
<!-- src="@/assets/images/official/v2_q2wxu6.gif"-->
<!-- >-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="introduction-footer">-->
<!-- <el-row>-->
<!-- <el-col :span="3" :offset="2">-->
<!-- <p class="title">关于我们</p>-->
<!-- <p class="subtitle" style="width: 182px;">-->
<!-- TDuck 是一款能够帮助-->
<!-- 你进行信息收集市场开拓-->
<!-- 客户挖掘并展开持续营销活-->
<!-- 动的管理平台-->
<!-- </p>-->
<!-- </el-col>-->
<!-- <el-col :span="3">-->
<!-- <p class="title"> 项目地址</p>-->
<!-- <p class="subtitle">-->
<!-- <svg-icon name="gitee" style="width: 14px; height: 14px;" />-->
<!-- <a href="https://gitee.com/TDuckApp/tduck-platform" target="_blank"> 码云</a>-->
<!-- </p>-->
<!-- <p class="subtitle">-->
<!-- <svg-icon name="github" style="width: 12px; height: 12px;" />-->
<!-- <a href="https://github.com/TDuckCloud/tduck-platform" target="_blank"> Github</a>-->
<!-- </p>-->
<!-- </el-col>-->
<!-- <el-col :span="4">-->
<!-- <p class="title"> 联系方式</p>-->
<!-- <p class="subtitle">-->
<!-- <i class="el-icon-message" />-->
<!-- pr@tduckapp.com-->
<!-- </p>-->
<!-- <p class="subtitle">-->
<!-- <i class="el-icon-phone" /> +86 15080929435-->
<!-- </p>-->
<!-- </el-col>-->
<!-- <el-col :span="3">-->
<!-- <p class="title"> 友情地址</p>-->
<!-- <p class="subtitle">-->
<!-- <a href="https://element.eleme.cn/#/zh-CN/" target="_blank">Element UI</a>-->
<!-- </p>-->
<!-- <p class="subtitle">-->
<!-- <a href="https://gitee.com/mrhj/form-generator" target="_blank"> form-generator</a>-->
<!-- </p>-->
<!-- </el-col>-->
<!-- <el-col :span="3" :offset="3">-->
<!-- <img style="width: 130px; height: 130px;"-->
<!-- src="https://freebrio.oss-cn-shanghai.aliyuncs.com/t/v2_q2x0aj.png"-->
<!-- >-->
<!-- <p class="title">微信公众号/关注我们</p>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- <el-divider />-->
<!-- <div class="copyright">-->
<!-- <a target="_blank" href="https://www.upyun.com/?utm_source=lianmeng&utm_medium=referral">-->
<!-- <img-->
<!-- style="height: 30px;"-->
<!-- src="http://tduck.test.upcdn.net/%E5%8F%88%E6%8B%8D%E4%BA%91_logo2.png"-->
<!-- >-->
<!-- </a>-->
<!-- <p class="subtitle" style="text-align: center;">-->
<!-- 本站由 又拍云 提供CDN加速/云储存服务-->
<!-- </p>-->
<!-- <p class="subtitle" style="text-align: center;">-->
<!-- Copyright © 2021 TDuckApp. All Rights Reserved. 湘ICP备18023961号-2 版权所有-->
<!-- </p>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!--</template>-->
<!--<script>-->
<!--export default {-->
<!-- data() {-->
<!-- return {}-->
<!-- },-->
<!-- computed: {},-->
<!-- watch: {},-->
<!-- mounted() {-->
<!-- }-->
<!--}-->
<!--</script>-->
<!--<style scoped>-->
<!--.body-btn {-->
<!-- margin: 30px 20px 0 0;-->
<!-- font-size: 20px;-->
<!-- padding-left: 20px;-->
<!-- padding-right: 20px;-->
<!--}-->
<!--.header-btn,-->
<!--.body-btn {-->
<!-- color: #205bb5;-->
<!-- border-color: #205bb5;-->
<!--}-->
<!--.primary {-->
<!-- color: #fff;-->
<!-- background-color: #205bb5;-->
<!--}-->
<!--.header-btn:focus,-->
<!--.header-btn:hover,-->
<!--.body-btn:focus,-->
<!--.body-btn:hover {-->
<!-- border-color: #205bb5;-->
<!-- color: #205bb5;-->
<!--}-->
<!--.primary:focus,-->
<!--.primary:hover {-->
<!-- border-color: #205bb5;-->
<!-- color: #fff;-->
<!--}-->
<!--.introduction {-->
<!-- background: url('~@/assets/images/official/offcial-bg01.png') repeat-x;-->
<!--}-->
<!--.introduction-body {-->
<!-- /* padding-top: 112px; */-->
<!--}-->
<!--.view-container {-->
<!-- height: 800px;-->
<!-- display: flex;-->
<!-- justify-content: center;-->
<!-- align-content: center;-->
<!-- align-items: center;-->
<!-- justify-items: center;-->
<!-- flex-direction: column;-->
<!--}-->
<!--.view-container-content {-->
<!-- background-color: transparent;-->
<!--}-->
<!--.view-container-content p {-->
<!-- color: #205bb5;-->
<!--}-->
<!--.body-title {-->
<!-- font-size: 35px;-->
<!-- font-weight: 900;-->
<!-- line-height: 25px;-->
<!-- margin: 0 0 30px 0;-->
<!--}-->
<!--.body-slogan {-->
<!-- font-size: 45px;-->
<!-- font-weight: 900;-->
<!-- line-height: 45px;-->
<!-- margin: 0 0 5px 0;-->
<!--}-->
<!--.body-sloganEn {-->
<!-- line-height: 15px;-->
<!-- margin: 0 0 25px 0;-->
<!--}-->
<!--.body-detail {-->
<!-- line-height: 15px;-->
<!-- margin: 0 0 10px 0;-->
<!--}-->
<!--.body-sloganEn,-->
<!--.body-detail {-->
<!-- font-size: 15px;-->
<!--}-->
<!--.body-right-img {-->
<!-- /* height: 665px; */-->
<!-- width: 100%;-->
<!-- height: 100%;-->
<!--}-->
<!--.title-divider-line {-->
<!-- height: 3px;-->
<!-- line-height: 20px;-->
<!-- background-color: rgba(32, 91, 181, 100);-->
<!-- text-align: center;-->
<!-- border: 1px solid rgba(255, 255, 255, 100);-->
<!--}-->
<!--.introduction-footer {-->
<!-- height: 420px;-->
<!-- line-height: 20px;-->
<!-- background-color: rgba(32, 91, 181, 100);-->
<!-- text-align: center;-->
<!-- border: 1px solid rgba(187, 187, 187, 100);-->
<!-- padding: 69px 0 0 0;-->
<!--}-->
<!--.introduction-footer .title {-->
<!-- color: rgba(255, 255, 255, 100);-->
<!-- font-size: 20px;-->
<!-- text-align: left;-->
<!--}-->
<!--.introduction-footer .subtitle {-->
<!-- color: rgba(255, 255, 255, 100);-->
<!-- font-size: 14px;-->
<!-- text-align: left;-->
<!--}-->
<!--.copyright {-->
<!-- height: 95px;-->
<!--}-->
<!--</style>-->

13
src/views/official/proposal.vue

@ -1,13 +0,0 @@
<template>
<div id="proposal">
提出建议
</div>
</template>
<script>
export default {
name: 'Proposal',
data() {
return { }
}
}
</script>

13
src/views/official/sources.vue

@ -1,13 +0,0 @@
<template>
<div id="sources">
开源版本
</div>
</template>
<script>
export default {
name: 'Sources',
data() {
return { }
}
}
</script>

103
vue.config.js

@ -1,53 +1,5 @@
const fs = require('fs')
const path = require('path')
const spritesmithPlugin = require('webpack-spritesmith')
const terserPlugin = require('terser-webpack-plugin')
const cdnDependencies = require('./dependencies.cdn')
const spritesmithTasks = []
fs.readdirSync('src/assets/sprites').map(dirname => {
if (fs.statSync(`src/assets/sprites/${dirname}`).isDirectory()) {
spritesmithTasks.push(
new spritesmithPlugin({
src: {
cwd: path.resolve(__dirname, `src/assets/sprites/${dirname}`),
glob: '*.png'
},
target: {
image: path.resolve(__dirname, `src/assets/sprites/${dirname}.[hash].png`),
css: [
[path.resolve(__dirname, `src/assets/sprites/_${dirname}.scss`), {
format: 'handlebars_based_template',
spritesheetName: dirname
}]
]
},
customTemplates: {
'handlebars_based_template': path.resolve(__dirname, 'scss.template.hbs')
},
// 样式文件中调用雪碧图地址写法
apiOptions: {
cssImageRef: `~${dirname}.[hash].png`
},
spritesmithOptions: {
algorithm: 'binary-tree',
padding: 10
}
})
)
}
})
// CDN 相关
const isCDN = process.env.VUE_APP_CDN == 'ON'
const externals = {}
cdnDependencies.forEach(pkg => {
externals[pkg.name] = pkg.library
})
const cdn = {
css: cdnDependencies.map(e => e.css).filter(e => e),
js: cdnDependencies.map(e => e.js).filter(e => e)
}
module.exports = {
publicPath: '/',
@ -65,16 +17,13 @@ module.exports = {
}
},
configureWebpack: config => {
config.resolve.modules = ['node_modules', 'assets/sprites']
config.resolve.modules = ['node_modules']
config.resolve.alias = {
'@': path.resolve(__dirname, 'src')
}
config.plugins.push(...spritesmithTasks)
if (isCDN) {
config.externals = externals
}
config.optimization = {
minimizer: [
//https://webpack.docschina.org/plugins/terser-webpack-plugin/
new terserPlugin({
terserOptions: {
compress: {
@ -88,12 +37,6 @@ module.exports = {
]
}
},
pluginOptions: {
lintStyleOnBuild: true,
stylelint: {
fix: true
}
},
chainWebpack: config => {
const oneOfsMap = config.module.rule('scss').oneOfs.store
oneOfsMap.forEach(item => {
@ -102,7 +45,6 @@ module.exports = {
.options({
resources: [
'./src/assets/styles/resources/*.scss',
'./src/assets/sprites/*.scss'
]
})
.end()
@ -125,12 +67,47 @@ module.exports = {
config.plugin('html')
.tap(args => {
args[0].title = process.env.VUE_APP_TITLE
if (isCDN) {
args[0].cdn = cdn
}
args[0].debugTool = process.env.VUE_APP_DEBUG_TOOL
return args
})
.end()
config
.when(process.env.NODE_ENV !== 'development',
config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.end()
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
}
)
}
}

Loading…
Cancel
Save