pipipi-pikachu hace 5 años
padre
commit
9b121dd871
Se han modificado 45 ficheros con 3573 adiciones y 203 borrados
  1. 51 2
      .eslintrc.js
  2. 33 0
      .stylelintrc.js
  3. 10 0
      babel.config.js
  4. 1599 56
      package-lock.json
  5. 15 1
      package.json
  6. 4 3
      public/index.html
  7. 2 23
      src/App.vue
  8. BIN
      src/assets/logo.png
  9. 63 0
      src/assets/styles/global.scss
  10. 30 0
      src/assets/styles/mixin.scss
  11. 2 0
      src/assets/styles/variable.scss
  12. 0 64
      src/components/HelloWorld.vue
  13. 5 0
      src/components/IconFont.ts
  14. 160 0
      src/configs/animation.ts
  15. 1 0
      src/configs/canvas.ts
  16. 54 0
      src/configs/chart.ts
  17. 81 0
      src/configs/defaultElement.ts
  18. 42 0
      src/configs/element.ts
  19. 32 0
      src/configs/fontName.ts
  20. 130 0
      src/configs/icons.ts
  21. 51 0
      src/configs/imageClip.ts
  22. 18 0
      src/configs/keyCode.ts
  23. 21 0
      src/configs/lines.ts
  24. 77 0
      src/configs/shadows.ts
  25. 350 0
      src/configs/shapes.ts
  26. 117 0
      src/configs/tableTheme.ts
  27. 6 1
      src/main.ts
  28. 8 11
      src/router/index.ts
  29. 23 0
      src/store/editor.ts
  30. 26 7
      src/store/index.ts
  31. 24 0
      src/store/slides.ts
  32. 126 0
      src/types/slides.ts
  33. 198 0
      src/utils/index.ts
  34. 0 5
      src/views/About.vue
  35. 13 0
      src/views/Editor/Canvas/index.vue
  36. 21 0
      src/views/Editor/CanvasTool/index.vue
  37. 22 0
      src/views/Editor/EditorHeader/index.vue
  38. 22 0
      src/views/Editor/Thumbnails/index.vue
  39. 22 0
      src/views/Editor/Toolbar/index.vue
  40. 60 0
      src/views/Editor/index.vue
  41. 0 18
      src/views/Home.vue
  42. 13 0
      src/views/Player/index.vue
  43. 0 12
      tests/unit/example.spec.ts
  44. 1 0
      tsconfig.json
  45. 40 0
      vue.config.js

+ 51 - 2
.eslintrc.js

@@ -12,8 +12,57 @@ module.exports = {
     ecmaVersion: 2020
   },
   rules: {
-    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
-    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
+    'curly': ['error', 'multi-line'], // if、while等仅允许在单行中省略大括号
+    'quotes': ['error', 'single', { // 字符串使用单引号(允许含有单引号的字符串使用双引号,允许模板字符串)
+      'avoidEscape': true,
+      'allowTemplateLiterals': true,
+    }],
+    'key-spacing': ['error', { // 强制在对象字面量的键和值之间使用一致的空格
+      'beforeColon': false,
+      'afterColon': true,
+      'mode': 'strict',
+    }],
+    'no-empty': 'error', // 禁止空白块
+    'no-else-return': 'error', // 禁止 if 语句中 return 语句之后有 else 块
+    'no-multi-spaces': 'error', // 禁止出现多个空格
+    'require-await': 'error', // 禁止使用不带 await 表达式的 async 函数
+    'brace-style': ['error', 'stroustrup'], // 大括号风格要求
+    'spaced-comment': ['error', 'always'], // 要求在注释前有空白
+    'arrow-spacing': 'error', // 要求箭头函数的箭头之前或之后有空格
+    'no-duplicate-imports': 'error', // 禁止重复导入
+    'semi': ['error', 'never'], // 禁止行末分号
+    'comma-spacing': ['error', { 'before': false, 'after': true }], // 强制在逗号周围使用空格
+    'indent': ['error', 2, {'SwitchCase': 1}], // 两个空格的缩进
+    'eqeqeq': ['error', 'always', {'null': 'ignore'}], // 必须使用全等判断(null的判断除外)
+    'default-case': 'error', // switch块必须有default结尾
+    'no-eval': 'error', // 禁止eval 
+    'no-var': 'error', // 禁止var
+    'no-with': 'error', // 禁止with
+    'max-depth': ['error', 5], // 代码最大嵌套5层
+    'consistent-this': ['error', 'self'], // 只能使用self代替this
+    'max-lines': ['error', 1200], // 单文件最大1200行
+    'no-multi-str': 'error', // 禁止多行字符串
+    'space-infix-ops': 'error', // 中缀操作符周围有空格
+    'space-before-blocks': ['error', 'always'], // 函数大括号前有空格
+    'space-before-function-paren': ['error', { // 函数小括号前无空格(匿名异步函数前有)
+      'anonymous': 'never',
+      'named': 'never',
+      'asyncArrow': 'always',
+    }],
+    'keyword-spacing': ['error', { 'overrides': { // 强制关键字周围空格的一致性
+      'if': { 'after': false },
+      'for': { 'after': false },
+      'while': { 'after': false },
+      'function': { 'after': false },
+      'switch': { 'after': false },
+    }}],
+    'prefer-const': 'error', // 必须优先使用const
+    'no-useless-return': 'error', // 禁止多余的return
+    'array-bracket-spacing': 'error', // 强制数组方括号中使用一致的空格
+    'no-useless-escape': 'off', // 关闭禁用不必要的转义
+    'no-alert': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 禁止alert
+    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 禁止console
+    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 禁止debugger
   },
   overrides: [
     {

+ 33 - 0
.stylelintrc.js

@@ -0,0 +1,33 @@
+module.exports = {
+  extends: 'stylelint-config-standard',
+  rules: {
+    'at-rule-empty-line-before': null,
+    'rule-empty-line-before': null,
+    'no-missing-end-of-source-newline': null,
+    'selector-list-comma-newline-after': null,
+    'font-family-no-missing-generic-family-keyword': null,
+    'no-descending-specificity': null,
+    'number-leading-zero': null,
+    'at-rule-no-unknown': null,
+    'max-empty-lines': null,
+    'selector-pseudo-element-no-unknown': null,
+    'color-hex-case': 'lower',
+    'color-hex-length': 'short',
+    'color-no-invalid-hex': true,
+    'font-weight-notation': 'numeric',
+    'function-calc-no-unspaced-operator': true,
+    'function-url-quotes': 'never',
+    'string-no-newline': true,
+    'string-quotes': 'single',
+    'block-no-empty': true,
+    'block-opening-brace-newline-after': 'always',
+    'block-opening-brace-space-before': 'always',
+    'indentation': 2,
+    'max-nesting-depth': 5,
+    'no-eol-whitespace': true,
+    'declaration-block-no-duplicate-properties': true,
+    'declaration-block-semicolon-newline-after': 'always',
+    'declaration-block-trailing-semicolon': 'always',
+    'selector-pseudo-element-colon-notation': 'double',
+  }
+}

+ 10 - 0
babel.config.js

@@ -1,5 +1,15 @@
 module.exports = {
   presets: [
     '@vue/cli-plugin-babel/preset'
+  ],
+  plugins: [
+    [
+      'import',
+      {
+        libraryName: 'ant-design-vue',
+        libraryDirectory: 'es',
+        style: true,
+      }
+    ]
   ]
 }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1599 - 56
package-lock.json


+ 15 - 1
package.json

@@ -9,12 +9,20 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "@ant-design/icons-vue": "^5.1.7",
+    "ant-design-vue": "^2.0.0-rc.3",
+    "clipboard": "^2.0.6",
     "core-js": "^3.6.5",
+    "crypto-js": "^4.0.0",
+    "lodash": "^4.17.20",
+    "store2": "^2.12.0",
     "vue": "^3.0.0",
     "vue-router": "^4.0.0-0",
     "vuex": "^4.0.0-0"
   },
   "devDependencies": {
+    "@types/clipboard": "^2.0.1",
+    "@types/crypto-js": "^4.0.1",
     "@types/jest": "^24.0.19",
     "@typescript-eslint/eslint-plugin": "^2.33.0",
     "@typescript-eslint/parser": "^2.33.0",
@@ -30,9 +38,15 @@
     "@vue/test-utils": "^2.0.0-0",
     "eslint": "^6.7.2",
     "eslint-plugin-vue": "^7.0.0-0",
+    "babel-plugin-import": "^1.13.3",
+    "less": "^3.12.2",
+    "less-loader": "^7.1.0",
     "sass": "^1.26.5",
     "sass-loader": "^8.0.2",
     "typescript": "~3.9.3",
-    "vue-jest": "^5.0.0-0"
+    "vue-jest": "^5.0.0-0",
+    "stylelint": "^13.8.0",
+    "stylelint-config-standard": "^20.0.0",
+    "stylelint-webpack-plugin": "^2.1.1"
   }
 }

