This commit is contained in:
pipipi-pikachu 2020-12-14 18:12:45 +08:00
parent 59cfc3dc6f
commit dc388ac6ab
14 changed files with 1585 additions and 197 deletions

348
package-lock.json generated
View File

@ -2258,6 +2258,122 @@
"tslint": "^5.20.1", "tslint": "^5.20.1",
"webpack": "^4.0.0", "webpack": "^4.0.0",
"yorkie": "^2.0.0" "yorkie": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1606792369066&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
"integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz?cache=0&sync_timestamp=1591687000046&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-4.1.0.tgz",
"integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz?cache=0&sync_timestamp=1566248870121&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcolor-convert%2Fdownload%2Fcolor-convert-2.0.1.tgz",
"integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
"integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
"dev": true,
"optional": true
},
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npm.taobao.org/fork-ts-checker-webpack-plugin/download/fork-ts-checker-webpack-plugin-5.2.1.tgz?cache=0&sync_timestamp=1607912103019&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffork-ts-checker-webpack-plugin%2Fdownload%2Ffork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha1-eTJthpeXkG+osk4qvPlCH8gFRQ0=",
"dev": true,
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz",
"integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
"dev": true,
"optional": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npm.taobao.org/lru-cache/download/lru-cache-6.0.0.tgz?cache=0&sync_timestamp=1594427484405&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flru-cache%2Fdownload%2Flru-cache-6.0.0.tgz",
"integrity": "sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ=",
"dev": true,
"optional": true,
"requires": {
"yallist": "^4.0.0"
}
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-2.7.0.tgz",
"integrity": "sha1-FxUfdtjq5n+793lgwzxnatn078c=",
"dev": true,
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"semver": {
"version": "7.3.4",
"resolved": "https://registry.npm.taobao.org/semver/download/semver-7.3.4.tgz?cache=0&sync_timestamp=1606852064928&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-7.3.4.tgz",
"integrity": "sha1-J6qn0uTKdkUvmNOt0JOnLJQ+3Jc=",
"dev": true,
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz?cache=0&sync_timestamp=1606205010380&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-7.2.0.tgz",
"integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npm.taobao.org/yallist/download/yallist-4.0.0.tgz",
"integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=",
"dev": true,
"optional": true
}
} }
}, },
"@vue/cli-plugin-unit-jest": { "@vue/cli-plugin-unit-jest": {
@ -2421,6 +2537,17 @@
"unique-filename": "^1.1.1" "unique-filename": "^1.1.1"
} }
}, },
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz?cache=0&sync_timestamp=1591687000046&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-4.1.0.tgz",
"integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"cliui": { "cliui": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npm.taobao.org/cliui/download/cliui-6.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcliui%2Fdownload%2Fcliui-6.0.0.tgz", "resolved": "https://registry.npm.taobao.org/cliui/download/cliui-6.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcliui%2Fdownload%2Fcliui-6.0.0.tgz",
@ -2483,6 +2610,18 @@
"graceful-fs": "^4.1.6" "graceful-fs": "^4.1.6"
} }
}, },
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-2.0.0.tgz",
"integrity": "sha1-5MrOW4FtQloWa18JfhDNErNgZLA=",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz", "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz",
@ -2531,6 +2670,18 @@
"integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY=", "integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY=",
"dev": true "dev": true
}, },
"vue-loader-v16": {
"version": "npm:vue-loader@16.1.1",
"resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-16.1.1.tgz?cache=0&sync_timestamp=1607093677581&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-16.1.1.tgz",
"integrity": "sha1-9bKG1grGiGaExjoXoYQ5HMngGZo=",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
}
},
"wrap-ansi": { "wrap-ansi": {
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-6.2.0.tgz", "resolved": "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-6.2.0.tgz",
@ -7317,122 +7468,6 @@
"worker-rpc": "^0.1.0" "worker-rpc": "^0.1.0"
} }
}, },
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npm.taobao.org/fork-ts-checker-webpack-plugin/download/fork-ts-checker-webpack-plugin-5.2.1.tgz?cache=0&sync_timestamp=1607084938170&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffork-ts-checker-webpack-plugin%2Fdownload%2Ffork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha1-eTJthpeXkG+osk4qvPlCH8gFRQ0=",
"dev": true,
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1606792302448&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
"integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz",
"integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz",
"integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
"integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz?cache=0&sync_timestamp=1596294337050&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhas-flag%2Fdownload%2Fhas-flag-4.0.0.tgz",
"integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
"dev": true,
"optional": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npm.taobao.org/lru-cache/download/lru-cache-6.0.0.tgz",
"integrity": "sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ=",
"dev": true,
"optional": true,
"requires": {
"yallist": "^4.0.0"
}
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-2.7.0.tgz",
"integrity": "sha1-FxUfdtjq5n+793lgwzxnatn078c=",
"dev": true,
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"semver": {
"version": "7.3.4",
"resolved": "https://registry.npm.taobao.org/semver/download/semver-7.3.4.tgz?cache=0&sync_timestamp=1606852122426&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-7.3.4.tgz",
"integrity": "sha1-J6qn0uTKdkUvmNOt0JOnLJQ+3Jc=",
"dev": true,
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz?cache=0&sync_timestamp=1606205010380&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-7.2.0.tgz",
"integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npm.taobao.org/yallist/download/yallist-4.0.0.tgz",
"integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=",
"dev": true,
"optional": true
}
}
},
"form-data": { "form-data": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npm.taobao.org/form-data/download/form-data-2.3.3.tgz", "resolved": "https://registry.npm.taobao.org/form-data/download/form-data-2.3.3.tgz",
@ -15868,87 +15903,6 @@
} }
} }
}, },
"vue-loader-v16": {
"version": "npm:vue-loader@16.1.1",
"resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-16.1.1.tgz?cache=0&sync_timestamp=1607093677581&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-16.1.1.tgz",
"integrity": "sha1-9bKG1grGiGaExjoXoYQ5HMngGZo=",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1606792302448&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
"integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz",
"integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz",
"integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
"integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz?cache=0&sync_timestamp=1596294337050&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhas-flag%2Fdownload%2Fhas-flag-4.0.0.tgz",
"integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-2.0.0.tgz",
"integrity": "sha1-5MrOW4FtQloWa18JfhDNErNgZLA=",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz?cache=0&sync_timestamp=1606205010380&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-7.2.0.tgz",
"integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"vue-router": { "vue-router": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npm.taobao.org/vue-router/download/vue-router-4.0.1.tgz?cache=0&sync_timestamp=1607347245114&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-router%2Fdownload%2Fvue-router-4.0.1.tgz", "resolved": "https://registry.npm.taobao.org/vue-router/download/vue-router-4.0.1.tgz?cache=0&sync_timestamp=1607347245114&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-router%2Fdownload%2Fvue-router-4.0.1.tgz",

View File

@ -0,0 +1,139 @@
@keyframes bounceIn {
from,
20%,
40%,
60%,
80%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: scale3d(0.3, 0.3, 0.3);
}
20% {
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
transform: scale3d(0.9, 0.9, 0.9);
}
60% {
opacity: 1;
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
transform: scale3d(0.97, 0.97, 0.97);
}
to {
opacity: 1;
transform: scale3d(1, 1, 1);
}
}
@keyframes bounceInDown {
from,
60%,
75%,
90%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: translate3d(0, -3000px, 0);
}
60% {
opacity: 1;
transform: translate3d(0, 25px, 0);
}
75% {
transform: translate3d(0, -10px, 0);
}
90% {
transform: translate3d(0, 5px, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes bounceInLeft {
from,
60%,
75%,
90%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: translate3d(-3000px, 0, 0);
}
60% {
opacity: 1;
transform: translate3d(25px, 0, 0);
}
75% {
transform: translate3d(-10px, 0, 0);
}
90% {
transform: translate3d(5px, 0, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes bounceInRight {
from,
60%,
75%,
90%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
from {
opacity: 0;
transform: translate3d(3000px, 0, 0);
}
60% {
opacity: 1;
transform: translate3d(-25px, 0, 0);
}
75% {
transform: translate3d(10px, 0, 0);
}
90% {
transform: translate3d(-5px, 0, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes bounceInUp {
from,
60%,
75%,
90%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
from {
opacity: 0;
transform: translate3d(0, 3000px, 0);
}
60% {
opacity: 1;
transform: translate3d(0, -20px, 0);
}
75% {
transform: translate3d(0, 10px, 0);
}
90% {
transform: translate3d(0, -5px, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}

View File

@ -0,0 +1,52 @@
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translate3d(0, -100%, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes fadeInLeft {
from {
opacity: 0;
transform: translate3d(-100%, 0, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes fadeInRight {
from {
opacity: 0;
transform: translate3d(100%, 0, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translate3d(0, 100%, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}

View File

@ -0,0 +1,43 @@
@keyframes flipInX {
from {
transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
animation-timing-function: ease-in;
opacity: 0;
}
40% {
transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
animation-timing-function: ease-in;
}
60% {
transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
opacity: 1;
}
80% {
transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
}
to {
transform: perspective(400px);
}
}
@keyframes flipInY {
from {
transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
animation-timing-function: ease-in;
opacity: 0;
}
40% {
transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
animation-timing-function: ease-in;
}
60% {
transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
opacity: 1;
}
80% {
transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
}
to {
transform: perspective(400px);
}
}

View File

@ -0,0 +1,103 @@
@import './bounce.scss';
@import './fade.scss';
@import './flip.scss';
@import './rotate.scss';
@import './slide.scss';
@import './zoom.scss';
.animate {
animation-duration: 0ms;
&.duration-500 {
animation-duration: 500ms;
}
&.duration-1000 {
animation-duration: 1000ms;
}
&.duration-1500 {
animation-duration: 1500ms;
}
&.duration-2000 {
animation-duration: 2000ms;
}
&.bounceIn {
animation-name: bounceIn;
}
&.bounceInDown {
animation-name: bounceInDown;
}
&.bounceInLeft {
animation-name: bounceInLeft;
}
&.bounceInRight {
animation-name: bounceInRight;
}
&.bounceInUp {
animation-name: bounceInUp;
}
&.fadeIn {
animation-name: fadeIn;
}
&.fadeInDown {
animation-name: fadeInDown;
}
&.fadeInLeft {
animation-name: fadeInLeft;
}
&.fadeInRight {
animation-name: fadeInRight;
}
&.fadeInUp {
animation-name: fadeInUp;
}
&.flipInX {
animation-name: flipInX;
}
&.flipInY {
animation-name: flipInY;
}
&.rotateIn {
animation-name: rotateIn;
}
&.rotateInDownLeft {
animation-name: rotateInDownLeft;
}
&.rotateInDownRight {
animation-name: rotateInDownRight;
}
&.rotateInUpLeft {
animation-name: rotateInUpLeft;
}
&.rotateInUpRight {
animation-name: rotateInUpRight;
}
&.slideInDown {
animation-name: slideInDown;
}
&.slideInLeft {
animation-name: slideInLeft;
}
&.slideInRight {
animation-name: slideInRight;
}
&.slideInUp {
animation-name: slideInUp;
}
&.zoomIn {
animation-name: zoomIn;
}
&.zoomInDown {
animation-name: zoomInDown;
}
&.zoomInLeft {
animation-name: zoomInLeft;
}
&.zoomInRight {
animation-name: zoomInRight;
}
&.zoomInUp {
animation-name: zoomInUp;
}
}

View File

@ -0,0 +1,64 @@
@keyframes rotateIn {
from {
transform-origin: center;
transform: rotate3d(0, 0, 1, -200deg);
opacity: 0;
}
to {
transform-origin: center;
transform: translate3d(0, 0, 0);
opacity: 1;
}
}
@keyframes rotateInDownLeft {
from {
transform-origin: left bottom;
transform: rotate3d(0, 0, 1, -45deg);
opacity: 0;
}
to {
transform-origin: left bottom;
transform: translate3d(0, 0, 0);
opacity: 1;
}
}
@keyframes rotateInDownRight {
from {
transform-origin: right bottom;
transform: rotate3d(0, 0, 1, 45deg);
opacity: 0;
}
to {
transform-origin: right bottom;
transform: translate3d(0, 0, 0);
opacity: 1;
}
}
@keyframes rotateInUpLeft {
from {
transform-origin: left bottom;
transform: rotate3d(0, 0, 1, 45deg);
opacity: 0;
}
to {
transform-origin: left bottom;
transform: translate3d(0, 0, 0);
opacity: 1;
}
}
@keyframes rotateInUpRight {
from {
transform-origin: right bottom;
transform: rotate3d(0, 0, 1, -90deg);
opacity: 0;
}
to {
transform-origin: right bottom;
transform: translate3d(0, 0, 0);
opacity: 1;
}
}

View File

@ -0,0 +1,39 @@
@keyframes slideInDown {
from {
transform: translate3d(0, -100%, 0);
visibility: visible;
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes slideInLeft {
from {
transform: translate3d(-100%, 0, 0);
visibility: visible;
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes slideInRight {
from {
transform: translate3d(100%, 0, 0);
visibility: visible;
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes slideInUp {
from {
transform: translate3d(0, 100%, 0);
visibility: visible;
}
to {
transform: translate3d(0, 0, 0);
}
}

View File

@ -0,0 +1,61 @@
@keyframes zoomIn {
from {
opacity: 0;
transform: scale3d(0.3, 0.3, 0.3);
}
50% {
opacity: 1;
}
}
@keyframes zoomInDown {
from {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomInLeft {
from {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomInRight {
from {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomInUp {
from {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}

View File

@ -0,0 +1,192 @@
<template>
<div
class="editable-element"
:id="'editable-element-' + elementInfo.elId"
:style="{ zIndex: elementIndex }"
>
<component
:is="currentElementComponent"
:elementInfo="elementInfo"
:canvasScale="canvasScale"
:isActive="isActive"
:isHandleEl="isHandleEl"
:isMultiSelect="isMultiSelect"
:selectElement="selectElement"
:rotateElement="rotateElement"
:scaleElement="scaleElement"
:contextmenus="contextmenus"
></component>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { PPTElement } from '@/types/slides'
import ImageElement from './ImageElement.index.vue'
import TextElement from './TextElement.index.vue'
export default defineComponent({
name: 'editable-element',
props: {
elementInfo: {
type: Object as PropType<PPTElement>,
required: true,
},
elementIndex: {
type: Number,
required: true,
},
canvasScale: {
type: Number,
required: true,
},
isActive: {
type: Boolean,
required: true,
},
isHandleEl: {
type: Boolean,
required: true,
},
isMultiSelect: {
type: Boolean,
required: true,
},
selectElement: {
type: Function as PropType<(e: MouseEvent, element: PPTElement, canMove: boolean) => void>,
required: true,
},
rotateElement: {
type: Function as PropType<(element: PPTElement) => void>,
required: true,
},
scaleElement: {
type: Function as PropType<(e: MouseEvent, element: PPTElement, direction: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9) => void>,
required: true,
},
updateZIndex: {
type: Function as PropType<(element: PPTElement, operation: 'up' | 'down' | 'top' | 'bottom') => void>,
required: true,
},
combineElements: {
type: Function as PropType<() => void>,
required: true,
},
uncombineElements: {
type: Function as PropType<() => void>,
required: true,
},
alignElement: {
type: Function as PropType<(direction: 'top' | 'verticalCenter' | 'bottom' | 'left' | 'horizontalCenter' | 'right') => void>,
required: true,
},
deleteElement: {
type: Function as PropType<() => void>,
required: true,
},
lockElement: {
type: Function as PropType<(element: PPTElement, handle: 'lock' | 'unlock') => void>,
required: true,
},
copyElement: {
type: Function as PropType<() => void>,
required: true,
},
cutElement: {
type: Function as PropType<() => void>,
required: true,
},
},
setup(props) {
const currentElementComponent = computed(() => {
const elementTypeMap = {
'image': ImageElement,
'text': TextElement,
}
return elementTypeMap[props.elementInfo.type] || null
})
const contextmenus = () => {
if(props.elementInfo.isLock) {
return [{
text: '解锁',
icon: 'icon-unlock',
action: () => props.lockElement(props.elementInfo, 'unlock'),
}]
}
return [
{
text: '剪切',
subText: 'Ctrl + X',
icon: 'icon-scissor',
action: props.cutElement,
},
{
text: '复制',
subText: 'Ctrl + C',
icon: 'icon-copy',
action: props.copyElement,
},
{ divider: true },
{
text: '层级',
icon: 'icon-top-layer',
disable: props.isMultiSelect && !props.elementInfo.groupId,
children: [
{ text: '置顶层', action: () => props.updateZIndex(props.elementInfo, 'top') },
{ text: '置底层', action: () => props.updateZIndex(props.elementInfo, 'bottom') },
{ divider: true },
{ text: '上移一层', action: () => props.updateZIndex(props.elementInfo, 'up') },
{ text: '下移一层', action: () => props.updateZIndex(props.elementInfo, 'down') },
],
},
{
text: '水平对齐',
icon: 'icon-align-left',
children: [
{ text: '水平居中', action: () => props.alignElement('horizontalCenter') },
{ text: '左对齐', action: () => props.alignElement('left') },
{ text: '右对齐', action: () => props.alignElement('right') },
],
},
{
text: '垂直对齐',
icon: 'icon-align-bottom',
children: [
{ text: '垂直居中', action: () => props.alignElement('verticalCenter') },
{ text: '上对齐', action: () => props.alignElement('top') },
{ text: '下对齐', action: () => props.alignElement('bottom') },
],
},
{ divider: true },
{
text: props.elementInfo.groupId ? '取消组合' : '组合',
subText: 'Ctrl + G',
icon: 'icon-block',
action: props.elementInfo.groupId ? props.uncombineElements : props.combineElements,
hide: !props.isMultiSelect,
},
{
text: '锁定',
subText: 'Ctrl + L',
icon: 'icon-lock',
action: () => props.lockElement(props.elementInfo, 'lock'),
},
{
text: '删除',
subText: 'Delete',
icon: 'icon-delete',
action: () => props.deleteElement(),
},
]
}
return {
currentElementComponent,
contextmenus,
}
},
})
</script>

View File

@ -0,0 +1,54 @@
<template>
<SvgWrapper
class="element-border"
overflow="visible"
:width="width"
:height="height"
>
<path
vector-effect="non-scaling-stroke"
stroke-linecap="butt"
stroke-miterlimit="8"
stroke-linejoin
fill="transparent"
:d="`M0,0 L${width},0 L${width},${height} L0,${height} Z`"
:stroke="borderColor"
:stroke-width="borderWidth"
:stroke-dasharray="borderStyle === 'dashed' ? '12 9' : '0 0'"
></path>
</SvgWrapper>
</template>
<script lang="ts">
export default {
name: 'element-border',
props: {
width: {
type: Number,
required: true,
},
height: {
type: Number,
required: true,
},
borderColor: {
type: String,
},
borderWidth: {
type: Number,
},
borderStyle: {
type: String,
},
},
}
</script>
<style lang="scss" scoped>
svg {
overflow: visible;
position: absolute;
top: 0;
left: 0;
}
</style>

View File

@ -0,0 +1,499 @@
<template>
<div
class="image-clip-handler"
:style="clipWrapperPositionStyle"
v-click-outside="clip"
>
<img
class="bottom-img"
:src="imgUrl"
:draggable="false"
alt=""
:style="bottomImgPositionStyle"
/>
<div
class="top-img-wrapper"
:style="{
...topImgWrapperPositionStyle,
clipPath: clipPath,
}"
>
<img
class="top-img"
:src="imgUrl"
:draggable="false"
alt=""
:style="topImgPositionStyle"
/>
</div>
<div
class="operate"
:style="topImgWrapperPositionStyle"
@mousedown.stop="$event => moveClipRange($event)"
>
<div
:class="['clip-point', point]"
v-for="point in ['t-l', 't-r', 'b-l', 'b-r']"
:key="point"
@mousedown.stop="$event => scaleClipRange($event, point)"
>
<SvgWrapper width="12" height="12" fill="#fff" stroke="#666">
<path d="M 12 0 L 0 0 L 0 12 L 3 12 L 3 3 L 12 3 L 12 0 Z"></path>
</SvgWrapper>
</div>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, onUnmounted, PropType, reactive, ref } from 'vue'
import { KEYCODE } from '@/configs/keyCode'
type ClipDataRange = [[number, number], [number, number]]
interface ClipData {
range: ClipDataRange;
path: string;
}
type ScaleClipRangeType = 't-l' | 't-r' | 'b-l' | 'b-r'
export default defineComponent({
name: 'image-clip-handler',
props: {
imgUrl: {
type: String,
required: true,
},
clipData: {
type: Object as PropType<ClipData>,
required: true,
},
clipPath: {
type: String,
required: true,
},
canvasScale: {
type: Number,
required: true,
},
width: {
type: Number,
required: true,
},
height: {
type: Number,
required: true,
},
top: {
type: Number,
required: true,
},
left: {
type: Number,
required: true,
},
},
setup(props, { emit }) {
const topImgWrapperPosition = reactive({
top: 0,
left: 0,
width: 0,
height: 0,
})
const clipWrapperPositionStyle = reactive({
top: '0',
left: '0',
})
const isSettingClipRange = ref(false)
const currentRange = ref<ClipDataRange | null>(null)
const getClipDataTransformInfo = () => {
const [start, end] = props.clipData ? props.clipData.range : [[0, 0], [100, 100]]
const widthScale = (end[0] - start[0]) / 100
const heightScale = (end[1] - start[1]) / 100
const left = start[0] / widthScale
const top = start[1] / heightScale
return { widthScale, heightScale, left, top }
}
const imgPosition = computed(() => {
const { widthScale, heightScale, left, top } = getClipDataTransformInfo()
return {
left: -left,
top: -top,
width: 100 / widthScale,
height: 100 / heightScale,
}
})
const bottomImgPositionStyle = computed(() => {
return {
top: imgPosition.value.top + '%',
left: imgPosition.value.left + '%',
width: imgPosition.value.width + '%',
height: imgPosition.value.height + '%',
}
})
const topImgWrapperPositionStyle = computed(() => {
return {
top: topImgWrapperPosition.top + '%',
left: topImgWrapperPosition.left + '%',
width: topImgWrapperPosition.width + '%',
height: topImgWrapperPosition.height + '%',
}
})
const topImgPositionStyle = computed(() => {
const bottomWidth = imgPosition.value.width
const bottomHeight = imgPosition.value.height
const topLeft = topImgWrapperPosition.left
const topTop = topImgWrapperPosition.top
const topWidth = topImgWrapperPosition.width
const topHeight = topImgWrapperPosition.height
return {
left: -topLeft * (100 / topWidth) + '%',
top: -topTop * (100 / topHeight) + '%',
width: bottomWidth / topWidth * 100 + '%',
height: bottomHeight / topHeight * 100 + '%',
}
})
const initClipPosition = () => {
const { left, top } = getClipDataTransformInfo()
topImgWrapperPosition.left = left
topImgWrapperPosition.top = top
topImgWrapperPosition.width = 100
topImgWrapperPosition.height = 100
clipWrapperPositionStyle.top = -top + '%'
clipWrapperPositionStyle.left = -left + '%'
}
const clip = () => {
if(isSettingClipRange.value) return
if(!currentRange.value) {
emit('clip', null)
return
}
const { left, top } = getClipDataTransformInfo()
const position = {
left: (topImgWrapperPosition.left - left) / 100 * props.width,
top: (topImgWrapperPosition.top - top) / 100 * props.height,
width: (topImgWrapperPosition.width - 100) / 100 * props.width,
height: (topImgWrapperPosition.height - 100) / 100 * props.height,
}
emit('clip', {
range: currentRange.value,
position,
})
}
const keyboardClip = (e: KeyboardEvent) => {
if(e.keyCode === KEYCODE.ENTER) clip()
}
onMounted(() => {
initClipPosition()
document.addEventListener('keydown', keyboardClip)
})
onUnmounted(() => {
document.removeEventListener('keydown', keyboardClip)
})
const getRange = () => {
const retPosition = {
left: parseInt(topImgPositionStyle.value.left),
top: parseInt(topImgPositionStyle.value.top),
width: parseInt(topImgPositionStyle.value.width),
height: parseInt(topImgPositionStyle.value.height),
}
const widthScale = 100 / retPosition.width
const heightScale = 100 / retPosition.height
const start: [number, number] = [
-retPosition.left * widthScale,
-retPosition.top * heightScale,
]
const end: [number, number] = [
widthScale * 100 + start[0],
heightScale * 100 + start[1],
]
currentRange.value = [start, end]
}
const moveClipRange = (e: MouseEvent) => {
isSettingClipRange.value = true
let isMouseDown = true
const startPageX = e.pageX
const startPageY = e.pageY
const bottomPosition = imgPosition.value
const originPositopn = {
left: topImgWrapperPosition.left,
top: topImgWrapperPosition.top,
width: topImgWrapperPosition.width,
height: topImgWrapperPosition.height,
}
document.onmousemove = e => {
if(!isMouseDown) return
const currentPageX = e.pageX
const currentPageY = e.pageY
const moveX = (currentPageX - startPageX) / props.canvasScale / props.width * 100
const moveY = (currentPageY - startPageY) / props.canvasScale / props.height * 100
let targetLeft = originPositopn.left + moveX
let targetTop = originPositopn.top + moveY
//
if(targetLeft < 0) targetLeft = 0
else if(targetLeft + originPositopn.width > bottomPosition.width) {
targetLeft = bottomPosition.width - originPositopn.width
}
if(targetTop < 0) targetTop = 0
else if(targetTop + originPositopn.height > bottomPosition.height) {
targetTop = bottomPosition.height - originPositopn.height
}
topImgWrapperPosition.left = targetLeft
topImgWrapperPosition.top = targetTop
}
document.onmouseup = () => {
isMouseDown = false
document.onmousemove = null
document.onmouseup = null
getRange()
setTimeout(() => {
isSettingClipRange.value = false
}, 0)
}
}
const scaleClipRange = (e: MouseEvent, type: ScaleClipRangeType) => {
isSettingClipRange.value = true
let isMouseDown = true
const minWidth = 32 / props.width * 100
const minHeight = 32 / props.height * 100
const startPageX = e.pageX
const startPageY = e.pageY
const bottomPosition = imgPosition.value
const originPositopn = {
left: topImgWrapperPosition.left,
top: topImgWrapperPosition.top,
width: topImgWrapperPosition.width,
height: topImgWrapperPosition.height,
}
document.onmousemove = e => {
if(!isMouseDown) return
const currentPageX = e.pageX
const currentPageY = e.pageY
let moveX = (currentPageX - startPageX) / props.canvasScale / props.width * 100
let moveY = (currentPageY - startPageY) / props.canvasScale / props.height * 100
let targetLeft, targetTop, targetWidth, targetHeight
//
if(type === 't-l') {
if(originPositopn.left + moveX < 0) {
moveX = -originPositopn.left
}
if(originPositopn.top + moveY < 0) {
moveY = -originPositopn.top
}
if(originPositopn.width - moveX < minWidth) {
moveX = originPositopn.width - minWidth
}
if(originPositopn.height - moveY < minHeight) {
moveY = originPositopn.height - minHeight
}
targetWidth = originPositopn.width - moveX
targetHeight = originPositopn.height - moveY
targetLeft = originPositopn.left + moveX
targetTop = originPositopn.top + moveY
}
else if(type === 't-r') {
if(originPositopn.left + originPositopn.width + moveX > bottomPosition.width) {
moveX = bottomPosition.width - (originPositopn.left + originPositopn.width)
}
if(originPositopn.top + moveY < 0) {
moveY = -originPositopn.top
}
if(originPositopn.width + moveX < minWidth) {
moveX = minWidth - originPositopn.width
}
if(originPositopn.height - moveY < minHeight) {
moveY = originPositopn.height - minHeight
}
targetWidth = originPositopn.width + moveX
targetHeight = originPositopn.height - moveY
targetLeft = originPositopn.left
targetTop = originPositopn.top + moveY
}
else if(type === 'b-l') {
if(originPositopn.left + moveX < 0) {
moveX = -originPositopn.left
}
if(originPositopn.top + originPositopn.height + moveY > bottomPosition.height) {
moveY = bottomPosition.height - (originPositopn.top + originPositopn.height)
}
if(originPositopn.width - moveX < minWidth) {
moveX = originPositopn.width - minWidth
}
if(originPositopn.height + moveY < minHeight) {
moveY = minHeight - originPositopn.height
}
targetWidth = originPositopn.width - moveX
targetHeight = originPositopn.height + moveY
targetLeft = originPositopn.left + moveX
targetTop = originPositopn.top
}
else {
if(originPositopn.left + originPositopn.width + moveX > bottomPosition.width) {
moveX = bottomPosition.width - (originPositopn.left + originPositopn.width)
}
if(originPositopn.top + originPositopn.height + moveY > bottomPosition.height) {
moveY = bottomPosition.height - (originPositopn.top + originPositopn.height)
}
if(originPositopn.width + moveX < minWidth) {
moveX = minWidth - originPositopn.width
}
if(originPositopn.height + moveY < minHeight) {
moveY = minHeight - originPositopn.height
}
targetWidth = originPositopn.width + moveX
targetHeight = originPositopn.height + moveY
targetLeft = originPositopn.left
targetTop = originPositopn.top
}
topImgWrapperPosition.left = targetLeft
topImgWrapperPosition.top = targetTop
topImgWrapperPosition.width = targetWidth
topImgWrapperPosition.height = targetHeight
}
document.onmouseup = () => {
isMouseDown = false
document.onmousemove = null
document.onmouseup = null
getRange()
setTimeout(() => isSettingClipRange.value = false, 0)
}
}
return {
clipWrapperPositionStyle,
bottomImgPositionStyle,
topImgWrapperPositionStyle,
topImgPositionStyle,
clip,
moveClipRange,
scaleClipRange,
}
},
})
</script>
<style lang="scss" scoped>
.image-clip-handler {
width: 100%;
height: 100%;
position: relative;
.bottom-img {
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: .5;
}
img {
width: 100%;
height: 100%;
}
.top-img-wrapper {
position: absolute;
overflow: hidden;
img {
position: absolute;
}
}
}
.operate {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
cursor: move;
}
.clip-point {
position: absolute;
width: 12px;
height: 12px;
left: 0;
top: 0;
transform-origin: 0 0;
display: flex;
justify-content: center;
align-items: center;
svg {
overflow: visible;
}
&.t-l {
cursor: nwse-resize;
left: 0;
top: 0;
}
&.t-r {
cursor: nesw-resize;
left: 100%;
top: 0;
transform: rotate(90deg);
}
&.b-l {
cursor: nesw-resize;
left: 0;
top: 100%;
transform: rotate(-90deg);
}
&.b-r {
cursor: nwse-resize;
left: 100%;
top: 100%;
transform: rotate(180deg);
}
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<SvgWrapper
class="image-ellipse-border"
overflow="visible"
:width="width"
:height="height"
>
<ellipse
vector-effect="non-scaling-stroke"
stroke-linecap="butt"
stroke-miterlimit="8"
stroke-linejoin
fill="transparent"
:cx="width / 2"
:cy="height / 2"
:rx="width / 2"
:ry="height / 2"
:stroke="borderColor"
:stroke-width="borderWidth"
:stroke-dasharray="borderStyle === 'dashed' ? '12 9' : '0 0'"
></ellipse>
</SvgWrapper>
</template>
<script lang="ts">
export default {
name: 'image-ellipse-border',
props: {
width: {
type: Number,
required: true,
},
height: {
type: Number,
required: true,
},
borderColor: {
type: String,
default: '',
},
borderWidth: {
type: Number,
default: 0,
},
borderStyle: {
type: String,
default: '',
},
},
}
</script>
<style lang="scss" scoped>
svg {
overflow: visible;
position: absolute;
z-index: 2;
top: 0;
left: 0;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<SvgWrapper
class="image-polygon-border"
overflow="visible"
:width="width"
:height="height"
>
<path
vector-effect="non-scaling-stroke"
stroke-linecap="butt"
stroke-miterlimit="8"
stroke-linejoin
fill="transparent"
:d="createPath(width, height)"
:stroke="borderColor"
:stroke-width="borderWidth"
:stroke-dasharray="borderStyle === 'dashed' ? '12 9' : '0 0'"
></path>
</SvgWrapper>
</template>
<script lang="ts">
export default {
name: 'image-polygon-border',
props: {
width: {
type: Number,
required: true,
},
height: {
type: Number,
required: true,
},
borderColor: {
type: String,
default: '',
},
borderWidth: {
type: Number,
default: 0,
},
borderStyle: {
type: String,
default: '',
},
createPath: {
type: Function,
required: true,
},
},
}
</script>
<style lang="scss" scoped>
svg {
overflow: visible;
position: absolute;
z-index: 2;
top: 0;
left: 0;
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<SvgWrapper
class="image-rect-border"
overflow="visible"
:width="width"
:height="height"
>
<rect
vector-effect="non-scaling-stroke"
stroke-linecap="butt"
stroke-miterlimit="8"
stroke-linejoin
fill="transparent"
:rx="radius"
:ry="radius"
:width="width"
:height="height"
:stroke="borderColor"
:stroke-width="borderWidth"
:stroke-dasharray="borderStyle === 'dashed' ? '12 9' : '0 0'"
></rect>
</SvgWrapper>
</template>
<script lang="ts">
export default {
name: 'image-rect-border',
props: {
width: {
type: Number,
required: true,
},
height: {
type: Number,
required: true,
},
borderColor: {
type: String,
default: '',
},
borderWidth: {
type: Number,
default: 0,
},
borderStyle: {
type: String,
default: '',
},
radius: {
type: String,
default: '0',
},
},
}
</script>
<style lang="scss" scoped>
svg {
overflow: visible;
position: absolute;
z-index: 2;
top: 0;
left: 0;
}
</style>