├── .gitignore
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
├── .DS_Store
├── favicon.ico
├── index.html
└── static
│ ├── 12c3946fa8c445e8a4f6de664893ae22.png
│ ├── 3adc468aa17543e0902330f1a74c856a.92e9cd1b464643c7957426b8e2735ef8.jpg
│ ├── 5690278ce1b44cdaab95a90684ec8803.png
│ ├── 6acfc5f2a8f04981a8b785fde5b2a87c.png
│ ├── 75abc05c659f4d5aa047b1c4e9bdd81a.png
│ ├── 9206b8e038074cc9b6ff54330348cc01.png
│ ├── c365ab6dbe274c1587df1ce13b1bbe29.png
│ ├── f769bc0e1115441f99dc3468fe4e1db7.png
│ └── new-pic.png
├── scripts
├── common.js
├── generateComponent
│ ├── index.js
│ └── template.js
├── generateStore
│ ├── index.js
│ └── template.js
└── generateView
│ ├── index.js
│ └── template.js
├── src
├── App.vue
├── api
│ ├── API.js
│ └── index.js
├── assets
│ ├── .DS_Store
│ ├── images
│ │ ├── lock-icon.svg
│ │ ├── lock-open-icon.svg
│ │ └── rotate-icon.svg
│ ├── logo.png
│ └── styles
│ │ ├── base.css
│ │ └── common.css
├── components
│ ├── .DS_Store
│ ├── MicroEdit
│ │ ├── AddMaterials.vue
│ │ ├── BannerEdit.vue
│ │ ├── BottomOperate.vue
│ │ ├── MicroLayer.vue
│ │ ├── ParametersEdit.vue
│ │ ├── TopOperate.vue
│ │ ├── bannerEditCommon.js
│ │ ├── fabricOperate.js
│ │ ├── guideLines.js
│ │ └── index.js
│ └── index.js
├── main.js
├── router
│ └── index.js
├── store
│ ├── index.js
│ └── mutationTypes.js
├── utils
│ └── index.js
└── views
│ └── Home
│ ├── Home.vue
│ └── index.js
└── vue.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | README.md
2 | src/.DS_Store
3 | /node_modules
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # onLinePS
2 | js基于canvas实现的一个类似ps的工具
3 | 本系统是基于fabric.js实现的canvas版图片,文本编辑器,支持对图片的放大,缩小,旋转,镜面翻转,拖动,显示/隐藏图层,删除图层,替换图层等操作,对文本支持修改文本内容,颜色,字体,加粗,斜体,下划线,背景色等,同时支持图片已有的操作,拖动图层有辅助线功能,可对画布做放大缩小功能,多操作可撤销/回退功能,可直接导出图片,ps基本操作都已支持。
4 |
5 | 这个是项目中做的功能,由于过程比较坎坷,fabric.js库很强大,但是中文资料很少,导致解决问题的时候花费了很多功夫,所以摘出来供需要的人使用,由于有些东西需要后端配合,所以把一部分功能砍掉了,所有代码中有一些冗余代码,大家有需要那些功能的也可以宅后台配合把这些功能也做起来,前端的逻辑都已经实现了.
6 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ],
5 | "env":{
6 | "development": {
7 | "plugins": ["dynamic-import-node"]
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mydemo1",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint",
9 | "new:view": "node ./scripts/generateView/index",
10 | "new:comp": "node ./scripts/generateComponent/index",
11 | "new:store": "node ./scripts/generateStore/index"
12 | },
13 | "dependencies": {
14 | "axios": "^0.21.2",
15 | "core-js": "^2.6.5",
16 | "element-ui": "^2.6.1",
17 | "fabric": "^4.3.0",
18 | "fontfaceobserver": "^2.1.0",
19 | "glfx.js__temp": "^1.0.2",
20 | "vue": "^2.5.2",
21 | "vue-router": "^3.0.1",
22 | "vuex": "^3.0.1"
23 | },
24 | "devDependencies": {
25 | "@vue/cli-plugin-babel": "^3.12.1",
26 | "@vue/cli-plugin-eslint": "^3.5.0",
27 | "@vue/cli-service": "^5.0.8",
28 | "babel-eslint": "^10.0.1",
29 | "chalk": "^2.0.1",
30 | "compression-webpack-plugin": "^2.0.0",
31 | "css-loader": "^3.1.0",
32 | "eslint": "^5.16.0",
33 | "eslint-plugin-vue": "^5.0.0",
34 | "less": "^3.9.0",
35 | "less-loader": "^5.0.0",
36 | "style-loader": "^0.23.1",
37 | "uglifyjs-webpack-plugin": "^2.1.3",
38 | "vue-template-compiler": "^2.5.21"
39 | },
40 | "eslintConfig": {
41 | "root": true,
42 | "env": {
43 | "node": true
44 | },
45 | "extends": [
46 | "plugin:vue/essential",
47 | "eslint:recommended"
48 | ],
49 | "rules": {},
50 | "parserOptions": {
51 | "parser": "babel-eslint"
52 | }
53 | },
54 | "postcss": {
55 | "plugins": {
56 | "autoprefixer": {}
57 | }
58 | },
59 | "browserslist": [
60 | "> 1%",
61 | "last 2 versions"
62 | ]
63 | }
64 |
--------------------------------------------------------------------------------
/public/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/public/.DS_Store
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | onlineps
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/static/12c3946fa8c445e8a4f6de664893ae22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/public/static/12c3946fa8c445e8a4f6de664893ae22.png
--------------------------------------------------------------------------------
/public/static/3adc468aa17543e0902330f1a74c856a.92e9cd1b464643c7957426b8e2735ef8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/public/static/3adc468aa17543e0902330f1a74c856a.92e9cd1b464643c7957426b8e2735ef8.jpg
--------------------------------------------------------------------------------
/public/static/5690278ce1b44cdaab95a90684ec8803.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/public/static/5690278ce1b44cdaab95a90684ec8803.png
--------------------------------------------------------------------------------
/public/static/6acfc5f2a8f04981a8b785fde5b2a87c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/public/static/6acfc5f2a8f04981a8b785fde5b2a87c.png
--------------------------------------------------------------------------------
/public/static/75abc05c659f4d5aa047b1c4e9bdd81a.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/public/static/75abc05c659f4d5aa047b1c4e9bdd81a.png
--------------------------------------------------------------------------------
/public/static/9206b8e038074cc9b6ff54330348cc01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/public/static/9206b8e038074cc9b6ff54330348cc01.png
--------------------------------------------------------------------------------
/public/static/c365ab6dbe274c1587df1ce13b1bbe29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/public/static/c365ab6dbe274c1587df1ce13b1bbe29.png
--------------------------------------------------------------------------------
/public/static/f769bc0e1115441f99dc3468fe4e1db7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/public/static/f769bc0e1115441f99dc3468fe4e1db7.png
--------------------------------------------------------------------------------
/public/static/new-pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/public/static/new-pic.png
--------------------------------------------------------------------------------
/scripts/common.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk')
2 | const path = require('path')
3 | const fs = require('fs')
4 | const resolvePath = (...file) => path.resolve(__dirname, ...file)
5 | const log = message => console.log(chalk.green(`${message}`))
6 | const successLog = message => console.log(chalk.blue(`${message}`))
7 | const errorLog = error => console.log(chalk.red(`${error}`))
8 | const resolveComponentName = componentName => {
9 | componentName = componentName.toLowerCase();
10 | //截取单词,按大驼峰命名拼接
11 | let componentNameStr = componentName.split("|");
12 | if (componentNameStr.length > 1) {
13 | componentNameStr = componentNameStr.map((item) => {
14 | if (item.trim()) {
15 | return item.substring(0, 1).toUpperCase() + item.substring(1);
16 | }
17 | return '';
18 | });
19 | return componentNameStr.join('');
20 | } else {
21 | errorLog('格式错误!名称各单词之间用|隔开,单个单词如home|,多个单词如home|index')
22 | return '';
23 | }
24 | }
25 |
26 | // 递归创建目录
27 | const mkdirs=(directory, callback)=> {
28 | var exists = fs.existsSync(directory)
29 | if (exists) {
30 | callback()
31 | } else {
32 | mkdirs(path.dirname(directory), function () {
33 | fs.mkdirSync(directory)
34 | callback()
35 | })
36 | }
37 | }
38 |
39 | module.exports ={
40 | dotExistDirectoryCreate : (directory) => {
41 | return new Promise((resolve) => {
42 | mkdirs(directory, function () {
43 | resolve(true)
44 | })
45 | })
46 | },
47 | generateFile : (path, data) => {
48 | if (fs.existsSync(path)) {
49 | errorLog(`${path}文件已存在`)
50 | return
51 | }
52 | return new Promise((resolve, reject) => {
53 | fs.writeFile(path, data, 'utf8', err => {
54 | if (err) {
55 | errorLog(err.message)
56 | reject(err)
57 | } else {
58 | resolve(true)
59 | }
60 | })
61 | })
62 | },
63 | path: path,
64 | chalk: chalk,
65 | fs: fs,
66 | log: log,
67 | successLog: successLog,
68 | errorLog: errorLog,
69 | resolvePath: resolvePath,
70 | resolveComponentName: resolveComponentName
71 |
72 | }
--------------------------------------------------------------------------------
/scripts/generateComponent/index.js:
--------------------------------------------------------------------------------
1 | // index.js`
2 | //导入公用方法
3 | const {
4 | dotExistDirectoryCreate,
5 | generateFile,
6 | fs,
7 | log,
8 | resolvePath,
9 | successLog,
10 | errorLog,
11 | resolveComponentName
12 | } = require('../common.js');
13 | const {
14 | componentTemplate,
15 | entryTemplate,
16 | globalRegisterImportTemplate,
17 | globalRegisterExportTemplate
18 | } = require('./template')
19 |
20 | log('请输入要生成的组件名称、会生成在 components/目录下,如需生成全局组件,请加 g/ 前缀,名称各单词之间用|隔开,单个单词如home|,多个单词如home|index')
21 | let componentName = ''
22 | let isGlobal=false
23 | process.stdin.on('data', async chunk => {
24 | const inputName = String(chunk).trim().toString()
25 | if (inputName.includes('/')) {
26 | const inputArr = inputName.split('/')
27 | componentName = inputArr[inputArr.length - 1]
28 | if (inputArr[0]=='g')
29 | {
30 | isGlobal=true;
31 | }
32 | else{
33 | isGlobal = false;
34 | errorLog("设置全局参数前缀错误,无法全局注册");
35 | }
36 | } else {
37 | componentName = inputName
38 | }
39 | //判断名字是否包含|,不包含提示错误,重新输入,包含则按大驼峰拼接
40 | componentName = resolveComponentName(componentName);
41 | if (!componentName) {
42 | return;
43 | }
44 | /**
45 | * 组件目录路径
46 | */
47 | const componentDirectory = resolvePath('../src/components', componentName)
48 |
49 | /**
50 | * vue组件路径
51 | */
52 | const componentVueName = resolvePath(componentDirectory, `${componentName}.vue`)
53 | /**
54 | * 入口文件路径
55 | */
56 | const entryComponentName = resolvePath(componentDirectory, 'index.js')
57 |
58 | const hasComponentDirectory = fs.existsSync(componentDirectory)
59 | if (hasComponentDirectory) {
60 | errorLog(`${componentName}组件目录已存在,请重新输入`)
61 | return
62 | } else {
63 | log(`正在生成 component 目录 ${componentDirectory}`)
64 | await dotExistDirectoryCreate(componentDirectory)
65 | }
66 | try {
67 | log(`正在生成 vue 文件 ${componentVueName}`)
68 | await generateFile(componentVueName, componentTemplate(componentName))
69 | log(`正在生成 entry 文件 ${entryComponentName}`)
70 | await generateFile(entryComponentName, entryTemplate(componentName))
71 | if (isGlobal)
72 | {
73 | log(`正在注册全局组件文件 ${componentName}`)
74 | await addGlobalComponent(componentName);
75 | }
76 | successLog('组件生成成功')
77 | } catch (e) {
78 | errorLog(e.message)
79 | }
80 |
81 | process.stdin.emit('end')
82 | })
83 | process.stdin.on('end', () => {
84 | log('exit')
85 | process.exit()
86 | })
87 |
88 | //添加全局注册
89 | function addGlobalComponent(fileName) {
90 | const file = resolvePath('../src/components', 'index.js');
91 | return new Promise((resolve,reject)=>{
92 | if (fs.existsSync(file)) {
93 | fs.readFile(file, 'utf-8', (err, data) => {
94 | if (err) {
95 | errorLog(err.message);
96 | reject();
97 | } else {
98 | let dataStr = data.toString();
99 | let importStr, exportStr;
100 | if (!dataStr) {
101 | emptyComponentAdd(file, fileName).then(()=>{
102 | resolve();
103 | });
104 | return;
105 | }
106 | let dataList = dataStr.split("export");
107 | if (dataList && dataList.length == 2) {
108 | importStr = dataList[0];
109 | importStr += globalRegisterImportTemplate(fileName);
110 | exportStr = 'export' + dataList[1];
111 | exportStr = exportStr.replace(/}/, globalRegisterExportTemplate(fileName));
112 | let writeStr = importStr + '\n' + exportStr;
113 | writeDataToFile(file, writeStr).then(() => {
114 | resolve();
115 | });;
116 | } else {
117 | errorLog("components/index.js格式不规范,请调整为标准格式后重试");
118 | reject();
119 | }
120 | }
121 | });
122 | } else {
123 | emptyComponentAdd(file, fileName).then(()=>{
124 | resolve();
125 | });
126 | }
127 | })
128 |
129 |
130 | function emptyComponentAdd(file,fileName) {
131 | let importStr = globalRegisterImportTemplate(fileName);
132 | let exportStr = `export default (Vue) => {\n${globalRegisterExportTemplate(fileName)}`;
133 | let writeStr = importStr + '\n' + exportStr;
134 | return writeDataToFile(file, writeStr);
135 | }
136 |
137 | function writeDataToFile(file, data) {
138 | return new Promise((resolve,reject)=>{
139 | fs.writeFile(file, data, {
140 | 'flag': 'w'
141 | }, function (err) {
142 | if (err) {
143 | errorLog(err.message);
144 | reject();
145 | }
146 | successLog('全局注册成功!');
147 | resolve();
148 | });
149 | })
150 |
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/scripts/generateComponent/template.js:
--------------------------------------------------------------------------------
1 | // template.js
2 | module.exports = {
3 | componentTemplate: compoentName => {
4 | return `
5 |
6 |
7 |
8 |
13 | `
15 | },
16 | entryTemplate: compoentName=> `import ${compoentName} from './${compoentName}.vue'
17 | export default ${compoentName}`,
18 | globalRegisterImportTemplate: compoentName => `import ${compoentName} from './${compoentName}'`,
19 | globalRegisterExportTemplate: compoentName => `Vue.component(${compoentName}.name, ${compoentName})\n}`
20 | }
21 |
--------------------------------------------------------------------------------
/scripts/generateStore/index.js:
--------------------------------------------------------------------------------
1 | // index.js
2 | //导入公用方法
3 | const {
4 | dotExistDirectoryCreate,
5 | generateFile,
6 | fs,
7 | log,
8 | resolvePath,
9 | successLog,
10 | errorLog,
11 | resolveComponentName
12 | } = require('../common.js');
13 | // 导入模板
14 | const {
15 | storeTemplate,
16 | storeRegisterImportTemplate,
17 | storeRegisterExportTemplate
18 | } = require('./template')
19 |
20 | log('请输入要生成的store名称、会生成在 store/modules/目录下,名称各单词之间用|隔开,单个单词如home|,多个单词如home|index')
21 | let componentName = ''
22 | process.stdin.on('data', async chunk => {
23 | // store名称
24 | const inputName = String(chunk).trim().toString()
25 | // 获取要创建的store名称
26 | if (inputName.includes('/')) {
27 | const inputArr = inputName.split('/')
28 | componentName = inputArr[inputArr.length - 1]
29 | } else {
30 | componentName = inputName
31 | }
32 | //判断名字是否包含|,不包含提示错误,重新输入,包含则按大驼峰拼接
33 | componentName = resolveComponentName(componentName);
34 | if (!componentName) {
35 | return;
36 | }
37 | //store用小驼峰
38 | componentName = componentName.substring(0, 1).toLowerCase() + componentName.substring(1);
39 | // store跟页面路径
40 | const storePath = resolvePath('../src', "store")
41 | if (!fs.existsSync(storePath)) {
42 | log(`正在生成 store 文件夹`)
43 | await dotExistDirectoryCreate(storePath)
44 | }
45 | // store跟页面路径
46 | const componentPath = resolvePath(storePath, "modules")
47 | if (!fs.existsSync(componentPath)) {
48 | log(`正在生成 modules 文件夹`)
49 | await dotExistDirectoryCreate(componentPath)
50 | }
51 | // store文件
52 | const storeFile = resolvePath(componentPath, `${componentName}.js`)
53 |
54 | // 判断要创建的store文件是否存在
55 | const hasComponentExists = fs.existsSync(storeFile)
56 | if (hasComponentExists) {
57 | errorLog(`${inputName}文件已存在,请重新输入`)
58 | return
59 | }
60 | try {
61 | log(`正在生成 store 文件 ${storeFile}`)
62 | await generateFile(storeFile, storeTemplate(componentName))
63 | log(`正在注册store文件 ${storeFile}`)
64 | await addStore(componentName);
65 | successLog('store生成成功')
66 | } catch (e) {
67 | errorLog(e.message)
68 | }
69 |
70 | process.stdin.emit('end')
71 | })
72 | process.stdin.on('end', () => {
73 | log('exit')
74 | process.exit()
75 | })
76 |
77 | function addStore(fileName) {
78 | const file = resolvePath('../src/store', 'index.js');
79 | let storeListItem;
80 | return new Promise((resolve,reject)=>{
81 | if (fs.existsSync(file)) {
82 | fs.readFile(file, 'utf-8', (err, data) => {
83 | if (err) {
84 | errorLog(err.message);
85 | reject(err.message);
86 | } else {
87 | let dataStr = data.toString();
88 | if (!dataStr) {
89 | emptyIndexAdd(file, fileName).then(() => {
90 | resolve();
91 | });;
92 | } else {
93 | addStoreResgiter(dataStr, fileName).then(() => {
94 | resolve();
95 | });;
96 | }
97 | }
98 | });
99 | } else {
100 | emptyIndexAdd(file, fileName).then(()=>{
101 | resolve();
102 | });
103 | }
104 | })
105 |
106 | function addStoreResgiter(dataStr, fileName) {
107 | return new Promise((resolve,reject)=>{
108 | //1.根据Vue.use(Vuex)分割,0是import注册部分,1是引入部分
109 | let storeRegisterList = dataStr.split('Vue.use(Vuex)');
110 | if (storeRegisterList && storeRegisterList.length==2)
111 | {
112 | let storeRegisterImport = storeRegisterList[0];
113 | storeRegisterImport +=storeRegisterImportTemplate(fileName);
114 | let storeRegisterUse = storeRegisterList[1];
115 | //匹配modules:{},往modules对象中加内容
116 | let reg = /(modules\s*:\s*\{)((.|\s)*?)\}/;
117 | let storeList = storeRegisterUse.match(reg);
118 | if (storeList && storeList.input) {
119 | storeListItem = storeList[1] + storeList[2] + (storeList[2].trim()?",":"") + storeRegisterExportTemplate(fileName) + "\n}";
120 | dataStr = storeRegisterUse.replace(reg, storeListItem);
121 | dataStr = storeRegisterImport + "\nVue.use(Vuex)" + dataStr;
122 | console.log(dataStr);
123 | writeDataToFile(file, dataStr).then(()=>{
124 | resolve();
125 | });
126 | } else {
127 | errorLog("store/index.js页面结构不正确,本次注册store失败!");
128 | reject();
129 | }
130 | }else{
131 | errorLog("store/index.js页面结构不正确,本次注册store失败!");
132 | reject();
133 | }
134 | })
135 |
136 | }
137 |
138 | function emptyIndexAdd(file, fileName) {
139 | let importStr = `import Vue from 'vue'
140 | import Vuex from 'vuex'
141 | import types from './mutationTypes'
142 | import ${fileName} from './modules/${fileName}'
143 | Vue.use(Vuex)
144 | export default new Vuex.Store({
145 | types,
146 | modules: {
147 | ${fileName}
148 | }
149 | })`;
150 | return writeDataToFile(file, importStr);
151 | }
152 |
153 | function writeDataToFile(file, data) {
154 | return new Promise((resolve,reject)=>{
155 | fs.writeFile(file, data, function (err) {
156 | console.log(err);
157 | if (err) {
158 | errorLog(err.message);
159 | reject(err.message);
160 | }
161 | successLog('store注册成功!');
162 | resolve();
163 | });
164 | });
165 |
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/scripts/generateStore/template.js:
--------------------------------------------------------------------------------
1 | // template.js
2 | module.exports = {
3 | storeTemplate: compoenntName => {
4 | return `const ${compoenntName} = {
5 | state: {
6 | },
7 | getters: {
8 | },
9 | actions: {
10 | },
11 | mutations: {
12 | }
13 | }
14 | export default {...${compoenntName}}`
15 | },
16 | storeRegisterImportTemplate: compoenntName => {
17 | return `import ${compoenntName} from './modules/${compoenntName}'`;
18 | },
19 | storeRegisterExportTemplate: compoenntName => {
20 | return `${compoenntName}`;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/scripts/generateView/index.js:
--------------------------------------------------------------------------------
1 | // index.js
2 | //导入公用方法
3 | const {
4 | dotExistDirectoryCreate,
5 | generateFile,
6 | fs,
7 | log,
8 | resolvePath,
9 | successLog,
10 | errorLog,
11 | resolveComponentName
12 | } = require('../common.js');
13 | // 导入模板
14 | const {
15 | viewTemplate,
16 | entryTemplate,
17 | viewRouterTemplate
18 | } = require('./template');
19 |
20 |
21 | log('请输入要生成的页面名称、会生成在 views/目录下,页面名称各单词之间用|隔开,单个单词如home|,多个单词如home|index')
22 | let componentName = ''
23 | process.stdin.on('data', async chunk => {
24 | // 组件名称
25 | const inputName = String(chunk).trim().toString()
26 | // 获取组件名
27 | if (inputName.includes('/')) {
28 | const inputArr = inputName.split('/')
29 | componentName = inputArr[inputArr.length - 1]
30 | } else {
31 | componentName = inputName
32 | }
33 | //判断名字是否包含|,不包含提示错误,重新输入,包含则按大驼峰拼接
34 | componentName = resolveComponentName(componentName);
35 | if (!componentName)
36 | {
37 | return;
38 | }
39 | // Vue页面组件路径
40 | const componentPath = resolvePath('../src/views', componentName)
41 | // vue文件
42 | const vueFile = resolvePath(componentPath, `${componentName}.vue`)
43 | // 入口文件
44 | const entryFile = resolvePath(componentPath, 'index.js')
45 | // 判断组件文件夹是否存在
46 | const hasComponentExists = fs.existsSync(componentPath)
47 | if (hasComponentExists) {
48 | errorLog(`${inputName}页面组件已存在,请重新输入`)
49 | return
50 | } else {
51 | log(`正在生成 view 目录 ${componentPath}`)
52 | await dotExistDirectoryCreate(componentPath)
53 | }
54 | try {
55 | log(`正在生成 vue 文件 ${vueFile}`)
56 | await generateFile(vueFile, viewTemplate(componentName))
57 | log(`正在生成 entry 文件 ${entryFile}`)
58 | await generateFile(entryFile, entryTemplate(componentName))
59 | log(`正在注册路由文件 ${componentName}`)
60 | await addRouter(componentName);
61 | successLog('页面生成成功')
62 | } catch (e) {
63 | errorLog(e.message)
64 | }
65 |
66 | process.stdin.emit('end')
67 | })
68 | process.stdin.on('end', () => {
69 | log('exit')
70 | process.exit()
71 | })
72 |
73 | function addRouter(fileName) {
74 | const file = resolvePath('../src/router', 'index.js');
75 | let reg = /(routes\s*:\s*\[)((.|\s)*?)\]/;
76 | let routerListItem;
77 | return new Promise((resolve, reject) => {
78 | if (fs.existsSync(file)) {
79 | fs.readFile(file, 'utf-8', (err, data) => {
80 | if (err) {
81 | errorLog(err.message);
82 | reject();
83 | } else {
84 | let dataStr = data.toString();
85 | if (!dataStr) {
86 | emptyIndexAdd(file, fileName).then(()=>{
87 | resolve();
88 | });
89 | } else {
90 | let routerList = dataStr.match(reg);
91 | if (routerList && routerList.input) {
92 | routerListItem = routerList[1] + routerList[2] + (routerList[2].trim() ?
93 | "," : '') + viewRouterTemplate(fileName) + "]";
94 | dataStr = dataStr.replace(reg, routerListItem);
95 | writeDataToFile(file, dataStr).then(()=>{
96 | resolve();
97 | });
98 |
99 | } else {
100 | errorLog("router/index.js页面结构不正确,本次注册路由失败!");
101 | reject();
102 | }
103 | }
104 | }
105 |
106 | })
107 | } else {
108 | emptyIndexAdd(file, fileName).then(()=>{
109 | resolve();
110 | });
111 | }
112 | });
113 |
114 | function emptyIndexAdd(file, fileName) {
115 |
116 | let importStr = `
117 | import Vue from 'vue'
118 | import Router from 'vue-router'
119 | Vue.use(Router)
120 | let router = new Router({
121 | mode: 'hash',
122 | routes: [
123 | {
124 | path: '/${fileName}',
125 | name: ${fileName},
126 | component: () => import("@/views/${fileName}/${fileName}")
127 | }
128 | ]
129 | })
130 | router.beforeEach((to, from, next) => {
131 | next();
132 | })
133 | export default router
134 | `
135 | return writeDataToFile(file, importStr);
136 | }
137 |
138 | function writeDataToFile(file, data) {
139 | return new Promise((resolve,reject)=>{
140 | fs.writeFile(file, data, {
141 | 'flag': 'w'
142 | }, function (err) {
143 | if (err) {
144 | errorLog(err.message);
145 | reject(err.message);
146 | }
147 | successLog('路由注册成功!');
148 | resolve();
149 | });
150 | })
151 |
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/scripts/generateView/template.js:
--------------------------------------------------------------------------------
1 | // template.js
2 | module.exports = {
3 | viewTemplate: compoenntName => {
4 | return `
5 |
6 |
7 |
8 |
13 | `
15 | },
16 | entryTemplate: compoenntName => {
17 | return `import ${compoenntName} from './${compoenntName}.vue'
18 | export {
19 | ${compoenntName}
20 | }`
21 | },
22 | viewRouterTemplate: compoenntName => `{
23 | path: '/${compoenntName.toLocaleLowerCase?compoenntName.toLocaleLowerCase():compoenntName}',
24 | name: '${compoenntName.toLocaleLowerCase?compoenntName.toLocaleLowerCase():compoenntName}',
25 | component: () => import("@/views/${compoenntName}/${compoenntName}")
26 | }\n`
27 | }
28 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
26 |
--------------------------------------------------------------------------------
/src/api/API.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import {Message} from 'element-ui'
3 | let errCode = ['400', '500', '502', '503', '504'];
4 | axios.defaults.baseURL = '';
5 | axios.defaults.timeout = 10000
6 | axios.defaults.headers = {
7 | }
8 |
9 | // 请求拦截器
10 | axios.interceptors.request.use(config => {
11 |
12 | // config.responseType='blob';
13 | return config;
14 | }, err => {
15 | return Promise.reject(err);
16 | });
17 |
18 |
19 | // 响应拦截器
20 | axios.interceptors.response.use(res=>{
21 | return res;
22 | },err=>{
23 | let errStatus=err.response.status.toString();
24 | let errCodeStr=errCode.join(',');
25 | if (errCodeStr.indexOf(errStatus)>=0) {
26 | Message({
27 | messgae: '亲,当前访问服务的人太多,请稍后重试',
28 | type: 'warning'
29 | });
30 | }
31 | else if (errStatus == '403')
32 | {
33 | Message({
34 | messgae: '亲,您当前无权访问页面,请稍后重试',
35 | type: 'warning'
36 | });
37 | }
38 | else if (errStatus == '404') {
39 | Message({
40 | messgae: '亲,您的请求已经丢失,您可以逛逛别的页面',
41 | type: 'warning'
42 | });
43 | }
44 | return Promise.reject(err);
45 | });
46 |
47 |
48 | class API {
49 | constructor(url) {
50 | this.url = url
51 | }
52 |
53 | // 创建axios配置项
54 | static _createAxiosConfig({url, action, method = 'get', data, timeout, baseURL,responseType}) {
55 | var config = {
56 | method: method,
57 | url: url + action
58 | }
59 | if (baseURL) {
60 | config.baseURL = baseURL;
61 | }
62 | if (['post', 'put', 'patch'].includes(config.method)) {
63 | config.data = data
64 | } else {
65 | config.params = data
66 | }
67 | if (timeout!=undefined) {
68 | config.timeout = timeout
69 | }
70 | if(responseType)
71 | {
72 | config.responseType=responseType;
73 | }
74 | return config
75 | }
76 |
77 | // 请求url, data, method = 'get'
78 | static request(params, isAuth = true) {
79 | if (this instanceof API && !params.url) {
80 | params.url = this.url;
81 | } else {
82 | params.url = ''
83 | }
84 | return isAuth ? API._probeAuthStatusAndRequest(params) : axios(API._createAxiosConfig(params))
85 | }
86 |
87 |
88 |
89 |
90 | //此处可以加鉴权处理,或者在每次请求之前加的处理逻辑
91 | static _probeAuthStatusAndRequest(params) {
92 | var promise = new Promise((resolve, reject) => {
93 | //这里可以添加权限验证,登录验证等
94 | axios(API._createAxiosConfig(params)).then(res => {
95 | return resolve(res)
96 | }).catch(err => {
97 | return reject(err)
98 | })
99 | })
100 | return promise
101 | }
102 | }
103 | export default API
104 |
--------------------------------------------------------------------------------
/src/api/index.js:
--------------------------------------------------------------------------------
1 | import API from './API'
--------------------------------------------------------------------------------
/src/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/src/assets/.DS_Store
--------------------------------------------------------------------------------
/src/assets/images/lock-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/lock-open-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/rotate-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/styles/base.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | div,
4 | span,
5 | applet,
6 | object,
7 | iframe,
8 | h1,
9 | h2,
10 | h3,
11 | h4,
12 | h5,
13 | h6,
14 | p,
15 | blockquote,
16 | pre,
17 | a,
18 | abbr,
19 | acronym,
20 | address,
21 | big,
22 | cite,
23 | code,
24 | del,
25 | dfn,
26 | em,
27 | img,
28 | ins,
29 | kbd,
30 | q,
31 | s,
32 | samp,
33 | small,
34 | strike,
35 | strong,
36 | sub,
37 | sup,
38 | tt,
39 | var,
40 | b,
41 | u,
42 | i,
43 | center,
44 | dl,
45 | dt,
46 | dd,
47 | ol,
48 | ul,
49 | li,
50 | fieldset,
51 | form,
52 | label,
53 | legend,
54 | table,
55 | caption,
56 | tbody,
57 | tfoot,
58 | thead,
59 | tr,
60 | th,
61 | td,
62 | article,
63 | aside,
64 | canvas,
65 | details,
66 | embed,
67 | figure,
68 | figcaption,
69 | footer,
70 | header,
71 | menu,
72 | nav,
73 | output,
74 | ruby,
75 | section,
76 | summary,
77 | time,
78 | mark,
79 | audio,
80 | video,
81 | input {
82 | margin: 0;
83 | padding: 0;
84 | border: 0;
85 | font-size: 100%;
86 | font-weight: normal;
87 | vertical-align: baseline;
88 | box-sizing: border-box;
89 | }
90 |
91 | /* HTML5 display-role reset for older browsers */
92 | article,
93 | aside,
94 | details,
95 | figcaption,
96 | figure,
97 | footer,
98 | header,
99 | menu,
100 | nav,
101 | section {
102 | display: block;
103 | }
104 |
105 | body {
106 | line-height: 1;
107 | }
108 |
109 | blockquote,
110 | q {
111 | quotes: none;
112 | }
113 |
114 | blockquote:before,
115 | blockquote:after,
116 | q:before,
117 | q:after {
118 | content: none;
119 | }
120 |
121 | table {
122 | border-collapse: collapse;
123 | border-spacing: 0;
124 | }
125 |
126 | /* custom */
127 | a {
128 | color: #7e8c8d;
129 | text-decoration: none;
130 | -webkit-backface-visibility: hidden;
131 | }
132 |
133 | li {
134 | list-style: none;
135 | }
136 |
137 | ::-webkit-scrollbar {
138 | width: 5px;
139 | height: 5px;
140 | }
141 |
142 | ::-webkit-scrollbar-track-piece {
143 | background-color: rgba(0, 0, 0, 0.2);
144 | -webkit-border-radius: 6px;
145 | }
146 |
147 | ::-webkit-scrollbar-thumb:vertical {
148 | height: 5px;
149 | background-color: rgba(125, 125, 125, 0.7);
150 | -webkit-border-radius: 6px;
151 | }
152 |
153 | ::-webkit-scrollbar-thumb:horizontal {
154 | width: 5px;
155 | background-color: rgba(125, 125, 125, 0.7);
156 | -webkit-border-radius: 6px;
157 | }
158 |
159 | html,
160 | body {
161 | width: 100%;
162 | }
163 |
164 | body {
165 | -webkit-text-size-adjust: none;
166 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
167 | }
--------------------------------------------------------------------------------
/src/assets/styles/common.css:
--------------------------------------------------------------------------------
1 | .normal_btn,.focus_btn{
2 | cursor: pointer;
3 | box-shadow: 0 6px 10px 0 rgba(66,0,193,0.24);
4 | border:1px solid;
5 | color: #1817E6;
6 | border-color:#5A59FF #4D1EA9;
7 | }
8 | .normal_btn:hover{
9 | opacity: 0.92;
10 | background: #EEEDFF;
11 | box-shadow: 0 4px 7px -2px rgba(66,0,193,0.24);
12 | }
13 | .focus_btn{
14 | background-image: linear-gradient(-40deg,#5A59FF 0%, #6128CF 100%);
15 | color: #fff!important;
16 | }
17 | .focus_btn:hover{
18 | opacity: 0.8;
19 | box-shadow: 0 6px 10px 0 rgba(66,0,193,0.10);
20 | }
21 | .cl_both:before,
22 | .cl_both:after {
23 | content: " ";
24 | display: table;
25 | }
26 | .cl_both:after {
27 | clear: both;
28 | }
29 | .c_flex{
30 | display: flex;
31 | display: -webkit-flex;
32 | }
33 | .cl_l {
34 | float: left;
35 | }
36 |
37 | .cl_r {
38 | float: right;
39 | }
--------------------------------------------------------------------------------
/src/components/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/src/components/.DS_Store
--------------------------------------------------------------------------------
/src/components/MicroEdit/AddMaterials.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{item.text}}
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
53 |
57 |
58 |
59 |
60 |
61 |
425 |
514 |
515 |
516 |
--------------------------------------------------------------------------------
/src/components/MicroEdit/BannerEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
19 |
20 |
21 |
22 |
23 |
24 |
34 |
35 |
36 |
37 |
38 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
60 |
61 |
324 |
429 |
430 |
441 |
--------------------------------------------------------------------------------
/src/components/MicroEdit/BottomOperate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
保存
6 |
下载
7 |
投放
8 |
9 |
10 |
60 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/src/components/MicroEdit/MicroLayer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
16 |
17 |
{{item.text}}
18 |
19 |
20 |
21 |
22 |
23 |
233 |
313 |
314 |
315 |
--------------------------------------------------------------------------------
/src/components/MicroEdit/ParametersEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
画布尺寸{{matherSetSize.width+'*'+matherSetSize.height}}
4 |
5 |
图层名称{{selectObj.title}}
6 |
7 |
8 |
9 |
10 |
11 |
12 | X
13 |
14 |
15 |
16 |
17 |
18 |
19 | Y
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 宽
33 |
34 |
35 |
36 |
37 |
38 | 高
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 角度
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | B
82 | I
83 | U
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
237 |
406 |
407 |
408 |
--------------------------------------------------------------------------------
/src/components/MicroEdit/TopOperate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
{{zoom}}%
16 |
17 |
18 |
19 |
20 |
21 | 一键还原
22 |
23 |
24 |
25 |
27 |
28 |
29 |
89 |
280 |
281 |
--------------------------------------------------------------------------------
/src/components/MicroEdit/bannerEditCommon.js:
--------------------------------------------------------------------------------
1 | import 'glfx.js__temp'
2 | //canvas操作相关
3 | let fabricCommon = function (fabricObject,that) {
4 | //tojson包含自定义的属性内容
5 | let toJsonWithParams = [
6 | "realLeft",
7 | "realTop",
8 | "realType",
9 | "realWidth",
10 | "realHeight",
11 | "order",
12 | "oldSrc",
13 | "oldId",
14 | "oldPictureName",
15 | "oldPictureNum",
16 | "oldPictType",
17 | "oldUserId",
18 | "lockUniScaling",
19 | "PictureName",
20 | "PictureNum",
21 | "id",
22 | "pictType",
23 | "userId",
24 | "aCoords",
25 | "oldWidth",//图片本身真实宽度
26 | "oldHeight", //图片本身真实高度
27 | "title",
28 | "oldRealUrl",
29 | "realUrl",
30 | "bboxWidth",//图片要显示的宽度
31 | "bboxHeight", //图片要显示的高度
32 | "realFontFamily",//用于存储选中的字体,在字体文件加载完成之后替换到fontFamiy
33 | 'lockScalingY',
34 | 'lockScalingX',
35 | 'lockMovementY',
36 | 'lockMovementX',
37 | 'layerIsEdit',
38 | 'isGif'
39 | ];
40 | let _fabricInstance = fabricObject;
41 | _fabricInstance.setToJsonWithParams(toJsonWithParams);
42 | let _canvasObject = _fabricInstance.fabricInstance;
43 |
44 | //记录偏移量x,y的值
45 | let _offsetObj = {offsetX:0,offsetY:0};
46 |
47 | //文本类型
48 | let txtList = ["3-1", "3-2", "3-3"];
49 | //数据渲染
50 | let addObject = function (bannerMergeDataList) {
51 | //判断宽度是否大于1000或者高度是否大于600
52 | let objs = bannerMergeDataList.filter(e => {
53 | return e.type == "1-1";
54 | });
55 | if (objs && objs.length > 0) {
56 |
57 | // if (objs[0].bboxWidth > 1000 || objs[0].bboxHeight > 600) {
58 | // // setZoom(0.5);
59 | // if (that.initZoom)
60 | // {
61 | // that.initZoom(0.5);
62 | // }
63 | // _offsetObj.offsetX = (1000 - objs[0].bboxWidth) / 2;
64 | // _offsetObj.offsetY = (600 - objs[0].bboxHeight) / 2;
65 | // } else {
66 | // _offsetObj.offsetX = Math.abs((1000 - objs[0].bboxWidth) / 2);
67 | // _offsetObj.offsetY = Math.abs((600 - objs[0].bboxHeight) / 2);
68 | // }
69 | }
70 | console.log(_offsetObj);
71 | let cloneJson = JSON.parse(JSON.stringify(bannerMergeDataList));
72 | loadDataToCanvas(cloneJson);
73 | _canvasObject.renderAll();
74 | };
75 |
76 | //创建图片,dataList不传则为新增
77 | let createImage = function (obj, dataList) {
78 | let order=(_canvasObject.getObjects()&&_canvasObject.getObjects().length)||0
79 | let item = obj;
80 | let url = '/static/'+item.layerPictureUrl;
81 | let top, left;
82 | if (item.type!="1-1")
83 | {
84 | left = parseInt(item.bboxX1) + parseInt(item.bboxWidth) / 2 + _offsetObj.offsetX;
85 | top = parseInt(item.bboxY1) + parseInt(item.bboxHeight) / 2 + _offsetObj.offsetY;
86 | }
87 | else{
88 | left = parseInt(item.bboxWidth) / 2 + _offsetObj.offsetX;
89 | top = parseInt(item.bboxHeight) / 2 + _offsetObj.offsetY;
90 | }
91 | let imgConfig = {
92 | left: left,
93 | top: top,
94 | order: order,
95 | id: item.id,
96 | PictureName: item.PictureName,
97 | PictureNum: item.motherSetNumRef,
98 | userId: item.userId,
99 | pictType: item.pictType,
100 | angle: 0,
101 | realLeft: parseInt(item.bboxX1),
102 | realTop: parseInt(item.bboxY1),
103 | realType: item.type,
104 | title: item.title,
105 | realUrl: item.layerPictureUrl,
106 | bboxWidth: item.bboxWidth,
107 | bboxHeight: item.bboxHeight,
108 | lockScalingY:false,
109 | lockScalingX:false,
110 | lockMovementY:false,
111 | lockMovementX:false,
112 | layerIsEdit:item.layerIsEdit,
113 | isGif:item.isGif
114 | }
115 | _fabricInstance.createImage(url, imgConfig, !item.bboxWidth, item.type!='1-1').then(img=>{
116 | if(!item.bboxWidth)
117 | {
118 | setActiveSelect(order);
119 | canvasObjectSelected();
120 | }
121 | if(dataList)
122 | {
123 | loadDataCallback(dataList);
124 | }
125 | });
126 |
127 | }
128 |
129 | //创建文本,dataList不传则为新增/或者替换
130 | let createTxt = function (item, dataList) {
131 | let order=(_canvasObject.getObjects()&&_canvasObject.getObjects().length)||0;
132 | if(that.getFont)
133 | {
134 | let result = that.getFont(item.fontType);
135 | if(!result)
136 | {
137 | item.fontType='FZZCHJW--GB1-0';
138 | }
139 | }
140 | let scale=(!item.fontSize||item.fontSize>=12)?1:item.fontSize/12
141 | let txtConfig = {
142 | left: parseInt(item.bboxX1) + parseInt(item.bboxWidth) / 2 + _offsetObj.offsetX, //+parseInt(item.bboxWidth)/2
143 | top: parseInt(item.bboxY1) + parseInt(item.bboxHeight) / 2 + _offsetObj.offsetY, //+parseInt(item.bboxWidth)/2
144 | order: order,
145 | fill: item.color, //content.color
146 | fontSize: item.fontSize ? item.fontSize:20,
147 | angle: 0,
148 | fontFamily: "", //content.fontFamily
149 | realFontFamily: item.fontType,
150 | fontWeight: item.fontBold=="1" ? "bold" : "normal",
151 | fontStyle: item.fontItalic == "0" ? "normal" : "italic",
152 | userId: item.userId,
153 | realLeft: parseInt(item.bboxX1),
154 | realTop: parseInt(item.bboxY1),
155 | realType: item.type,
156 | title: item.title,
157 | realWidth: parseInt(item.bboxWidth),
158 | realHeight: parseInt(item.bboxHeight),
159 | // lockScalingY: true,
160 | // lockScalingX: true,
161 | lockMovementY: false,
162 | lockMovementX: false,
163 | width: parseInt(item.bboxWidth),
164 | height: parseInt(item.bboxHeight),
165 | scaleX:scale,
166 | scaleY:scale,
167 | layerIsEdit:item.layerIsEdit,
168 | isGif:item.isGif
169 | }
170 | let txtInstabce= _fabricInstance.createItext(item.text, txtConfig, !item.bboxWidth);
171 | if (item.fontType) {
172 | _fabricInstance.loadFont(txtInstabce,item.fontType);
173 | }
174 | txtInstabce.on("editing:exited", function () {
175 | let obj = _canvasObject.getActiveObject();
176 | checkTxt(obj);
177 | })
178 | if(!item.bboxWidth)
179 | {
180 | setActiveSelect(order);
181 | canvasObjectSelected();
182 | }
183 | if(dataList)
184 | {
185 | loadDataCallback(dataList);
186 | }
187 | }
188 |
189 | //检查编辑的时候文本内容长度是否符合要求
190 | let checkTxt=function(obj){
191 | if (obj && obj.text && obj.realType && that.copyRightLengthCheck)
192 | {
193 | that.copyRightLengthCheck({txt:obj.text,type: obj.realType});
194 | }
195 |
196 | }
197 |
198 | //加载数据到画布的回调函数,适用于loadDataToCanvas方法
199 | let loadDataCallback = function (list) {
200 | list.shift();
201 | loadDataToCanvas(list);
202 | }
203 |
204 | //处理加载过来的数据,递归加载
205 | let loadDataToCanvas = function (dataList) {
206 | if (dataList && dataList.length > 0) {
207 | let obj = dataList[0];
208 | if (obj.type == "0-1") {
209 | loadDataCallback(dataList);
210 | return;
211 | }
212 | if (txtList.indexOf(obj.type) < 0) {
213 | //图片
214 | createImage(obj, dataList);
215 | } else {
216 | //文字
217 | createTxt(obj, dataList);
218 | }
219 | } else {
220 | _fabricInstance.firstAddToState(true);
221 | _fabricInstance.updateCanvasState();
222 | }
223 | }
224 |
225 | //新增/修改文案信息
226 | let modifyCopyright = function (list) {
227 | let objectList = _canvasObject.getObjects();
228 | for (let i = 0; i < list.length; i++) {
229 | let mainObj = objectList.filter(e => {
230 | return e.realType == list[i].type;
231 | });
232 | if (mainObj && mainObj.length > 0) { //已有
233 | let title = list[i].text;
234 | let obj = {
235 | cssName: 'text',
236 | cssValue: title
237 | };
238 | updateTxtCss(obj, mainObj[0]);
239 | _canvasObject.renderAll();
240 | _fabricInstance.updateCanvasState();
241 | } else {
242 | createTxt(list[i]);
243 | }
244 | }
245 | }
246 |
247 | let isMouseUp=true;
248 |
249 | //是否开启整体移动
250 | let isOpenAllMove=false;
251 |
252 | //用于整体拖动,记录拖动开始位置
253 | let point={x:0,y:0};
254 |
255 | let selectedObj={};
256 | _canvasObject.on("mouse:down", e => {
257 | if(isOpenAllMove)
258 | {
259 | Object.assign(point,e.absolutePointer);
260 | }
261 | isMouseUp=false;
262 | if (e && e.target && e.target.selectable) {
263 | //用于鼠标移动到的元素边框显示
264 | // isMouseUp=false;
265 | // clearDotted();
266 | if (selectedObj != e.target)
267 | {
268 | selectedObj = e.target;
269 | }
270 | console.log(selectedObj);
271 | }else if(e&&!e.target){//点击到canvas中无图片渲染区域,重置图层选中状态为未选中
272 | let currentActive={order:-1}
273 | that.setActiveObjectCallback(currentActive);
274 | }
275 | });
276 | _canvasObject.on("mouse:move", function (e) {
277 | if(isOpenAllMove&&!isMouseUp)
278 | {
279 | _canvasObject.relativePan({ x:e.absolutePointer.x- point.x, y: e.absolutePointer.y- point.y });
280 | }
281 | if (e && e.target && isMouseUp) {
282 | //用于鼠标移动到的元素边框显示
283 | // if (lastMoved != e.target)
284 | // {
285 | // drawDottedBorder(e.target.aCoords);
286 | // lastMoved = e.target;
287 | // }
288 | }
289 | });
290 | _canvasObject.on("mouse:up", e => {
291 | if (e && e.target) {
292 | canvasObjectSelected();
293 | }
294 | //用于鼠标移动到的元素边框显示
295 | isMouseUp = true;
296 | });
297 | _canvasObject.on("object:selected", e => {
298 | if (e && e.target) {
299 | }
300 | });
301 | _canvasObject.on("object:modified", function (e) {
302 | if(e&&e.target)
303 | {
304 | console.log('modified');
305 | }
306 |
307 | });
308 | _canvasObject.on("object:scaled", e => {
309 | //文本类放大完成事件,把缩放率更新到字体大小
310 | if (e && e.target&&e.target.isType('i-text')) {
311 | let scale = e.target.scaleX;
312 | let fontSize = e.target.fontSize ? e.target.fontSize:20;
313 | fontSize =Math.round(fontSize * scale);
314 | let obj={
315 | cssName:'scaleX',
316 | cssValue:1
317 | }
318 | updateTxtCss(obj,e.target);
319 | obj = {
320 | cssName: 'scaleY',
321 | cssValue: 1
322 | }
323 | updateTxtCss(obj, e.target);
324 | obj = {
325 | cssName: 'fontSize',
326 | cssValue: fontSize
327 | }
328 | updateTxtCss(obj, e.target);
329 | }
330 | });
331 |
332 | //选中元素事件,重置realLeft和realTop
333 | let canvasObjectSelected = function () {
334 | let currentActive =_fabricInstance.getActiveObj();
335 | if (currentActive) {
336 | let left, top, width, height;
337 | if (currentActive.type == "image") {
338 | //图片
339 | width = currentActive.oldWidth * currentActive.scaleX;
340 | height = currentActive.oldHeight * currentActive.scaleY;
341 | left = currentActive.left - _offsetObj.offsetX - currentActive.width * currentActive.scaleX / 2;
342 | top = currentActive.top - _offsetObj.offsetY - currentActive.height * currentActive.scaleY / 2;
343 | currentActive.realWidth = width;
344 | currentActive.realHeight = height;
345 | } else {
346 | //文本,不需要再去减二分之一的宽高
347 | left = currentActive.left - parseInt(currentActive.width) / 2 - _offsetObj.offsetX;
348 | top = currentActive.top - parseInt(currentActive.height) / 2 - _offsetObj.offsetY;
349 | }
350 | currentActive.realTop = top;
351 | currentActive.realLeft = left;
352 | _canvasObject.renderAll();
353 | that.setActiveObjectCallback(currentActive);
354 | }
355 | };
356 |
357 | //修改属性值
358 | let setCss = function (obj) {
359 | let currentActive = _canvasObject.getActiveObject();
360 | if (!currentActive) {
361 | return;
362 | }
363 | //是否是背景,是否是编辑修改图片功能
364 | if (currentActive.realType=='1-1')//背景,只能替换或者恢复图片
365 | {
366 | if (obj.cssName != "src" && obj.cssName != "resetSrc")
367 | {
368 | return;
369 | }
370 | }
371 | //图片和文字都有的操作,需要重置位置的
372 | if (obj.cssName == "left" || obj.cssName == "top") {
373 | updateLeftOrTop(obj, currentActive);
374 | }
375 | else if (obj.cssName == "src") { //更换图片
376 | updatePic(obj, currentActive);
377 | return;
378 | }
379 | else if (obj.cssName == "resetSrc") { //恢复图片
380 | resetPic(currentActive);
381 | return;
382 | }
383 | else if (obj.cssName == "width" || obj.cssName == "height") {
384 | updateWidthOrHeight(obj, currentActive);
385 | }
386 | else {
387 | if (currentActive.isType("i-text"))//剩下的就是文本类的操作
388 | {
389 | updateTxtCss(obj, currentActive);
390 | }
391 | else{
392 | currentActive.set(obj.cssName,obj.cssValue).setCoords();
393 | }
394 | }
395 | _canvasObject.renderAll();
396 | _fabricInstance.updateCanvasState();
397 | };
398 |
399 | //更新文本类属性值
400 | let updateTxtCss = function (obj, currentActive,isSetSelect=true) {
401 | //字体类的修改
402 | if (obj.cssName != "realFontFamily") { //不是修改字体样式
403 | currentActive.set(obj.cssName, obj.cssValue).setCoords();
404 | // if (obj.cssName == "fontSize"&&obj.cssValue<12) {//修改字体大小,且字体大小是小于12的
405 | // let scale =obj.cssValue/12;
406 | // console.log(scale);
407 | // currentActive.scale(scale).setCoords();
408 | // }
409 | // 解决切换颜色值,字体大小,下划线,加粗等无法重绘文字内容的问题
410 | let stroke = currentActive.stroke;
411 | if(stroke !== '#000000'){
412 | currentActive.set('stroke','#000000');
413 | }else{
414 | currentActive.set('stroke','#ffffff');
415 | }
416 | currentActive.set('stroke',stroke);
417 | // if (currentActive.fontFamily == "黑体") {
418 | // currentActive.set("fontFamily", "微软雅黑").setCoords();
419 | // } else {
420 | // currentActive.set("fontFamily", "黑体").setCoords();
421 | // }
422 | // currentActive.set("fontFamily", oldValue).setCoords();
423 | }else { //修改字体样式
424 | currentActive.set(obj.cssName, obj.cssValue).setCoords();
425 | console.log('_fabricInstance.loadFont');
426 | _fabricInstance.loadFont(currentActive, obj.cssValue);
427 | }
428 | if (isSetSelect)
429 | {
430 | canvasObjectSelected();
431 | }
432 | }
433 |
434 | //更换图片
435 | let updatePic = function (obj, currentActive) {
436 | let src = currentActive.getSrc();
437 | //判断是否更换的同一张
438 | if (src == obj.cssValue) {
439 | return;
440 | }
441 | currentActive.set("oldSrc", src); //跨域转换后的url
442 | currentActive.set("oldId", currentActive.id);
443 | currentActive.set("oldPictureName", currentActive.PictureName);
444 | currentActive.set("oldPictureNum", currentActive.PictureNum);
445 | currentActive.set("oldPictType", currentActive.pictType);
446 | currentActive.set("oldUserId", currentActive.userId);
447 | let content = obj.cssValue;
448 | currentActive.set({
449 | id: content.id,
450 | PictureName: content.PictureName,
451 | PictureNum: content.motherSetNumRef,
452 | userId: content.userId,
453 | pictType: content.type,
454 | oldRealUrl: currentActive.realUrl, //替换前的真实url
455 | realUrl: content.layerPictureUrl //替换后的真实url
456 | });
457 | let width = currentActive.realWidth;
458 | let url = './app/bannerMaterialManager/getPicture?pictureUrl=' + content.layerPictureUrl; //跨域转换后的url
459 | currentActive.setSrc(url, function (e) {
460 | let scaleX = width / e.width;
461 | let scaleY = scaleX;
462 | if(currentActive.realType=='1-1')
463 | {
464 | scaleY=currentActive.realHeight/e.height;
465 | }
466 | currentActive.set("scaleX", scaleX).setCoords();
467 | currentActive.set("scaleY", scaleY).setCoords();
468 | currentActive.set("oldWidth", parseInt(e.width)).setCoords();
469 | currentActive.set("oldHeight", parseInt(e.height)).setCoords();
470 | _canvasObject.renderAll();
471 | _fabricInstance.updateCanvasState();
472 | canvasObjectSelected();
473 | });
474 | return;
475 | }
476 |
477 | //恢复图片
478 | let resetPic = function (currentActive) {
479 | let width = currentActive.realWidth;
480 | let height = currentActive.realHeight;
481 | currentActive.set({
482 | id: currentActive.oldId,
483 | PictureName: currentActive.oldPictureName,
484 | PictureNum: currentActive.oldPictureNum,
485 | userId: currentActive.oldUserId,
486 | pictType: currentActive.oldPictType,
487 | realUrl: currentActive.oldRealUrl
488 | });
489 | let url = currentActive.oldSrc;
490 | currentActive.set("oldSrc", "");
491 | currentActive.set("oldId", "");
492 | currentActive.set("oldPictureName", "");
493 | currentActive.set("oldPictureNum", "");
494 | currentActive.set("oldPictType", "");
495 | currentActive.set("oldUserId", "");
496 | currentActive.set("oldRealUrl", "");
497 |
498 | currentActive.setSrc(url, function (e) {
499 | let scaleX = width / e.width;
500 | let scaleY = scaleX;
501 | if(currentActive.realType=='1-1')
502 | {
503 | scaleY=currentActive.realHeight/e.height;
504 | }
505 | currentActive.set("scaleX", scaleX).setCoords();
506 | currentActive.set("scaleY", scaleY).setCoords();
507 | currentActive.set("oldWidth", parseInt(e.width)).setCoords();
508 | currentActive.set("oldHeight", parseInt(e.height)).setCoords();
509 | _canvasObject.renderAll();
510 | _fabricInstance.updateCanvasState();
511 | canvasObjectSelected();
512 | });
513 | return;
514 | }
515 |
516 | //更新left/top的值
517 | let updateLeftOrTop = function (obj, currentActive) {
518 | let value;
519 | if (currentActive.isType("image")) {
520 | if (obj.cssName == "top")
521 | {
522 | value = currentActive.realTop + currentActive.height * currentActive.scaleY / 2 + _offsetObj.offsetY;
523 | }
524 | else if (obj.cssName == 'left')
525 | {
526 | value = currentActive.realLeft + currentActive.width * currentActive.scaleX / 2 + _offsetObj.offsetX;
527 | }
528 | }
529 | else {
530 | if (obj.cssName == "top") {
531 | value = currentActive.realTop + parseInt(currentActive.height) / 2 + _offsetObj.offsetY;
532 | } else if (obj.cssName == 'left') {
533 | value = currentActive.realLeft + parseInt(currentActive.width) / 2 + _offsetObj.offsetX;
534 | }
535 | }
536 | currentActive.set(obj.cssName, value).setCoords();
537 | }
538 |
539 | //更新width/height的值
540 | let updateWidthOrHeight = function (obj, currentActive) {
541 | if (obj.cssName == "width")
542 | {
543 | if (currentActive.isType("image")) {
544 | let scaleX = currentActive.realWidth / currentActive.oldWidth;
545 | let scaleY = scaleX * currentActive.scaleY / currentActive.scaleX;
546 | currentActive.set("scaleX", scaleX).setCoords();
547 | currentActive.set("scaleY", scaleY).setCoords();
548 | canvasObjectSelected();
549 | }
550 | }
551 | else{
552 | if (currentActive.isType("image")) {
553 | let scaleY = currentActive.realHeight / currentActive.oldHeight;
554 | let scaleX = scaleY * currentActive.scaleX / currentActive.scaleY;
555 | currentActive.set("scaleY", scaleY).setCoords();
556 | currentActive.set("scaleX", scaleX).setCoords();
557 | canvasObjectSelected();
558 | }
559 | }
560 | //文本类修改
561 | currentActive.set(obj.cssName, obj.cssValue).setCoords();
562 | }
563 |
564 | //设置选中项
565 | let setActiveSelect = function (order) {
566 | _fabricInstance.setActiveSelect(order).then(res=>{
567 | canvasObjectSelected();
568 | });
569 | };
570 |
571 | //导出json数据
572 | let toJSon = function () {
573 | stopEditing();
574 | let canvasList = _canvasObject.toJSON(toJsonWithParams).objects;
575 | let result=[];
576 | canvasList.forEach(item=>{
577 | console.log(item);
578 | let obj={};
579 | let width = 0;
580 | let height = 0;
581 | if(item.realType.indexOf('3-')<0)
582 | {
583 | width = item.oldWidth * item.scaleX;
584 | height = item.oldHeight * item.scaleY;
585 | obj.layerPictureUrl=item.realUrl
586 | obj.motherSetNumRef=item.PictureNum
587 | obj.pictType=item.pictType
588 | obj.PictureName=item.PictureName
589 | obj.id=item.id
590 | obj.PictureName=item.PictureName
591 | }else{
592 | width = item.width ? item.width : 0;
593 | height = item.height ? item.height : 0;
594 | obj.fontType=item.realFontFamily
595 | obj.color=item.fill
596 | obj.fontBold=item.fontWeight == "bold"?1:0
597 | obj.fontItalic=item.fontStyle != "normal"?1:0
598 | obj.fontSize=item.fontSize
599 | obj.text=item.text
600 | }
601 | obj.bboxWidth=width
602 | obj.bboxHeight=height
603 | obj.bboxX1=item.realLeft
604 | obj.bboxX2=item.realLeft+width
605 | obj.bboxY1=item.realTop
606 | obj.bboxY2=item.realTop+height
607 | obj.isGif=item.isGif
608 | obj.layerIsEdit=item.layerIsEdit
609 | obj.order=item.order
610 | obj.type=item.realType
611 | obj.userId=item.userId
612 | result.push(obj);
613 | });
614 | return result;
615 | };
616 |
617 | //导出图片
618 | let downLoadImage=function(){
619 | let imgURL = _fabricInstance.getImgBase64Url();
620 | dowmLoadFunc(imgURL);
621 | // glfxImg(imgURL).then(url=>{
622 | // dowmLoadFunc(url);
623 | // });
624 | }
625 |
626 | //使用glfx.js给图片加亮度
627 | const glfxImg = img=>{
628 | return new Promise((resolve,reject)=>{
629 | let imgInstance=document.createElement('img');
630 | imgInstance.src=img;
631 | imgInstance.onload=()=>{
632 | let url='';
633 | try {
634 | var canvas = fx.canvas();
635 | } catch (e) {
636 | alert(e);
637 | reject();
638 | }
639 | var texture = canvas.texture(imgInstance);
640 | canvas.draw(texture).unsharpMask(1, 1.5).update();
641 | url= canvas.toDataURL('image/png');
642 | console.log(url);
643 | resolve(url);
644 | }
645 | })
646 | }
647 |
648 | //下载通用方法,模拟a标签
649 | const dowmLoadFunc=(url,MIME_TYPE='image/png')=>{
650 | //创建一个a链接,模拟点击下载
651 | let dlLink = document.createElement('a');
652 | let filename = '合成图_' + (new Date()).getTime() + '.png';
653 | dlLink.download = filename;
654 | dlLink.href = url;
655 | dlLink.dataset.downloadurl = [MIME_TYPE, dlLink.download, dlLink.href].join(':');
656 | document.body.appendChild(dlLink);
657 | dlLink.click();
658 | document.body.removeChild(dlLink);
659 | }
660 |
661 | //画布缩放
662 | let setZoom=function(value){
663 | _fabricInstance.setZoom(value);
664 | }
665 |
666 | //退出编辑模式
667 | let stopEditing =function(){
668 | if (selectedObj && selectedObj.isType && selectedObj.isType("i-text"))
669 | {
670 | if (selectedObj.exitEditing)
671 | {
672 | console.log('stopEditing');
673 | selectedObj.exitEditing();
674 | }
675 | }
676 | }
677 |
678 | return {
679 | addObject: addObject,
680 | toJSon: toJSon,
681 | setActiveSelect: setActiveSelect,
682 | setCss: setCss,
683 | setZoom: setZoom,
684 | createImage: createImage,
685 | createTxt: createTxt,
686 | modifyCopyright: modifyCopyright,
687 | downLoadImage: downLoadImage,
688 | undo: _fabricInstance.undo,
689 | redo: _fabricInstance.redo,
690 | initialize: _fabricInstance.initialize,
691 | toggleVisiable: _fabricInstance.toggleVisiable,
692 | updateLevel: _fabricInstance.updateLevel,
693 | delItem: _fabricInstance.delItem,
694 | flipActiveObj: _fabricInstance.flipActiveObj,
695 | stopEditing: stopEditing
696 | };
697 | };
698 | export {
699 | fabricCommon
700 | }
701 |
--------------------------------------------------------------------------------
/src/components/MicroEdit/fabricOperate.js:
--------------------------------------------------------------------------------
1 | import FontFaceObserver from "fontfaceobserver";
2 | import initAligningGuidelines from "./guideLines.js";
3 | import "fabric";
4 | import rotateIcon from "@/assets/images/rotate-icon.svg";
5 | import lockIcon from "@/assets/images/lock-icon.svg";
6 | import lockOpenIcon from "@/assets/images/lock-open-icon.svg";
7 | const rotateImg = new Image();
8 | rotateImg.src = rotateIcon;
9 | const lockImage = new Image();
10 | lockImage.src = lockIcon;
11 | const lockOpenImage = new Image();
12 | lockOpenImage.src = lockOpenIcon;
13 | const fabric = window.fabric;
14 | let fabricObject = function(that, id = "canvas") {
15 | /* 默认fabric配置 */
16 | let _fabricConfig = {
17 | backgroundColor: "#F7F8FF",
18 | preserveObjectStacking: true,
19 | selection: false, //取消框选
20 | };
21 | // 设置复制itext文本不带格式
22 | fabric.disableStyleCopyPaste = true;
23 |
24 | fabric.Object.prototype.set({
25 | borderColor: "rgba(0,0,0,0.8)",
26 | cornerColor: "rgba(0,0,0,0.8)", //激活状态角落图标的填充颜色
27 | cornerStrokeColor: "rgba(0,0,0,0.8)", //激活状态角落图标的边框颜色
28 | borderOpacityWhenMoving: 1,
29 | borderScaleFactor: 1,
30 | cornerSize: 8,
31 | cornerStyle: "circle", //rect,circle
32 | centeredScaling: false, //角落放大缩小是否是以图形中心为放大原点
33 | centeredRotation: true, //旋转按钮旋转是否是左上角为圆心旋转
34 | transparentCorners: false, //激活状态角落的图标是否透明
35 | rotatingPointOffset: 20, //旋转距旋转体的距离
36 | originX: "center",
37 | originY: "center",
38 | lockUniScaling: false, //只显示四角的操作
39 | hasRotatingPoint: true, //是否显示旋转按钮
40 | showLock: true, // 控制是否显示lock
41 | });
42 |
43 | // 不显示m
44 | const notShowControls = ["ml", "mr", "mb", "mt"];
45 | notShowControls.forEach((control) => {
46 | fabric.Object.prototype.controls[control].visible = false;
47 | });
48 |
49 | // 重写mtr 的render函数
50 | fabric.Object.prototype.controls.mtr.render = function(
51 | ctx,
52 | left,
53 | top,
54 | styleOverride,
55 | fabricObject
56 | ) {
57 | styleOverride = styleOverride || {};
58 | const transparentCorners =
59 | typeof styleOverride.transparentCorners !== "undefined"
60 | ? styleOverride.transparentCorners
61 | : fabricObject.transparentCorners;
62 | const stroke =
63 | !transparentCorners &&
64 | (styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor);
65 | ctx.save();
66 | ctx.fillStyle = styleOverride.cornerColor || fabricObject.cornerColor;
67 | ctx.strokeStyle =
68 | styleOverride.strokeCornerColor || fabricObject.strokeCornerColor;
69 | ctx.lineWidth = 1;
70 | ctx.translate(left, top);
71 | ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
72 | if (stroke) {
73 | ctx.drawImage(rotateImg, -10, -12, 22, 22);
74 | }
75 | ctx.restore();
76 | };
77 |
78 | const lockIcon = {
79 | width: 22,
80 | height: 22,
81 | };
82 | // 扩展 增加lock操作图标 可以在controls中增加自定义control
83 | fabric.Object.prototype.controls.lock = new fabric.Control({
84 | visible: true,
85 | x: -0.5,
86 | y: -0.5,
87 | offsetX: +lockIcon.width + 5,
88 | offsetY: +lockIcon.height + 5,
89 | sizeX: lockIcon.width,
90 | sizeY: lockIcon.height,
91 | actionName: "lockgroup",
92 | mouseDownHandler: function(eventData, transformData, x, y) {
93 | const current = transformData.target;
94 | const lockStatus = current.get("lockStatus");
95 | if (lockStatus === 1) {
96 | current.set("lockStatus", 0);
97 | } else {
98 | current.set("lockStatus", 1);
99 | }
100 | },
101 | cursorStyle: "pointer",
102 | render: function(ctx, left, top, styleOverride, fabricObject) {
103 | if (!fabricObject.showLock) {
104 | return;
105 | }
106 | styleOverride = styleOverride || {};
107 | const transparentCorners =
108 | typeof styleOverride.transparentCorners !== "undefined"
109 | ? styleOverride.transparentCorners
110 | : fabricObject.transparentCorners;
111 | const stroke =
112 | !transparentCorners &&
113 | (styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor);
114 | ctx.save();
115 | ctx.fillStyle = styleOverride.cornerColor || fabricObject.cornerColor;
116 | ctx.strokeStyle =
117 | styleOverride.strokeCornerColor || fabricObject.strokeCornerColor;
118 | ctx.lineWidth = 1;
119 | ctx.translate(left, top);
120 | ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
121 | if (stroke) {
122 | ctx.drawImage(
123 | fabricObject.lockStatus === 1 ? lockOpenImage : lockImage,
124 | -lockIcon.width / 2,
125 | -lockIcon.height / 2,
126 | lockIcon.width,
127 | lockIcon.height
128 | );
129 | }
130 | ctx.restore();
131 | },
132 | });
133 |
134 | //重绘旋转按钮
135 |
136 | //是否是初始状态
137 | let isInitialStatus = true;
138 |
139 | /* fabric对象 */
140 | let _fabricObj = new fabric.Canvas(id, _fabricConfig);
141 | //添加辅助线
142 | initAligningGuidelines(_fabricObj);
143 |
144 | //对fabric.Canvas扩展
145 | fabric.Canvas.prototype.setZoomByCenter = function(value) {
146 | this.zoomToPoint(
147 | new fabric.Point(_fabricObj.width / 2, _fabricObj.height / 2),
148 | value
149 | );
150 | return this;
151 | };
152 |
153 | //最后一次鼠标滑到的对象--用于鼠标移动到的元素边框显示--用于鼠标移动到元素展示虚线
154 | let lastMoved = {};
155 |
156 | /* 导出json时需要导出的自定义字段 */
157 | let _toJsonWithParams = [];
158 |
159 | /* 初始化 */
160 | const _initConfig = function(config = {}) {
161 | Object.assign(_fabricConfig, config);
162 | fabricObj.setOptions(_fabricConfig);
163 | };
164 |
165 | /* 创建图片 */
166 | const _createImage = function(
167 | url,
168 | imgConfig,
169 | centerObject = false,
170 | hasControls = true
171 | ) {
172 | return new Promise((resolve, reject) => {
173 | try {
174 | let imgInstance = new fabric.Image.fromURL(url, function(img) {
175 | img.set(imgConfig);
176 | img
177 | .set({
178 | scaleX: imgConfig.bboxWidth
179 | ? parseInt(imgConfig.bboxWidth) / img.width
180 | : 1,
181 | scaleY: imgConfig.bboxHeight
182 | ? parseInt(imgConfig.bboxHeight) / img.height
183 | : 1,
184 | realWidth: imgConfig.bboxWidth
185 | ? parseInt(imgConfig.bboxWidth)
186 | : parseInt(img.width),
187 | realHeight: imgConfig.bboxHeight
188 | ? parseInt(imgConfig.bboxHeight)
189 | : parseInt(img.height),
190 | oldWidth: parseInt(img.width),
191 | oldHeight: parseInt(img.height),
192 | })
193 | .setCoords();
194 | if (!hasControls) {
195 | img.hasControls = hasControls;
196 | img.set("lockRotation", true);
197 | img.set("lockScalingX", true);
198 | img.set("lockScalingY", true);
199 | img.set("lockMovementX", true);
200 | img.set("lockMovementY", true);
201 | }
202 | if (centerObject) {
203 | _fabricObj.centerObject(img);
204 | }
205 | resolve(img);
206 | _fabricObj.add(img);
207 | _fabricObj.renderAll();
208 | });
209 | } catch (e) {
210 | reject();
211 | }
212 | });
213 | };
214 |
215 | /* 创建文本 */
216 | const _createItext = function(title, txtConfig, centerObject = false) {
217 | let textInstance = new fabric.IText(title, txtConfig);
218 | if (centerObject) {
219 | _fabricObj.centerObject(textInstance);
220 | }
221 | _fabricObj.add(textInstance);
222 | _fabricObj.renderAll();
223 | return textInstance;
224 | };
225 |
226 | /* 画虚线框相关开始 */
227 | //画虚线的列表--用于鼠标移动到的元素边框显示
228 | let _dottedLineList = [];
229 | //画曲线--用于鼠标移动到的元素边框显示
230 | let _drawDottedLine = function(fromX, fromY, toX, toY) {
231 | let canvasObject = new fabric.Line([fromX, fromY, toX, toY], {
232 | strokeDashArray: [3, 1],
233 | stroke: "red",
234 | strokeWidth: 1,
235 | });
236 | _dottedLineList.push(canvasObject);
237 | _fabricObj.add(canvasObject);
238 | _fabricObj.renderAll();
239 | };
240 |
241 | //画一个对象的虚线边框--用于鼠标移动到的元素边框显示
242 | let _drawDottedBorder = function(aCoords) {
243 | _clearDotted();
244 | let fx = aCoords.tl.x;
245 | let fy = aCoords.tl.y;
246 | let tx = aCoords.tr.x;
247 | let ty = aCoords.tr.y;
248 | _drawDottedLine(fx, fy, tx, ty);
249 | fx = aCoords.tr.x;
250 | fy = aCoords.tr.y;
251 | tx = aCoords.br.x;
252 | ty = aCoords.br.y;
253 | _drawDottedLine(fx, fy, tx, ty);
254 | fx = aCoords.br.x;
255 | fy = aCoords.br.y;
256 | tx = aCoords.bl.x;
257 | ty = aCoords.bl.y;
258 | _drawDottedLine(fx, fy, tx, ty);
259 | fx = aCoords.bl.x;
260 | fy = aCoords.bl.y;
261 | tx = aCoords.tl.x;
262 | ty = aCoords.tl.y;
263 | _drawDottedLine(fx, fy, tx, ty);
264 | };
265 |
266 | //清除虚线框--用于鼠标移动到的元素边框显示
267 | let _clearDotted = function() {
268 | for (let i = 0; i < _dottedLineList.length; i++) {
269 | _fabricObj.remove(_dottedLineList[i]);
270 | }
271 | _dottedLineList = [];
272 | };
273 | /* 画虚线框相关结束 */
274 |
275 | /* 鼠标事件监听相关--开始 */
276 | _fabricObj.on("object:modified", function(e) {
277 | if (_config.isStart) updateCanvasState();
278 | });
279 | _fabricObj.on("object:added", function() {
280 | if (_config.isStart) updateCanvasState();
281 | });
282 | _fabricObj.on("object:removed", function() {
283 | updateCanvasState();
284 | });
285 | _fabricObj.on("object:rotating", function() {
286 | updateCanvasState();
287 | });
288 | _fabricObj.on("mouse:down", (e) => {
289 | if (e && e.target && e.target.selectable) {
290 | }
291 | });
292 | _fabricObj.on("mouse:move", function(e) {
293 | if (e && e.target) {
294 | }
295 | });
296 | _fabricObj.on("mouse:up", (e) => {
297 | if (e && e.target) {
298 | }
299 | });
300 | _fabricObj.on("object:selected", (e) => {
301 | if (e && e.target) {
302 | }
303 | });
304 | /* 鼠标监控相关--结束 */
305 |
306 | //撤销回退相关操作状态配置表
307 | let _config = {
308 | canvasState: [], //存储各状态的json数据
309 | currentStateIndex: -1,
310 | undoStatus: false, //撤销
311 | redoStatus: false, //回退
312 | undoFinishedStatus: 1, //撤销中的状态
313 | redoFinishedStatus: 1, //回退中的状态
314 | undoBtnStatus: false, //撤销按钮可点击状态
315 | redoBtnStatus: false, //回退按钮可点击状态
316 | initialNum: 0,
317 | isStart: false,
318 | };
319 | //记录每步操作,用户撤销和回退操作
320 | let updateCanvasState = function() {
321 | if (_config.undoStatus == false && _config.redoStatus == false) {
322 | let jsonData = _fabricObj.toJSON(_toJsonWithParams);
323 | let canvasAsJson = JSON.stringify(jsonData);
324 | if (_config.currentStateIndex < _config.canvasState.length - 1) {
325 | //回退操作更新
326 | let oldData = JSON.parse(JSON.stringify(_config.canvasState));
327 | _config.canvasState = oldData.splice(0, _config.currentStateIndex + 1);
328 | _config.canvasState.push(canvasAsJson);
329 | } else {
330 | _config.canvasState.push(canvasAsJson);
331 | }
332 | _config.currentStateIndex = _config.canvasState.length - 1;
333 | updateUnRedoBtnStatus();
334 | if (
335 | _config.currentStateIndex == _config.canvasState.length - 1 &&
336 | _config.currentStateIndex == 0
337 | ) {
338 | return;
339 | }
340 | if (isInitialStatus) {
341 | isInitialStatus = false;
342 | updateInitialStatus(isInitialStatus);
343 | }
344 | }
345 | };
346 |
347 | //更新撤销/回退的操作状态
348 | let updateUnRedoBtnStatus = function() {
349 | if (!that.updateUnRedoStatus) {
350 | return;
351 | }
352 | //撤销按钮状态更新
353 | if (_config.currentStateIndex <= 0) {
354 | _config.undoBtnStatus = false;
355 | } else {
356 | _config.undoBtnStatus = true;
357 | }
358 | //回退按钮状态更新
359 | if (
360 | _config.canvasState.length > _config.currentStateIndex + 1 &&
361 | _config.canvasState.length > 1
362 | ) {
363 | _config.redoBtnStatus = true;
364 | } else {
365 | _config.redoBtnStatus = false;
366 | }
367 | let btnStatus = {
368 | undoStatus: _config.undoBtnStatus,
369 | redoStatus: _config.redoBtnStatus,
370 | };
371 | //更新主页面的btn状态
372 | that.updateUnRedoStatus(btnStatus);
373 | };
374 |
375 | //撤销
376 | let undo = function() {
377 | let currentTarget = _fabricObj.getActiveObject();
378 | if (_config.undoFinishedStatus) {
379 | if (_config.currentStateIndex == 0) {
380 | _config.undoStatus = false;
381 | } else {
382 | if (_config.canvasState.length >= 2) {
383 | _config.undoFinishedStatus = 0;
384 | _config.undoStatus = true;
385 | _fabricObj.loadFromJSON(
386 | _config.canvasState[_config.currentStateIndex - 1],
387 | function() {
388 | _fabricObj.renderAll();
389 | _config.undoStatus = false;
390 | _config.currentStateIndex -= 1;
391 | _config.undoFinishedStatus = 1;
392 | _fabricObj.renderAll.bind(_fabricObj);
393 | undoFinish();
394 | },
395 | canvasLoadFromJsonTxtLoadFont
396 | );
397 | }
398 | }
399 | }
400 | };
401 | //恢复
402 | let redo = function() {
403 | if (_config.redoFinishedStatus) {
404 | if (_config.currentStateIndex < _config.canvasState.length - 1) {
405 | if (
406 | _config.canvasState.length > _config.currentStateIndex + 1 &&
407 | _config.canvasState.length > 1
408 | ) {
409 | _config.redoFinishedStatus = 0;
410 | _config.redoStatus = true;
411 | _fabricObj.loadFromJSON(
412 | _config.canvasState[_config.currentStateIndex + 1],
413 | function() {
414 | _fabricObj.renderAll();
415 | _config.redoStatus = false;
416 | _config.currentStateIndex += 1;
417 | _config.redoFinishedStatus = 1;
418 | _fabricObj.renderAll.bind(_fabricObj);
419 | undoFinish();
420 | },
421 | canvasLoadFromJsonTxtLoadFont
422 | );
423 | }
424 | }
425 | }
426 | };
427 |
428 | //获取最新的层级关系列表
429 | let getNewLevelList = function() {
430 | let list = [];
431 | let objList = _fabricObj.getObjects();
432 | for (let i = 0; i < objList.length; i++) {
433 | let obj = {};
434 | obj.visiable = objList[i].visible;
435 | obj.text = objList[i].title;
436 | obj.id = i + 1;
437 | obj.type = objList[i].realType;
438 | list.push(obj);
439 | }
440 | return list;
441 | };
442 |
443 | //撤销/回退操作完成回调
444 | let undoFinish = function() {
445 | //banner微编辑
446 | if (that.undoSuccess) {
447 | that.undoSuccess(getNewLevelList());
448 | }
449 | //gif动图微编辑
450 | that.initialSuccessCallback &&
451 | that.initialSuccessCallback(getNewEditLevelList());
452 | updateUnRedoBtnStatus();
453 | };
454 |
455 | //gif动图,更新可编辑图层信息
456 | const getNewEditLevelList = () => {
457 | let newObject = {};
458 | let txtList = [];
459 | let imgList = [];
460 |
461 | let objList = _fabricObj.getObjects();
462 | for (let i = 0; i < objList.length; i++) {
463 | if (!objList[i].layerIsEdit || objList[i].layerIsEdit == "0") {
464 | continue;
465 | }
466 | if (objList[i].realType.indexOf("3-") < 0) {
467 | let obj = {
468 | type: objList[i].realType,
469 | order: objList[i].order,
470 | layerPictureUrl: objList[i].layerPictureUrl,
471 | };
472 | imgList.push(obj);
473 | } else {
474 | let obj = {
475 | type: objList[i].realType,
476 | order: objList[i].order,
477 | title: objList[i].text,
478 | };
479 | txtList.push(obj);
480 | }
481 | }
482 | newObject.imgList = imgList;
483 | newObject.txtList = txtList;
484 | return newObject;
485 | };
486 |
487 | //调整层级关系
488 | let updateLevel = function(moveOrder, movedOrder) {
489 | moveOrder = parseInt(moveOrder);
490 | movedOrder = parseInt(movedOrder);
491 | let upLevels = _fabricObj.getObjects();
492 | let currentUpdate = {};
493 | if (moveOrder < movedOrder) {
494 | //往外层移动
495 | for (let i = moveOrder; i <= movedOrder; i++) {
496 | if (upLevels[i].order > moveOrder) {
497 | upLevels[i].order = parseInt(upLevels[i].order) - 1;
498 | } else if (upLevels[i].order == moveOrder) {
499 | upLevels[i].order = movedOrder;
500 | currentUpdate = upLevels[i];
501 | }
502 | }
503 | //移动图层,向外移动多少层
504 | for (let i = moveOrder; i < movedOrder; i++) {
505 | _fabricObj.bringForward(currentUpdate); //上移
506 | }
507 | } else {
508 | //往内层移动
509 | for (let i = movedOrder; i <= moveOrder; i++) {
510 | if (upLevels[i].order < moveOrder) {
511 | upLevels[i].order = parseInt(upLevels[i].order) + 1;
512 | } else if (upLevels[i].order == moveOrder) {
513 | upLevels[i].order = movedOrder;
514 | currentUpdate = upLevels[i];
515 | }
516 | }
517 | //移动图层,向内移动多少层
518 | for (let i = movedOrder; i < moveOrder; i++) {
519 | _fabricObj.sendBackwards(currentUpdate); //下移
520 | }
521 | }
522 | _fabricObj.renderAll();
523 | updateCanvasState();
524 | };
525 |
526 | //重新加载相关数据初始化
527 | let initialize = function() {
528 | if (isInitialStatus) {
529 | return;
530 | }
531 | let json = _config.canvasState[0];
532 | _fabricObj.clear();
533 | _config.isStart = false;
534 | _config.currentStateIndex = -1;
535 | _config.canvasState = [];
536 | isInitialStatus = true;
537 | _fabricObj.loadFromJSON(
538 | json,
539 | () => {
540 | firstAddToState(true);
541 | updateCanvasState();
542 | undoFinish();
543 | },
544 | canvasLoadFromJsonTxtLoadFont
545 | );
546 | updateInitialStatus(isInitialStatus);
547 | };
548 |
549 | //更新是否是初始状态
550 | let updateInitialStatus = function() {
551 | if (that.updateInitialStatus) {
552 | that.updateInitialStatus(isInitialStatus);
553 | }
554 | };
555 |
556 | //获取画布图片的base64
557 | let getImgBase64Url = function() {
558 | let currentActive = _fabricObj.getObjects()[0];
559 | let width = currentActive.oldWidth * currentActive.scaleX;
560 | let height = currentActive.oldHeight * currentActive.scaleY;
561 | let zoom = _fabricObj.getZoom();
562 | if (zoom != 1) {
563 | setZoom(1);
564 | }
565 | //转换成base64
566 | let imgURL = _fabricObj.toDataURL({
567 | format: "jpeg",
568 | quality: 1,
569 | multiplier: 1,
570 | left: 0,
571 | top: 0,
572 | width: width,
573 | height: height,
574 | });
575 | setZoom(zoom);
576 | return imgURL;
577 | };
578 |
579 | //设置缩放
580 | let setZoom = function(num) {
581 | _fabricObj.setZoomByCenter(num);
582 | };
583 |
584 | //设置导出json时需要导出的自定义字段
585 | let setToJsonWithParams = function(list) {
586 | _toJsonWithParams = _toJsonWithParams.concat(list);
587 | };
588 |
589 | //设置是否显示
590 | let toggleVisiable = function(order, value) {
591 | let objs = _fabricObj.getObjects().filter((e) => {
592 | return e && e.order == order;
593 | });
594 | if (objs && objs.length > 0) {
595 | objs[0].set("visible", value);
596 | _fabricObj.renderAll();
597 | updateCanvasState();
598 | }
599 | };
600 |
601 | //删除元素
602 | let delItem = function(order) {
603 | let objs = _fabricObj.getObjects().filter((e) => {
604 | return e && e.order == order;
605 | });
606 | if (objs && objs.length > 0) {
607 | _fabricObj.remove(objs[0]);
608 | _fabricObj.renderAll();
609 | resetOrderToMapper(order);
610 | }
611 | };
612 |
613 | //删除时候用,删除层级以上的全部降一,已于当前getobjects()的层级相对应
614 | let resetOrderToMapper = function(order) {
615 | order = parseInt(order);
616 | for (let i = order; i < _fabricObj.getObjects().length; i++) {
617 | if (parseInt(_fabricObj.getObjects()[i].order) > order) {
618 | _fabricObj.getObjects()[i].order = i;
619 | }
620 | }
621 | };
622 |
623 | //开始记录操作,用于撤销回退
624 | let firstAddToState = function(isStart = false) {
625 | _config.isStart = isStart;
626 | };
627 |
628 | //设置选中项
629 | let setActiveSelect = function(order) {
630 | return new Promise((resolve, reject) => {
631 | let objs = _fabricObj.getObjects().filter((e) => {
632 | return e && e.order == order;
633 | });
634 | if (objs && objs.length > 0) {
635 | _fabricObj.setActiveObject(objs[0]);
636 | resolve();
637 | } else {
638 | reject();
639 | }
640 | });
641 | };
642 |
643 | //获取当前选中的元素
644 | let getActiveObj = function() {
645 | return _fabricObj.getActiveObject();
646 | };
647 |
648 | //设置元素属性值
649 | let setActiveObjCss = function(obj) {
650 | let currentActive = getActiveObj();
651 | if (currentActive) {
652 | // currentActive.setOptions(obj)
653 | currentActive.set(obj.cssName, obj.cssVlaue).setCoords();
654 | _fabricConfig.renderAll();
655 | }
656 | };
657 |
658 | //翻转元素,flip=flipX,水平翻转,flip=flipY垂直旋转
659 | let _flipActiveObj = function(flip) {
660 | let activeObj = getActiveObj();
661 | if (activeObj) {
662 | let fX = activeObj[flip];
663 | activeObj.set(flip, fX).setCoords();
664 | }
665 | };
666 |
667 | //加载json到canvas时加载每个元素的事件,用于异步加载字体
668 | let canvasLoadFromJsonTxtLoadFont = function(o, object) {
669 | if (
670 | object.isType("i-text") &&
671 | o.fontFamily == "" &&
672 | o.realFontFamily != ""
673 | ) {
674 | _loadFont(object, o.realFontFamily);
675 | }
676 | };
677 |
678 | //加载字体文件
679 | let _loadFont = function(instance, fontFamily) {
680 | let font = fontFamily;
681 | var myfont = new FontFaceObserver(font);
682 | myfont
683 | .load(null, 100000)
684 | .then(function() {
685 | console.log(instance, font);
686 | instance.set("fontFamily", font);
687 | _fabricObj.requestRenderAll();
688 | _loadFontSuccess(instance);
689 | })
690 | .catch(function(e) {
691 | console.log(e);
692 | });
693 | };
694 |
695 | //加载字体成功事件回调
696 | let _loadFontSuccess = function(currentActive) {
697 | let text = currentActive.text;
698 | currentActive.set("text", "").setCoords();
699 | currentActive.set("text", text).setCoords();
700 | _fabricObj.renderAll();
701 | };
702 |
703 | return {
704 | initFabricConfig: _initConfig,
705 | fabricInstance: _fabricObj,
706 | updateLevel: updateLevel,
707 | initialize: initialize,
708 | getImgBase64Url: getImgBase64Url,
709 | setZoom: setZoom,
710 | setToJsonWithParams: setToJsonWithParams,
711 | toggleVisiable: toggleVisiable,
712 | delItem: delItem,
713 | firstAddToState: firstAddToState,
714 | createImage: _createImage,
715 | createItext: _createItext,
716 | flipActiveObj: _flipActiveObj,
717 | updateCanvasState: updateCanvasState,
718 | setActiveSelect: setActiveSelect,
719 | getActiveObj: getActiveObj,
720 | setActiveObjCss: setActiveObjCss,
721 | undo: undo,
722 | redo: redo,
723 | loadFont: _loadFont,
724 | };
725 | };
726 | export { fabricObject };
727 |
--------------------------------------------------------------------------------
/src/components/MicroEdit/guideLines.js:
--------------------------------------------------------------------------------
1 | function initAligningGuidelines(canvas) {
2 |
3 | var ctx = canvas.getSelectionContext(),
4 | aligningLineOffset = 0,
5 | aligningLineMargin = 0,
6 | aligningLineWidth = 1,
7 | aligningLineColor = 'rgb(0,0,0)',
8 | viewportTransform,
9 | zoom = 1;
10 |
11 | function drawVerticalLine(coords) {
12 | drawLine(
13 | coords.x + 0.5,
14 | coords.y1 > coords.y2 ? coords.y2 : coords.y1,
15 | coords.x + 0.5,
16 | coords.y2 > coords.y1 ? coords.y2 : coords.y1);
17 | }
18 |
19 | function drawHorizontalLine(coords) {
20 | drawLine(
21 | coords.x1 > coords.x2 ? coords.x2 : coords.x1,
22 | coords.y + 0.5,
23 | coords.x2 > coords.x1 ? coords.x2 : coords.x1,
24 | coords.y + 0.5);
25 | }
26 |
27 | function drawLine(x1, y1, x2, y2) {
28 | ctx.save();
29 | ctx.lineWidth = aligningLineWidth;
30 | ctx.strokeStyle = aligningLineColor;
31 | ctx.beginPath();
32 | ctx.moveTo(((x1+viewportTransform[4])*zoom), ((y1+viewportTransform[5])*zoom));
33 | ctx.lineTo(((x2+viewportTransform[4])*zoom), ((y2+viewportTransform[5])*zoom));
34 | ctx.stroke();
35 | ctx.restore();
36 | }
37 |
38 | function isInRange(value1, value2) {
39 | value1 = Math.round(value1);
40 | value2 = Math.round(value2);
41 | for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) {
42 | if (i === value2) {
43 | return true;
44 | }
45 | }
46 | return false;
47 | }
48 |
49 | var verticalLines = [],
50 | horizontalLines = [];
51 |
52 | canvas.on('mouse:down', function () {
53 | viewportTransform = canvas.viewportTransform;
54 | zoom = canvas.getZoom();
55 | });
56 |
57 | canvas.on('object:moving', function(e) {
58 |
59 | var activeObject = e.target,
60 | canvasObjects = canvas.getObjects(),
61 | activeObjectCenter = activeObject.getCenterPoint(),
62 | activeObjectLeft = activeObjectCenter.x,
63 | activeObjectTop = activeObjectCenter.y,
64 | activeObjectBoundingRect = activeObject.getBoundingRect(),
65 | activeObjectHeight = activeObjectBoundingRect.height / viewportTransform[3],
66 | activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0],
67 | horizontalInTheRange = false,
68 | verticalInTheRange = false,
69 | transform = canvas._currentTransform;
70 |
71 | if (!transform) return;
72 |
73 | // It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions,
74 | // but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move
75 |
76 | for (var i = canvasObjects.length; i--; ) {
77 |
78 | if (canvasObjects[i] === activeObject) continue;
79 |
80 | var objectCenter = canvasObjects[i].getCenterPoint(),
81 | objectLeft = objectCenter.x,
82 | objectTop = objectCenter.y,
83 | objectBoundingRect = canvasObjects[i].getBoundingRect(),
84 | objectHeight = objectBoundingRect.height / viewportTransform[3],
85 | objectWidth = objectBoundingRect.width / viewportTransform[0];
86 |
87 | // snap by the horizontal center line
88 | if (isInRange(objectLeft, activeObjectLeft)) {
89 | verticalInTheRange = true;
90 | verticalLines.push({
91 | x: objectLeft,
92 | y1: (objectTop < activeObjectTop)
93 | ? (objectTop - objectHeight / 2 - aligningLineOffset)
94 | : (objectTop + objectHeight / 2 + aligningLineOffset),
95 | y2: (activeObjectTop > objectTop)
96 | ? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset)
97 | : (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
98 | });
99 | activeObject.setPositionByOrigin(new fabric.Point(objectLeft, activeObjectTop), 'center', 'center');
100 | }
101 |
102 | // snap by the left edge
103 | if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) {
104 | verticalInTheRange = true;
105 | verticalLines.push({
106 | x: objectLeft - objectWidth / 2,
107 | y1: (objectTop < activeObjectTop)
108 | ? (objectTop - objectHeight / 2 - aligningLineOffset)
109 | : (objectTop + objectHeight / 2 + aligningLineOffset),
110 | y2: (activeObjectTop > objectTop)
111 | ? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset)
112 | : (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
113 | });
114 | activeObject.setPositionByOrigin(new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop), 'center', 'center');
115 | }
116 |
117 | // snap by the right edge
118 | if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) {
119 | verticalInTheRange = true;
120 | verticalLines.push({
121 | x: objectLeft + objectWidth / 2,
122 | y1: (objectTop < activeObjectTop)
123 | ? (objectTop - objectHeight / 2 - aligningLineOffset)
124 | : (objectTop + objectHeight / 2 + aligningLineOffset),
125 | y2: (activeObjectTop > objectTop)
126 | ? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset)
127 | : (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
128 | });
129 | activeObject.setPositionByOrigin(new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop), 'center', 'center');
130 | }
131 |
132 | // snap by the vertical center line
133 | if (isInRange(objectTop, activeObjectTop)) {
134 | horizontalInTheRange = true;
135 | horizontalLines.push({
136 | y: objectTop,
137 | x1: (objectLeft < activeObjectLeft)
138 | ? (objectLeft - objectWidth / 2 - aligningLineOffset)
139 | : (objectLeft + objectWidth / 2 + aligningLineOffset),
140 | x2: (activeObjectLeft > objectLeft)
141 | ? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset)
142 | : (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
143 | });
144 | activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop), 'center', 'center');
145 | }
146 |
147 | // snap by the top edge
148 | if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
149 | horizontalInTheRange = true;
150 | horizontalLines.push({
151 | y: objectTop - objectHeight / 2,
152 | x1: (objectLeft < activeObjectLeft)
153 | ? (objectLeft - objectWidth / 2 - aligningLineOffset)
154 | : (objectLeft + objectWidth / 2 + aligningLineOffset),
155 | x2: (activeObjectLeft > objectLeft)
156 | ? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset)
157 | : (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
158 | });
159 | activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2), 'center', 'center');
160 | }
161 |
162 | // snap by the bottom edge
163 | if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
164 | horizontalInTheRange = true;
165 | horizontalLines.push({
166 | y: objectTop + objectHeight / 2,
167 | x1: (objectLeft < activeObjectLeft)
168 | ? (objectLeft - objectWidth / 2 - aligningLineOffset)
169 | : (objectLeft + objectWidth / 2 + aligningLineOffset),
170 | x2: (activeObjectLeft > objectLeft)
171 | ? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset)
172 | : (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
173 | });
174 | activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2), 'center', 'center');
175 | }
176 | }
177 |
178 | if (!horizontalInTheRange) {
179 | horizontalLines.length = 0;
180 | }
181 |
182 | if (!verticalInTheRange) {
183 | verticalLines.length = 0;
184 | }
185 | });
186 |
187 | canvas.on('before:render', function() {
188 | if(canvas&&canvas.contextTop)
189 | {
190 | canvas.clearContext(canvas.contextTop);
191 | }
192 | });
193 |
194 | canvas.on('after:render', function() {
195 | for (var i = verticalLines.length; i--; ) {
196 | drawVerticalLine(verticalLines[i]);
197 | }
198 | for (var i = horizontalLines.length; i--; ) {
199 | drawHorizontalLine(horizontalLines[i]);
200 | }
201 |
202 | verticalLines.length = horizontalLines.length = 0;
203 | });
204 |
205 | canvas.on('mouse:up', function() {
206 | verticalLines.length = horizontalLines.length = 0;
207 | canvas.renderAll();
208 | });
209 | }
210 |
211 | export default initAligningGuidelines;
--------------------------------------------------------------------------------
/src/components/MicroEdit/index.js:
--------------------------------------------------------------------------------
1 | import MicroEdit from './MicroEdit'
2 | export default MicroEdit
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 |
2 | export default(Vue) => {
3 | }
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from '@/router'
4 | import components from './components'
5 | import ElementUI,{Message} from 'element-ui'
6 | import 'element-ui/lib/theme-chalk/index.css'
7 | import '@/assets/styles/base.css'
8 | import '@/assets/styles/common.css'
9 |
10 | Vue.config.productionTip = false
11 | Vue.use(components)
12 | Vue.use(ElementUI)
13 | Vue.prototype.$message = Message
14 |
15 | new Vue({
16 | render: h => h(App),
17 | router
18 | }).$mount('#app')
19 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 |
6 | let router = new Router({
7 | mode: 'hash',
8 | routes: [
9 | {
10 | path: '/home',
11 | name: 'home',
12 | component: () => import("@/views/Home/Home")
13 | },
14 | {
15 | path: '*',
16 | component: () => import("@/views/Home/Home")
17 | }
18 | ]
19 | })
20 | router.beforeEach((to, from, next) => {
21 | next();
22 | })
23 | export default router
24 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 |
5 | Vue.use(Vuex)
6 | let state = {}
7 | let getters = {}
8 | let mutations = {}
9 | let actions = {}
10 | export default new Vuex.Store({
11 | state,
12 | getters,
13 | mutations,
14 | actions,
15 | modules: {
16 | }
17 | })
18 |
--------------------------------------------------------------------------------
/src/store/mutationTypes.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jeff-Bee/onLinePS/79737fa5333da831eeee28824c28204521f8fd55/src/store/mutationTypes.js
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | let qs = require('querystring')
2 | export const getQueryStringFromJson = function (json) {
3 | return qs.stringify(json)
4 | }
5 |
6 | // 时间格式化-yyyy-MM-dd 00or24
7 | export const getFormatYMDDateStr = function (date, isEnd = false) {
8 | if (!date) {
9 | return ''
10 | }
11 | var time = new Date(date)
12 | return `${time.getFullYear()}-${(time.getMonth() + 1)}-${time.getDate()} ${isEnd ? '23:59:59' : '00:00:00'}`
13 | }
14 |
15 | //格式化str时间为yyyy-MM-dd的字符串
16 | export const formatStrDataToYMDStr = function (date) {
17 | if (!date) {
18 | return ''
19 | }
20 | date = date.replace(/-/g, '/');
21 | let time = new Date(date);
22 | return `${time.getFullYear()}-${(time.getMonth() + 1)}-${time.getDate()}`
23 | }
24 |
25 | // 判断是否是ie浏览器
26 | export const isIE = () => {
27 | if (window.navigator.userAgent.indexOf('MSIE') >= 1 || (!!window.ActiveXObject || 'ActiveXObject' in window)) {
28 | return true
29 | }
30 | return false
31 | }
32 |
33 | //创建全局遮罩层
34 | export const createMask=function(data){
35 | let mask=document.createElement('div');
36 | mask.style.cssText ="position: fixed;left: 0;top: 0;right: 0;bottom: 0;z-index: 1110;background:#fff;opacity:0;";
37 | document.body.appendChild(mask);
38 | return mask;
39 | }
40 |
41 | // 判断是否是safari
42 | export const isSafari = () => {
43 | if (/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent)) {
44 | return true
45 | }
46 | return false
47 | }
48 | // 判断是否是FF
49 | export const isFireFox = () => {
50 | if (/Firefox/.test(navigator.userAgent)) {
51 | return true
52 | }
53 | return false
54 | }
55 |
56 | // 判断是否是Chrome
57 | export const isChrome = () => {
58 | if (/Chrome/.test(navigator.userAgent)) {
59 | return true
60 | }
61 | return false
62 | }
--------------------------------------------------------------------------------
/src/views/Home/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
49 |
--------------------------------------------------------------------------------
/src/views/Home/index.js:
--------------------------------------------------------------------------------
1 | import Home from './Home.vue'
2 | export {
3 | Home
4 | }
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
2 | const CompressionWebpackPlugin = require('compression-webpack-plugin');
3 |
4 | module.exports = {
5 | publicPath:'/',
6 | assetsDir:'static',
7 | devServer: {
8 | disableHostCheck: true
9 | },
10 | productionSourceMap: false,
11 | configureWebpack:{
12 | optimization: {
13 | minimizer: [
14 | new UglifyJsPlugin({
15 | uglifyOptions: {
16 | warnings: false,
17 | compress: {
18 | drop_console: true,//console
19 | drop_debugger: true,
20 | pure_funcs: ['console.log']//移除console
21 | }
22 | }
23 | })
24 | ]
25 | },
26 | plugins: [
27 | new CompressionWebpackPlugin({
28 | test:/\.js$|\.html$|\.css$/, //匹配文件名
29 | threshold: 10240,//对超过10k的数据压缩
30 | deleteOriginalAssets: false //不删除源文件
31 | })
32 | ]
33 | }
34 | }
--------------------------------------------------------------------------------