+ 4 - 3
public/index.html

@@ -1,11 +1,12 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="zh-CN">
   <head>
     <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="renderer" content="webkit" />
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
-    <title><%= htmlWebpackPlugin.options.title %></title>
+    <title>演示文稿在线编辑</title>
   </head>
   <body>
     <noscript>

+ 2 - 23
src/App.vue

@@ -1,30 +1,9 @@
 <template>
-  <div id="nav">
-    <router-link to="/">Home</router-link> |
-    <router-link to="/about">About</router-link>
-  </div>
   <router-view/>
 </template>
 
 <style lang="scss">
 #app {
-  font-family: Avenir, Helvetica, Arial, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  text-align: center;
-  color: #2c3e50;
+  height: 100%;
 }
-
-#nav {
-  padding: 30px;
-
-  a {
-    font-weight: bold;
-    color: #2c3e50;
-
-    &.router-link-exact-active {
-      color: #42b983;
-    }
-  }
-}
-</style>
+</style>

BIN
src/assets/logo.png


+ 63 - 0
src/assets/styles/global.scss

@@ -0,0 +1,63 @@
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  font-size: 100%;
+  vertical-align: baseline;
+  box-sizing: border-box;
+}
+
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+  display: block;
+}
+body {
+  line-height: 1;
+  font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
+}
+html, body {
+  height: 100%;
+  overflow: hidden;
+  background-color: #fff;
+  color: #333;
+}
+ol, ul {
+  list-style: none;
+}
+blockquote, q {
+  quotes: none;
+}
+blockquote::before, blockquote::after,
+q::before, q::after {
+  content: '';
+}
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+a {
+  text-decoration: none;
+  color: #fff;
+}
+
+::-webkit-scrollbar {
+  width: 5px;
+  height: 5px;
+  background-color: #fff;
+}
+::-webkit-scrollbar-thumb {
+  background-color: #e1e1e1;
+  border-radius: 5px;
+}

+ 30 - 0
src/assets/styles/mixin.scss

@@ -0,0 +1,30 @@
+// 单行文字行末省略
+@mixin ellipsis {
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
+// 多行文字尾行行末省略
+@mixin multi-ellipsis($line: 2) {
+  word-wrap: break-word;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: $line;
+  -webkit-box-orient: vertical;
+}
+
+// 表格式布局
+@mixin grid-layout-wrapper() {
+  display: flex;
+  flex-wrap: wrap;
+}
+@mixin grid-layout-item($col, $colWidth) {
+  width: $colWidth;
+  margin-bottom: calc(#{100 - $col * $colWidth} / #{$col - 1});
+
+  &:not(:nth-child(#{$col}n)) {
+    margin-right: calc(#{100 - $col * $colWidth} / #{$col - 1});
+  }
+}

+ 2 - 0
src/assets/styles/variable.scss

@@ -0,0 +1,2 @@
+$themeColor: #41464b;
+$borderRadius: 2px;

+ 0 - 64
src/components/HelloWorld.vue

@@ -1,64 +0,0 @@
-<template>
-  <div class="hello">
-    <h1>{{ msg }}</h1>
-    <p>
-      For a guide and recipes on how to configure / customize this project,<br>
-      check out the
-      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
-    </p>
-    <h3>Installed CLI Plugins</h3>
-    <ul>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-jest" target="_blank" rel="noopener">unit-jest</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
-    </ul>
-    <h3>Essential Links</h3>
-    <ul>
-      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
-      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
-      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
-      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
-      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
-    </ul>
-    <h3>Ecosystem</h3>
-    <ul>
-      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
-      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
-      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
-      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
-      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
-    </ul>
-  </div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-  name: 'HelloWorld',
-  props: {
-    msg: String,
-  },
-});
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped lang="scss">
-h3 {
-  margin: 40px 0 0;
-}
-ul {
-  list-style-type: none;
-  padding: 0;
-}
-li {
-  display: inline-block;
-  margin: 0 10px;
-}
-a {
-  color: #42b983;
-}
-</style>

+ 5 - 0
src/components/IconFont.ts

@@ -0,0 +1,5 @@
+import { createFromIconfontCN } from '@ant-design/icons-vue'
+
+export default createFromIconfontCN({
+  scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js',
+})

+ 160 - 0
src/configs/animation.ts

@@ -0,0 +1,160 @@
+export const ANIMATIONS_TYPES = ['弹跳', '淡入', '翻转', '旋转', '滑入', '缩放']
+
+export const ANIMATIONS = [
+  {
+    key: 'bounceIn',
+    type: '弹跳',
+    name: '弹跳',
+    icon: 'icon-anime-bounce'
+  },
+  {
+    key: 'bounceInDown',
+    type: '弹跳',
+    name: '上方弹跳',
+    icon: 'icon-anime-bounce-down',
+  },
+  {
+    key: 'bounceInLeft',
+    type: '弹跳',
+    name: '左侧弹跳',
+    icon: 'icon-anime-bounce-left',
+  },
+  {
+    key: 'bounceInRight',
+    type: '弹跳',
+    name: '右侧弹跳',
+    icon: 'icon-anime-bounce-right',
+  },
+  {
+    key: 'bounceInUp',
+    type: '弹跳',
+    name: '下方弹跳',
+    icon: 'icon-anime-bounce-up',
+  },
+  {
+    key: 'fadeIn',
+    type: '淡入',
+    name: '淡入',
+    icon: 'icon-anime-fade',
+  },
+  {
+    key: 'fadeInDown',
+    type: '淡入',
+    name: '上方淡入',
+    icon: 'icon-anime-fade-down',
+  },
+  {
+    key: 'fadeInLeft',
+    type: '淡入',
+    name: '左侧淡入',
+    icon: 'icon-anime-fade-left',
+  },
+  {
+    key: 'fadeInRight',
+    type: '淡入',
+    name: '右侧淡入',
+    icon: 'icon-anime-fade-right',
+  },
+  {
+    key: 'fadeInUp',
+    type: '淡入',
+    name: '下方淡入',
+    icon: 'icon-anime-fade-up',
+  },
+  {
+    key: 'flipInX',
+    type: '翻转',
+    name: '水平翻转',
+    icon: 'icon-anime-flip-x',
+  },
+  {
+    key: 'flipInY',
+    type: '翻转',
+    name: '垂直翻转',
+    icon: 'icon-anime-flip-y',
+  },
+  {
+    key: 'rotateIn',
+    type: '旋转',
+    name: '旋转',
+    icon: 'icon-anime-rotate',
+  },
+  {
+    key: 'rotateInDownLeft',
+    type: '旋转',
+    name: '左下旋转',
+    icon: 'icon-anime-rotate-up-right',
+  },
+  {
+    key: 'rotateInDownRight',
+    type: '旋转',
+    name: '右下旋转',
+    icon: 'icon-anime-rotate-up-left',
+  },
+  {
+    key: 'rotateInUpLeft',
+    type: '旋转',
+    name: '左上旋转',
+    icon: 'icon-anime-rotate-down-right',
+  },
+  {
+    key: 'rotateInUpRight',
+    type: '旋转',
+    name: '右上旋转',
+    icon: 'icon-anime-rotate-down-left',
+  },
+  {
+    key: 'slideInDown',
+    type: '滑入',
+    name: '上方滑入',
+    icon: 'icon-anime-slide-down',
+  },
+  {
+    key: 'slideInLeft',
+    type: '滑入',
+    name: '左侧滑入',
+    icon: 'icon-anime-slide-left',
+  },
+  {
+    key: 'slideInRight',
+    type: '滑入',
+    name: '右侧滑入',
+    icon: 'icon-anime-slide-right',
+  },
+  {
+    key: 'slideInUp',
+    type: '滑入',
+    name: '下方滑入',
+    icon: 'icon-anime-slide-up',
+  },
+  {
+    key: 'zoomIn',
+    type: '缩放',
+    name: '放大',
+    icon: 'icon-anime-zoom',
+  },
+  {
+    key: 'zoomInDown',
+    type: '缩放',
+    name: '上方放大',
+    icon: 'icon-anime-zoom-down',
+  },
+  {
+    key: 'zoomInLeft',
+    type: '缩放',
+    name: '左侧放大',
+    icon: 'icon-anime-zoom-left',
+  },
+  {
+    key: 'zoomInRight',
+    type: '缩放',
+    name: '右侧放大',
+    icon: 'icon-anime-zoom-right',
+  },
+  {
+    key: 'zoomInUp',
+    type: '缩放',
+    name: '下方放大',
+    icon: 'icon-anime-zoom-up',
+  },
+]

+ 1 - 0
src/configs/canvas.ts

@@ -0,0 +1 @@
+export const VIEWPORT_SIZE = 1000

+ 54 - 0
src/configs/chart.ts

@@ -0,0 +1,54 @@
+export const DEFAULT_BAR_DATA = {
+  axisData: ['类别1', '类别2', '类别3', '类别4', '类别5'],
+  series: [
+    { name: '系列1', data: [120, 200, 150, 80, 70] },
+    { name: '系列2', data: [80, 220, 170, 180, 40] }
+  ]
+}
+
+export const DEFAULT_PIE_DATA = [
+  { name: '类别1', value: 335 },
+  { name: '类别2', value: 310 },
+  { name: '类别3', value: 234 },
+  { name: '类别4', value: 135 },
+  { name: '类别5', value: 1548 },
+]
+
+export const CHARTS = [
+  {
+    key: 'bar',
+    name: '柱状图',
+    value: DEFAULT_BAR_DATA,
+  },
+  {
+    key: 'barY',
+    name: '条形图',
+    value: DEFAULT_BAR_DATA,
+  },
+  {
+    key: 'line',
+    name: '折线图',
+    value: DEFAULT_BAR_DATA,
+  },
+  {
+    key: 'pie',
+    name: '饼状图',
+    value: DEFAULT_PIE_DATA,
+  },
+  {
+    key: 'pieDoughnut',
+    name: '环形图',
+    value: DEFAULT_PIE_DATA,
+  },
+]
+
+export const CHART_THEME = {
+  purple: ['#8a7ca8', '#e098c7', '#8fd3e8', '#71669e', '#cc70af'],
+  shine: ['#c12e34', '#e6b600', '#0098d9', '#2b821d', '#005eaa'],
+  halloween: ['#ff715e', '#ffaf51', '#ffee51', '#797fba', '#715c87'],
+  vintage: ['#d87c7c', '#919e8b', '#d7ab82', '#6e7074', '#61a0a8'],
+  dark: ['#dd6b66', '#759aa0', '#e69d87', '#8dc1a9', '#ea7e53'],
+  westeros: ['#516b91', '#59c4e6', '#edafda', '#93b7e3', '#a5e7f0'],
+  wonderland: ['#4ea397', '#22c3aa', '#7bd9a5', '#d0648a', '#f58db2'],
+  chalk: ['#fc97af', '#87f7cf', '#f7f494', '#72ccff', '#f7c5a0'],
+}

+ 81 - 0
src/configs/defaultElement.ts

@@ -0,0 +1,81 @@
+const DEFAULT_COLOR = '#888'
+
+export const DEFAULT_TEXT = {
+  type: 'text',
+  left: 0,
+  top: 0,
+  width: 300,
+  height: 0,
+  padding: 5,
+  opacity: 1,
+  lineHeight: 1.5,
+  segmentSpacing: 5,
+  textType: 'content',
+  content: '<div>“单击此处添加文本”</div>',
+}
+
+export const DEFAULT_IMAGE = {
+  type: 'image',
+  left: 0,
+  top: 0,
+  lockRatio: true,
+}
+
+export const DEFAULT_SHAPE = {
+  type: 'shape',
+  fill: DEFAULT_COLOR,
+  lockRatio: false,
+}
+
+export const DEFAULT_SHAPE_LINE = {
+  type: 'shape',
+  borderStyle: 'solid',
+  borderWidth: 2,
+  borderColor: DEFAULT_COLOR,
+  fill: 'rgba(0, 0, 0, 0)',
+  lockRatio: false,
+}
+
+export const DEFAULT_ICON = {
+  type: 'icon',
+  left: 0,
+  top: 0,
+  width: 80,
+  height: 80,
+  color: DEFAULT_COLOR,
+  lockRatio: true,
+}
+
+export const DEFAULT_LINE = {
+  type: 'line',
+  style: 'solid',
+  marker: ['', ''],
+  width: 4,
+  color: DEFAULT_COLOR,
+}
+
+export const DEFAULT_CHART = {
+  type: 'chart',
+  left: 0,
+  top: 0,
+  width: 500,
+  height: 500,
+}
+
+export const DEFAULT_IFRAME = {
+  type: 'iframe',
+  left: 0,
+  top: 0,
+  width: 800,
+  height: 480,
+}
+
+export const DEFAULT_TABLE = {
+  type: 'table',
+  left: 0,
+  top: 0,
+  isLock: false,
+  borderStyle: 'solid',
+  borderWidth: 2,
+  borderColor: DEFAULT_COLOR,
+}

+ 42 - 0
src/configs/element.ts

@@ -0,0 +1,42 @@
+export const ELEMENT_SIZE_RANGE = {
+  text: { width: 15, height: 15 },
+  image: { width: 15, height: 15 },
+  shape: { width: 15, height: 15 },
+  icon: { width: 15, height: 15 },
+  chart: { width: 200, height: 200 },
+  iframe: { width: 200, height: 200 },
+  table: { width: 50, height: 30 },
+}
+
+export const ELEMENT_TYPE_TABS = {
+  text: { key: 'element-text', label: '文本属性' },
+  image: { key: 'element-image', label: '图片属性' },
+  shape: { key: 'element-shape', label: '形状属性' },
+  icon: { key: 'element-icon', label: '图标属性' },
+  line: { key: 'element-line', label: '线条属性' },
+  chart: { key: 'element-chart', label: '图表属性' },
+  iframe: { key: 'element-iframe', label: 'Iframe属性' },
+  table: { key: 'element-table', label: '表格属性' },
+}
+
+export const ELEMENTS = {
+  text: '文本',
+  image: '图片',
+  shape: '形状',
+  icon: '图标',
+  line: '线条',
+  chart: '图表',
+  iframe: 'Iframe',
+  table: '表格',
+}
+
+export const OPERATE_KEYS = {
+  LEFT_TOP: 1,
+  TOP: 2,
+  RIGHT_TOP: 3,
+  LEFT: 4,
+  RIGHT: 5,
+  LEFT_BOTTOM: 6,
+  BOTTOM: 7,
+  RIGHT_BOTTOM: 8,
+}

+ 32 - 0
src/configs/fontName.ts

@@ -0,0 +1,32 @@
+export const FONT_FAMILYS = [
+  { source: 'windows', zh: '微软雅黑', en: 'Microsoft Yahei' },
+  { source: 'windows', zh: '宋体', en: 'SimSun' },
+  { source: 'windows', zh: '黑体', en: 'SimHei' },
+  { source: 'windows', zh: '楷体', en: 'KaiTi' },
+  { source: 'windows', zh: '新宋体', en: 'NSimSun' },
+  { source: 'windows', zh: '仿宋', en: 'FangSong' },
+
+  { source: 'osx', zh: '苹方', en: 'PingFang SC' },
+  { source: 'osx', zh: '华文黑体', en: 'STHeiti' },
+  { source: 'osx', zh: '华文楷体', en: 'STKaiti' },
+  { source: 'osx', zh: '华文宋体', en: 'STSong' },
+  { source: 'osx', zh: '华文仿宋', en: 'STFangSong' },
+  { source: 'osx', zh: '华文中宋', en: 'STZhongSong' },
+  { source: 'osx', zh: '华文琥珀', en: 'STHupo' },
+  { source: 'osx', zh: '华文新魏', en: 'STXinwei' },
+  { source: 'osx', zh: '华文隶书', en: 'STLiti' },
+  { source: 'osx', zh: '华文行楷', en: 'STXingkai' },
+  { source: 'osx', zh: '冬青黑体简', en: 'Hiragino Sans GB' },
+  { source: 'osx', zh: '兰亭黑-简', en: 'Lantinghei SC' },
+  { source: 'osx', zh: '偏偏体-简', en: 'Hanzipen SC' },
+  { source: 'osx', zh: '手札体-简', en: 'Hannotate SC' },
+  { source: 'osx', zh: '宋体-简', en: 'Songti SC' },
+  { source: 'osx', zh: '娃娃体-简', en: 'Wawati SC' },
+  { source: 'osx', zh: '行楷-简', en: 'Xingkai SC' },
+  { source: 'osx', zh: '圆体-简', en: 'Yuanti SC' },
+  
+  { source: 'office', zh: '华文细黑', en: 'STXihei' },
+  { source: 'office', zh: '幼圆', en: 'YouYuan' },
+  { source: 'office', zh: '隶书', en: 'LiSu' },
+  { source: '', zh: 'Arial', en: 'Arial' },
+]

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 130 - 0
src/configs/icons.ts


+ 51 - 0
src/configs/imageClip.ts

@@ -0,0 +1,51 @@
+export const CLIPPATHS = {
+  rect: {
+    name: '矩形',
+    type: 'rect',
+    radius: '0',
+    style: '',
+  },
+  roundRect: {
+    name: '圆角矩形',
+    type: 'rect',
+    radius: '10%',
+    style: 'inset(0 0 0 0 round 10% 10% 10% 10%)',
+  },
+  ellipse: {
+    name: '圆形',
+    type: 'ellipse',
+    style: 'ellipse(50% 50% at 50% 50%)',
+  },
+  triangle: {
+    name: '三角形',
+    type: 'polygon',
+    style: 'polygon(50% 0%, 0% 100%, 100% 100%)',
+    createPath: (width: number, height: number) => {
+      return `M ${width / 2} 0 L 0 ${height} L ${width} ${height} Z`
+    },
+  },
+  pentagon: {
+    name: '五边形',
+    type: 'polygon',
+    style: 'polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%)',
+    createPath: (width: number, height: number) => {
+      return `M ${width / 2} 0 L ${width} ${0.38 * height} L ${0.82 * width} ${height} L ${0.18 * width} ${height} L 0 ${0.38 * height} Z`
+    },
+  },
+  rhombus: {
+    name: '菱形',
+    type: 'polygon',
+    style: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)',
+    createPath: (width: number, height: number) => {
+      return `M ${width / 2} 0 L ${width} ${height / 2} L ${width / 2} ${height} L 0 ${height / 2} Z`
+    },
+  },
+  star: {
+    name: '五角星',
+    type: 'polygon',
+    style: 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)',
+    createPath: (width: number, height: number) => {
+      return `M ${width / 2} 0 L ${0.61 * width} ${0.35 * height} L ${0.98 * width} ${0.35 * height} L ${0.68 * width} ${0.57 * height} L ${0.79 * width} ${0.91 * height} L ${0.50 * width} ${0.70 * height} L ${0.21 * width} ${0.91 * height} L ${0.32 * width} ${0.57 * height} L ${0.02 * width} ${0.35 * height} L ${0.39 * width} ${0.35 * height} Z`
+    },
+  },
+}

+ 18 - 0
src/configs/keyCode.ts

@@ -0,0 +1,18 @@
+export const KEYCODE = {
+  S: 83,
+  C: 67,
+  X: 88,
+  Z: 90,
+  Y: 89,
+  A: 65,
+  G: 71,
+  L: 76,
+  DELETE: 46,
+  UP: 38,
+  DOWN: 40,
+  LEFT: 37,
+  RIGHT: 39,
+  ENTER: 13,
+  SPACE: 32,
+  TAB: 9,
+}

+ 21 - 0
src/configs/lines.ts

@@ -0,0 +1,21 @@
+export const LINES = [
+  { type: 'line', path: 'M0,0 L20,20', style: 'solid', marker: ['', ''] },
+  { type: 'line', path: 'M0,0 L20,20', style: 'solid', marker: ['', 'arrow'] },
+  { type: 'line', path: 'M0,0 L20,20', style: 'solid', marker: ['arrow', 'arrow'] },
+  { type: 'line', path: 'M0,0 L20,20', style: 'solid', marker: ['', 'cusp'] },
+  { type: 'line', path: 'M0,0 L20,20', style: 'solid', marker: ['cusp', 'cusp'] },
+  { type: 'line', path: 'M0,0 L20,20', style: 'solid', marker: ['', 'dot'] },
+  { type: 'line', path: 'M0,0 L20,20', style: 'solid', marker: ['dot', 'dot'] },
+  { type: 'line', path: 'M0,0 L20,20', style: 'dashed', marker: ['', ''] },
+  { type: 'line', path: 'M0,0 L20,20', style: 'dashed', marker: ['', 'arrow'] },
+  { type: 'line', path: 'M0,0 L20,20', style: 'dashed', marker: ['arrow', 'arrow'] },
+
+  { type: 'polyline-x', path: 'M0,0 L0,20 L20,20', style: 'solid', marker: ['', 'arrow'] },
+  { type: 'polyline-y', path: 'M0,0 L20,0 L20,20', style: 'solid', marker: ['', 'arrow'] },
+
+  { type: 'polyline-x2', path: 'M0,0 L10,0 L10,20 L20,20', style: 'solid', marker: ['', 'arrow'] },
+  { type: 'polyline-y2', path: 'M0,0 L0,10 L20,10 L20,20', style: 'solid', marker: ['', 'arrow'] },
+
+  { type: 'curve-x', path: 'M0,0 C20,0 0,20 20,20', style: 'solid', marker: ['', 'arrow'] },
+  { type: 'curve-y', path: 'M0,0 C0,20 20,0 20,20', style: 'solid', marker: ['', 'arrow'] },
+]

+ 77 - 0
src/configs/shadows.ts

@@ -0,0 +1,77 @@
+export const TEXT_SHADOWS = [
+  {
+    key: '无阴影',
+    value: '',
+  },
+  {
+    key: '左上方偏移-深色',
+    value: '-1px -1px 3px #666',
+  },
+  {
+    key: '右上方偏移-深色',
+    value: '1px -1px 3px #666',
+  },
+  {
+    key: '左下方偏移-深色',
+    value: '-1px 1px 3px #666',
+  },
+  {
+    key: '右下方偏移-深色',
+    value: '1px 1px 3px #666',
+  },
+  {
+    key: '左上方偏移-浅色',
+    value: '-1px -1px 3px #ccc',
+  },
+  {
+    key: '右上方偏移-浅色',
+    value: '1px -1px 3px #ccc',
+  },
+  {
+    key: '左下方偏移-浅色',
+    value: '-1px 1px 3px #ccc',
+  },
+  {
+    key: '右下方偏移-浅色',
+    value: '1px 1px 3px #ccc',
+  },
+]
+
+export const SHAPE_SHADOWS = [
+  {
+    key: '无阴影',
+    value: '',
+  },
+  {
+    key: '左上-深色',
+    value: '-3px -3px 6px #666',
+  },
+  {
+    key: '左下-深色',
+    value: '-3px 3px 6px #666',
+  },
+  {
+    key: '右上-深色',
+    value: '3px -3px 6px #666',
+  },
+  {
+    key: '右下-深色',
+    value: '3px 3px 6px #666',
+  },
+  {
+    key: '左上-浅色',
+    value: '-3px -3px 6px #ccc',
+  },
+  {
+    key: '左下-浅色',
+    value: '-3px 3px 6px #ccc',
+  },
+  {
+    key: '右上-浅色',
+    value: '3px -3px 6px #ccc',
+  },
+  {
+    key: '右下-浅色',
+    value: '3px 3px 6px #ccc',
+  },
+]

+ 350 - 0
src/configs/shapes.ts

@@ -0,0 +1,350 @@
+export const SHAPES = [
+  {
+    key: 'rect',
+    path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z'
+  },
+  {
+    key: 'rect-2',
+    path: 'M 0 200 L 0 0 L 150 0 L 200 50 L 200 200 L 0 200'
+  },
+  {
+    key: 'rect-3',
+    path: 'M 0 150 L 0 0 L 150 0 L 200 50 L 200 200 L 50 200 L 0 150'
+  },
+  {
+    key: 'roundRect',
+    path: 'M 20 0 L 180 0 Q 200 0 200 20 L 200 180 Q 200 200 180 200 L 20 200 Q 0 200 0 180 L 0 20 Q 0 0 20 0 '
+  },
+  {
+    key: 'roundRect-2',
+    path: 'M 0 50 Q 0 0 50 0 L 150 0 Q 200 0 200 50 L 200 150 Q 200 200 150 200 L 50 200 Q 0 200 0 150 L 0 50 Z'
+  },
+  {
+    key: 'roundRect-3',
+    path: 'M 0 0 L 140 0 Q 200 0 200 60 L 200 200 L 60 200 Q 0 200 0 140 L 0 0 Z'
+  },
+  {
+    key: 'roundRect-4',
+    path: 'M 0 0 L 140 0 Q 200 0 200 60 L 200 200 L 0 200 L 0 0 Z'
+  },
+  {
+    key: 'ellipse',
+    path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z'
+  },
+  {
+    key: 'ellipse-half',
+    path: 'M 0 200 A 50 100 0 1 1 200 200 L 0 200 Z'
+  },  
+  {
+    key: 'ellipse-quarter',
+    path: 'M 200 0 Q 0 0 0 200 L 200 200 L 200 0'
+  },
+  {
+    key: 'pie',
+    path: 'M 100 0 A 100 100 0 1 1 0 100 L 100 100 L 100 0 Z'
+  },
+  {
+    key: 'pie-2',
+    path: 'M 200 100 A 100 100 0 1 1 160 20 L 100 100 Z'
+  },
+  {
+    key: 'triangle',
+    path: 'M 100 0 L 0 200 L 200 200 L 100 0 Z'
+  },
+  {
+    key: 'triangle-2',
+    path: 'M 0 0 L 200 0 L 100 200 L 0 0 Z'
+  },
+  {
+    key: 'triangle-3',
+    path: 'M 0 100 L 200 0 L 200 200 L 0 100 Z'
+  },
+  {
+    key: 'triangle-4',
+    path: 'M 0 0 L 200 100 L 0 200 L 0 0 Z'
+  },
+  {
+    key: 'triangle-5',
+    path: 'M 0 0 L 0 200 L 200 200 Z'
+  },
+  {
+    key: 'triangle-6',
+    path: 'M 0 0 L 200 0 L 200 200 Z'
+  },
+  {
+    key: 'parallelogram',
+    path: 'M 50 0 L 200 0 L 150 200 L 0 200 L 50 0 Z'
+  },
+  {
+    key: 'diamond',
+    path: 'M 100 0 L 0 100 L 100 200 L 200 100 L 100 0 Z'
+  },
+  {
+    key: 'trapezoid',
+    path: 'M 50 0 L 150 0 L 200 200 L 0 200 L 50 0 Z'
+  },
+  {
+    key: 'pentagon',
+    path: 'M 100 0 L 0 90 L 50 200 L 150 200 L 200 90 L 100 0 Z'
+  },
+  {
+    key: 'hexagon',
+    path: 'M 100 0 L 0 60 L 0 140 L 100 200 L 200 140 L 200 60 L 100 0 Z'
+  },
+  {
+    key: 'octagon',
+    path: 'M 60 0 L 140 0 L 200 60 L 200 140 L 140 200 L 60 200 L 0 140 L 0 60 L 60 0 Z'
+  },
+  {
+    key: 'water',
+    path: 'M 100 0 A 100 100 0 1 1 0 100 L 0 0 L 100 0 Z'
+  },  
+  {
+    key: 'leaf',
+    path: 'M 0 0 Q 200 0 200 200 Q 0 200 0 0'
+  },
+  {
+    key: 'leaf-2',
+    path: 'M 0 200 Q 0 0 200 0 Q 200 200 0 200'
+  },
+  {
+    key: 'moon',
+    path: 'M 100 0 A 50 50 0 1 0 200 120 A 100 100 0 1 1 100 0'
+  },
+  {
+    key: 'star',
+    path: 'M 100 0 L 122 70 L 196 70 L 136 114 L 158 182 L 100 140 L 42 182 L 64 114 L 4 70 L 78 70 Z'
+  },
+  {
+    key: 'lightning',
+    path: 'M 120 0 L 100 80 L 200 80 L 80 200 L 100 120 L 0 120 L 120 0'
+  },
+  {
+    key: 'cloud',
+    path: 'M 190 100 C 200 120 190 160 150 160 C 130 190 110 190 80 170 C 50 180 20 170 20 140 C 0 130 0 80 30 70 C 30 30 50 20 90 30 C 120 10 140 10 160 40 C 200 40 200 70 190 100'
+  },
+  {
+    key: 'arrow',
+    path: 'M 100 0 L 0 100 L 50 100 L 50 200 L 150 200 L 150 100 L 200 100 L 100 0 Z'
+  },
+  {
+    key: 'arrow-2',
+    path: 'M 100 200 L 200 100 L 150 100 L 150 0 L 50 0 L 50 100 L 0 100 L 100 200 Z'
+  },
+  {
+    key: 'arrow-3',
+    path: 'M 0 100 L 100 0 L 100 50 L 200 50 L 200 150 L 100 150 L 100 200 L 0 100 Z'
+  },
+  {
+    key: 'arrow-4',
+    path: 'M 200 100 L 100 0 L 100 50 L 0 50 L 0 150 L 100 150 L 100 200 L 200 100 Z'
+  },
+  {
+    key: 'arrow-5',
+    path: 'M 50 100 L 0 50 L 125 50 L 125 0 L 200 100 L 125 200 L 125 150 L 0 150 Z'
+  },
+  {
+    key: 'arrow-6',
+    path: 'M 150 100 L 200 50 L 75 50 L 75 0 L 0 100 L 75 200 L 75 150 L 200 150 Z'
+  },
+  {
+    key: 'arrow-7',
+    path: 'M 100 150 L 50 200 L 50 75 L 0 75 L 100 0 L 200 75 L 150 75 L 150 200 Z'
+  },
+  {
+    key: 'arrow-8',
+    path: 'M 100 50 L 50 0 L 50 125 L 0 125 L 100 200 L 200 125 L 150 125 L 150 0 Z'
+  },
+  {
+    key: 'arrow-9',
+    path: 'M 40 120 L 0 160 L 40 200 L 40 180 L 180 180 L 180 40 L 200 40 L 160 0 L 120 40 L 140 40 L 140 140 L 40 140 Z'
+  },
+  {
+    key: 'arrow-10',
+    path: 'M 160 60 L 160 80 L 200 40 L 160 0 L 160 20 L 20 20 L 20 160 L 0 160 L 40 200 L 80 160 L 60 160 L 60 60 Z'
+  },
+  {
+    key: 'arrow-11',
+    path: 'M 100 0 L 0 40 L 60 40 L 60 160 L 0 160 L 100 200 L 200 160 L 140 160 L 140 40 L 200 40 Z'
+  },
+  {
+    key: 'arrow-12',
+    path: 'M 0 100 L 40 200 L 40 140 L 160 140 L 160 200 L 200 100 L 160 0 L 160 60 L 40 60 L 40 0 Z'
+  },
+  {
+    key: 'arrow-13',
+    path: 'M 0 200 Q 0 25 150 25 L 150 0 L 200 50 L 150 100 L 150 75 Q 0 75 0 200',
+  },
+  {
+    key: 'arrow-14',
+    path: 'M 200 200 Q 175 25 50 25 L 50 0 L 0 50 L 50 100 L 50 75 Q 175 75 200 200'
+  },
+  {
+    key: 'message',
+    path: 'M 0 0 L 200 0 L 200 150 L 80 150 L 40 200 L 40 150 L 0 150 L 0 0 Z'
+  },
+  {
+    key: 'message-2',
+    path: 'M 0 0 L 200 0 L 200 150 L 160 150 L 160 200 L 120 150 L 0 150 L 0 10 Z'
+  },
+  {
+    key: 'v',
+    path: 'M 0 0 L 120 0 L 200 100 L 120 200 L 0 200 L 80 100 L 0 0 Z'
+  },
+  {
+    key: 'v-2',
+    path: 'M 80 0 L 200 0 L 120 100 L 200 200 L 80 200 L 0 100 L 80 0 Z'
+  },
+  {
+    key: 'point',
+    path: 'M 0 0 L 140 0 L 200 100 L 140 200 L 0 200 L 0 100 L 0 0 Z'
+  },
+  {
+    key: 'point-2',
+    path: 'M 60 0 L 200 0 L 200 100 L 200 200 L 60 200 L 0 100 L 60 0 Z'
+  },
+  {
+    key: 'plus-wide',
+    path: 'M 50 0 L 150 0 L 150 50 L 200 50 L 200 150 L 150 150 L 150 200 L 50 200 L 50 150 L 0 150 L 0 50 L 50 50 L 50 0'
+  },
+  {
+    key: 'plus',
+    path: 'M 70 0 L 70 70 L 0 70 L 0 130 L 70 130 L 70 200 L 130 200 L 130 130 L 200 130 L 200 70 L 130 70 L 130 0 L 70 0 Z'
+  },
+  {
+    key: 'cross',
+    path: 'M 40 0 L 0 40 L 60 100 L 0 160 L 40 200 L 100 140 L 160 200 L 200 160 L 140 100 L 200 40 L 160 0 L 100 60 L 40 0 Z'
+  },
+  {
+    key: 'funnel',
+    path: 'M 0 0 L 200 0 L 0 200 L 200 200 L 0 0 Z'
+  },
+  {
+    key: 'funnel-2',
+    path: 'M 200 0 Q 0 100 200 200 L 0 200 Q 200 100 0 0 L 200 0 Z'
+  },
+  {
+    key: 'funnel-3',
+    path: 'M 200 0 Q 100 100 200 200 L 0 200 Q 100 100 0 0 L 200 0 Z'
+  },
+  {
+    key: 'flag',
+    path: 'M 0 0 Q 50 50 100 25 Q 150 0 200 50 L 200 200 Q 150 150 100 175 Q 50 200 0 150 L 0 0 Z'
+  },
+  {
+    key: 'crown',
+    path: 'M 0 200 L 200 200 L 180 60 L 140 100 L 100 0 L 60 100 L 20 60 L 0 200 Z'
+  },
+  {
+    key: 'sector',
+    path: 'M 200 100 A 50 50 0 1 1 0 100 L 100 0 L 200 100 Z'
+  },
+  {
+    key: 'sector-2',
+    path: 'M 0 100 A 50 50 0 1 1 200 100 L 100 200 L 0 100 Z'
+  },
+  {
+    key: 'sector-3',
+    path: 'M 200 200 L 125 0 Q 0 0 0 125 Z'
+  },
+  {
+    key: 'darts',
+    path: 'M 100 0 L 60 60 L 0 100 L 60 140 L 100 200 L 140 140 L 200 100 L 140 60 L 100 0 Z'
+  },
+  {
+    key: 'break',
+    path: 'M 0 0 L 150 0 L 200 50 L 150 100 L 200 150 L 150 200 L 0 200 L 50 150 L 0 100 L 50 50 L 0 0 Z'
+  },
+  {
+    key: 'spray',
+    path: 'M 0 100 A 50 50 0 1 1 200 100 Q 100 50 100 200 Q 100 50 0 100 Z'
+  },
+  {
+    key: 'pinecones',
+    path: 'M 100 0 Q 0 50 0 175 Q 100 225 200 175 Q 200 50 100 0'
+  },
+  {
+    key: 'triangular-arrow',
+    path: 'M 50 100 L 0 0 L 200 100 L 0 200 Z'
+  },
+  {
+    key: 'triangular-arrow-2',
+    path: 'M 100 150 L 0 200 L 100 0 L 200 200 Z'
+  },
+  {
+    key: 'triangular-arrow-3',
+    path: 'M 150 100 L 200 200 L 0 100 L 200 0 Z'
+  },
+  {
+    key: 'triangular-arrow-4',
+    path: 'M 100 50 L 200 0 L 100 200 L 0 0 Z'
+  },
+  {
+    key: 'border',
+    path: 'M 0 0 L 200 0 L 160 40 L 40 40 L 40 160 L 0 200 Z'
+  },
+  {
+    key: 'border-2',
+    path: 'M 200 200 L 200 0 L 160 40 L 160 160 L 40 160 L 0 200 Z'
+  },
+  {
+    key: 'border-3',
+    path: 'M 40 40 L 200 40 L 200 0 L 0 0 L 0 200 L 40 200 Z'
+  },
+  {
+    key: 'border-4',
+    path: 'M 200 200 L 200 0 L 160 0 L 160 160 L 0 160 L 0 200 Z'
+  },
+  {
+    key: 'flower',
+    path: 'M 100 100 Q 50 50 100 0 Q 150 50 100 100 Q 150 50 200 100 Q 150 150 100 100 Q 150 150 100 200 Q 50 150 100 100 Q 50 150 0 100 Q 50 50 100 100'
+  },
+  {
+    key: 'flower-2',
+    path: 'M 100 100 Q 25 0 100 0 Q 175 0 100 100 Q 200 25 200 100 Q 200 175 100 100 Q 175 200 100 200 Q 25 200 100 100 Q 0 175 0 100 Q 0 25 100 100'
+  },
+  {
+    key: 'flower-3',
+    path: 'M 100 100 L 50 0 L 0 50 L 100 100 L 0 150 L 50 200 L 100 100 L 150 200 L 200 150 L 100 100 L 200 50 L 150 0 L 100 100'
+  },
+  {
+    key: 'brace-left',
+    path: 'M 200 0 L 80 20 L 80 90 L 0 100 L 80 110 L 80 180 L 200 200',
+    type: 'line'
+  },
+  {
+    key: 'brace-right',
+    path: 'M 0 0 L 120 20 L 120 90 L 200 100 L 120 110 L 120 180 L 0 200',
+    type: 'line'
+  },
+  {
+    key: 'fillet-brace-left',
+    path: 'M 200 0 Q 80 0 80 40 L 80 90 L 0 100 L 80 110 L 80 160 Q 80 200 200 200',
+    type: 'line'
+  },
+  {
+    key: 'fillet-brace-right',
+    path: 'M 0 0 Q 120 0 120 40 L 120 90 L 200 100 L 120 110 L 120 160 Q 120 200 0 200',
+    type: 'line'
+  },
+  {
+    key: 'bracket-left',
+    path: 'M 200 0 L 0 0 L 0 200 L 200 200',
+    type: 'line'
+  },
+  {
+    key: 'bracket-right',
+    path: 'M 0 0 L 200 0 L 200 200 L 0 200',
+    type: 'line'
+  },
+  {
+    key: 'parentheses-left',
+    path: 'M 200 0 Q 0 0 0 100 Q 0 200 200 200',
+    type: 'line'
+  },
+  {
+    key: 'parentheses-right',
+    path: 'M 0 0 Q 200 0 200 100 Q 200 200 0 200',
+    type: 'line'
+  },
+]

+ 117 - 0
src/configs/tableTheme.ts

@@ -0,0 +1,117 @@
+const commonProps = {
+  data: [
+    [
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+    ],
+    [
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+    ],
+    [
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+    ],
+    [
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+    ],
+    [
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+    ],
+    [
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+      { colspan: 1, rowspan: 1, content: '<div style=\"text-align: center;\">text</div>' },
+    ],
+  ],
+  rowSizes: [10, 10, 10, 10, 10, 10],
+  colSizes: [20, 20, 20, 20, 20],
+  width: 102,
+  height: 62,
+  borderColor: '#fff',
+  borderTheme: 'all',
+  borderStyle: 'solid',
+  borderWidth: 1,
+}
+
+export const TABLE_THEMES = [
+  {
+    ...commonProps,
+    tableTheme: 'style-default',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-0',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-1',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-2',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-3',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-4',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-5',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-6',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-7',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-8',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-9',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-10',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-11',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-12',
+  },
+  {
+    ...commonProps,
+    tableTheme: 'style-13',
+  },
+]

+ 6 - 1
src/main.ts

@@ -3,4 +3,9 @@ import App from './App.vue'
 import router from './router'
 import store from './store'
 
-createApp(App).use(store).use(router).mount('#app')
+import '@/assets/styles/global.scss'
+
+const app = createApp(App)
+app.use(store)
+app.use(router)
+app.mount('#app')

+ 8 - 11
src/router/index.ts

@@ -1,25 +1,22 @@
 import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
-import Home from '../views/Home.vue'
+import Editor from '@/views/Editor/index.vue'
 
 const routes: Array<RouteRecordRaw> = [
   {
     path: '/',
-    name: 'Home',
-    component: Home
+    name: 'Editor',
+    component: Editor,
   },
   {
-    path: '/about',
-    name: 'About',
-    // route level code-splitting
-    // this generates a separate chunk (about.[hash].js) for this route
-    // which is lazy-loaded when the route is visited.
-    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
-  }
+    path: '/player',
+    name: 'Player',
+    component: () => import(/* webpackChunkName: "Player" */ '@/views/Player/index.vue'),
+  },
 ]
 
 const router = createRouter({
   history: createWebHistory(process.env.BASE_URL),
-  routes
+  routes,
 })
 
 export default router

+ 23 - 0
src/store/editor.ts

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

+ 26 - 7
src/store/index.ts

@@ -1,12 +1,31 @@
 import { createStore } from 'vuex'
 
+import slides from './slides'
+import editor from './editor'
+
+const state = {
+
+}
+
+const getters = {
+
+}
+
+const mutations = {
+
+}
+
+const actions = {
+
+}
+
 export default createStore({
-  state: {
-  },
-  mutations: {
-  },
-  actions: {
-  },
   modules: {
-  }
+    slides,
+    editor,
+  },
+  state,
+  getters,
+  mutations,
+  actions,
 })

+ 24 - 0
src/store/slides.ts

@@ -0,0 +1,24 @@
+const state = {
+  slides: [],
+  slideIndex: 0,
+}
+
+const getters = {
+  
+}
+
+const mutations = {
+
+}
+
+const actions = {
+
+}
+
+export default {
+  namespaced: true,
+  state,
+  getters,
+  mutations,
+  actions,
+}

+ 126 - 0
src/types/slides.ts

@@ -0,0 +1,126 @@
+interface PPTElementBaseProps {
+  id: string;
+  isLock: boolean;
+  groupId: string;
+  left: number;
+  top: number;
+}
+
+interface PPTElementSizeProps {
+  width: number;
+  height: number;
+}
+
+interface PPTElementBorderProps {
+  borderStyle: string;
+  borderWidth: number;
+  borderColor: string;
+}
+
+interface PPTTextElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
+  rotate: number;
+  fill: string;
+  opactity: number;
+  lineHeight: number;
+  segmentSapcing: number;
+  letterSpacing: number;
+  shadow: string;
+  padding: number;
+  textType: string;
+  content: string;
+}
+
+interface PPTImageElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
+  rotate: number;
+  filter: string;
+  clip: string;
+  flip: string;
+  shadow: string;
+  lockRatio: boolean;
+  imgUrl: string;
+}
+
+interface PPTShapeElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
+  rotate: number;
+  fill: string;
+  opactity: number;
+  shadow: string;
+  lockRatio: boolean;
+  svgCode: string;
+  text: string;
+  textAlign: string;
+}
+
+interface PPTIconElement extends PPTElementBaseProps, PPTElementSizeProps {
+  rotate: number;
+  color: string;
+  shadow: string;
+  lockRatio: boolean;
+  svgCode: string;
+}
+
+interface PPTLineElement extends PPTElementBaseProps {
+  start: number[];
+  end: number[];
+  width: number;
+  style: string;
+  color: string;
+  marker: string[];
+  lineType: string;
+}
+
+interface BarChartSeries {
+  name: string;
+  data: number[];
+}
+interface BarChartData {
+  axisData: string[];
+  series: BarChartSeries[];
+}
+interface PieChartData {
+  name: string;
+  value: number
+}
+interface PPTChartElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
+  chartType: string;
+  theme: string;
+  data: PieChartData[] | BarChartData;
+}
+
+interface TableCell {
+  colspan: number;
+  rowspan: number;
+  content: string;
+  bgColor: string;
+}
+interface PPTTableElement extends PPTElementBaseProps, PPTElementSizeProps {
+  borderTheme: string;
+  theme: string;
+  rowSizes: number[];
+  colSizes: number[];
+  data: TableCell[][];
+}
+interface PPTIframeElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
+  src: string;
+}
+
+type PPTElement = PPTTextElement | 
+                  PPTImageElement | 
+                  PPTShapeElement | 
+                  PPTIconElement | 
+                  PPTLineElement | 
+                  PPTChartElement |
+                  PPTTableElement |
+                  PPTIframeElement
+
+interface PPTAnimation {
+  elId: string;
+  type: string;
+  duration: number;
+}
+
+interface Slide {
+  id: string;
+  elements: PPTElement[];
+  animations: PPTAnimation[];
+}

+ 198 - 0
src/utils/index.ts

@@ -0,0 +1,198 @@
+import padStart from 'lodash/padStart'
+import Clipboard from 'clipboard'
+import CryptoJS from 'crypto-js'
+
+const CRYPTO_KEY = 'zxc_ppt_online_editor'
+
+// 生成随机数
+export const createRandomNumber = (min: number, max: number) => {
+  return Math.floor(min + Math.random() * (max - min))
+}
+
+// 生成随机码
+export const createRandomCode = (len: number = 6) => {
+  const charset = `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`
+  const maxLen = charset.length
+  let ret = ''
+  for(let i = 0; i < len; i++) {
+    const randomIndex = Math.floor(Math.random() * maxLen)
+    ret += charset[randomIndex]
+  }
+  return ret
+}
+
+// 生成uuid
+export const createUUID = () => {
+  const url = URL.createObjectURL(new Blob())
+  const uuid = url.toString()
+  URL.revokeObjectURL(url)
+  return uuid.substr(uuid.lastIndexOf('/') + 1)
+}
+
+// 获取当前日期字符串
+export const getDateTime = (format: string = 'yyyy-MM-dd hh:mm:ss') => {
+  const date = new Date()
+
+  const formatMap = {
+    'y+': date.getFullYear(),
+    'M+': date.getMonth() + 1,
+    'd+': date.getDate(),
+    'h+': date.getHours(),
+    'm+': date.getMinutes(),
+    's+': date.getSeconds(),
+  }
+
+  for(const item of Object.keys(formatMap)) {
+    if(new RegExp('(' + item + ')').test(format)) {
+      const formated = (formatMap[item] + '').length < RegExp.$1.length ? padStart('' + formatMap[item], RegExp.$1.length, '0') : formatMap[item]
+      format = format.replace(RegExp.$1, formated)
+    }
+  }
+  return format
+}
+
+// 数字转中文,如1049 -> 一千零四十九
+export const digitalToChinese = (n: number) => {
+  const str = n + ''
+  const len = str.length - 1
+  const idxs = ['', '十', '百', '千']
+  const num = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
+  return str.replace(/([1-9]|0+)/g, ($, $1, idx) => {
+    const pos = len - idx
+    if($1 !== 0) {
+      if(idx === 0 && $1 === 1 && idxs[pos] === '十') return idxs[pos]
+      return num[$1] + idxs[pos]
+    }
+    if(idx + $1.length >= str.length) return ''
+    return '零'
+  })
+}
+
+// 数字补足位数,例如将6补足3位 -> 003
+export const fillDigit = (digit: number, len: number) => {
+  return padStart('' + digit, len, '0')
+}
+
+// 进入全屏
+export const enterFullscreen = () => {
+  const docElm = document.documentElement
+  docElm.requestFullscreen()
+}
+
+// 退出全屏
+export const exitFullscreen = document.exitFullscreen
+
+// 判断是否全屏
+export const isFullscreen = () => document.fullscreenEnabled
+
+// 判断用户的操作系统是否安装了某字体
+export const isSupportFontFamily = (fontFamily: string) => {
+  if(typeof fontFamily !== 'string') return false
+  const arial = 'Arial'
+  if(fontFamily.toLowerCase() === arial.toLowerCase()) return true
+  const a = 'a'
+  const size = 100
+  const width = 100
+  const height = 100
+
+  const canvas = document.createElement('canvas')
+  const ctx = canvas.getContext('2d')
+
+  if(!ctx) return false
+
+  canvas.width = width
+  canvas.height = height
+  ctx.textAlign = 'center'
+  ctx.fillStyle = 'black'
+  ctx.textBaseline = 'middle'
+
+  const getDotArray = (_fontFamily: string) => {
+    ctx.clearRect(0, 0, width, height)
+    ctx.font = `${size}px ${_fontFamily}, ${arial}`
+    ctx.fillText(a, width / 2, height / 2)
+    const imageData = ctx.getImageData(0, 0, width, height).data
+    return [].slice.call(imageData).filter(item => item !== 0)
+  }
+
+  return getDotArray(arial).join('') !== getDotArray(fontFamily).join('')
+}
+
+// 获取图片的原始宽高
+export const getImageSize = (imgUrl: string) => {
+  return new Promise((resolve, reject) => {
+    const img = document.createElement('img')
+    img.src = imgUrl
+    img.style.opacity = '0'
+    document.body.appendChild(img)
+
+    img.onload = () => {
+      const imgWidth = img.clientWidth
+      const imgHeight = img.clientHeight
+    
+      img.onload = null
+      img.onerror = null
+
+      document.body.removeChild(img)
+
+      resolve({ imgWidth, imgHeight })
+    }
+
+    img.onerror = () => {
+      img.onload = null
+      img.onerror = null
+
+      reject('图片加载失败')
+    }
+  })
+}
+
+// 复制文本到剪贴板
+export const copyText = (text: string) => {
+  return new Promise((resolve, reject) => {
+    const fakeElement = document.createElement('button')
+    const clipboard = new Clipboard(fakeElement, {
+      text: () => text,
+      action: () => 'copy',
+      container: document.body,
+    })
+    clipboard.on('success', e => {
+      clipboard.destroy()
+      resolve(e)
+    })
+    clipboard.on('error', e => {
+      clipboard.destroy()
+      reject(e)
+    })
+    document.body.appendChild(fakeElement)
+    fakeElement.click()
+    document.body.removeChild(fakeElement)
+  })
+}
+
+// 加密函数
+export const encrypt = (msg: string) => {
+  return CryptoJS.AES.encrypt(msg, CRYPTO_KEY).toString()
+}
+
+// 解密函数
+export const decrypt = (ciphertext: string) => {
+  const bytes  = CryptoJS.AES.decrypt(ciphertext, CRYPTO_KEY)
+  return bytes.toString(CryptoJS.enc.Utf8)
+}
+
+// 获取DOM节点样式
+export const getStyle = (el: HTMLElement, style: string) => {
+  if(!el) return null
+  return window.getComputedStyle(el, null).getPropertyValue(style)
+}
+
+// 检查元素是否处在可视区域内
+export const checkElementInViewport = (el: HTMLElement) => {
+  const rect = el.getBoundingClientRect()
+  return (
+    rect.top >= 0 &&
+    rect.left >= 0 &&
+    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
+    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
+  )
+}

+ 0 - 5
src/views/About.vue

@@ -1,5 +0,0 @@
-<template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
-</template>

+ 13 - 0
src/views/Editor/Canvas/index.vue

@@ -0,0 +1,13 @@
+<template>
+  <div class="canvas">
+    
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+
+export default defineComponent({
+  name: 'canvas',
+})
+</script>

+ 21 - 0
src/views/Editor/CanvasTool/index.vue

@@ -0,0 +1,21 @@
+<template>
+  <div class="canvas-tool">
+    
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+
+export default defineComponent({
+  name: 'canvas-tool',
+})
+</script>
+
+<style lang="scss" scoped>
+.canvas-tool {
+  height: 40px;
+  border-bottom: 1px solid #eee;
+  background-color: #fff;
+}
+</style>

+ 22 - 0
src/views/Editor/EditorHeader/index.vue

@@ -0,0 +1,22 @@
+<template>
+  <div class="editor-header">
+    
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+
+export default defineComponent({
+  name: 'editor-header',
+})
+</script>
+
+<style lang="scss" scoped>
+.editor-header {
+  background-color: #fff;
+  user-select: none;
+  overflow: hidden;
+  border-bottom: 1px solid #eee;
+}
+</style>

+ 22 - 0
src/views/Editor/Thumbnails/index.vue

@@ -0,0 +1,22 @@
+<template>
+  <div class="thumbnails">
+    
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+
+export default defineComponent({
+  name: 'thumbnails',
+})
+</script>
+
+<style lang="scss" scoped>
+.thumbnails {
+  border-right: solid 1px #eee;
+  background-color: #fff;
+  overflow: auto;
+  padding: 5px 0;
+}
+</style>

+ 22 - 0
src/views/Editor/Toolbar/index.vue

@@ -0,0 +1,22 @@
+<template>
+  <div class="toolbar">
+    
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+
+export default defineComponent({
+  name: 'toolbar',
+})
+</script>
+
+<style lang="scss" scoped>
+.toolbar {
+  border-left: solid 1px #eee;
+  background-color: #fff;
+  overflow: auto;
+  padding: 5px 0;
+}
+</style>

+ 60 - 0
src/views/Editor/index.vue

@@ -0,0 +1,60 @@
+<template>
+  <div class="editor">
+    <EditorHeader class="layout-header" />
+    <div class="layout-content">
+      <Thumbnails class="layout-content-left" />
+      <div class="layout-content-body">
+        <CanvasTool />
+        <Canvas />
+      </div>
+      <Toolbar class="layout-content-right" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+
+import EditorHeader from './EditorHeader/index.vue'
+import Canvas from './Canvas/index.vue'
+import CanvasTool from './CanvasTool/index.vue'
+import Thumbnails from './Thumbnails/index.vue'
+import Toolbar from './Toolbar/index.vue'
+
+export default defineComponent({
+  name: 'editor',
+  components: {
+    EditorHeader,
+    Canvas,
+    CanvasTool,
+    Thumbnails,
+    Toolbar,
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.editor {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+.layout-header {
+  height: 40px;
+}
+.layout-content {
+  flex: 1;
+  display: flex;
+}
+.layout-content-left {
+  width: 180px;
+  height: 100%;
+}
+.layout-content-body {
+  flex: 1;
+}
+.layout-content-right {
+  width: 260px;
+  height: 100%;
+}
+</style>

+ 0 - 18
src/views/Home.vue

@@ -1,18 +0,0 @@
-<template>
-  <div class="home">
-    <img alt="Vue logo" src="../assets/logo.png">
-    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
-  </div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
-
-export default defineComponent({
-  name: 'Home',
-  components: {
-    HelloWorld,
-  },
-});
-</script>

+ 13 - 0
src/views/Player/index.vue

@@ -0,0 +1,13 @@
+<template>
+  <div class="player">
+    player
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+
+export default defineComponent({
+  name: 'player',
+})
+</script>

+ 0 - 12
tests/unit/example.spec.ts

@@ -1,12 +0,0 @@
-import { shallowMount } from '@vue/test-utils'
-import HelloWorld from '@/components/HelloWorld.vue'
-
-describe('HelloWorld.vue', () => {
-  it('renders props.msg when passed', () => {
-    const msg = 'new message'
-    const wrapper = shallowMount(HelloWorld, {
-      props: { msg }
-    })
-    expect(wrapper.text()).toMatch(msg)
-  })
-})

+ 1 - 0
tsconfig.json

@@ -9,6 +9,7 @@
     "skipLibCheck": true,
     "esModuleInterop": true,
     "allowSyntheticDefaultImports": true,
+    "suppressImplicitAnyIndexErrors":true,
     "sourceMap": true,
     "baseUrl": ".",
     "types": [

+ 40 - 0
vue.config.js

@@ -0,0 +1,40 @@
+const StyleLintPlugin = require('stylelint-webpack-plugin')
+
+module.exports = {
+  css: {
+    loaderOptions: {
+      sass: {
+        prependData: `
+          @import "~@/assets/styles/variable.scss";
+          @import "~@/assets/styles/mixin.scss";
+        `,
+      },
+      less: {
+        lessOptions: {
+          modifyVars: {
+            'primary-color': '#41464b',
+            'link-color': '#41464b',
+          },
+          javascriptEnabled: true,
+        },
+      },
+    },
+  },
+  chainWebpack: config => {
+    config.module
+      .rule('images')
+      .use('url-loader')
+      .loader('url-loader')
+      .tap(options => Object.assign(options, { limit: 10240 }))
+  },
+  configureWebpack: {
+    plugins: [
+      new StyleLintPlugin({
+        files: ['src/**/*.{vue,html,css,scss,sass,less}'],
+        failOnError: false,
+        cache: false,
+        fix: false,
+      })
+    ],
+  },
+}