1.shopxo-diy装修项目,代码引入
sws 2024-08-12
19
.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
.history
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.local
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
pnpm-lock.yaml
|
||||||
|
stats.html
|
||||||
46
README.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# vue3-vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||||
|
|
||||||
|
## Type Support for `.vue` Imports in TS
|
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
|
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||||
|
|
||||||
|
1. Disable the built-in TypeScript Extension
|
||||||
|
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||||
|
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||||
|
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint with [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
16
index.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" style="overflow: hidden;">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>装修</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
57
package.json
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"name": "vue3-vite",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"build-pro": "vite build --mode production",
|
||||||
|
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"format": "prettier --write src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/dompurify": "^3.0.5",
|
||||||
|
"@vueuse/core": "^10.2.1",
|
||||||
|
"@wangeditor/editor": "^5.1.23",
|
||||||
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"dompurify": "^3.1.6",
|
||||||
|
"element-plus": "^2.3.7",
|
||||||
|
"pinia": "^2.1.3",
|
||||||
|
"qrcode": "^1.5.3",
|
||||||
|
"swiper": "^11.1.5",
|
||||||
|
"terser": "^5.31.5",
|
||||||
|
"tsparticles": "^2.11.0",
|
||||||
|
"unocss": "^0.53.5",
|
||||||
|
"vue": "^3.3.4",
|
||||||
|
"vue-draggable-plus": "^0.5.0",
|
||||||
|
"vue-router": "^4.0.13",
|
||||||
|
"vue3-draggable-resizable": "^1.6.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@iconify-json/ep": "^1.1.11",
|
||||||
|
"@rushstack/eslint-patch": "^1.2.0",
|
||||||
|
"@tsconfig/node18": "^2.0.1",
|
||||||
|
"@types/sortablejs": "^1.15.8",
|
||||||
|
"@vitejs/plugin-vue": "^4.2.3",
|
||||||
|
"@vue/eslint-config-prettier": "^7.1.0",
|
||||||
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
|
"@vue/tsconfig": "^0.4.0",
|
||||||
|
"eslint": "^8.39.0",
|
||||||
|
"eslint-plugin-vue": "^9.11.0",
|
||||||
|
"fast-glob": "3.2.11",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"prettier": "^3.0.0",
|
||||||
|
"sass": "^1.63.6",
|
||||||
|
"typescript": "~5.0.4",
|
||||||
|
"unplugin-auto-import": "^0.16.6",
|
||||||
|
"unplugin-icons": "^0.16.3",
|
||||||
|
"unplugin-vue-components": "^0.25.1",
|
||||||
|
"vite": "^4.3.9",
|
||||||
|
"vite-plugin-svg-icons": "2.0.1",
|
||||||
|
"vue-tsc": "^1.6.5",
|
||||||
|
"vue3-particles": "^2.10.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
6
src/App.vue
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
23
src/api/auth/types.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* 登录请求参数
|
||||||
|
*/
|
||||||
|
export interface LoginData {
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
username: string;
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码缓存key
|
||||||
|
*/
|
||||||
|
// verifyCodeKey?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码
|
||||||
|
*/
|
||||||
|
// verifyCode?: string;
|
||||||
|
}
|
||||||
539
src/assets/icons/demo.css
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
/* Logo 字体 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "iconfont logo";
|
||||||
|
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
|
||||||
|
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
|
||||||
|
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
|
||||||
|
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
|
||||||
|
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-family: "iconfont logo";
|
||||||
|
font-size: 160px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tabs */
|
||||||
|
.nav-tabs {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-more {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 42px;
|
||||||
|
line-height: 42px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabs {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabs li {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#tabs .active {
|
||||||
|
border-bottom-color: #f00;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-container .content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面布局 */
|
||||||
|
.main {
|
||||||
|
padding: 30px 100px;
|
||||||
|
width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .logo {
|
||||||
|
color: #333;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
line-height: 1;
|
||||||
|
height: 110px;
|
||||||
|
margin-top: -50px;
|
||||||
|
overflow: hidden;
|
||||||
|
*zoom: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .logo a {
|
||||||
|
font-size: 160px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helps {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helps pre {
|
||||||
|
padding: 20px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: solid 1px #e7e1cd;
|
||||||
|
background-color: #fffdef;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists {
|
||||||
|
width: 100% !important;
|
||||||
|
overflow: hidden;
|
||||||
|
*zoom: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists li {
|
||||||
|
width: 100px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-right: 20px;
|
||||||
|
text-align: center;
|
||||||
|
list-style: none !important;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists li .code-name {
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists .icon {
|
||||||
|
display: block;
|
||||||
|
height: 100px;
|
||||||
|
line-height: 100px;
|
||||||
|
font-size: 42px;
|
||||||
|
margin: 10px auto;
|
||||||
|
color: #333;
|
||||||
|
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
|
||||||
|
-moz-transition: font-size 0.25s linear, width 0.25s linear;
|
||||||
|
transition: font-size 0.25s linear, width 0.25s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists .icon:hover {
|
||||||
|
font-size: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists .svg-icon {
|
||||||
|
/* 通过设置 font-size 来改变图标大小 */
|
||||||
|
width: 1em;
|
||||||
|
/* 图标和文字相邻时,垂直对齐 */
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
/* 通过设置 color 来改变 SVG 的颜色/fill */
|
||||||
|
fill: currentColor;
|
||||||
|
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
|
||||||
|
normalize.css 中也包含这行 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists li .name,
|
||||||
|
.icon_lists li .code-name {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* markdown 样式 */
|
||||||
|
.markdown {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown img {
|
||||||
|
vertical-align: middle;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1 {
|
||||||
|
color: #404040;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 40px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h2,
|
||||||
|
.markdown h3,
|
||||||
|
.markdown h4,
|
||||||
|
.markdown h5,
|
||||||
|
.markdown h6 {
|
||||||
|
color: #404040;
|
||||||
|
margin: 1.6em 0 0.6em 0;
|
||||||
|
font-weight: 500;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h2 {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h4 {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h5 {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h6 {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown hr {
|
||||||
|
height: 1px;
|
||||||
|
border: 0;
|
||||||
|
background: #e9e9e9;
|
||||||
|
margin: 16px 0;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown p {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>p,
|
||||||
|
.markdown>blockquote,
|
||||||
|
.markdown>.highlight,
|
||||||
|
.markdown>ol,
|
||||||
|
.markdown>ul {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown ul>li {
|
||||||
|
list-style: circle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>ul li,
|
||||||
|
.markdown blockquote ul>li {
|
||||||
|
margin-left: 20px;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>ul li p,
|
||||||
|
.markdown>ol li p {
|
||||||
|
margin: 0.6em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown ol>li {
|
||||||
|
list-style: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>ol li,
|
||||||
|
.markdown blockquote ol>li {
|
||||||
|
margin-left: 20px;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown code {
|
||||||
|
margin: 0 3px;
|
||||||
|
padding: 0 5px;
|
||||||
|
background: #eee;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown strong,
|
||||||
|
.markdown b {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0px;
|
||||||
|
empty-cells: show;
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
width: 95%;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table th {
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table th,
|
||||||
|
.markdown>table td {
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
padding: 8px 16px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table th {
|
||||||
|
background: #F7F7F7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown blockquote {
|
||||||
|
font-size: 90%;
|
||||||
|
color: #999;
|
||||||
|
border-left: 4px solid #e9e9e9;
|
||||||
|
padding-left: 0.8em;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown blockquote p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown .anchor {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown .waiting {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1:hover .anchor,
|
||||||
|
.markdown h2:hover .anchor,
|
||||||
|
.markdown h3:hover .anchor,
|
||||||
|
.markdown h4:hover .anchor,
|
||||||
|
.markdown h5:hover .anchor,
|
||||||
|
.markdown h6:hover .anchor {
|
||||||
|
opacity: 1;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>br,
|
||||||
|
.markdown>p>br {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
display: block;
|
||||||
|
background: white;
|
||||||
|
padding: 0.5em;
|
||||||
|
color: #333333;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-meta {
|
||||||
|
color: #969896;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-strong,
|
||||||
|
.hljs-emphasis,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #df5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag,
|
||||||
|
.hljs-type {
|
||||||
|
color: #a71d5d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-attribute {
|
||||||
|
color: #0086b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-section,
|
||||||
|
.hljs-name {
|
||||||
|
color: #63a35c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-tag {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-attr,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-selector-attr,
|
||||||
|
.hljs-selector-pseudo {
|
||||||
|
color: #795da3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-addition {
|
||||||
|
color: #55a532;
|
||||||
|
background-color: #eaffea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-deletion {
|
||||||
|
color: #bd2c00;
|
||||||
|
background-color: #ffecec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-link {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 代码高亮 */
|
||||||
|
/* PrismJS 1.15.0
|
||||||
|
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
|
||||||
|
/**
|
||||||
|
* prism.js default theme for JavaScript, CSS and HTML
|
||||||
|
* Based on dabblet (http://dabblet.com)
|
||||||
|
* @author Lea Verou
|
||||||
|
*/
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: black;
|
||||||
|
background: none;
|
||||||
|
text-shadow: 0 1px white;
|
||||||
|
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-moz-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]::-moz-selection,
|
||||||
|
pre[class*="language-"] ::-moz-selection,
|
||||||
|
code[class*="language-"]::-moz-selection,
|
||||||
|
code[class*="language-"] ::-moz-selection {
|
||||||
|
text-shadow: none;
|
||||||
|
background: #b3d4fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]::selection,
|
||||||
|
pre[class*="language-"] ::selection,
|
||||||
|
code[class*="language-"]::selection,
|
||||||
|
code[class*="language-"] ::selection {
|
||||||
|
text-shadow: none;
|
||||||
|
background: #b3d4fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
pre[class*="language-"] {
|
||||||
|
padding: 1em;
|
||||||
|
margin: .5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(pre)>code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
background: #f5f2f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
:not(pre)>code[class*="language-"] {
|
||||||
|
padding: .1em;
|
||||||
|
border-radius: .3em;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
color: slategray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace {
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property,
|
||||||
|
.token.tag,
|
||||||
|
.token.boolean,
|
||||||
|
.token.number,
|
||||||
|
.token.constant,
|
||||||
|
.token.symbol,
|
||||||
|
.token.deleted {
|
||||||
|
color: #905;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.selector,
|
||||||
|
.token.attr-name,
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.builtin,
|
||||||
|
.token.inserted {
|
||||||
|
color: #690;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.operator,
|
||||||
|
.token.entity,
|
||||||
|
.token.url,
|
||||||
|
.language-css .token.string,
|
||||||
|
.style .token.string {
|
||||||
|
color: #9a6e3a;
|
||||||
|
background: hsla(0, 0%, 100%, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.atrule,
|
||||||
|
.token.attr-value,
|
||||||
|
.token.keyword {
|
||||||
|
color: #07a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.function,
|
||||||
|
.token.class-name {
|
||||||
|
color: #DD4A68;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.regex,
|
||||||
|
.token.important,
|
||||||
|
.token.variable {
|
||||||
|
color: #e90;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.important,
|
||||||
|
.token.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
1546
src/assets/icons/demo_index.html
Normal file
320
src/assets/icons/iconfont.css
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "iconfont"; /* Project id 4607934 */
|
||||||
|
src: url('iconfont.woff2?t=1723196559821') format('woff2'),
|
||||||
|
url('iconfont.woff?t=1723196559821') format('woff'),
|
||||||
|
url('iconfont.ttf?t=1723196559821') format('truetype'),
|
||||||
|
url('iconfont.svg?t=1723196559821#iconfont') format('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-family: "iconfont" !important;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tips:before {
|
||||||
|
content: "\e74a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-arrow-right-dbl:before {
|
||||||
|
content: "\e7b6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-EXE:before {
|
||||||
|
content: "\e782";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-txt:before {
|
||||||
|
content: "\e786";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-a-1ge:before {
|
||||||
|
content: "\e794";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-heng2:before {
|
||||||
|
content: "\e789";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-a-4x4:before {
|
||||||
|
content: "\e78a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-zuo1you2:before {
|
||||||
|
content: "\e78b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shu3:before {
|
||||||
|
content: "\e78c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shang2xia1:before {
|
||||||
|
content: "\e78d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-zuo1youshang1youxia2:before {
|
||||||
|
content: "\e78e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shang1xia2:before {
|
||||||
|
content: "\e78f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tianzige:before {
|
||||||
|
content: "\e790";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shang2xia3:before {
|
||||||
|
content: "\e792";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-zuo2you1:before {
|
||||||
|
content: "\e793";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shu2:before {
|
||||||
|
content: "\e77b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-upload:before {
|
||||||
|
content: "\e72b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-true:before {
|
||||||
|
content: "\e741";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-cart:before {
|
||||||
|
content: "\e791";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-pdf:before {
|
||||||
|
content: "\e787";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-ppt:before {
|
||||||
|
content: "\e788";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-word:before {
|
||||||
|
content: "\e785";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-vsd:before {
|
||||||
|
content: "\e783";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-apk:before {
|
||||||
|
content: "\e784";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-vf:before {
|
||||||
|
content: "\e77d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-video:before {
|
||||||
|
content: "\e77e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-zip:before {
|
||||||
|
content: "\e77f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-ipa:before {
|
||||||
|
content: "\e780";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-excel:before {
|
||||||
|
content: "\e781";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-file:before {
|
||||||
|
content: "\e77c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-error-img:before {
|
||||||
|
content: "\e779";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-folder:before {
|
||||||
|
content: "\e77a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-true-o:before {
|
||||||
|
content: "\e777";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-checked-1:before {
|
||||||
|
content: "\e778";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-eye-close:before {
|
||||||
|
content: "\e775";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-eye:before {
|
||||||
|
content: "\e776";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-position:before {
|
||||||
|
content: "\e6af";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-add:before {
|
||||||
|
content: "\e71b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-arrow-left:before {
|
||||||
|
content: "\e73f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-arrow-right:before {
|
||||||
|
content: "\e737";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-arrow-top:before {
|
||||||
|
content: "\e738";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-radius-l-b:before {
|
||||||
|
content: "\e774";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-radius-r-t:before {
|
||||||
|
content: "\e773";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-img:before {
|
||||||
|
content: "\e768";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-out-l:before {
|
||||||
|
content: "\e769";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-out-b:before {
|
||||||
|
content: "\e76a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-del:before {
|
||||||
|
content: "\e76b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-drag:before {
|
||||||
|
content: "\e76c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-out-r:before {
|
||||||
|
content: "\e76d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-line-point:before {
|
||||||
|
content: "\e76e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-del-o:before {
|
||||||
|
content: "\e76f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-copy:before {
|
||||||
|
content: "\e770";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-line:before {
|
||||||
|
content: "\e771";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-close-o:before {
|
||||||
|
content: "\e772";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-text:before {
|
||||||
|
content: "\e766";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-auxiliary-line:before {
|
||||||
|
content: "\e767";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-close:before {
|
||||||
|
content: "\e764";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-edit:before {
|
||||||
|
content: "\e765";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-radius-r-b:before {
|
||||||
|
content: "\e758";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-arrow-bottom:before {
|
||||||
|
content: "\e759";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-elliptic:before {
|
||||||
|
content: "\e75a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-out-t:before {
|
||||||
|
content: "\e75b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-enter-r:before {
|
||||||
|
content: "\e75c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-single-sheet:before {
|
||||||
|
content: "\e752";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-spread-over:before {
|
||||||
|
content: "\e753";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-enter-l:before {
|
||||||
|
content: "\e75d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-round-dot:before {
|
||||||
|
content: "\e75e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-enter-b:before {
|
||||||
|
content: "\e75f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-search:before {
|
||||||
|
content: "\e760";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tile:before {
|
||||||
|
content: "\e761";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-center:before {
|
||||||
|
content: "\e762";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-enter-t:before {
|
||||||
|
content: "\e763";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-left:before {
|
||||||
|
content: "\e755";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-reset:before {
|
||||||
|
content: "\e756";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-radius-l-t:before {
|
||||||
|
content: "\e757";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-right:before {
|
||||||
|
content: "\e754";
|
||||||
|
}
|
||||||
|
|
||||||
1
src/assets/icons/iconfont.js
Normal file
541
src/assets/icons/iconfont.json
Normal file
@ -0,0 +1,541 @@
|
|||||||
|
{
|
||||||
|
"id": "4607934",
|
||||||
|
"name": "装修拖拽",
|
||||||
|
"font_family": "iconfont",
|
||||||
|
"css_prefix_text": "icon-",
|
||||||
|
"description": "web端",
|
||||||
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "40405526",
|
||||||
|
"name": "问号",
|
||||||
|
"font_class": "tips",
|
||||||
|
"unicode": "e74a",
|
||||||
|
"unicode_decimal": 59210
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41252801",
|
||||||
|
"name": "双箭头 右",
|
||||||
|
"font_class": "arrow-right-dbl",
|
||||||
|
"unicode": "e7b6",
|
||||||
|
"unicode_decimal": 59318
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41231177",
|
||||||
|
"name": "EXE",
|
||||||
|
"font_class": "EXE",
|
||||||
|
"unicode": "e782",
|
||||||
|
"unicode_decimal": 59266
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41231176",
|
||||||
|
"name": "TXT",
|
||||||
|
"font_class": "txt",
|
||||||
|
"unicode": "e786",
|
||||||
|
"unicode_decimal": 59270
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230988",
|
||||||
|
"name": "1个",
|
||||||
|
"font_class": "a-1ge",
|
||||||
|
"unicode": "e794",
|
||||||
|
"unicode_decimal": 59284
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230997",
|
||||||
|
"name": "横2",
|
||||||
|
"font_class": "heng2",
|
||||||
|
"unicode": "e789",
|
||||||
|
"unicode_decimal": 59273
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230995",
|
||||||
|
"name": "4x4",
|
||||||
|
"font_class": "a-4x4",
|
||||||
|
"unicode": "e78a",
|
||||||
|
"unicode_decimal": 59274
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230998",
|
||||||
|
"name": "左1右2",
|
||||||
|
"font_class": "zuo1you2",
|
||||||
|
"unicode": "e78b",
|
||||||
|
"unicode_decimal": 59275
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230996",
|
||||||
|
"name": "竖3",
|
||||||
|
"font_class": "shu3",
|
||||||
|
"unicode": "e78c",
|
||||||
|
"unicode_decimal": 59276
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230994",
|
||||||
|
"name": "上2下1",
|
||||||
|
"font_class": "shang2xia1",
|
||||||
|
"unicode": "e78d",
|
||||||
|
"unicode_decimal": 59277
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230990",
|
||||||
|
"name": "左1右上1右下2",
|
||||||
|
"font_class": "zuo1youshang1youxia2",
|
||||||
|
"unicode": "e78e",
|
||||||
|
"unicode_decimal": 59278
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230993",
|
||||||
|
"name": "上1下2",
|
||||||
|
"font_class": "shang1xia2",
|
||||||
|
"unicode": "e78f",
|
||||||
|
"unicode_decimal": 59279
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230992",
|
||||||
|
"name": "田字格",
|
||||||
|
"font_class": "tianzige",
|
||||||
|
"unicode": "e790",
|
||||||
|
"unicode_decimal": 59280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230989",
|
||||||
|
"name": "上2下3",
|
||||||
|
"font_class": "shang2xia3",
|
||||||
|
"unicode": "e792",
|
||||||
|
"unicode_decimal": 59282
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230991",
|
||||||
|
"name": "左2右1",
|
||||||
|
"font_class": "zuo2you1",
|
||||||
|
"unicode": "e793",
|
||||||
|
"unicode_decimal": 59283
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41230999",
|
||||||
|
"name": "竖2",
|
||||||
|
"font_class": "shu2",
|
||||||
|
"unicode": "e77b",
|
||||||
|
"unicode_decimal": 59259
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "38722414",
|
||||||
|
"name": "上传",
|
||||||
|
"font_class": "upload",
|
||||||
|
"unicode": "e72b",
|
||||||
|
"unicode_decimal": 59179
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40047122",
|
||||||
|
"name": "进销存对号",
|
||||||
|
"font_class": "true",
|
||||||
|
"unicode": "e741",
|
||||||
|
"unicode_decimal": 59201
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39755359",
|
||||||
|
"name": "购物车1",
|
||||||
|
"font_class": "cart",
|
||||||
|
"unicode": "e791",
|
||||||
|
"unicode_decimal": 59281
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41176633",
|
||||||
|
"name": "PDF",
|
||||||
|
"font_class": "pdf",
|
||||||
|
"unicode": "e787",
|
||||||
|
"unicode_decimal": 59271
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41176632",
|
||||||
|
"name": "ppt",
|
||||||
|
"font_class": "ppt",
|
||||||
|
"unicode": "e788",
|
||||||
|
"unicode_decimal": 59272
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41176635",
|
||||||
|
"name": "word",
|
||||||
|
"font_class": "word",
|
||||||
|
"unicode": "e785",
|
||||||
|
"unicode_decimal": 59269
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41176637",
|
||||||
|
"name": "vsd",
|
||||||
|
"font_class": "vsd",
|
||||||
|
"unicode": "e783",
|
||||||
|
"unicode_decimal": 59267
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41176636",
|
||||||
|
"name": "APK",
|
||||||
|
"font_class": "apk",
|
||||||
|
"unicode": "e784",
|
||||||
|
"unicode_decimal": 59268
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41176686",
|
||||||
|
"name": "音频",
|
||||||
|
"font_class": "vf",
|
||||||
|
"unicode": "e77d",
|
||||||
|
"unicode_decimal": 59261
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41176682",
|
||||||
|
"name": "视频1",
|
||||||
|
"font_class": "video",
|
||||||
|
"unicode": "e77e",
|
||||||
|
"unicode_decimal": 59262
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41176680",
|
||||||
|
"name": "压缩包",
|
||||||
|
"font_class": "zip",
|
||||||
|
"unicode": "e77f",
|
||||||
|
"unicode_decimal": 59263
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41176681",
|
||||||
|
"name": "IPA",
|
||||||
|
"font_class": "ipa",
|
||||||
|
"unicode": "e780",
|
||||||
|
"unicode_decimal": 59264
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41176679",
|
||||||
|
"name": "Excel",
|
||||||
|
"font_class": "excel",
|
||||||
|
"unicode": "e781",
|
||||||
|
"unicode_decimal": 59265
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41176712",
|
||||||
|
"name": "文件1",
|
||||||
|
"font_class": "file",
|
||||||
|
"unicode": "e77c",
|
||||||
|
"unicode_decimal": 59260
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41172227",
|
||||||
|
"name": "图片",
|
||||||
|
"font_class": "error-img",
|
||||||
|
"unicode": "e779",
|
||||||
|
"unicode_decimal": 59257
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41172226",
|
||||||
|
"name": "文件",
|
||||||
|
"font_class": "folder",
|
||||||
|
"unicode": "e77a",
|
||||||
|
"unicode_decimal": 59258
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41168417",
|
||||||
|
"name": "对号 线",
|
||||||
|
"font_class": "true-o",
|
||||||
|
"unicode": "e777",
|
||||||
|
"unicode_decimal": 59255
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41150407",
|
||||||
|
"name": "选中1",
|
||||||
|
"font_class": "checked-1",
|
||||||
|
"unicode": "e778",
|
||||||
|
"unicode_decimal": 59256
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41131422",
|
||||||
|
"name": "眼睛关闭",
|
||||||
|
"font_class": "eye-close",
|
||||||
|
"unicode": "e775",
|
||||||
|
"unicode_decimal": 59253
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41131421",
|
||||||
|
"name": "眼睛",
|
||||||
|
"font_class": "eye",
|
||||||
|
"unicode": "e776",
|
||||||
|
"unicode_decimal": 59254
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "37137568",
|
||||||
|
"name": "icon-index-zxmd-dress",
|
||||||
|
"font_class": "position",
|
||||||
|
"unicode": "e6af",
|
||||||
|
"unicode_decimal": 59055
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "38587548",
|
||||||
|
"name": "加号",
|
||||||
|
"font_class": "add",
|
||||||
|
"unicode": "e71b",
|
||||||
|
"unicode_decimal": 59163
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40047124",
|
||||||
|
"name": "进销存左箭头",
|
||||||
|
"font_class": "arrow-left",
|
||||||
|
"unicode": "e73f",
|
||||||
|
"unicode_decimal": 59199
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40047129",
|
||||||
|
"name": "进销存右箭头",
|
||||||
|
"font_class": "arrow-right",
|
||||||
|
"unicode": "e737",
|
||||||
|
"unicode_decimal": 59191
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40047131",
|
||||||
|
"name": "进销存上箭头",
|
||||||
|
"font_class": "arrow-top",
|
||||||
|
"unicode": "e738",
|
||||||
|
"unicode_decimal": 59192
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40964540",
|
||||||
|
"name": "左下圆角",
|
||||||
|
"font_class": "radius-l-b",
|
||||||
|
"unicode": "e774",
|
||||||
|
"unicode_decimal": 59252
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40964513",
|
||||||
|
"name": "右上圆角",
|
||||||
|
"font_class": "radius-r-t",
|
||||||
|
"unicode": "e773",
|
||||||
|
"unicode_decimal": 59251
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963370",
|
||||||
|
"name": "图片icon",
|
||||||
|
"font_class": "img",
|
||||||
|
"unicode": "e768",
|
||||||
|
"unicode_decimal": 59240
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963369",
|
||||||
|
"name": "外左",
|
||||||
|
"font_class": "out-l",
|
||||||
|
"unicode": "e769",
|
||||||
|
"unicode_decimal": 59241
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963368",
|
||||||
|
"name": "外下",
|
||||||
|
"font_class": "out-b",
|
||||||
|
"unicode": "e76a",
|
||||||
|
"unicode_decimal": 59242
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963367",
|
||||||
|
"name": "删除3",
|
||||||
|
"font_class": "del",
|
||||||
|
"unicode": "e76b",
|
||||||
|
"unicode_decimal": 59243
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963365",
|
||||||
|
"name": "拖动",
|
||||||
|
"font_class": "drag",
|
||||||
|
"unicode": "e76c",
|
||||||
|
"unicode_decimal": 59244
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963366",
|
||||||
|
"name": "外右",
|
||||||
|
"font_class": "out-r",
|
||||||
|
"unicode": "e76d",
|
||||||
|
"unicode_decimal": 59245
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963361",
|
||||||
|
"name": "点线",
|
||||||
|
"font_class": "line-point",
|
||||||
|
"unicode": "e76e",
|
||||||
|
"unicode_decimal": 59246
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963360",
|
||||||
|
"name": "删除2",
|
||||||
|
"font_class": "del-o",
|
||||||
|
"unicode": "e76f",
|
||||||
|
"unicode_decimal": 59247
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963363",
|
||||||
|
"name": "复制",
|
||||||
|
"font_class": "copy",
|
||||||
|
"unicode": "e770",
|
||||||
|
"unicode_decimal": 59248
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963364",
|
||||||
|
"name": "实线",
|
||||||
|
"font_class": "line",
|
||||||
|
"unicode": "e771",
|
||||||
|
"unicode_decimal": 59249
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963362",
|
||||||
|
"name": "关闭1",
|
||||||
|
"font_class": "close-o",
|
||||||
|
"unicode": "e772",
|
||||||
|
"unicode_decimal": 59250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963407",
|
||||||
|
"name": "文本",
|
||||||
|
"font_class": "text",
|
||||||
|
"unicode": "e766",
|
||||||
|
"unicode_decimal": 59238
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963406",
|
||||||
|
"name": "线条",
|
||||||
|
"font_class": "auxiliary-line",
|
||||||
|
"unicode": "e767",
|
||||||
|
"unicode_decimal": 59239
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963287",
|
||||||
|
"name": "关闭",
|
||||||
|
"font_class": "close",
|
||||||
|
"unicode": "e764",
|
||||||
|
"unicode_decimal": 59236
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963286",
|
||||||
|
"name": "编辑",
|
||||||
|
"font_class": "edit",
|
||||||
|
"unicode": "e765",
|
||||||
|
"unicode_decimal": 59237
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963301",
|
||||||
|
"name": "右下圆角",
|
||||||
|
"font_class": "radius-r-b",
|
||||||
|
"unicode": "e758",
|
||||||
|
"unicode_decimal": 59224
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963300",
|
||||||
|
"name": "下箭头",
|
||||||
|
"font_class": "arrow-bottom",
|
||||||
|
"unicode": "e759",
|
||||||
|
"unicode_decimal": 59225
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963298",
|
||||||
|
"name": "椭圆",
|
||||||
|
"font_class": "elliptic",
|
||||||
|
"unicode": "e75a",
|
||||||
|
"unicode_decimal": 59226
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963299",
|
||||||
|
"name": "外上",
|
||||||
|
"font_class": "out-t",
|
||||||
|
"unicode": "e75b",
|
||||||
|
"unicode_decimal": 59227
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963297",
|
||||||
|
"name": "内右",
|
||||||
|
"font_class": "enter-r",
|
||||||
|
"unicode": "e75c",
|
||||||
|
"unicode_decimal": 59228
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963296",
|
||||||
|
"name": "单张",
|
||||||
|
"font_class": "single-sheet",
|
||||||
|
"unicode": "e752",
|
||||||
|
"unicode_decimal": 59218
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963295",
|
||||||
|
"name": "铺满",
|
||||||
|
"font_class": "spread-over",
|
||||||
|
"unicode": "e753",
|
||||||
|
"unicode_decimal": 59219
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963292",
|
||||||
|
"name": "内左",
|
||||||
|
"font_class": "enter-l",
|
||||||
|
"unicode": "e75d",
|
||||||
|
"unicode_decimal": 59229
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963294",
|
||||||
|
"name": "圆点",
|
||||||
|
"font_class": "round-dot",
|
||||||
|
"unicode": "e75e",
|
||||||
|
"unicode_decimal": 59230
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963293",
|
||||||
|
"name": "内下",
|
||||||
|
"font_class": "enter-b",
|
||||||
|
"unicode": "e75f",
|
||||||
|
"unicode_decimal": 59231
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963291",
|
||||||
|
"name": "搜索",
|
||||||
|
"font_class": "search",
|
||||||
|
"unicode": "e760",
|
||||||
|
"unicode_decimal": 59232
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963290",
|
||||||
|
"name": "平铺",
|
||||||
|
"font_class": "tile",
|
||||||
|
"unicode": "e761",
|
||||||
|
"unicode_decimal": 59233
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963289",
|
||||||
|
"name": "居中",
|
||||||
|
"font_class": "center",
|
||||||
|
"unicode": "e762",
|
||||||
|
"unicode_decimal": 59234
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963288",
|
||||||
|
"name": "内上",
|
||||||
|
"font_class": "enter-t",
|
||||||
|
"unicode": "e763",
|
||||||
|
"unicode_decimal": 59235
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963305",
|
||||||
|
"name": "左对齐",
|
||||||
|
"font_class": "left",
|
||||||
|
"unicode": "e755",
|
||||||
|
"unicode_decimal": 59221
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963304",
|
||||||
|
"name": "重置",
|
||||||
|
"font_class": "reset",
|
||||||
|
"unicode": "e756",
|
||||||
|
"unicode_decimal": 59222
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963303",
|
||||||
|
"name": "左上圆角",
|
||||||
|
"font_class": "radius-l-t",
|
||||||
|
"unicode": "e757",
|
||||||
|
"unicode_decimal": 59223
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40963302",
|
||||||
|
"name": "右对齐",
|
||||||
|
"font_class": "right",
|
||||||
|
"unicode": "e754",
|
||||||
|
"unicode_decimal": 59220
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
171
src/assets/icons/iconfont.svg
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
src/assets/icons/iconfont.ttf
Normal file
BIN
src/assets/icons/iconfont.woff
Normal file
BIN
src/assets/icons/iconfont.woff2
Normal file
BIN
src/assets/images/components/model-hot/test-1.png
Normal file
|
After Width: | Height: | Size: 395 KiB |
BIN
src/assets/images/components/model-hot/test-2.png
Normal file
|
After Width: | Height: | Size: 314 KiB |
BIN
src/assets/images/components/model-shop-list/price.png
Normal file
|
After Width: | Height: | Size: 622 B |
BIN
src/assets/images/components/model-user-info/avatar.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
src/assets/images/components/model-user-info/notice.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/images/components/model-user-info/set.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/images/components/model-video/video.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/images/custom/auxiliary-line.png
Normal file
|
After Width: | Height: | Size: 722 B |
BIN
src/assets/images/custom/img.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/images/custom/text.png
Normal file
|
After Width: | Height: | Size: 927 B |
BIN
src/assets/images/empty.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src/assets/images/layout/main/main-top.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/images/layout/main/phone.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
src/assets/images/layout/siderbar/article-list.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/images/layout/siderbar/article-tabs.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/images/layout/siderbar/auxiliary-blank.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/layout/siderbar/carousel.png
Normal file
|
After Width: | Height: | Size: 850 B |
BIN
src/assets/images/layout/siderbar/custom.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/assets/images/layout/siderbar/float-window.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/images/layout/siderbar/hot-zone.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/images/layout/siderbar/img-magic.png
Normal file
|
After Width: | Height: | Size: 639 B |
BIN
src/assets/images/layout/siderbar/nav-group.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/layout/siderbar/notice.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/assets/images/layout/siderbar/rich-text.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/assets/images/layout/siderbar/row-line.png
Normal file
|
After Width: | Height: | Size: 745 B |
BIN
src/assets/images/layout/siderbar/search.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/images/layout/siderbar/shop-list.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/images/layout/siderbar/shop-tabs.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/images/layout/siderbar/tabs.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/layout/siderbar/text-title.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/assets/images/layout/siderbar/user-info.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/images/layout/siderbar/video.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/movie.mp4
Normal file
19
src/components/base/card-container/index.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card" :style="'padding:' + padding">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
// 内边距
|
||||||
|
padding: {
|
||||||
|
type: String,
|
||||||
|
default: "3rem",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
42
src/components/base/color-picker/index.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex-row align-c gap-12">
|
||||||
|
<el-color-picker v-model="color" show-alpha :predefine="predefine_colors" @change="color_picker_change" />
|
||||||
|
<icon name="reset" color="primary" size="16" :class="['c-pointer', { disable: color == defaultColor }]" @click="reset_event"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
|
const predefine_colors = ref(['#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1', '#1e90ff', '#c71585', 'rgba(255, 69, 0, 0.68)', 'rgb(255, 120, 0)', 'hsv(51, 100, 98)', 'hsva(120, 40, 94, 0.5)', 'hsl(181, 100%, 37%)', 'hsla(209, 100%, 56%, 0.73)', '#c7158577']);
|
||||||
|
interface Props {
|
||||||
|
defaultColor?: string;
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
defaultColor: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const color = defineModel({ type: String });
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
const color_picker_change = (val: string | null) => {
|
||||||
|
emit('update:value', val);
|
||||||
|
};
|
||||||
|
const reset_event = () => {
|
||||||
|
if (color.value == props.defaultColor) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (!isEmpty(props.defaultColor)) {
|
||||||
|
color.value = props.defaultColor;
|
||||||
|
} else {
|
||||||
|
color.value = '';
|
||||||
|
}
|
||||||
|
emit('update:value', color.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.flex-row .disable {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
74
src/components/base/drag/index.vue
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<VueDraggable v-model="from" :animation="500" target=".sort-target" handle=".icon-drag" :scroll="true" :on-sort="on_sort">
|
||||||
|
<TransitionGroup type="transition" tag="ul" name="fade" class="sort-target flex-col gap-x-20">
|
||||||
|
<li v-for="(item, index) in from" :key="item.id" :class="className" class="flex gap-y-16 re" @click="on_click(item, index)">
|
||||||
|
<el-icon class="iconfont icon-drag size-16 cursor-move" />
|
||||||
|
<slot :row="item" :index="index" />
|
||||||
|
<el-icon v-if="type == 'line'" class="iconfont icon-del-o size-16" @click="remove(index)" />
|
||||||
|
<el-icon v-if="type == 'card'" class="iconfont icon-close-o size-16 abs cr-c top-de-5 right-de-5" @click="remove(index)" />
|
||||||
|
</li>
|
||||||
|
</TransitionGroup>
|
||||||
|
</VueDraggable>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { VueDraggable } from 'vue-draggable-plus';
|
||||||
|
const emits = defineEmits(['click', 'remove', 'onSort']);
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: any; // 拖拽列表数据
|
||||||
|
type?: string; // card: 卡片区域 line: 一行
|
||||||
|
spaceCol?: number; // 上下间距
|
||||||
|
iconPosition?: string; // top/bottom/center
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
type: () => 'line',
|
||||||
|
spaceCol: () => 5,
|
||||||
|
iconPosition: 'center',
|
||||||
|
});
|
||||||
|
const className = ref('');
|
||||||
|
if (props.type == 'card') {
|
||||||
|
className.value += `card-background box-shadow-sm pt-${props.spaceCol} pb-${props.spaceCol}`;
|
||||||
|
if (props.iconPosition == 'top') {
|
||||||
|
className.value += '';
|
||||||
|
} else if (props.iconPosition == 'bottom') {
|
||||||
|
className.value += ' align-e';
|
||||||
|
} else {
|
||||||
|
className.value += ' align-c';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 不是卡片类型的设置居中显示
|
||||||
|
className.value = 'align-c';
|
||||||
|
}
|
||||||
|
|
||||||
|
const from = ref(props.data);
|
||||||
|
|
||||||
|
const on_click = (item: any, index: number) => {
|
||||||
|
emits('click', item, index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = (index: number) => {
|
||||||
|
emits('remove', index);
|
||||||
|
};
|
||||||
|
// 拖拽更新之后用户更新数据
|
||||||
|
const on_sort = () => {
|
||||||
|
emits('onSort', from);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.card-background {
|
||||||
|
background: #fff;
|
||||||
|
padding-left: 1.6rem;
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
.size-16 {
|
||||||
|
font-size: 1.6rem !important;
|
||||||
|
}
|
||||||
|
.icon-del-o {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.cursor-move {
|
||||||
|
color: #ddd;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
50
src/components/base/icon/index.vue
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<i class="iconfont" :class="className" :style="'font-size:' + size + 'px;' + (props.color.indexOf('#') !== -1 ? 'color:' + props.color : '') + styles" @click="onClick">
|
||||||
|
<slot></slot>
|
||||||
|
</i>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps({
|
||||||
|
// 通过传递类名,来自定义样式
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// const className = ref(`icon-${props.name} ${props.color ? (props.color.indexOf('#') == -1 ? `cr-${props.color}` : '') : ''}`);
|
||||||
|
const className = computed(() => {
|
||||||
|
return `icon-${props.name} ${props.color ? (props.color.indexOf('#') == -1 ? `cr-${props.color}` : '') : ''}`;
|
||||||
|
});
|
||||||
|
const emit = defineEmits(['click']);
|
||||||
|
function onClick() {
|
||||||
|
// 回调方法
|
||||||
|
emit('click');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '@/assets/icons/iconfont.css';
|
||||||
|
i.iconfont {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: inherit;
|
||||||
|
gap: 0.5rem;
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
33
src/components/base/image-empty/index.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<el-image :src="image?.url || ''" class="flex align-c jc-c">
|
||||||
|
<template #error>
|
||||||
|
<div class="image-slot" :style="errorStyle">
|
||||||
|
<img :src="error_image" :style="errorImgStyle" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
errorImgStyle: {
|
||||||
|
type: String,
|
||||||
|
default: () => '',
|
||||||
|
},
|
||||||
|
errorStyle: {
|
||||||
|
type: String,
|
||||||
|
default: () => '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const image = defineModel({ type: Object, default: () => {} });
|
||||||
|
const error_image = ref(new URL(`../../../assets/images/empty.png`, import.meta.url).href);
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.image-slot {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f4fcff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
46
src/components/base/input-number/index.vue
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<!-- 数字输入框 -->
|
||||||
|
<template>
|
||||||
|
<div class="input-number" :class="iconName ? 'has-icon' : ''">
|
||||||
|
<icon v-if="iconName" :name="iconName" size="14" color="3" class="input-icon"></icon>
|
||||||
|
<el-input-number v-model="internal_value" :min="min" :max="max" type="number" placeholder="0" controls-position="right"></el-input-number>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps({
|
||||||
|
iconName: {
|
||||||
|
type: String,
|
||||||
|
default: () => '',
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: () => 0,
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: () => 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const internal_value = defineModel({ type: Number, default: 0 });
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.input-number {
|
||||||
|
position: relative;
|
||||||
|
:deep(.el-input-number.is-controls-right .el-input__wrapper) {
|
||||||
|
.el-input__inner {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.has-icon {
|
||||||
|
:deep(.el-input-number.is-controls-right .el-input__wrapper) {
|
||||||
|
padding-left: 3rem;
|
||||||
|
}
|
||||||
|
.input-icon {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: 0.1rem;
|
||||||
|
left: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
123
src/components/base/maps/bd-map.vue
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<!-- vue 3 引入百度api -->
|
||||||
|
<template>
|
||||||
|
<!-- 百度地图 -->
|
||||||
|
<div class="container w">
|
||||||
|
<div id="map" class="map radius-md" :style="{ width: width, height: height }"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '上海市黄浦区上海中心大厦',
|
||||||
|
},
|
||||||
|
draggable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%',
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '216px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['point'],
|
||||||
|
setup(props, context) {
|
||||||
|
const map = ref(null);
|
||||||
|
const lng = ref(121.478);
|
||||||
|
const lat = ref(31.223);
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
if (!val) return;
|
||||||
|
map_event(val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onMounted(() => {
|
||||||
|
load_map_script(); // 加载地图资源
|
||||||
|
});
|
||||||
|
const load_map_script = () => {
|
||||||
|
// 此处在所需页面引入资源就是,不用再public/index.html中引入
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.className = 'loadmap'; // 给script一个类名
|
||||||
|
script.src = 'https://api.map.baidu.com/getscript?v=3.0&ak=XSdiGjfg3wOHiKjpYEMG6CYA';
|
||||||
|
// 此处需要注意:申请ak时,一定要应用类别一定要选浏览器端,不能选服务端,不然地图会报ak无效
|
||||||
|
script.onload = () => {
|
||||||
|
// 使用script.onload,待资源加载完成,再初始化地图
|
||||||
|
init();
|
||||||
|
};
|
||||||
|
let loadmap = document.getElementsByClassName('loadmap');
|
||||||
|
if (loadmap) {
|
||||||
|
// 每次append script之前判断一下,避免重复添加script资源标签
|
||||||
|
for (var i = 0; i < loadmap.length; i++) {
|
||||||
|
document.body.removeChild(loadmap[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(script);
|
||||||
|
};
|
||||||
|
// 初始化地图
|
||||||
|
const init = () => {
|
||||||
|
const BMap = window.BMap;
|
||||||
|
map.value = new BMap.Map('map', {
|
||||||
|
enableMapClick: false,
|
||||||
|
});
|
||||||
|
let point = new BMap.Point(lng.value, lat.value);
|
||||||
|
map.value.centerAndZoom(point, 10); // 初始化地图,设置中心点坐标和地图级别
|
||||||
|
// 添加控件
|
||||||
|
let navigationControl = new BMap.NavigationControl({
|
||||||
|
// 靠左上角位置
|
||||||
|
anchor: window.BMAP_ANCHOR_TOP_LEFT,
|
||||||
|
// LARGE类型
|
||||||
|
type: window.BMAP_NAVIGATION_CONTROL_LARGE,
|
||||||
|
});
|
||||||
|
map.value.addControl(navigationControl);
|
||||||
|
let marker = new BMap.Marker(point);
|
||||||
|
map.value.addOverlay(marker);
|
||||||
|
if (props.draggable) {
|
||||||
|
// 修正marker的初始化
|
||||||
|
marker.enableDragging();
|
||||||
|
marker.addEventListener('dragend', function (e) {
|
||||||
|
map.value.panTo(e.point);
|
||||||
|
lat.value = e.point.lat;
|
||||||
|
lng.value = e.point.lng;
|
||||||
|
context.emit('point', lng, lat);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置标注提示信息
|
||||||
|
let cr = new BMap.CopyrightControl({ anchor: window.BMAP_ANCHOR_BOTTOM_RIGHT });
|
||||||
|
map.value.addControl(cr); //添加版权控件
|
||||||
|
let bs = map.value.getBounds(); //返回地图可视区域
|
||||||
|
cr.addCopyright({ id: 1, content: '<div class="map-dragging-tips"><span>' + '拖动红色图标直接定位' + '</span></div>', bounds: bs });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const map_event = (value) => {
|
||||||
|
// 创建地址解析器实例
|
||||||
|
let geo = new window.BMap.Geocoder();
|
||||||
|
// 将地址解析结果显示在地图上,并调整地图视野
|
||||||
|
geo.getPoint(
|
||||||
|
value,
|
||||||
|
function (point) {
|
||||||
|
if (point) {
|
||||||
|
lng.value = point.lng;
|
||||||
|
lat.value = point.lat;
|
||||||
|
context.emit('point', lng.value, lat.value);
|
||||||
|
init();
|
||||||
|
} else {
|
||||||
|
ElMessage.info(point?.getMsg() || '您选择地址没有解析到结果!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'全国'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped></style>
|
||||||
116
src/components/base/maps/gd-map.vue
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 天地图 -->
|
||||||
|
<div class="container w">
|
||||||
|
<div id="map" class="map radius-md" :style="{ width: width, height: height }"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '上海市黄浦区上海中心大厦',
|
||||||
|
},
|
||||||
|
// 是否可拖拽
|
||||||
|
draggable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%',
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '216px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['point'],
|
||||||
|
setup(props, context) {
|
||||||
|
const map = ref(null);
|
||||||
|
const lng = ref(121.47894);
|
||||||
|
const lat = ref(31.223);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
if (!val) return;
|
||||||
|
map_event(val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onMounted(() => {
|
||||||
|
load_map_script(); // 加载地图资源
|
||||||
|
});
|
||||||
|
|
||||||
|
const load_map_script = () => {
|
||||||
|
// 此处在所需页面引入资源就是,不用再public/index.html中引入
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.className = 'loadmap'; // 给script一个类名
|
||||||
|
script.src = 'https://webapi.amap.com/maps?v=2.0&key=3e92c6bfdd5ddb4aac39ed5e4d0db663';
|
||||||
|
// 此处需要注意:申请ak时,一定要应用类别一定要选浏览器端,不能选服务端,不然地图会报ak无效
|
||||||
|
script.onload = () => {
|
||||||
|
// 使用script.onload,待资源加载完成,再初始化地图
|
||||||
|
init();
|
||||||
|
};
|
||||||
|
let loadmap = document.getElementsByClassName('loadmap');
|
||||||
|
if (loadmap) {
|
||||||
|
// 每次append script之前判断一下,避免重复添加script资源标签
|
||||||
|
for (var i = 0; i < loadmap.length; i++) {
|
||||||
|
document.body.removeChild(loadmap[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.body.appendChild(script);
|
||||||
|
};
|
||||||
|
const init = () => {
|
||||||
|
map.value = new AMap.Map('map', {
|
||||||
|
zoomEnable: true,
|
||||||
|
resizeEnable: false,
|
||||||
|
scrollWheel: false,
|
||||||
|
zoom: 10, // 初始化地图级别
|
||||||
|
center: [lng.value, lat.value], // 初始化地图中心点位置
|
||||||
|
});
|
||||||
|
AMap.plugin(['AMap.ToolBar'], function () {
|
||||||
|
// 在图面添加工具条控件, 工具条控件只有缩放功能
|
||||||
|
map.value.addControl(new AMap.ToolBar());
|
||||||
|
});
|
||||||
|
// 创建标注
|
||||||
|
var marker_config = {
|
||||||
|
position: map.value.getCenter(),
|
||||||
|
// offset: new AMap.Pixel(-13, -30),
|
||||||
|
draggable: props.draggable,
|
||||||
|
};
|
||||||
|
let marker4 = new AMap.Marker(marker_config);
|
||||||
|
marker4.setMap(map);
|
||||||
|
// 标注可拖拽回调
|
||||||
|
if (props.draggable) {
|
||||||
|
marker4.on('dragend', (e) => {
|
||||||
|
map.value.panTo(e.lnglat);
|
||||||
|
lng.value = e.lnglat.lng;
|
||||||
|
lat.value = e.lnglat.lat;
|
||||||
|
context.emit('point', lng.value, lat.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
map.value.add(marker4);
|
||||||
|
};
|
||||||
|
|
||||||
|
const map_event = (value) => {
|
||||||
|
AMap.plugin('AMap.Geocoder', () => {
|
||||||
|
var geo = new AMap.Geocoder();
|
||||||
|
geo.getLocation(value, (status, result) => {
|
||||||
|
if (status === 'complete' && result.geocodes.length) {
|
||||||
|
var lnglat = result.geocodes[0].location;
|
||||||
|
lng.value = lnglat.lng;
|
||||||
|
lat.value = lnglat.lat;
|
||||||
|
init();
|
||||||
|
context.emit('point', lng.value, lat.value);
|
||||||
|
} else {
|
||||||
|
ElMessage.info('您选择地址没有解析到结果!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
291
src/components/base/maps/index.vue
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 地图 -->
|
||||||
|
<div class="container w">
|
||||||
|
<div id="map" class="map radius-md" :style="{ width: width, height: height }"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '上海市黄浦区上海中心大厦',
|
||||||
|
},
|
||||||
|
// 是否可拖拽
|
||||||
|
draggable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%',
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '350px',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: '1', // 地图类型 默认1.天地图/2.百度地图/3.腾讯地图/4.高德地图
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['point'],
|
||||||
|
setup(props, context) {
|
||||||
|
const map = ref(null);
|
||||||
|
const lng = ref(121.47894);
|
||||||
|
const lat = ref(31.223);
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
if (!val) return;
|
||||||
|
map_event(val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onMounted(() => {
|
||||||
|
load_map_script(); // 加载地图资源
|
||||||
|
});
|
||||||
|
const load_map_script = () => {
|
||||||
|
// 此处在所需页面引入资源就是,不用再public/index.html中引入
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.className = 'loadmap'; // 给script一个类名
|
||||||
|
if (props.type === '1') {
|
||||||
|
// 天地图
|
||||||
|
script.src = 'https://api.tianditu.gov.cn/api?v=4.0&tk=bf0676d6b99ee6f7f917640a54af0415';
|
||||||
|
} else if (props.type === '2') {
|
||||||
|
// 百度地图
|
||||||
|
script.src = 'https://api.map.baidu.com/getscript?v=3.0&ak=XSdiGjfg3wOHiKjpYEMG6CYA';
|
||||||
|
} else if (props.type === '3') {
|
||||||
|
// 腾讯地图
|
||||||
|
script.src = 'https://map.qq.com/api/js?v=2.exp&key=IMYBZ-QJ6C3-QPZ3Y-OUKL6-IVU5S-ZYBKA&callback=init';
|
||||||
|
} else if (props.type === '4') {
|
||||||
|
// 高德地图
|
||||||
|
script.src = 'https://webapi.amap.com/maps?v=2.0&key=3e92c6bfdd5ddb4aac39ed5e4d0db663';
|
||||||
|
}
|
||||||
|
// 使用script.onload,待资源加载完成,再初始化地图
|
||||||
|
if (props.type === '3') {
|
||||||
|
window.init = () => {
|
||||||
|
init();
|
||||||
|
};
|
||||||
|
load_tx_map();
|
||||||
|
} else {
|
||||||
|
script.onload = () => {
|
||||||
|
init();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let loadmap = document.getElementsByClassName('loadmap');
|
||||||
|
if (loadmap) {
|
||||||
|
// 每次append script之前判断一下,避免重复添加script资源标签
|
||||||
|
for (var i = 0; i < loadmap.length; i++) {
|
||||||
|
document.body.removeChild(loadmap[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (props.type === '4') {
|
||||||
|
window._AMapSecurityConfig = {
|
||||||
|
securityJsCode: '6d68c17c7b2a96a0616b1b8c371f391f',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
document.body.appendChild(script);
|
||||||
|
};
|
||||||
|
|
||||||
|
const load_tx_map = () => {
|
||||||
|
// 此处在所需页面引入资源就是,不用再public/index.html中引入
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.className = 'loadmap2'; // 给script一个类名
|
||||||
|
script.src = 'https://map.qq.com/api/gljs?v=1.exp&key=IMYBZ-QJ6C3-QPZ3Y-OUKL6-IVU5S-ZYBKA&libraries=service';
|
||||||
|
let loadmap2 = document.getElementsByClassName('loadmap2');
|
||||||
|
if (loadmap2) {
|
||||||
|
// 每次append script之前判断一下,避免重复添加script资源标签
|
||||||
|
for (var i = 0; i < loadmap2.length; i++) {
|
||||||
|
document.body.removeChild(loadmap2[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.body.appendChild(script);
|
||||||
|
};
|
||||||
|
// 初始化地图
|
||||||
|
const init = () => {
|
||||||
|
switch (props.type) {
|
||||||
|
case '1':
|
||||||
|
const T = window.T;
|
||||||
|
// 坐标
|
||||||
|
map.value = new T.Map('map');
|
||||||
|
let point = new T.LngLat(lng.value, lat.value);
|
||||||
|
map.value.centerAndZoom(point, 10);
|
||||||
|
// 禁止鼠标滚动缩小放大
|
||||||
|
map.value.disableScrollWheelZoom();
|
||||||
|
|
||||||
|
// 添加控件
|
||||||
|
//创建缩放平移控件对象
|
||||||
|
let control = new T.Control.Zoom();
|
||||||
|
// control.setPosition(T_ANCHOR_TOP_RIGHT);
|
||||||
|
//添加缩放平移控件
|
||||||
|
map.value.addControl(control);
|
||||||
|
|
||||||
|
map.value.clearOverLays();
|
||||||
|
let marker = new T.Marker(point);
|
||||||
|
map.value.addOverLay(marker);
|
||||||
|
if (props.draggable) {
|
||||||
|
marker.enableDragging();
|
||||||
|
marker.addEventListener('dragend', function (e) {
|
||||||
|
map.value.panTo(new T.LngLat(e.lnglat.lng, e.lnglat.lat));
|
||||||
|
lat.value = e.lnglat.lat;
|
||||||
|
lng.value = e.lnglat.lng;
|
||||||
|
context.emit('point', lng, lat);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
const BMap = window.BMap;
|
||||||
|
map.value = new BMap.Map('map', {
|
||||||
|
enableMapClick: false,
|
||||||
|
});
|
||||||
|
let point2 = new BMap.Point(lng.value, lat.value);
|
||||||
|
map.value.centerAndZoom(point2, 10); // 初始化地图,设置中心点坐标和地图级别
|
||||||
|
// 添加控件
|
||||||
|
let navigationControl = new BMap.NavigationControl({
|
||||||
|
// 靠左上角位置
|
||||||
|
anchor: window.BMAP_ANCHOR_TOP_LEFT,
|
||||||
|
// LARGE类型
|
||||||
|
type: window.BMAP_NAVIGATION_CONTROL_LARGE,
|
||||||
|
});
|
||||||
|
map.value.addControl(navigationControl);
|
||||||
|
let marker2 = new BMap.Marker(point2);
|
||||||
|
map.value.addOverlay(marker2);
|
||||||
|
if (props.draggable) {
|
||||||
|
// 修正marker的初始化
|
||||||
|
marker2.enableDragging();
|
||||||
|
marker2.addEventListener('dragend', function (e) {
|
||||||
|
map.value.panTo(e.point);
|
||||||
|
lat.value = e.point.lat;
|
||||||
|
lng.value = e.point.lng;
|
||||||
|
context.emit('point', lng, lat);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置标注提示信息
|
||||||
|
let cr = new BMap.CopyrightControl({ anchor: window.BMAP_ANCHOR_BOTTOM_RIGHT });
|
||||||
|
map.value.addControl(cr); //添加版权控件
|
||||||
|
let bs = map.value.getBounds(); //返回地图可视区域
|
||||||
|
cr.addCopyright({ id: 1, content: '<div class="map-dragging-tips"><span>' + '拖动红色图标直接定位' + '</span></div>', bounds: bs });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
const qq_maps = window.qq.maps;
|
||||||
|
let point3 = new qq_maps.LatLng(lat.value, lng.value);
|
||||||
|
map.value = new qq_maps.Map('map', {
|
||||||
|
center: point3,
|
||||||
|
zoom: 10,
|
||||||
|
});
|
||||||
|
let marker3 = new qq_maps.Marker({
|
||||||
|
map: map.value,
|
||||||
|
position: point3,
|
||||||
|
draggable: props.draggable,
|
||||||
|
});
|
||||||
|
qq_maps.event.addListener(marker3, 'dragend', function (e) {
|
||||||
|
lat.value = e.latLng.lat;
|
||||||
|
lng.value = e.latLng.lng;
|
||||||
|
map.value.panTo(e.latLng);
|
||||||
|
context.emit('point', lng, lat);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case '4':
|
||||||
|
const AMap = window.AMap;
|
||||||
|
map.value = new AMap.Map('map', {
|
||||||
|
zoomEnable: true,
|
||||||
|
resizeEnable: false,
|
||||||
|
scrollWheel: false,
|
||||||
|
zoom: 10, // 初始化地图级别
|
||||||
|
center: [lng.value, lat.value], // 初始化地图中心点位置
|
||||||
|
});
|
||||||
|
AMap.plugin(['AMap.ToolBar'], function () {
|
||||||
|
// 在图面添加工具条控件, 工具条控件只有缩放功能
|
||||||
|
map.value.addControl(new AMap.ToolBar());
|
||||||
|
});
|
||||||
|
// 创建标注
|
||||||
|
var marker_config = {
|
||||||
|
position: map.value.getCenter(),
|
||||||
|
// offset: new AMap.Pixel(-13, -30),
|
||||||
|
draggable: props.draggable,
|
||||||
|
};
|
||||||
|
let marker4 = new AMap.Marker(marker_config);
|
||||||
|
marker4.setMap(map);
|
||||||
|
// 标注可拖拽回调
|
||||||
|
if (props.draggable) {
|
||||||
|
marker4.on('dragend', (e) => {
|
||||||
|
map.value.panTo(e.lnglat);
|
||||||
|
lng.value = e.lnglat.lng;
|
||||||
|
lat.value = e.lnglat.lat;
|
||||||
|
context.emit('point', lng.value, lat.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
map.value.add(marker4);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const map_event = (value) => {
|
||||||
|
switch (props.type) {
|
||||||
|
case '1':
|
||||||
|
let geo = new T.Geocoder();
|
||||||
|
geo.getPoint(value, function (result) {
|
||||||
|
let point = result.getLocationPoint();
|
||||||
|
if (result.getStatus() == 0) {
|
||||||
|
lng.value = point.lng;
|
||||||
|
lat.value = point.lat;
|
||||||
|
init();
|
||||||
|
map.value.panTo(new T.LngLat(lng.value, lat.value));
|
||||||
|
context.emit('point', lng.value, lat.value);
|
||||||
|
} else {
|
||||||
|
ElMessage.info(point?.getMsg() || '您选择地址没有解析到结果!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
// 创建地址解析器实例
|
||||||
|
let geo2 = new window.BMap.Geocoder();
|
||||||
|
// 将地址解析结果显示在地图上,并调整地图视野
|
||||||
|
geo2.getPoint(
|
||||||
|
value,
|
||||||
|
function (point) {
|
||||||
|
if (point) {
|
||||||
|
lng.value = point.lng;
|
||||||
|
lat.value = point.lat;
|
||||||
|
context.emit('point', lng.value, lat.value);
|
||||||
|
init();
|
||||||
|
} else {
|
||||||
|
ElMessage.info(point?.getMsg() || '您选择地址没有解析到结果!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'全国'
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
let geo3 = new TMap.service.Geocoder();
|
||||||
|
geo3.getLocation({ address: value }).then((result) => {
|
||||||
|
let lnglat = result.result.location;
|
||||||
|
lng.value = lnglat.lng;
|
||||||
|
lat.value = lnglat.lat;
|
||||||
|
init();
|
||||||
|
context.emit('point', lng.value, lat.value);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case '4':
|
||||||
|
AMap.plugin('AMap.Geocoder', () => {
|
||||||
|
new AMap.Geocoder().getLocation(value, (status, result) => {
|
||||||
|
if (status === 'complete' && result.geocodes.length) {
|
||||||
|
var lnglat = result.geocodes[0].location;
|
||||||
|
lng.value = lnglat.lng;
|
||||||
|
lat.value = lnglat.lat;
|
||||||
|
init();
|
||||||
|
context.emit('point', lng.value, lat.value);
|
||||||
|
} else {
|
||||||
|
ElMessage.info('您选择地址没有解析到结果!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
108
src/components/base/maps/t-map.vue
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 天地图 -->
|
||||||
|
<div class="container w">
|
||||||
|
<div id="map" class="map radius-md" :style="{ width: width, height: height }"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '上海市黄浦区上海中心大厦',
|
||||||
|
},
|
||||||
|
// 是否可拖拽
|
||||||
|
draggable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%',
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '216px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['point'],
|
||||||
|
setup(props, context) {
|
||||||
|
const map = ref(null);
|
||||||
|
const lng = ref(121.47894);
|
||||||
|
const lat = ref(31.223);
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
if (!val) return;
|
||||||
|
map_event(val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onMounted(() => {
|
||||||
|
load_map_script(); // 加载地图资源
|
||||||
|
});
|
||||||
|
const load_map_script = () => {
|
||||||
|
// 此处在所需页面引入资源就是,不用再public/index.html中引入
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.className = 'loadmap'; // 给script一个类名
|
||||||
|
script.src = 'https://api.tianditu.gov.cn/api?v=4.0&tk=bf0676d6b99ee6f7f917640a54af0415';
|
||||||
|
// 此处需要注意:申请ak时,一定要应用类别一定要选浏览器端,不能选服务端,不然地图会报ak无效
|
||||||
|
script.onload = () => {
|
||||||
|
// 使用script.onload,待资源加载完成,再初始化地图
|
||||||
|
init();
|
||||||
|
};
|
||||||
|
let loadmap = document.getElementsByClassName('loadmap');
|
||||||
|
if (loadmap) {
|
||||||
|
// 每次append script之前判断一下,避免重复添加script资源标签
|
||||||
|
for (var i = 0; i < loadmap.length; i++) {
|
||||||
|
document.body.removeChild(loadmap[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.body.appendChild(script);
|
||||||
|
};
|
||||||
|
// 初始化地图
|
||||||
|
const init = () => {
|
||||||
|
const T = window.T;
|
||||||
|
// 坐标
|
||||||
|
map.value = new T.Map('map');
|
||||||
|
let point = new T.LngLat(lng.value, lat.value);
|
||||||
|
map.value.centerAndZoom(point, 10);
|
||||||
|
// 禁止鼠标滚动缩小放大
|
||||||
|
map.value.disableScrollWheelZoom();
|
||||||
|
|
||||||
|
// 添加控件
|
||||||
|
//创建缩放平移控件对象
|
||||||
|
let control = new T.Control.Zoom();
|
||||||
|
// control.setPosition(T_ANCHOR_TOP_RIGHT);
|
||||||
|
//添加缩放平移控件
|
||||||
|
map.value.addControl(control);
|
||||||
|
|
||||||
|
map.value.clearOverLays();
|
||||||
|
let marker = new T.Marker(point);
|
||||||
|
map.value.addOverLay(marker);
|
||||||
|
if (props.draggable) {
|
||||||
|
marker.enableDragging();
|
||||||
|
marker.addEventListener('dragend', function (e) {
|
||||||
|
map.value.panTo(new T.LngLat(e.lnglat.lng, e.lnglat.lat));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const map_event = (value) => {
|
||||||
|
let geo = new T.Geocoder();
|
||||||
|
geo.getPoint(value, function (result) {
|
||||||
|
let point = result.getLocationPoint();
|
||||||
|
if (result.getStatus() == 0) {
|
||||||
|
lng.value = point.lng;
|
||||||
|
lat.value = point.lat;
|
||||||
|
init();
|
||||||
|
map.value.panTo(new T.LngLat(lng.value, lat.value));
|
||||||
|
context.emit('point', lng.value, lat.value);
|
||||||
|
} else {
|
||||||
|
ElMessage.info(point?.getMsg() || '您选择地址没有解析到结果!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
107
src/components/base/maps/tx-map.vue
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 地图 -->
|
||||||
|
<div class="container w">
|
||||||
|
<div id="map" class="map radius-md" :style="{ width: width, height: height }"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '上海市黄浦区上海中心大厦',
|
||||||
|
},
|
||||||
|
// 是否可拖拽
|
||||||
|
draggable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%',
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '216px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['point'],
|
||||||
|
setup(props, context) {
|
||||||
|
const map = ref(null);
|
||||||
|
const lng = ref(121.47894);
|
||||||
|
const lat = ref(31.223);
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
if (!val) return;
|
||||||
|
map_event(val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onMounted(() => {
|
||||||
|
load_map_script1(); // 加载地图资源
|
||||||
|
load_map_script2(); // 加载地图资源
|
||||||
|
});
|
||||||
|
window.init = () => {
|
||||||
|
init();
|
||||||
|
};
|
||||||
|
const load_map_script1 = () => {
|
||||||
|
// 此处在所需页面引入资源就是,不用再public/index.html中引入
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.className = 'loadmap'; // 给script一个类名
|
||||||
|
script.src = 'https://map.qq.com/api/js?v=2.exp&key=IMYBZ-QJ6C3-QPZ3Y-OUKL6-IVU5S-ZYBKA&callback=init';
|
||||||
|
let loadmap = document.getElementsByClassName('loadmap');
|
||||||
|
if (loadmap) {
|
||||||
|
// 每次append script之前判断一下,避免重复添加script资源标签
|
||||||
|
for (var i = 0; i < loadmap.length; i++) {
|
||||||
|
document.body.removeChild(loadmap[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.body.appendChild(script);
|
||||||
|
};
|
||||||
|
const load_map_script2 = () => {
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.className = 'loadmap2'; // 给script一个类名
|
||||||
|
script.src = 'https://map.qq.com/api/gljs?v=1.exp&key=IMYBZ-QJ6C3-QPZ3Y-OUKL6-IVU5S-ZYBKA&libraries=service';
|
||||||
|
let loadmap2 = document.getElementsByClassName('loadmap2');
|
||||||
|
// 每次append script之前判断一下,避免重复添加script资源标签
|
||||||
|
for (var i = 0; i < loadmap2.length; i++) {
|
||||||
|
document.body.removeChild(loadmap2[i]);
|
||||||
|
}
|
||||||
|
document.body.appendChild(script);
|
||||||
|
};
|
||||||
|
// 初始化地图
|
||||||
|
const init = () => {
|
||||||
|
const qq_maps = window.qq.maps;
|
||||||
|
let point = new qq_maps.LatLng(lat.value, lng.value);
|
||||||
|
map.value = new qq_maps.Map('map', {
|
||||||
|
center: point,
|
||||||
|
zoom: 10,
|
||||||
|
});
|
||||||
|
let marker3 = new qq_maps.Marker({
|
||||||
|
map: map.value,
|
||||||
|
position: point,
|
||||||
|
draggable: props.draggable,
|
||||||
|
});
|
||||||
|
qq_maps.event.addListener(marker3, 'dragend', function (e) {
|
||||||
|
lat.value = e.latLng.lat;
|
||||||
|
lng.value = e.latLng.lng;
|
||||||
|
map.value.panTo(e.latLng);
|
||||||
|
context.emit('point', lng, lat);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const map_event = (value) => {
|
||||||
|
let geo3 = new TMap.service.Geocoder();
|
||||||
|
geo3.getLocation({ address: value }).then((result) => {
|
||||||
|
let lnglat = result.result.location;
|
||||||
|
lng.value = lnglat.lng;
|
||||||
|
lat.value = lnglat.lat;
|
||||||
|
context.emit('point', lng.value, lat.value);
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
64
src/components/base/padding/index.vue
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex-col gap-10 w">
|
||||||
|
<slider v-model="form.padding" @update:model-value="padding_event"></slider>
|
||||||
|
<div class="flex-row flex-wrap gap-x-20 mt-10">
|
||||||
|
<div class="flex-width-half pr-10">
|
||||||
|
<input-number v-model="form.padding_top" icon-name="enter-t" @update:model-value="pt_event"></input-number>
|
||||||
|
</div>
|
||||||
|
<div class="flex-width-half pl-10">
|
||||||
|
<input-number v-model="form.padding_bottom" icon-name="enter-b" @update:model-value="pb_event"></input-number>
|
||||||
|
</div>
|
||||||
|
<div class="flex-width-half pr-10">
|
||||||
|
<input-number v-model="form.padding_left" icon-name="enter-l" @update:model-value="pl_event"></input-number>
|
||||||
|
</div>
|
||||||
|
<div class="flex-width-half pl-10">
|
||||||
|
<input-number v-model="form.padding_right" icon-name="enter-r" @update:model-value="pr_event"></input-number>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 用于页面判断显示
|
||||||
|
const state = reactive({
|
||||||
|
form: props.value || {},
|
||||||
|
});
|
||||||
|
// 如果需要解构,确保使用toRefs
|
||||||
|
const { form } = toRefs(state);
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
|
||||||
|
const padding_event = (val: number | undefined) => {
|
||||||
|
form.value.padding = Number(val);
|
||||||
|
form.value.padding_top = Number(val);
|
||||||
|
form.value.padding_bottom = Number(val);
|
||||||
|
form.value.padding_left = Number(val);
|
||||||
|
form.value.padding_right = Number(val);
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const pt_event = (val: number | undefined) => {
|
||||||
|
form.value.padding_top = Number(val);
|
||||||
|
form.value.padding = 0;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const pb_event = (val: number | undefined) => {
|
||||||
|
form.value.padding_bottom = Number(val);
|
||||||
|
form.value.padding = 0;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const pl_event = (val: number | undefined) => {
|
||||||
|
form.value.padding_left = Number(val);
|
||||||
|
form.value.padding = 0;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const pr_event = (val: number | undefined) => {
|
||||||
|
form.value.padding_right = Number(val);
|
||||||
|
form.value.padding = 0;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
99
src/components/base/qrcode/index.vue
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<!-- 二维码解析 -->
|
||||||
|
<template>
|
||||||
|
<div class="qrcode flex-col gap-5">
|
||||||
|
<div class="re flex-row qrcode-img">
|
||||||
|
<div v-if="isMask" class="mask">
|
||||||
|
{{ mask }}
|
||||||
|
</div>
|
||||||
|
<el-image :src="qrCodeUrl" fit="contain" class="w">
|
||||||
|
<template #error>
|
||||||
|
<div class="bg-f5 img flex-row jc-c align-c radius h w">
|
||||||
|
<icon name="error-img" size="42"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
</div>
|
||||||
|
<div class="flex-row align-c gap-10 size-12">
|
||||||
|
{{ src }}
|
||||||
|
<div class="copy" @click="clipboard_event">复制</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineProps, ref, onMounted } from 'vue';
|
||||||
|
import QRCode from 'qrcode';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
src: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
isMask: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
mask: {
|
||||||
|
type: String,
|
||||||
|
default: () => '请先选择分组',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const qrCodeUrl = ref('');
|
||||||
|
|
||||||
|
const generateQRCode = async (text: string, margin: number) => {
|
||||||
|
try {
|
||||||
|
const dataUrl = await QRCode.toDataURL(text, { margin });
|
||||||
|
qrCodeUrl.value = dataUrl;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating QR code:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const clipboard_event = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(props.src);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('复制失败', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 在组件挂载后自动调用生成二维码方法
|
||||||
|
onMounted(() => {
|
||||||
|
generateQRCode(props.src.trim(), 2);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.qrcode {
|
||||||
|
.mask {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
backdrop-filter: blur(0.2rem);
|
||||||
|
}
|
||||||
|
&-img {
|
||||||
|
width: 12rem;
|
||||||
|
height: 12rem;
|
||||||
|
border: 0.1rem solid #eeeeee;
|
||||||
|
}
|
||||||
|
.copy {
|
||||||
|
width: 3.2rem;
|
||||||
|
height: 2rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
border: 0.1rem solid #eeeeee;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #666666;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: $cr-primary;
|
||||||
|
border-color: $cr-primary-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
80
src/components/base/radius/index.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<!-- 通用样式 -->
|
||||||
|
<template>
|
||||||
|
<div class="flex-col gap-10 w">
|
||||||
|
<slider v-model="form.radius" @update:model-value="radius_event"></slider>
|
||||||
|
<div class="flex-row flex-wrap gap-x-20 mt-10">
|
||||||
|
<div class="flex-width-half pr-10">
|
||||||
|
<input-number v-model="form.radius_top_left" icon-name="radius-l-t" @update:model-value="rtl_event"></input-number>
|
||||||
|
</div>
|
||||||
|
<div class="flex-width-half pl-10">
|
||||||
|
<input-number v-model="form.radius_top_right" icon-name="radius-r-t" @update:model-value="rtr_event"></input-number>
|
||||||
|
</div>
|
||||||
|
<div class="flex-width-half pr-10">
|
||||||
|
<input-number v-model="form.radius_bottom_left" icon-name="radius-l-b" @update:model-value="rbl_event"></input-number>
|
||||||
|
</div>
|
||||||
|
<div class="flex-width-half pl-10">
|
||||||
|
<input-number v-model="form.radius_bottom_right" icon-name="radius-r-b" @update:model-value="rbr_event"></input-number>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
// interface common_radius {
|
||||||
|
// radius: number;
|
||||||
|
// radius_top_left: number;
|
||||||
|
// radius_top_right: number;
|
||||||
|
// radius_bottom_left: number;
|
||||||
|
// radius_bottom_right: number;
|
||||||
|
// }
|
||||||
|
// interface Props {
|
||||||
|
// value: common_radius;
|
||||||
|
// }
|
||||||
|
// const props = defineProps<Props>();
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 用于页面判断显示
|
||||||
|
const state = reactive({
|
||||||
|
form: props.value || {},
|
||||||
|
});
|
||||||
|
// 如果需要解构,确保使用toRefs
|
||||||
|
const { form } = toRefs(state);
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
const radius_event = (val: number | undefined) => {
|
||||||
|
form.value.radius = Number(val);
|
||||||
|
form.value.radius_top_left = Number(val);
|
||||||
|
form.value.radius_top_right = Number(val);
|
||||||
|
form.value.radius_bottom_left = Number(val);
|
||||||
|
form.value.radius_bottom_right = Number(val);
|
||||||
|
emit('update:value', form.value);
|
||||||
|
};
|
||||||
|
const rtl_event = (val: number | undefined) => {
|
||||||
|
form.value.radius_top_left = Number(val);
|
||||||
|
form.value.radius = 0;
|
||||||
|
emit('update:value', form.value);
|
||||||
|
};
|
||||||
|
const rtr_event = (val: number | undefined) => {
|
||||||
|
form.value.radius_top_right = Number(val);
|
||||||
|
form.value.radius = 0;
|
||||||
|
emit('update:value', form.value);
|
||||||
|
};
|
||||||
|
const rbl_event = (val: number | undefined) => {
|
||||||
|
form.value.radius_bottom_left = Number(val);
|
||||||
|
form.value.radius = 0;
|
||||||
|
emit('update:value', form.value);
|
||||||
|
};
|
||||||
|
const rbr_event = (val: number | undefined) => {
|
||||||
|
form.value.radius_bottom_right = Number(val);
|
||||||
|
form.value.radius = 0;
|
||||||
|
emit('update:value', form.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.common-styles {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
38
src/components/base/slider/index.vue
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<div class="slider w">
|
||||||
|
<el-slider v-model="internal_value" :min="min" :max="max" :step="step" />
|
||||||
|
<input-number v-model="internal_value" class="slider-input" :min="min" :max="max"></input-number>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps({
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 100,
|
||||||
|
},
|
||||||
|
step: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const internal_value = defineModel({ type: Number, default: 0 });
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.slider {
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
.slider-input {
|
||||||
|
:deep(.el-input-number) {
|
||||||
|
width: 9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
99
src/components/common/carousel-indicator/index.vue
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mb-12">指示器设置</div>
|
||||||
|
<el-form-item label="是否显示">
|
||||||
|
<el-switch v-model="form.is_show" size="large" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否滚动">
|
||||||
|
<el-switch v-model="form.is_roll" size="large" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="间隔时间">
|
||||||
|
<slider v-model="form.interval_time" :max="100"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<template v-if="form.is_show">
|
||||||
|
<el-form-item label="指示器样式">
|
||||||
|
<el-radio-group v-model="form.indicator_style" is-button>
|
||||||
|
<el-tooltip content="点" placement="top" effect="light">
|
||||||
|
<el-radio-button value="dot"><icon name="iconfont icon-round-dot"></icon></el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="线" placement="top" effect="light">
|
||||||
|
<el-radio-button value="elliptic"><icon name="iconfont icon-elliptic"></icon></el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="数字" placement="top" effect="light">
|
||||||
|
<el-radio-button value="num">1/5</el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="指示器位置">
|
||||||
|
<el-radio-group v-model="form.indicator_location" is-button>
|
||||||
|
<el-tooltip content="左对齐" placement="top" effect="light">
|
||||||
|
<el-radio-button value="flex-start"><icon name="iconfont icon-left"></icon></el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="居中" placement="top" effect="light">
|
||||||
|
<el-radio-button value="center"><icon name="iconfont icon-center"></icon></el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="右对齐" placement="top" effect="light">
|
||||||
|
<el-radio-button value="flex-end"><icon name="iconfont icon-right"></icon></el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="指示器大小">
|
||||||
|
<slider v-model="form.indicator_size" :max="100"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="指示器色值">
|
||||||
|
<div class="flex-col gap-20">
|
||||||
|
<color-picker v-model="form.actived_color" default-color="#2A94FF" @update:value="color_picker_change($event, 'actived_color')"></color-picker>
|
||||||
|
<color-picker v-model="form.color" default-color="#DDDDDD" @update:value="color_picker_change($event, 'color')"></color-picker>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="form.indicator_style != 'num'" label="指示器圆角">
|
||||||
|
<radius :value="form.indicator_radius" @update:value="indicator_radius_change"></radius>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
// interface indicator {
|
||||||
|
// is_show: boolean;
|
||||||
|
// is_roll: boolean;
|
||||||
|
// interval_time: number;
|
||||||
|
// indicator_style: string;
|
||||||
|
// indicator_location: string;
|
||||||
|
// indicator_size: number;
|
||||||
|
// indicator_radius: radiusStyle;
|
||||||
|
// actived_color: string;
|
||||||
|
// color: string;
|
||||||
|
// }
|
||||||
|
// interface Props {
|
||||||
|
// value: indicator;
|
||||||
|
// }
|
||||||
|
// const props = defineProps<Props>();
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
form: props.value
|
||||||
|
});
|
||||||
|
// 如果需要解构,确保使用toRefs
|
||||||
|
const { form } = toRefs(state);
|
||||||
|
const color_picker_change = (color: string, type: string) => {
|
||||||
|
if (type === 'actived_color') {
|
||||||
|
form.value.actived_color = color;
|
||||||
|
} else {
|
||||||
|
form.value.color = color;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 指示器圆角
|
||||||
|
const indicator_radius_change = (radius: radiusStyle) => {
|
||||||
|
form.value.indicator_radius = Object.assign(form.value.indicator_radius, pick(radius, [
|
||||||
|
'radius',
|
||||||
|
'radius_top_left',
|
||||||
|
'radius_top_right',
|
||||||
|
'radius_bottom_left',
|
||||||
|
'radius_bottom_right',
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
174
src/components/common/common-styles/index.vue
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
<!-- 通用样式 -->
|
||||||
|
<template>
|
||||||
|
<card-container>
|
||||||
|
<div class="common-styles">
|
||||||
|
<el-form :model="form" label-width="70">
|
||||||
|
<div class="mb-12">通用</div>
|
||||||
|
<el-form-item label="底部背景">
|
||||||
|
<div class="flex-col gap-10 w">
|
||||||
|
<div class="size-12">背景色</div>
|
||||||
|
<mult-color-picker :value="form.color_list" :type="form.direction" @update:value="mult_color_picker_event"></mult-color-picker>
|
||||||
|
<div class="flex-row jc-sb align-c">
|
||||||
|
<div class="size-12">背景图</div>
|
||||||
|
<el-radio-group v-model="form.background_img_style" is-button @change="background_img_style_change">
|
||||||
|
<el-tooltip content="单张" placement="top" effect="light">
|
||||||
|
<el-radio-button value="0"><icon name="single-sheet"></icon></el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="平铺" placement="top" effect="light">
|
||||||
|
<el-radio-button value="1"><icon name="tile"></icon></el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="铺满" placement="top" effect="light">
|
||||||
|
<el-radio-button value="2"><icon name="spread-over"></icon></el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
<upload v-model="form.background_img_url" :limit="1" @update:model-value="background_img_url_change"></upload>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="内边距">
|
||||||
|
<padding :value="form" @update:value="padding_change"></padding>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="外边距">
|
||||||
|
<div class="flex-col gap-10 w">
|
||||||
|
<slider v-model="form.margin" @update:model-value="margin_event"></slider>
|
||||||
|
<div class="flex-row flex-wrap gap-x-20 mt-10">
|
||||||
|
<div class="flex-width-half pr-10">
|
||||||
|
<input-number v-model="form.margin_top" icon-name="out-t" @update:model-value="mt_event"></input-number>
|
||||||
|
</div>
|
||||||
|
<div class="flex-width-half pl-10">
|
||||||
|
<input-number v-model="form.margin_bottom" icon-name="out-b" @update:model-value="mb_event"></input-number>
|
||||||
|
</div>
|
||||||
|
<div class="flex-width-half pr-10">
|
||||||
|
<input-number v-model="form.margin_left" icon-name="out-l" @update:model-value="ml_event"></input-number>
|
||||||
|
</div>
|
||||||
|
<div class="flex-width-half pl-10">
|
||||||
|
<input-number v-model="form.margin_right" icon-name="out-r" @update:model-value="mr_event"></input-number>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="圆角">
|
||||||
|
<radius :value="form" @update:value="radius_change"></radius>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="阴影设置">
|
||||||
|
<div class="flex-col gap-10 w">
|
||||||
|
<el-form-item label="颜色" label-width="45">
|
||||||
|
<color-picker v-model="form.box_shadow_color" @update:value="box_shadow_color_event"></color-picker>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="X轴" label-width="45">
|
||||||
|
<slider v-model="form.box_shadow_x" :min="-20" :max="20"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Y轴" label-width="45">
|
||||||
|
<slider v-model="form.box_shadow_y" :min="-20" :max="20"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="模糊" label-width="45">
|
||||||
|
<slider v-model="form.box_shadow_blur"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="扩展" label-width="45">
|
||||||
|
<slider v-model="form.box_shadow_spread"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</card-container>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 初始化表单数据
|
||||||
|
const init_form = reactive({
|
||||||
|
direction: '0deg',
|
||||||
|
background_img_url: [] as uploadList[],
|
||||||
|
color_list: [''],
|
||||||
|
background_img_style: 2,
|
||||||
|
padding: 0,
|
||||||
|
padding_top: 0,
|
||||||
|
padding_bottom: 0,
|
||||||
|
padding_left: 0,
|
||||||
|
padding_right: 0,
|
||||||
|
margin: 0,
|
||||||
|
margin_top: 0,
|
||||||
|
margin_bottom: 0,
|
||||||
|
margin_left: 0,
|
||||||
|
margin_right: 0,
|
||||||
|
radius: 0,
|
||||||
|
radius_top_left: 0,
|
||||||
|
radius_top_right: 0,
|
||||||
|
radius_bottom_left: 0,
|
||||||
|
radius_bottom_right: 0,
|
||||||
|
box_shadow_color: '',
|
||||||
|
box_shadow_x: 0,
|
||||||
|
box_shadow_y: 0,
|
||||||
|
box_shadow_blur: 0,
|
||||||
|
box_shadow_spread: 0,
|
||||||
|
});
|
||||||
|
// value 和初始化数据合并数据
|
||||||
|
let form = reactive(Object.assign({}, init_form, props.value));
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
const mult_color_picker_event = (arry: string[], type: number) => {
|
||||||
|
form.color_list = arry;
|
||||||
|
form.direction = type.toString();
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const background_img_style_change = (style: any) => {
|
||||||
|
form.background_img_style = style;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const background_img_url_change = (arry: uploadList[]) => {
|
||||||
|
form.background_img_url = arry;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
|
||||||
|
const margin_event = (val: number | undefined) => {
|
||||||
|
form.margin = Number(val);
|
||||||
|
form.margin_top = Number(val);
|
||||||
|
form.margin_bottom = Number(val);
|
||||||
|
form.margin_left = Number(val);
|
||||||
|
form.margin_right = Number(val);
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const mt_event = (val: number | undefined) => {
|
||||||
|
form.margin_top = Number(val);
|
||||||
|
form.margin = 0;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const mb_event = (val: number | undefined) => {
|
||||||
|
form.margin_bottom = Number(val);
|
||||||
|
form.margin = 0;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const ml_event = (val: number | undefined) => {
|
||||||
|
form.margin_left = Number(val);
|
||||||
|
form.margin = 0;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const mr_event = (val: number | undefined) => {
|
||||||
|
form.margin_right = Number(val);
|
||||||
|
form.margin = 0;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const radius_change = (radius: any) => {
|
||||||
|
form = Object.assign(form, pick(radius, ['radius', 'radius_top_left', 'radius_top_right', 'radius_bottom_left', 'radius_bottom_right']));
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
|
||||||
|
const padding_change = (padding: any) => {
|
||||||
|
form = Object.assign(form, pick(padding, ['padding', 'padding_top', 'padding_bottom', 'padding_left', 'padding_right']));
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const box_shadow_color_event = (val: string) => {
|
||||||
|
form.box_shadow_color = val;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.common-styles {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
68
src/components/common/custom-module/model-image/index.vue
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<div class="img-outer re w h" :style="border_style">
|
||||||
|
<image-empty v-model="img_src" :style="image_style"></image-empty>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { percentage_count, radius_computer } from '@/utils';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isPercentage: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 用于页面判断显示
|
||||||
|
const form = reactive(props.value);
|
||||||
|
|
||||||
|
const img_src = computed(() => {
|
||||||
|
if (!isEmpty(form.img_src[0])) {
|
||||||
|
return form.img_src[0];
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
url: form.data_source_list.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const image_style = computed(() => {
|
||||||
|
return `${ set_count() };transform: rotate(${form.img_rotate}deg); ${ radius_computer(form.img_radius) };`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const border_style = computed(() => {
|
||||||
|
let style = ``;
|
||||||
|
if (form.border_show) {
|
||||||
|
style += `border: ${form.border_size}px ${form.border_style} ${form.border_color}; ${ radius_computer(form.border_radius) };`
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
});
|
||||||
|
const set_count = () => {
|
||||||
|
if (props.isPercentage) {
|
||||||
|
return `width: ${ percentage_count(form.img_width, form.com_width) }; height: ${ percentage_count(form.img_height, form.com_height) };`;
|
||||||
|
} else {
|
||||||
|
return `width: ${form.img_width}px; height: ${form.img_height}px;`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.img-outer {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
:deep(.el-image) {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
.el-image__inner {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.image-slot img {
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,134 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w h bg-f">
|
||||||
|
<el-form :model="form" label-width="60">
|
||||||
|
<card-container>
|
||||||
|
<div class="mb-12">图片设置</div>
|
||||||
|
<el-form-item label="上传图片">
|
||||||
|
<div class="flex-column w">
|
||||||
|
<upload v-model="form.img_src" :limit="1" size="50"></upload>
|
||||||
|
<div class="flex-row align-c gap-10 mt-12">
|
||||||
|
<el-select v-model="form.data_source_id" value-key="id" placeholder="请选择图片数据字段" size="default" class="flex-1">
|
||||||
|
<el-option v-for="item in options" :key="item.id" :label="item.label" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
<el-popover placement="top-start" :width="200" trigger="hover" content="this is content, this is content, this is content">
|
||||||
|
<template #reference>
|
||||||
|
<icon name="tips" size="24"></icon>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="链接">
|
||||||
|
<url-value v-model="form.link"></url-value>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="图片圆角">
|
||||||
|
<radius :value="form.img_radius" @update:value="img_radius_change"></radius>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="图片宽度">
|
||||||
|
<slider v-model="form.img_width" :max="1000"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="图片高度">
|
||||||
|
<slider v-model="form.img_height" :max="1000"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="旋转角度">
|
||||||
|
<slider v-model="form.img_rotate" :max="1000"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否置底">
|
||||||
|
<el-switch v-model="form.bottom_up" />
|
||||||
|
</el-form-item>
|
||||||
|
</card-container>
|
||||||
|
<div class="bg-f5 partition-line" />
|
||||||
|
<card-container class="h">
|
||||||
|
<div class="mb-12">边框设置</div>
|
||||||
|
<el-form-item label="边框显示">
|
||||||
|
<el-radio-group v-model="form.border_show" class="ml-4">
|
||||||
|
<el-radio :value="true">显示</el-radio>
|
||||||
|
<el-radio :value="false">隐藏</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<template v-if="form.border_show">
|
||||||
|
<el-form-item label="边框颜色">
|
||||||
|
<color-picker v-model="form.border_color" default-color="#FF3F3F"></color-picker>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="边框样式">
|
||||||
|
<el-radio-group v-model="form.border_style" class="ml-4">
|
||||||
|
<el-radio value="dashed"><div class="border-style-item" style="border: 1px dashed #979797"></div></el-radio>
|
||||||
|
<el-radio value="solid"><div class="border-style-item" style="border: 1px solid #979797"></div></el-radio>
|
||||||
|
<el-radio value="dotted"><div class="border-style-item" style="border: 1px dotted #979797"></div></el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="边框圆角">
|
||||||
|
<radius :value="form.border_radius" @update:value="border_radius_change"></radius>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="边框粗细">
|
||||||
|
<slider v-model="form.border_size" :max="1000"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</card-container>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { location_compute } from '@/utils';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array<any>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 默认值
|
||||||
|
const state = reactive({
|
||||||
|
diy_data: props.value,
|
||||||
|
});
|
||||||
|
// 如果需要解构,确保使用toRefs
|
||||||
|
const { diy_data } = toRefs(state);
|
||||||
|
const form = ref(diy_data.value.com_data);
|
||||||
|
const center_height = defineModel('height', { type: Number, default: 0 });
|
||||||
|
|
||||||
|
const img_radius_change = (radius: any) => {
|
||||||
|
form.value.img_radius = Object.assign(form.value.img_radius, pick(radius, ['radius', 'radius_top_left', 'radius_top_right', 'radius_bottom_left', 'radius_bottom_right']));
|
||||||
|
};
|
||||||
|
const border_radius_change = (radius: any) => {
|
||||||
|
form.value.border_radius = Object.assign(form.value.border_radius, pick(radius, ['radius', 'radius_top_left', 'radius_top_right', 'radius_bottom_left', 'radius_bottom_right']));
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
diy_data,
|
||||||
|
(val) => {
|
||||||
|
let width = form.value.img_width;
|
||||||
|
let height = form.value.img_height;
|
||||||
|
if (form.value.border_show) {
|
||||||
|
width += form.value.border_size * 2;
|
||||||
|
height += form.value.border_size * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
diy_data.value.location.x = location_compute(width, val.location.x, 390);
|
||||||
|
diy_data.value.location.y = location_compute(height, val.location.y, center_height.value);
|
||||||
|
diy_data.value.location.staging_y = diy_data.value.location.y;
|
||||||
|
|
||||||
|
form.value.com_width = width;
|
||||||
|
form.value.com_height = height;
|
||||||
|
form.value.staging_height = height;
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card.mb-8 {
|
||||||
|
.el-form-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.border-style-item {
|
||||||
|
width: 3rem;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
.partition-line {
|
||||||
|
height: 0.8rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
52
src/components/common/custom-module/model-lines/index.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<div :style="border_style"></div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isPercentage: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 用于页面判断显示
|
||||||
|
const form = reactive(props.value);
|
||||||
|
|
||||||
|
const border_style = computed(() => {
|
||||||
|
if (form.line_settings === 'horizontal') {
|
||||||
|
return `${ set_count() } margin: 5px 0;border-bottom: ${form.line_size }px ${form.line_style} ${form.line_color};`;
|
||||||
|
} else {
|
||||||
|
return `${ set_count() } margin: 0 5px;border-right: ${form.line_size }px ${form.line_style} ${form.line_color};`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const set_count = () => {
|
||||||
|
if (props.isPercentage) {
|
||||||
|
return '';
|
||||||
|
} else {
|
||||||
|
if (form.line_settings === 'horizontal') {
|
||||||
|
return `width: ${ form.com_width }px;`;
|
||||||
|
} else {
|
||||||
|
return `height: ${ form.com_height }px;`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-image) {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
.el-image__inner {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.image-slot img {
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w h bg-f">
|
||||||
|
<el-form :model="form" label-width="80">
|
||||||
|
<card-container class="">
|
||||||
|
<div class="mb-12">线条设置</div>
|
||||||
|
<el-form-item label="竖线横线">
|
||||||
|
<el-radio-group v-model="form.line_settings">
|
||||||
|
<el-radio value="horizontal">横线</el-radio>
|
||||||
|
<el-radio value="vertical">竖线</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="线条样式">
|
||||||
|
<el-radio-group v-model="form.line_style">
|
||||||
|
<el-radio value="dashed">
|
||||||
|
<icon name="line-point" class="re top-2" size="32"></icon>
|
||||||
|
</el-radio>
|
||||||
|
<el-radio value="solid">
|
||||||
|
<icon name="line" class="re top-2" size="32"></icon>
|
||||||
|
</el-radio>
|
||||||
|
<el-radio value="dotted">
|
||||||
|
<icon name="line-point" class="re top-2" size="32"></icon>
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="form.line_settings === 'horizontal' ? '线条宽度' : '线条高度'">
|
||||||
|
<slider v-model="form.line_width" :max="1000"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="线条粗细">
|
||||||
|
<slider v-model="form.line_size" :min="1" :max="100"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="线条颜色">
|
||||||
|
<color-picker v-model="form.line_color" default-color="#FF3F3F"></color-picker>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否置底">
|
||||||
|
<el-switch v-model="form.bottom_up" />
|
||||||
|
</el-form-item>
|
||||||
|
</card-container>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { location_compute } from '@/utils';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 默认值
|
||||||
|
const state = reactive({
|
||||||
|
diy_data: props.value
|
||||||
|
});
|
||||||
|
// 如果需要解构,确保使用toRefs
|
||||||
|
const { diy_data } = toRefs(state);
|
||||||
|
const form = ref(diy_data.value.com_data);
|
||||||
|
const center_height = defineModel("height", { type: Number, default: 0 })
|
||||||
|
|
||||||
|
watch(diy_data, (val) => {
|
||||||
|
let width = 0;
|
||||||
|
let height = 0;
|
||||||
|
if (form.value.line_settings === 'horizontal') {
|
||||||
|
width = form.value.line_width;
|
||||||
|
height = form.value.line_size + 10;
|
||||||
|
} else {
|
||||||
|
width = form.value.line_size + 10;
|
||||||
|
height = form.value.line_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
diy_data.value.location.x = location_compute(width, val.location.x, 390);
|
||||||
|
diy_data.value.location.y = location_compute(height, val.location.y, center_height.value);
|
||||||
|
diy_data.value.location.staging_y = diy_data.value.location.y;
|
||||||
|
|
||||||
|
form.value.com_width = width;
|
||||||
|
form.value.com_height = height;
|
||||||
|
form.value.staging_height = height;
|
||||||
|
|
||||||
|
}, {immediate: true, deep: true});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card.mb-8 {
|
||||||
|
.el-form-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.border-style-item {
|
||||||
|
width: 3rem;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
49
src/components/common/custom-module/model-text/index.vue
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<div class="img-outer re" :style="com_style">
|
||||||
|
<div :style="text_style">{{ form.text_title }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { radius_computer, padding_computer } from '@/utils';
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isPercentage: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 用于页面判断显示
|
||||||
|
const form = reactive(props.value);
|
||||||
|
|
||||||
|
const text_style = computed(() => {
|
||||||
|
let style = `font-size: ${ form.text_size }px;color: ${ form.text_color }; text-align: ${ form.text_location }; transform: rotate(${form.text_rotate}deg);text-decoration: ${ form.text_option };${ padding_computer(form.text_padding) };`;
|
||||||
|
if (form.text_weight == 'italic') {
|
||||||
|
style += `font-style: italic`;
|
||||||
|
} else if (form.text_weight == '500') {
|
||||||
|
style += `font-weight: 500`;
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
});
|
||||||
|
|
||||||
|
const com_style = computed(() => {
|
||||||
|
let style = `${ set_count() } background-color: ${ form.com_bg };`;
|
||||||
|
if (form.border_show) {
|
||||||
|
style += `border: ${form.border_size}px ${form.border_style} ${form.border_color}; ${ radius_computer(form.border_radius) };`;
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
});
|
||||||
|
const set_count = () => {
|
||||||
|
if (props.isPercentage) {
|
||||||
|
return '';
|
||||||
|
} else {
|
||||||
|
return `width: ${ form.com_width }px; height: ${ form.com_height }px;`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w h bg-f">
|
||||||
|
<el-form :model="form" label-width="80">
|
||||||
|
<card-container>
|
||||||
|
<div class="mb-12">文本设置</div>
|
||||||
|
<el-form-item label="文本内容">
|
||||||
|
<div class="flex-column w">
|
||||||
|
<el-input v-model="form.text_title" placeholder="请输入链接" type="textarea" :rows="3"></el-input>
|
||||||
|
<div class="flex-row align-c gap-10 mt-12">
|
||||||
|
<el-select v-model="form.data_source_id" value-key="id" placeholder="请选择图片数据字段" size="default" class="flex-1">
|
||||||
|
<el-option v-for="item in options" :key="item.id" :label="item.label" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
<el-popover placement="top-start" :width="200" trigger="hover" content="this is content, this is content, this is content">
|
||||||
|
<template #reference>
|
||||||
|
<icon name="tips" size="24"></icon>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="链接">
|
||||||
|
<url-value v-model="form.text_link"></url-value>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="文字颜色">
|
||||||
|
<color-picker v-model="form.text_color" default-color="#FF3F3F"></color-picker>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="文字大小">
|
||||||
|
<el-radio-group v-model="form.text_weight" class="ml-4">
|
||||||
|
<el-radio value="500">加粗</el-radio>
|
||||||
|
<el-radio value="normal">正常</el-radio>
|
||||||
|
<el-radio value="italic">倾斜</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-form-item label="字号" label-width="40" class="mb-0 w">
|
||||||
|
<slider v-model="form.text_size" :max="100"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="字符选项">
|
||||||
|
<el-radio-group v-model="form.text_option" class="ml-4">
|
||||||
|
<el-radio value="none"><span style="text-decoration: none">Aa</span></el-radio>
|
||||||
|
<el-radio value="underline"><span style="text-decoration: underline">Aa</span></el-radio>
|
||||||
|
<el-radio value="line-through"><span style="text-decoration: line-through">Aa</span></el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="文字位置">
|
||||||
|
<el-radio-group v-model="form.text_location" is-button>
|
||||||
|
<el-tooltip content="左对齐" placement="top" effect="light">
|
||||||
|
<el-radio-button value="left"><icon name="iconfont icon-left"></icon></el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="居中" placement="top" effect="light">
|
||||||
|
<el-radio-button value="center"><icon name="iconfont icon-center"></icon></el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="右对齐" placement="top" effect="light">
|
||||||
|
<el-radio-button value="right"><icon name="iconfont icon-right"></icon></el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="内边距">
|
||||||
|
<padding :value="form.text_padding" @update:value="padding_change"></padding>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="旋转角度">
|
||||||
|
<slider v-model="form.text_rotate" :max="1000"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否置底">
|
||||||
|
<el-switch v-model="form.bottom_up" />
|
||||||
|
</el-form-item>
|
||||||
|
</card-container>
|
||||||
|
<div class="bg-f5 partition-line" />
|
||||||
|
<card-container>
|
||||||
|
<div class="mb-12">容器设置</div>
|
||||||
|
<el-form-item label="容器宽度">
|
||||||
|
<slider v-model="form.com_width" :max="1000"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="容器高度">
|
||||||
|
<slider v-model="form.com_height" :max="1000"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="背景颜色">
|
||||||
|
<color-picker v-model="form.com_bg" default-color="#FF3F3F"></color-picker>
|
||||||
|
</el-form-item>
|
||||||
|
</card-container>
|
||||||
|
<div class="bg-f5 partition-line" />
|
||||||
|
<card-container>
|
||||||
|
<div class="mb-12">边框设置</div>
|
||||||
|
<el-form-item label="边框显示">
|
||||||
|
<el-radio-group v-model="form.border_show" class="ml-4">
|
||||||
|
<el-radio :value="true">显示</el-radio>
|
||||||
|
<el-radio :value="false">隐藏</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<template v-if="form.border_show">
|
||||||
|
<el-form-item label="边框颜色">
|
||||||
|
<color-picker v-model="form.border_color" default-color="#FF3F3F"></color-picker>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="边框样式">
|
||||||
|
<el-radio-group v-model="form.border_style" class="ml-4">
|
||||||
|
<el-radio value="dashed"><div class="border-style-item" style="border: 1px dashed #979797"></div></el-radio>
|
||||||
|
<el-radio value="solid"><div class="border-style-item" style="border: 1px solid #979797"></div></el-radio>
|
||||||
|
<el-radio value="dotted"><div class="border-style-item" style="border: 1px dotted #979797"></div></el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="边框圆角">
|
||||||
|
<radius :value="form.border_radius" @update:value="border_radius_change"></radius>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="边框粗细">
|
||||||
|
<slider v-model="form.border_size" :max="1000"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</card-container>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { location_compute } from '@/utils';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array<any>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 默认值
|
||||||
|
const state = reactive({
|
||||||
|
diy_data: props.value,
|
||||||
|
});
|
||||||
|
// 如果需要解构,确保使用toRefs
|
||||||
|
const { diy_data } = toRefs(state);
|
||||||
|
const form = ref(diy_data.value.com_data);
|
||||||
|
const center_height = defineModel('height', { type: Number, default: 0 });
|
||||||
|
|
||||||
|
const padding_change = (padding: any) => {
|
||||||
|
form.value.text_padding = Object.assign(form.value.text_padding, pick(padding, ['padding', 'padding_top', 'padding_bottom', 'padding_left', 'padding_right']));
|
||||||
|
};
|
||||||
|
const border_radius_change = (radius: any) => {
|
||||||
|
form.value.border_radius = Object.assign(form.value.border_radius, pick(radius, ['radius', 'radius_top_left', 'radius_top_right', 'radius_bottom_left', 'radius_bottom_right']));
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
diy_data,
|
||||||
|
(val) => {
|
||||||
|
diy_data.value.location.x = location_compute(form.value.com_width, val.location.x, 390);
|
||||||
|
diy_data.value.location.y = location_compute(form.value.com_height, val.location.y, center_height.value);
|
||||||
|
diy_data.value.location.staging_y = diy_data.value.location.y;
|
||||||
|
|
||||||
|
form.value.staging_height = form.value.com_height;
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card.mb-8 {
|
||||||
|
.el-form-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.border-style-item {
|
||||||
|
width: 3rem;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
.partition-line {
|
||||||
|
height: 0.8rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
57
src/components/common/flex-gradients-create/index.vue
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<div v-for="(item, index) in list" :key="index" class="flex-row align-s gap-12">
|
||||||
|
<div class="flex-col">
|
||||||
|
<el-color-picker v-model="item.color" show-alpha :predefine="predefine_colors" />
|
||||||
|
<div v-if="index == 0" class="connect-line"></div>
|
||||||
|
</div>
|
||||||
|
<icon name="reset" color="primary" size="16" class="c-pointer" @click="reset_event(index)"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const predefine_colors = ref(['#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1', '#1e90ff', '#c71585', 'rgba(255, 69, 0, 0.68)', 'rgb(255, 120, 0)', 'hsv(51, 100, 98)', 'hsva(120, 40, 94, 0.5)', 'hsl(181, 100%, 37%)', 'hsla(209, 100%, 56%, 0.73)', '#c7158577']);
|
||||||
|
interface list_page {
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
interface Props {
|
||||||
|
colorList: list_page[];
|
||||||
|
defaultColor: string;
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
defaultColor: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = ref(props.colorList);
|
||||||
|
// 默认值
|
||||||
|
const reset_event = (index: number) => {
|
||||||
|
list.value[index].color = props.defaultColor;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.connect-line {
|
||||||
|
width: 0.1rem;
|
||||||
|
height: 1.6rem;
|
||||||
|
background: #d8d8d8;
|
||||||
|
position: relative;
|
||||||
|
left: 1rem;
|
||||||
|
// 合并before和after重复代码
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
left: -0.2rem;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ddd;
|
||||||
|
}
|
||||||
|
&::before {
|
||||||
|
top: -0.25rem;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
bottom: -0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
379
src/components/common/hot/index.vue
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
<!-- 上传组件 -->
|
||||||
|
<template>
|
||||||
|
<el-dialog v-model="dialog_visible" append-to-body fullscreen @close="close_event">
|
||||||
|
<template #header>
|
||||||
|
<div class="title re">
|
||||||
|
<div class="tc size-16 fw">编辑热区</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-scrollbar class="content-scrollbar">
|
||||||
|
<div class="pa-40 flex-row gap-40">
|
||||||
|
<div class="left-content flex-1 pa-20">
|
||||||
|
<el-scrollbar class="img-scrollbar">
|
||||||
|
<div class="img-container">
|
||||||
|
<div ref="imgBoxRef" @mousedown.prevent="start_drag" @mousemove.prevent="move_drag" @mouseup.prevent="end_drag">
|
||||||
|
<el-image :src="hot_list.img" class="w img" @selectstart.prevent @contextmenu.prevent @dragstart.prevent></el-image>
|
||||||
|
<div ref="areaRef" class="area" :style="init_drag_style"></div>
|
||||||
|
<div v-for="(item, index) in hot_list.hot" :key="index" class="area-box" :style="rect_style(item.drag_start, item.drag_end)" @mousedown.prevent="start_drag_area_box(index, $event)" @dblclick="dbl_drag_event(item, index)">
|
||||||
|
<div class="del-btn" @click.stop="del_area_event(index)"><icon name="close"></icon></div>
|
||||||
|
<div class="drag-btn" :data-index="index" @mousedown.prevent="start_drag_btn(index, $event)"></div>
|
||||||
|
<div class="text">
|
||||||
|
<div class="name">{{ item.name }}</div>
|
||||||
|
<div class="status" :class="!is_obj_empty(item.link) ? 'cr-primary' : 'cr-error'">{{ !is_obj_empty(item.link) ? '已设置' : '未设置' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
<div class="right-content flex-1 pa-20">
|
||||||
|
<div class="size-16 fw mb-10">图片热区</div>
|
||||||
|
<div class="size-12 cr-9 mb-20">框选热区范围,双击设置热区信息</div>
|
||||||
|
<div class="flex-col gap-20 item">
|
||||||
|
<div v-for="(item, index) in hot_list.hot" :key="index" class="flex-row align-c gap-10">
|
||||||
|
<el-input v-model="item.name" class="name" placeholder="名称"></el-input>
|
||||||
|
<url-value v-model="item.link"></url-value>
|
||||||
|
<icon name="del" size="20" @click="del_event(index)"></icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button class="plr-28 ptb-10" type="primary" @click="confirm_event">完成</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<el-dialog v-model="hot_dialog_visible" width="560" append-to-body @close="hot_close_event">
|
||||||
|
<template #header>
|
||||||
|
<div class="title re">
|
||||||
|
<div class="tc size-16 fw">设置热区</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="content">
|
||||||
|
<el-form ref="formRef" :model="form" label-width="85px" class="pa-20 mt-16">
|
||||||
|
<el-form-item label="名称">
|
||||||
|
<el-input v-model="form.name" placeholder="请输入名称"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="热区跳转链接">
|
||||||
|
<url-value v-model="form.link"></url-value>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button class="plr-28 ptb-10" @click="hot_close_event">取消</el-button>
|
||||||
|
<el-button class="plr-28 ptb-10" type="primary" @click="hot_confirm_event">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<el-button class="w" @click="open_hot_event"><icon name="add">编辑热区</icon></el-button>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { is_obj_empty } from '@/utils';
|
||||||
|
const app = getCurrentInstance();
|
||||||
|
/**
|
||||||
|
* @description: 热区
|
||||||
|
* @param modelValue{Object} 默认值
|
||||||
|
* @param dialog_visible {Boolean} 弹窗显示
|
||||||
|
* @return {*} update:modelValue
|
||||||
|
*/
|
||||||
|
const props = defineProps({});
|
||||||
|
const modelValue = defineModel({ type: Object as PropType<hotData>, default: {} });
|
||||||
|
const dialog_visible = defineModel('visibleDialog', { type: Boolean, default: false });
|
||||||
|
const hot_list = ref<hotData>({
|
||||||
|
img: '',
|
||||||
|
hot: [],
|
||||||
|
});
|
||||||
|
const hot_list_index = ref(0);
|
||||||
|
watch(
|
||||||
|
() => modelValue.value,
|
||||||
|
(val) => {
|
||||||
|
hot_list.value = cloneDeep(val);
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
//#region 左侧画布-----------------------------------------------start
|
||||||
|
const imgBoxRef = ref<HTMLElement | null>(null);
|
||||||
|
const rect_start = ref<rectCoords>({ x: 0, y: 0, width: 0, height: 0 });
|
||||||
|
const rect_end = ref<rectCoords>({ x: 0, y: 0, width: 0, height: 0 });
|
||||||
|
const areaRef = ref<HTMLElement | null>(null);
|
||||||
|
const init_drag_style = ref('');
|
||||||
|
const drag_bool = ref(false);
|
||||||
|
const drag_box_bool = ref(false);
|
||||||
|
const drag_box_scale_bool = ref(false);
|
||||||
|
const start_drag = (event: MouseEvent) => {
|
||||||
|
drag_bool.value = true;
|
||||||
|
if (!imgBoxRef.value) return;
|
||||||
|
rect_start.value.x = event.clientX - imgBoxRef.value.getBoundingClientRect().left;
|
||||||
|
rect_start.value.y = event.clientY - imgBoxRef.value.getBoundingClientRect().top;
|
||||||
|
rect_start.value.width = 0;
|
||||||
|
rect_start.value.height = 0;
|
||||||
|
};
|
||||||
|
const move_drag = (event: MouseEvent) => {
|
||||||
|
if (drag_bool.value) {
|
||||||
|
if (!imgBoxRef.value) return;
|
||||||
|
rect_end.value.x = event.clientX - imgBoxRef.value.getBoundingClientRect().left;
|
||||||
|
rect_end.value.y = event.clientY - imgBoxRef.value.getBoundingClientRect().top;
|
||||||
|
rect_end.value.width = rect_end.value.x - rect_start.value.x > 0 ? rect_end.value.x - rect_start.value.x : 0;
|
||||||
|
rect_end.value.height = rect_end.value.y - rect_start.value.y > 0 ? rect_end.value.y - rect_start.value.y : 0;
|
||||||
|
init_drag_style.value = `left: ${rect_start.value.x}px;top: ${rect_start.value.y}px;width: ${Math.max(rect_end.value.width, 1)}px;height: ${Math.max(rect_end.value.height, 1)}px;display: flex;`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const end_drag = (event: MouseEvent) => {
|
||||||
|
drag_bool.value = false;
|
||||||
|
if (areaRef.value) areaRef.value.style.display = 'none';
|
||||||
|
if (!imgBoxRef.value) return;
|
||||||
|
init_drag_style.value = ``;
|
||||||
|
if (rect_end.value.width > 16 && rect_end.value.height > 16) {
|
||||||
|
hot_list.value.hot.push({
|
||||||
|
name: '热区' + (hot_list.value.hot.length + 1),
|
||||||
|
link: {},
|
||||||
|
drag_start: cloneDeep(rect_start.value),
|
||||||
|
drag_end: cloneDeep(rect_end.value),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
rect_end.value = { x: 0, y: 0, width: 0, height: 0 };
|
||||||
|
};
|
||||||
|
const area_box_point = ref({ x: 0, y: 0 });
|
||||||
|
// area-box
|
||||||
|
const dbl_drag_event = (item: hotListData, index: number) => {
|
||||||
|
hot_dialog_visible.value = true;
|
||||||
|
form.value.link = item.link;
|
||||||
|
form.value.name = item.name;
|
||||||
|
hot_list_index.value = index;
|
||||||
|
};
|
||||||
|
const start_drag_area_box = (index: number, event: MouseEvent) => {
|
||||||
|
hot_list_index.value = index;
|
||||||
|
event.stopPropagation();
|
||||||
|
drag_box_bool.value = true;
|
||||||
|
let clone_drag_start = cloneDeep(hot_list.value.hot[hot_list_index.value].drag_start);
|
||||||
|
let clone_drag_end = cloneDeep(hot_list.value.hot[hot_list_index.value].drag_end);
|
||||||
|
// 记录原始位置
|
||||||
|
area_box_point.value = {
|
||||||
|
x: clone_drag_start.x - event.clientX,
|
||||||
|
y: clone_drag_start.y - event.clientY,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当子元素拖拽方法触发后夫元素方法不触发
|
||||||
|
document.onmousemove = (areaBoxEvent) => {
|
||||||
|
areaBoxEvent.stopPropagation();
|
||||||
|
if (drag_box_bool.value) {
|
||||||
|
if (!imgBoxRef.value) return;
|
||||||
|
const new_coordinate = {
|
||||||
|
x: areaBoxEvent.clientX + area_box_point.value.x,
|
||||||
|
y: areaBoxEvent.clientY + area_box_point.value.y,
|
||||||
|
};
|
||||||
|
// 左上边界判断
|
||||||
|
if (new_coordinate.x < 0) {
|
||||||
|
new_coordinate.x = 0;
|
||||||
|
}
|
||||||
|
if (new_coordinate.y < 0) {
|
||||||
|
new_coordinate.y = 0;
|
||||||
|
}
|
||||||
|
// 右下边界判断
|
||||||
|
if (new_coordinate.x + Math.max(clone_drag_end.width, 1) > imgBoxRef.value.getBoundingClientRect().width) {
|
||||||
|
new_coordinate.x = imgBoxRef.value.getBoundingClientRect().width - Math.max(clone_drag_end.width, 1) - 4;
|
||||||
|
}
|
||||||
|
if (new_coordinate.y + Math.max(clone_drag_end.height, 1) > imgBoxRef.value.getBoundingClientRect().height) {
|
||||||
|
new_coordinate.y = imgBoxRef.value.getBoundingClientRect().height - Math.max(clone_drag_end.height, 1) - 7;
|
||||||
|
}
|
||||||
|
hot_list.value.hot[hot_list_index.value].drag_start.x = new_coordinate.x;
|
||||||
|
hot_list.value.hot[hot_list_index.value].drag_start.y = new_coordinate.y;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.onmouseup = (areaBoxEvent) => {
|
||||||
|
areaBoxEvent.stopPropagation();
|
||||||
|
drag_box_bool.value = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// drag-btn
|
||||||
|
const start_drag_btn = (index: number, event: MouseEvent) => {
|
||||||
|
hot_list_index.value = index;
|
||||||
|
event.stopPropagation();
|
||||||
|
drag_box_scale_bool.value = true;
|
||||||
|
let clone_drag_start = hot_list.value.hot[hot_list_index.value].drag_start;
|
||||||
|
let clone_drag_end = hot_list.value.hot[hot_list_index.value].drag_end;
|
||||||
|
document.onmousemove = (dragBtnEvent) => {
|
||||||
|
dragBtnEvent.stopPropagation();
|
||||||
|
//用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
|
||||||
|
if (drag_box_scale_bool.value) {
|
||||||
|
if (!imgBoxRef.value) return;
|
||||||
|
clone_drag_end.x = dragBtnEvent.clientX - imgBoxRef.value.getBoundingClientRect().left;
|
||||||
|
clone_drag_end.y = dragBtnEvent.clientY - imgBoxRef.value.getBoundingClientRect().top;
|
||||||
|
hot_list.value.hot[hot_list_index.value].drag_end = {
|
||||||
|
x: clone_drag_end.x,
|
||||||
|
y: clone_drag_end.y,
|
||||||
|
width: clone_drag_end.x - clone_drag_start.x > 0 ? clone_drag_end.x - clone_drag_start.x : 0,
|
||||||
|
height: clone_drag_end.y - clone_drag_start.y > 0 ? clone_drag_end.y - clone_drag_start.y : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.onmouseup = (dragBtnEvent2) => {
|
||||||
|
dragBtnEvent2.stopPropagation();
|
||||||
|
drag_box_scale_bool.value = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const del_area_event = (index: number) => {
|
||||||
|
hot_list.value.hot.splice(index, 1);
|
||||||
|
};
|
||||||
|
const rect_style = computed(() => {
|
||||||
|
return (start: rectCoords, end: rectCoords) => {
|
||||||
|
return `left: ${start.x}px;top: ${start.y}px;width: ${Math.max(end.width, 1)}px;height: ${Math.max(end.height, 1)}px;display: flex;`;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
//#endregion 左侧画布-----------------------------------------------end
|
||||||
|
|
||||||
|
//#region 右侧热区编辑-----------------------------------------------start
|
||||||
|
const del_event = (index: number) => {
|
||||||
|
hot_list.value.hot.splice(index, 1);
|
||||||
|
};
|
||||||
|
//#endregion 右侧热区编辑-----------------------------------------------end
|
||||||
|
|
||||||
|
//#region 设置热区弹窗-----------------------------------------------start
|
||||||
|
const hot_dialog_visible = ref(false);
|
||||||
|
const form = ref({
|
||||||
|
link: {},
|
||||||
|
name: '',
|
||||||
|
});
|
||||||
|
const hot_close_event = () => {
|
||||||
|
hot_dialog_visible.value = false;
|
||||||
|
};
|
||||||
|
const hot_confirm_event = () => {
|
||||||
|
hot_list.value.hot[hot_list_index.value].link = form.value.link;
|
||||||
|
hot_list.value.hot[hot_list_index.value].name = form.value.name;
|
||||||
|
hot_close_event();
|
||||||
|
};
|
||||||
|
//#endregion 设置热区弹窗-----------------------------------------------end
|
||||||
|
|
||||||
|
//#region 热区开启关闭确认取消回调 -----------------------------------------------start
|
||||||
|
// 打开热区弹窗
|
||||||
|
const open_hot_event = () => {
|
||||||
|
if (modelValue.value.img.length > 0) {
|
||||||
|
dialog_visible.value = true;
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
type: 'warning',
|
||||||
|
message: '请先选择图片',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消回调
|
||||||
|
const close_event = () => {
|
||||||
|
dialog_visible.value = false;
|
||||||
|
};
|
||||||
|
// 确认回调
|
||||||
|
const confirm_event = () => {
|
||||||
|
modelValue.value = hot_list.value;
|
||||||
|
close_event();
|
||||||
|
};
|
||||||
|
//#endregion 热区开启关闭确认取消回调 -----------------------------------------------end
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.content-scrollbar {
|
||||||
|
height: calc(100vh - 13.8rem);
|
||||||
|
margin: 0 -1.6rem;
|
||||||
|
.left-content {
|
||||||
|
.img-scrollbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
.img-container {
|
||||||
|
max-width: 60rem;
|
||||||
|
min-width: 30rem;
|
||||||
|
height: calc(100vh - 25.8rem);
|
||||||
|
position: relative;
|
||||||
|
.img {
|
||||||
|
user-select: none;
|
||||||
|
cursor: crosshair;
|
||||||
|
padding: 0 0.4rem 0.4rem 0;
|
||||||
|
}
|
||||||
|
.area {
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(41, 128, 185, 0.3);
|
||||||
|
border: 1px dashed #34495e;
|
||||||
|
width: 0px;
|
||||||
|
height: 0px;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.area-box {
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(42, 148, 255, 0.25);
|
||||||
|
border: 1px dashed #8ec6ff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #1989fa;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: move;
|
||||||
|
transition: transform 0.1s;
|
||||||
|
.del-btn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #1890ff;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 0 0 0 0.3rem;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.7rem;
|
||||||
|
top: 0.7rem;
|
||||||
|
transform: translate3d(50%, -50%, 0);
|
||||||
|
cursor: default;
|
||||||
|
width: 1.6rem;
|
||||||
|
height: 1.6rem;
|
||||||
|
line-height: 1.6rem;
|
||||||
|
z-index: 1;
|
||||||
|
i {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drag-btn {
|
||||||
|
position: absolute;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border: 1px solid #333;
|
||||||
|
right: -0.4rem;
|
||||||
|
bottom: -0.4rem;
|
||||||
|
cursor: nwse-resize;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
.name {
|
||||||
|
color: #fff;
|
||||||
|
margin: 0 0.2rem;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
margin: 0 0.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right-content {
|
||||||
|
.item {
|
||||||
|
max-width: 47.8rem;
|
||||||
|
.name {
|
||||||
|
width: 9.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
132
src/components/common/mult-color-picker/index.vue
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mult-color-picker">
|
||||||
|
<el-radio-group v-model="direction_type" @change="direction_type_change">
|
||||||
|
<el-radio value="0deg">横向</el-radio>
|
||||||
|
<el-radio value="90deg">纵向</el-radio>
|
||||||
|
<el-radio value="315deg">左斜</el-radio>
|
||||||
|
<el-radio value="45deg">右斜</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<div class="flex-col">
|
||||||
|
<div v-for="(item, index) in color_list" :key="index" class="flex-row align-s gap-12">
|
||||||
|
<div class="flex-col">
|
||||||
|
<el-color-picker v-model="item.color" show-alpha :predefine="predefine_colors" @change="change_color(index, $event)" />
|
||||||
|
<div v-if="index + 1 !== color_list.length" class="connect-line"></div>
|
||||||
|
</div>
|
||||||
|
<template v-if="index == 0">
|
||||||
|
<icon name="reset" color="primary" size="16" class="c-pointer" @click="reset_event"></icon>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<icon name="close" color="c" size="12" class="c-pointer" @click="del_event(index)" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="add-color mt-15" @click="add_event">
|
||||||
|
<icon name="add"></icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: '0deg',
|
||||||
|
},
|
||||||
|
// 颜色数组 ['','']
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
[''];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const predefine_colors = ref(['#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1', '#1e90ff', '#c71585', 'rgba(255, 69, 0, 0.68)', 'rgb(255, 120, 0)', 'hsv(51, 100, 98)', 'hsva(120, 40, 94, 0.5)', 'hsl(181, 100%, 37%)', 'hsla(209, 100%, 56%, 0.73)', '#c7158577']);
|
||||||
|
const direction_type = ref(props.type);
|
||||||
|
let color_list = reactive(
|
||||||
|
//将数组['#fff']改为对象数组
|
||||||
|
props.value.map((item: any) => {
|
||||||
|
return {
|
||||||
|
color: item,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
const direction_type_change = (type: any) => {
|
||||||
|
direction_type.value = type.toString();
|
||||||
|
update_value();
|
||||||
|
};
|
||||||
|
const reset_event = () => {
|
||||||
|
color_list = [{ color: '' }];
|
||||||
|
update_value();
|
||||||
|
};
|
||||||
|
const del_event = (index: number) => {
|
||||||
|
color_list.splice(index, 1);
|
||||||
|
update_value();
|
||||||
|
};
|
||||||
|
const add_event = () => {
|
||||||
|
color_list.push({ color: '' });
|
||||||
|
update_value();
|
||||||
|
};
|
||||||
|
const change_color = (index: number, color: string | null) => {
|
||||||
|
color_list[index].color = color;
|
||||||
|
update_value();
|
||||||
|
};
|
||||||
|
const update_value = () => {
|
||||||
|
let new_color_list = color_list.map((item) => item.color) || [];
|
||||||
|
emit('update:value', new_color_list, direction_type.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mult-color-picker {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
.connect-line {
|
||||||
|
width: 0.1rem;
|
||||||
|
height: 1.6rem;
|
||||||
|
background: #d8d8d8;
|
||||||
|
position: relative;
|
||||||
|
left: 1rem;
|
||||||
|
// 合并before和after重复代码
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
left: -0.2rem;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ddd;
|
||||||
|
}
|
||||||
|
&::before {
|
||||||
|
top: -0.25rem;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
bottom: -0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.add-color {
|
||||||
|
width: 3.2rem;
|
||||||
|
height: 3.2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
color: #dcdfe6;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: #d4d7de;
|
||||||
|
border-color: #d4d7de;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.icon-close:hover {
|
||||||
|
color: $cr-error;
|
||||||
|
}
|
||||||
|
.icon-reset:hover {
|
||||||
|
color: $cr-primary-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
300
src/components/common/picture-cube/index.vue
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="decorate-cube">
|
||||||
|
<ul v-for="(n, index) in densityNum" :key="index" class="cube-col">
|
||||||
|
<li v-for="(i, index1) in densityNum" :key="index1" class="cube-item" :style="{ width: cubeCellWidth + 'px', height: cubeCellHeight + 'px' }" :data-x="n" :data-y="i" @click="onClickCubeItem($event)" @mouseenter="onEnterCubeItem($event)">
|
||||||
|
<div :class="['w h item', { 'item-selecting': isSelecting(n, i), 'item-selected': isSelected(n, i) }]">
|
||||||
|
<icon name="add" color="9" :style="{ 'line-height': cubeCellHeight + 'px' }"></icon>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div v-for="(item, index) in selectedList" :key="index" :class="['cube-selected', {'cube-selected_active': selected_active == index }]" :style="selected_style(item)" @click="selected_click(index)">
|
||||||
|
<div v-if="selected_active == index && props.flag" class="cube-del" @click.stop="on_selected_del(index)">
|
||||||
|
<icon name="close" color="f" size="8"></icon>
|
||||||
|
</div>
|
||||||
|
<template v-if="!isEmpty(item.img[0])">
|
||||||
|
<image-empty v-model="item.img[0]"></image-empty>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="cube-selected-text">
|
||||||
|
{{ Math.round((750 / densityNum) * (item.end.y - item.start.y + 1)) }}
|
||||||
|
x
|
||||||
|
{{ Math.round((750 / densityNum) * (item.end.x - item.start.x + 1)) }}
|
||||||
|
像素
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
interface CubeItem {
|
||||||
|
start: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
end: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
img: uploadList[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
flag: boolean;
|
||||||
|
list: CubeItem[];
|
||||||
|
cubeWidth: number;
|
||||||
|
cubeHeight: number;
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
list: () => [],
|
||||||
|
flag: false,
|
||||||
|
cubeWidth: 390,
|
||||||
|
cubeHeight: 390,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selected_active = ref(0);
|
||||||
|
//#region 容器大小变更
|
||||||
|
const density = ref('4');
|
||||||
|
//#endregion
|
||||||
|
const selectingItem = reactive<any>({
|
||||||
|
tempStart: null,
|
||||||
|
tempEnd: null,
|
||||||
|
start: null,
|
||||||
|
end: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedList = ref(props.list);
|
||||||
|
|
||||||
|
//单元魔方宽度。
|
||||||
|
const cubeCellWidth = computed(() => props.cubeWidth / parseInt(density.value));
|
||||||
|
//密度值。
|
||||||
|
const densityNum = computed(() => parseInt(density.value));
|
||||||
|
//单元魔方高度。
|
||||||
|
const cubeCellHeight = computed(() => props.cubeHeight / parseInt(density.value));
|
||||||
|
const emits = defineEmits(['selected_click']);
|
||||||
|
|
||||||
|
// 判断选择的内容的长度是否发生变化
|
||||||
|
watch(() => selectedList.value.length, (val) => {
|
||||||
|
if (val > 1) {
|
||||||
|
selected_active.value = val - 1;
|
||||||
|
} else {
|
||||||
|
selected_active.value = 0;
|
||||||
|
}
|
||||||
|
emits('selected_click', selected_active.value);
|
||||||
|
}, {deep: true});
|
||||||
|
|
||||||
|
const updateSelecting = () => {
|
||||||
|
//获取开始和结束之间的所有魔方单元。
|
||||||
|
const tempStart = selectingItem.tempStart;
|
||||||
|
const tempEnd = selectingItem.tempEnd;
|
||||||
|
|
||||||
|
selectingItem.start = {
|
||||||
|
x: Math.min(tempStart.x, tempEnd.x),
|
||||||
|
y: Math.min(tempStart.y, tempEnd.y),
|
||||||
|
};
|
||||||
|
selectingItem.end = {
|
||||||
|
x: Math.max(tempStart.x, tempEnd.x),
|
||||||
|
y: Math.max(tempStart.y, tempEnd.y),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
//清除正在选择的。
|
||||||
|
const clearSelecting = () => {
|
||||||
|
selectingItem.tempStart = null;
|
||||||
|
selectingItem.tempEnd = null;
|
||||||
|
selectingItem.start = null;
|
||||||
|
selectingItem.end = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const coordFromCubeEvent = (event: any) => {
|
||||||
|
var el = event.currentTarget;
|
||||||
|
var x = el.getAttribute('data-x');
|
||||||
|
var y = el.getAttribute('data-y');
|
||||||
|
return { x: x, y: y };
|
||||||
|
};
|
||||||
|
|
||||||
|
const isContain = (x: number, y: number, item: CubeItem) => {
|
||||||
|
return item.start.x <= x && x <= item.end.x && item.start.y <= y && y <= item.end.y;
|
||||||
|
};
|
||||||
|
//魔方点击事件。
|
||||||
|
const onClickCubeItem = (event: any) => {
|
||||||
|
let domclass = event.currentTarget.getAttribute('class');
|
||||||
|
if (-1 !== domclass.indexOf('item-selected')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let coord = coordFromCubeEvent(event);
|
||||||
|
|
||||||
|
if (null == selectingItem.tempStart) {
|
||||||
|
selectingItem.tempStart = coord;
|
||||||
|
selectingItem.tempEnd = coord;
|
||||||
|
selectingItem.start = coord;
|
||||||
|
selectingItem.end = coord;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectingItem.tempEnd = coord;
|
||||||
|
updateSelecting();
|
||||||
|
|
||||||
|
//加入选中的。
|
||||||
|
let selectedItem = {
|
||||||
|
start: selectingItem.start,
|
||||||
|
end: selectingItem.end,
|
||||||
|
img: [],
|
||||||
|
};
|
||||||
|
selectedList.value.push(selectedItem);
|
||||||
|
clearSelecting();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEnterCubeItem = (event: any) => {
|
||||||
|
if (selectingItem.tempStart) {
|
||||||
|
var coord = coordFromCubeEvent(event);
|
||||||
|
selectingItem.tempEnd = coord;
|
||||||
|
updateSelecting();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 删除当前选中的内容
|
||||||
|
const on_selected_del = (index: number) => {
|
||||||
|
clearSelecting();
|
||||||
|
// splice() 会先从原数组中添加/删除项目 然后返回被删除的项目。
|
||||||
|
selectedList.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
//判断是否正在选择
|
||||||
|
const isSelecting = (x: number, y: number) => {
|
||||||
|
const item = selectingItem;
|
||||||
|
if (item.tempStart) {
|
||||||
|
return isContain(x, y, item);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
//判断是否已经选择。
|
||||||
|
const isSelected = (x: number, y: number) => {
|
||||||
|
for (var i = 0; i < selectedList.value.length; i++) {
|
||||||
|
if (isContain(x, y, selectedList.value[i])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
//计算选中层的宽度。
|
||||||
|
const getSelectedWidth = (item: CubeItem) => {
|
||||||
|
return (item.end.x - item.start.x + 1) * cubeCellWidth.value;
|
||||||
|
};
|
||||||
|
//计算选中层的高度。
|
||||||
|
const getSelectedHeight = (item: CubeItem) => {
|
||||||
|
return (item.end.y - item.start.y + 1) * cubeCellHeight.value;
|
||||||
|
};
|
||||||
|
//计算选中层的右边距离。
|
||||||
|
const getSelectedTop = (item: CubeItem) => {
|
||||||
|
return (item.start.y - 1) * cubeCellHeight.value;
|
||||||
|
};
|
||||||
|
//计算选中层的左边距离。
|
||||||
|
const getSelectedLeft = (item: CubeItem) => {
|
||||||
|
return (item.start.x - 1) * cubeCellWidth.value;
|
||||||
|
};
|
||||||
|
// 生成的样式
|
||||||
|
const selected_style = (item: CubeItem) => {
|
||||||
|
return `width: ${ getSelectedWidth(item) }px; height: ${ getSelectedHeight(item) }px; top: ${ getSelectedTop(item) }px; left: ${ getSelectedLeft(item) }px;`
|
||||||
|
}
|
||||||
|
// 选中的点击事件
|
||||||
|
const selected_click = (index: number) => {
|
||||||
|
selected_active.value = index;
|
||||||
|
emits('selected_click', index);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.decorate-cube {
|
||||||
|
position: relative;
|
||||||
|
.cube-col {
|
||||||
|
float: left;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.cube-item {
|
||||||
|
background: #f5f5f5;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
.item {
|
||||||
|
border: 1px solid #fff;
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
.item-selecting {
|
||||||
|
background: #e0edff;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
.item-selected {
|
||||||
|
background: #e0edff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cube-item:first-child {
|
||||||
|
.item {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cube-item:last-child {
|
||||||
|
.item {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cube-col:first-child {
|
||||||
|
.cube-item .item {
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cube-col:last-of-type {
|
||||||
|
.cube-item .item {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cube-selected-text {
|
||||||
|
font-size: 12px;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) translateY(-50%);
|
||||||
|
}
|
||||||
|
.cube-selected {
|
||||||
|
position: absolute;
|
||||||
|
background-color: #eee;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
text-align: center;
|
||||||
|
color: $cr-primary;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.cube-selected_active {
|
||||||
|
border: 1px solid $cr-primary;
|
||||||
|
}
|
||||||
|
.cube-del {
|
||||||
|
background: $cr-primary;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1.6rem;
|
||||||
|
width: 1.6rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
:deep(.el-image) {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
.el-image__inner {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.image-slot img {
|
||||||
|
width: 6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
129
src/components/common/product-show-config/index.vue
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<template>
|
||||||
|
<card-container class="card-container-br">
|
||||||
|
<div class="mb-12">显示内容</div>
|
||||||
|
<el-form-item label="是否显示">
|
||||||
|
<el-checkbox-group v-model="form.is_show">
|
||||||
|
<el-checkbox v-for="item in base_list.list_show_list" :key="item.value" :value="item.value">{{ item.name }}</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-form-item>
|
||||||
|
</card-container>
|
||||||
|
<card-container>
|
||||||
|
<div class="mb-12">购物车设置</div>
|
||||||
|
<el-form-item label="是否显示">
|
||||||
|
<el-switch v-model="form.is_shop_show"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="按钮样式" class="align-c">
|
||||||
|
<div class="flex-row align-c jc-s gap-20 shopping_button_all">
|
||||||
|
<div v-for="item in base_list.shopping_button_list" :key="item.value" :class="['pa-10 re', { 'br-c br-primary radius-sm': shop_type(item) }]" @click="shopping_button_click(item)">
|
||||||
|
<template v-if="item.value == '0'">
|
||||||
|
<div class="pl-13 pr-13 round size-12 bg-primary cr-f shopping_button">{{ item.name }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.value == '1'">
|
||||||
|
<div class="pl-11 pr-11 round size-12 bg-primary cr-f shopping_button">{{ item.name }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.value == '2'">
|
||||||
|
<icon class="shopping_button round pl-6 pr-6 bg-primary " name="add" color="f" size="16"></icon>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<icon class="shopping_button round pl-6 pr-6 bg-primary" name="cart" color="f" size="16"></icon>
|
||||||
|
</template>
|
||||||
|
<div v-if="shop_type(item)" class="button-checked">
|
||||||
|
<icon name="true" color="f" size="8"></icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否显示" label-width="140">
|
||||||
|
<el-radio-group v-model="form.shop_button_size">
|
||||||
|
<el-radio v-for="item in base_list.shopping_button_size" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否显示">
|
||||||
|
<el-radio-group v-model="form.shop_button_effect">
|
||||||
|
<el-radio v-for="item in base_list.shopping_cart_list" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</card-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
form: props.value,
|
||||||
|
});
|
||||||
|
// 如果需要解构,确保使用toRefs
|
||||||
|
const { form } = toRefs(state);
|
||||||
|
|
||||||
|
const base_list = {
|
||||||
|
list_show_list: [
|
||||||
|
{ name: '商品名称', value: '0' },
|
||||||
|
{ name: '商品标签', value: '1' },
|
||||||
|
{ name: '商品售价', value: '2' },
|
||||||
|
{ name: '商品销量', value: '3' },
|
||||||
|
{ name: '商品评分', value: '4' },
|
||||||
|
{ name: '商品原价', value: '5' },
|
||||||
|
],
|
||||||
|
shopping_button_list: [
|
||||||
|
{ name: '购买', value: '0' },
|
||||||
|
{ name: '立即购买', value: '1' },
|
||||||
|
{ name: '添加', value: '2' },
|
||||||
|
{ name: '购物车', value: '3' },
|
||||||
|
],
|
||||||
|
shopping_cart_list: [
|
||||||
|
{ name: '进入商品详情页', value: '0' },
|
||||||
|
{ name: '商品加购', value: '1' }
|
||||||
|
],
|
||||||
|
shopping_button_size: [
|
||||||
|
{ name: '大', value: '0' },
|
||||||
|
{ name: '中', value: '1' },
|
||||||
|
{ name: '小', value: '2' },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const shop_type = computed(() => {
|
||||||
|
return (item: { value: string; }) => {
|
||||||
|
return item.value == form.value.shop_type;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const shopping_button_click = (item: { value: string; }) => {
|
||||||
|
form.value.shop_type = item.value;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.card-container-br {
|
||||||
|
border-bottom: 0.8rem solid #f0f2f5;
|
||||||
|
}
|
||||||
|
.shopping_button {
|
||||||
|
height: 2.7rem;
|
||||||
|
line-height: 2.7rem;
|
||||||
|
}
|
||||||
|
.shopping_button_all {
|
||||||
|
height: 4.7rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.button-checked {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border: 1.1rem solid #000;
|
||||||
|
border-color: transparent $cr-primary $cr-primary transparent;
|
||||||
|
.iconfont {
|
||||||
|
width: 1rem;
|
||||||
|
height: 2.1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
164
src/components/common/tabs-view/index.vue
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tabs flex-row oh">
|
||||||
|
<template v-for="(item, index) in tabs.content.tabs_list" :key="index">
|
||||||
|
<div class="item nowrap flex-col jc-c gap-4" :class="tabs_style + (index == 0 ? ' active' : '')">
|
||||||
|
<img class="img" src="@/assets/images/components/model-user-info/avatar.png" />
|
||||||
|
<div class="title" :style="index == 0 ? tabs_style_style.tabs_title_checked : tabs_style_style.tabs_title">{{ item.title }}</div>
|
||||||
|
<div class="desc">{{ item.desc }}</div>
|
||||||
|
<icon name="checked-1" class="icon"></icon>
|
||||||
|
<div class="bottom_line" :style="tabs_style_style.tabs_check"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { gradient_computer } from '@/utils';
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const tabs = ref(props.value);
|
||||||
|
const tabs_style = computed(() => {
|
||||||
|
let tabs_style = '';
|
||||||
|
if (tabs.value.content.tabs_style == '1') {
|
||||||
|
tabs_style = 'tabs-style-2';
|
||||||
|
} else if (tabs.value.content.tabs_style == '2') {
|
||||||
|
tabs_style = 'tabs-style-3';
|
||||||
|
} else if (tabs.value.content.tabs_style == '3') {
|
||||||
|
tabs_style = 'tabs-style-4';
|
||||||
|
} else if (tabs.value.content.tabs_style == '4') {
|
||||||
|
tabs_style = 'tabs-style-5';
|
||||||
|
} else {
|
||||||
|
tabs_style = 'tabs-style-1';
|
||||||
|
}
|
||||||
|
return tabs_style;
|
||||||
|
});
|
||||||
|
const tabs_style_style = computed(() => {
|
||||||
|
const new_gradient_params = {
|
||||||
|
color_list: tabs.value.style.tabs_checked,
|
||||||
|
direction: tabs.value.style.tabs_direction,
|
||||||
|
};
|
||||||
|
const new_style = {
|
||||||
|
tabs_check: gradient_computer(new_gradient_params),
|
||||||
|
tabs_title_checked: 'font-weight:' + tabs.value.style.tabs_weight_checked + ';' + 'font-size:' + tabs.value.style.tabs_size_checked + 'px;' + 'color:' + tabs.value.style.tabs_color_checked,
|
||||||
|
tabs_title: 'font-weight:' + tabs.value.style.tabs_weight + ';' + 'font-size:' + tabs.value.style.tabs_size + 'px;' + 'color:' + tabs.value.style.tabs_color,
|
||||||
|
};
|
||||||
|
return new_style;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tabs {
|
||||||
|
max-width: 39rem;
|
||||||
|
.item {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
margin: 0 1rem;
|
||||||
|
position: relative;
|
||||||
|
&:first-of-type {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
&:last-of-type {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.bottom_line {
|
||||||
|
width: 100%;
|
||||||
|
height: 0.3rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background-color: red;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2rem;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
width: 3.9rem;
|
||||||
|
height: 3.9rem;
|
||||||
|
border-radius: 100%;
|
||||||
|
border: 0.1rem solid transparent;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&.tabs-style-1 {
|
||||||
|
&.active {
|
||||||
|
.bottom_line {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.tabs-style-2 {
|
||||||
|
&.active {
|
||||||
|
.desc {
|
||||||
|
background: red;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 0.2rem 0.6rem;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.tabs-style-3 {
|
||||||
|
&.active {
|
||||||
|
.title {
|
||||||
|
background: red;
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 0.2rem 1.2rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.tabs-style-4 {
|
||||||
|
padding-bottom: 1.8rem;
|
||||||
|
&.active {
|
||||||
|
.title {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
color: red;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.tabs-style-5 {
|
||||||
|
align-items: center;
|
||||||
|
&.active {
|
||||||
|
.title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
background: red;
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 0.2rem 0.7rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
border-color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
27
src/components/common/text-size-type/index.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<el-radio-group v-model="typeface" class="ml-4">
|
||||||
|
<el-radio v-for="item in font_weight" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-form-item label="字号" label-width="40" class="mb-0 w">
|
||||||
|
<slider v-model="size" :max="100"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const typeface = defineModel('typeface', {
|
||||||
|
type: String,
|
||||||
|
default: 'normal'
|
||||||
|
});
|
||||||
|
const size = defineModel('size', {
|
||||||
|
type: Number,
|
||||||
|
default: 15
|
||||||
|
});
|
||||||
|
|
||||||
|
const font_weight = [
|
||||||
|
{ name: '加粗', value: '500' },
|
||||||
|
{ name: '正常', value: 'normal' },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
||||||
157
src/components/common/upload/index.scss
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
.upload-content {
|
||||||
|
height: 57.4rem;
|
||||||
|
gap: 4.5rem;
|
||||||
|
.left-content {
|
||||||
|
width: 22.5rem;
|
||||||
|
.el-tree {
|
||||||
|
--el-tree-node-content-height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right-content {
|
||||||
|
position: relative;
|
||||||
|
.el-button--primary.is-plain {
|
||||||
|
--el-button-bg-color: transparent;
|
||||||
|
}
|
||||||
|
.right-classify {
|
||||||
|
width: 21.9rem;
|
||||||
|
}
|
||||||
|
.right-search {
|
||||||
|
width: 22.5rem;
|
||||||
|
}
|
||||||
|
.img-content {
|
||||||
|
width: calc(100% + 2rem);
|
||||||
|
margin: 0 -1rem;
|
||||||
|
.item {
|
||||||
|
width: calc(100% / 7 - 1.286rem);
|
||||||
|
.badge {
|
||||||
|
:deep(.el-badge__content.is-fixed) {
|
||||||
|
min-width: 1.8rem;
|
||||||
|
height: 1.8rem;
|
||||||
|
line-height: 1.8rem;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.item-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 10rem;
|
||||||
|
.check-icon {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
&.active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.oprate {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1;
|
||||||
|
height: 2.4rem;
|
||||||
|
overflow: hidden;
|
||||||
|
.oprate-content {
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.5s linear;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2.4rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background-color: #ccc;
|
||||||
|
border-bottom-left-radius: 0.4rem;
|
||||||
|
border-bottom-right-radius: 0.4rem;
|
||||||
|
height: 2.4rem;
|
||||||
|
}
|
||||||
|
.oprate-icon {
|
||||||
|
position: relative;
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
width: 0.1rem;
|
||||||
|
height: 40%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background-color: #bbb;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
width: 0.1rem;
|
||||||
|
height: 40%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background-color: #bbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
height: 2.8rem;
|
||||||
|
line-height: 2.8rem;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.oprate-content {
|
||||||
|
opacity: 1 !important;
|
||||||
|
bottom: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.upload-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
&-style-0 {
|
||||||
|
position: relative;
|
||||||
|
background: #fafcff;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
border: 0.1rem dashed #d7eeff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
&-style-1 {
|
||||||
|
position: relative;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
.upload-btn-bottom-text {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1.8rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(0, 0, 0, 0.59);
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-style-2 {
|
||||||
|
position: relative;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.upload-del-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
right: -6px;
|
||||||
|
z-index: 1;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
542
src/components/common/upload/index.vue
Normal file
@ -0,0 +1,542 @@
|
|||||||
|
<!-- 上传组件 -->
|
||||||
|
<template>
|
||||||
|
<el-dialog v-model="dialogVisible" class="radius-lg" width="1168" append-to-body>
|
||||||
|
<template #header>
|
||||||
|
<div class="title re">
|
||||||
|
<el-radio-group v-model="upload_type" is-button @change="upload_type_change">
|
||||||
|
<el-radio-button value="img" :disabled="!(upload_type == 'img') && isCheckConfirm">图片</el-radio-button>
|
||||||
|
<el-radio-button value="video" :disabled="!(upload_type == 'video') && isCheckConfirm">视频</el-radio-button>
|
||||||
|
<el-radio-button value="file" :disabled="!(upload_type == 'file') && isCheckConfirm">文件</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<div class="middle size-16 fw">附件管理</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="upload-content pa-20 flex-row">
|
||||||
|
<div class="left-content">
|
||||||
|
<el-input v-model="search_filter" class="mb-10" placeholder="请输入分类名称">
|
||||||
|
<template #suffix>
|
||||||
|
<icon name="search" size="18"></icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-scrollbar height="490px">
|
||||||
|
<el-tree ref="treeRef" class="filter-tree" :data="type_data" node-key="id" highlight-current :expand-on-click-node="false" :props="defaultProps" empty-text="无数据" default-expand-all :filter-node-method="filter_node" @node-click="tree_node_event" />
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
<div class="right-content flex-1 flex-width">
|
||||||
|
<div class="flex-row jc-sb align-c mb-15">
|
||||||
|
<div class="right-oprate flex-row">
|
||||||
|
<el-button type="primary" plain @click="upload_model_open">上传{{ upload_type_name }}</el-button>
|
||||||
|
<el-button @click="mult_del_event">删除{{ upload_type_name }}</el-button>
|
||||||
|
<el-cascader class="right-classify ml-12" :options="category_list" :placeholder="upload_type_name + '移动至'" :show-all-levels="false"></el-cascader>
|
||||||
|
</div>
|
||||||
|
<div class="right-search">
|
||||||
|
<el-input v-model="search_name" :placeholder="'请输入' + upload_type_name + '名称'" @input="get_list">
|
||||||
|
<template #suffix>
|
||||||
|
<icon name="search" size="18"></icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="img-content pr">
|
||||||
|
<!-- 574px -->
|
||||||
|
<el-scrollbar height="440px">
|
||||||
|
<div class="flex-row flex-wrap align-c gap-y-15 gap-x-10 pa-10">
|
||||||
|
<div v-for="(item, index) in upload_list" :key="index" class="item" @click="check_img_event(item)">
|
||||||
|
<el-badge :value="view_list_value.findIndex((i) => i.id === item.id) == -1 ? '' : view_list_value.findIndex((i) => i.id === item.id) + 1" class="badge flex-col gap-5 w" :hidden="view_list_value.findIndex((i) => i.id === item.id) == -1">
|
||||||
|
<div class="item-content re br-f5 radius">
|
||||||
|
<template v-if="upload_type == 'video'">
|
||||||
|
<video :src="item.url" class="w h" @error="handle_error(index)"></video>
|
||||||
|
<div v-if="item.error == true" class="bg-f5 img flex-row jc-c align-c radius h w abs top-0">
|
||||||
|
<icon name="video" size="42" color="9"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="upload_type == 'file'">
|
||||||
|
<div class="bg-f5 img flex-row jc-c align-c radius h w">
|
||||||
|
<icon :name="ext_file_name_list_map.filter((ext) => ext.type == item.type).length > 0 && ext_file_name_list_map.filter((ext) => ext.type == item.type)[0].type == item.type ? ext_file_name_list_map.filter((ext) => ext.type == item.type)[0].icon : 'file'" size="42" color="9"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-image :src="item.url" fit="contain" class="w h">
|
||||||
|
<template #error>
|
||||||
|
<div class="bg-f5 img flex-row jc-c align-c radius h w">
|
||||||
|
<icon name="error-img" size="42" color="9"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
</template>
|
||||||
|
<div class="check-icon fill flex-row jc-c align-c" :class="view_list_value.findIndex((i) => i.id === item.id) !== -1 ? 'active' : ''">
|
||||||
|
<icon name="true-o" color="f" size="26"></icon>
|
||||||
|
</div>
|
||||||
|
<div class="oprate">
|
||||||
|
<div class="oprate-content flex-row jc-sa align-c">
|
||||||
|
<div class="flex-1 tc c-pointer" @click.stop="edit_event(item, index)">
|
||||||
|
<icon name="edit" class="flex-1" size="14" color="f"></icon>
|
||||||
|
</div>
|
||||||
|
<div v-if="upload_type !== 'file'" class="oprate-icon flex-1 tc c-pointer" @click.stop="preview_event(item, index)">
|
||||||
|
<icon name="eye" size="14" color="f"></icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 tc c-pointer" @click.stop="del_event(item)">
|
||||||
|
<icon name="del" size="14" color="f"></icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-line-1 name">
|
||||||
|
<template v-if="edit_index !== -1 && edit_index === index">
|
||||||
|
<el-input v-model="item.original" type="text" placeholder="请输入内容" size="small" @change="edit_input_change" @blur="edit_input_blur" />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="ptb-1 plr-7">
|
||||||
|
{{ item.original }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</el-badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
<div v-if="preview_switch_video && upload_type == 'video'">
|
||||||
|
<div class="middle clickable-area" :class="preview_url ? '' : 'hide'">
|
||||||
|
<!-- 视频预览 -->
|
||||||
|
<!-- 自动播放 -->
|
||||||
|
<video ref="videoPlayer" width="320" height="240" controls autoplay :src="preview_url"></video>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-10 flex-row jc-e">
|
||||||
|
<el-pagination :current-page="page" :page-size="21" :pager-count="5" layout="prev, pager, next" :total="data_total" @current-change="get_list" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-if="isCheckConfirm" #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button class="plr-28 ptb-10" @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button class="plr-28 ptb-10" type="primary" @click="confirm_event">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<template v-if="!isCustomDialog">
|
||||||
|
<div class="flex-col h">
|
||||||
|
<div class="flex-row flex-wrap gap-10 h">
|
||||||
|
<div v-for="(item, index) in modelValue" :key="item.id" :class="'upload-btn upload-btn-style-' + styles + ' ' + (styles == 2 ? 'br-none' : '')" :style="'height:' + upload_size + ';width:' + upload_size + ';'" @click="replace_file_event(index)">
|
||||||
|
<div class="upload-del-icon" @click.stop="del_upload_event(index)">
|
||||||
|
<icon name="close-o" color="c" size="14"></icon>
|
||||||
|
</div>
|
||||||
|
<template v-if="type == 'video'">
|
||||||
|
<video :src="item.url" class="w h"></video>
|
||||||
|
<div v-if="item.error == true" class="bg-f5 img flex-row jc-c align-c radius h w abs top-0">
|
||||||
|
<icon name="video" :size="Number(size) / 2 + ''" color="9"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type == 'file'">
|
||||||
|
<div class="bg-f5 img flex-row jc-c align-c radius h w">
|
||||||
|
<icon :name="ext_file_name_list_map.filter((ext) => ext.type == item.type).length > 0 && ext_file_name_list_map.filter((ext) => ext.type == item.type)[0].type == item.type ? ext_file_name_list_map.filter((ext) => ext.type == item.type)[0].icon : 'file'" :size="Number(size) / 2 + ''" color="9"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-image :src="item.url" fit="contain" class="w h">
|
||||||
|
<template #error>
|
||||||
|
<div class="bg-f5 img flex-row jc-c align-c radius h w">
|
||||||
|
<icon name="error-img" :size="Number(size) / 2 + ''" color="9"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
</template>
|
||||||
|
<template v-if="styles == 1">
|
||||||
|
<div class="upload-btn-bottom-text">替换</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-if="limit !== modelValue.length" :class="'upload-btn upload-btn-style-' + styles" :style="'height:' + upload_size + ';width:' + upload_size + ';'" @click="dialogVisible = true">
|
||||||
|
<icon name="add" :size="Number(size) / 2 + ''" color="c"></icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="isTips" class="size-12 cr-9">{{ tipsText }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 图片预览 -->
|
||||||
|
<el-image-viewer v-if="preview_switch_img && upload_type == 'img'" :z-index="999999" :url-list="[preview_url]" :hide-on-click-modal="true" @close="preview_close"></el-image-viewer>
|
||||||
|
<upload-model v-model="upload_model_visible" :type="upload_type" :exts="props.type == 'img' ? ext_img_name_list : props.type == 'video' ? ext_video_name_list : ext_file_name_list" @close="close_upload_model"></upload-model>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const app = getCurrentInstance();
|
||||||
|
/**
|
||||||
|
* @description: 图片上传
|
||||||
|
* @param modelValue{uploadList[]} 默认值
|
||||||
|
* @param visibleDialog{Boolean} 弹窗开启关闭
|
||||||
|
* @param type{String} 上传类型 默认图片 1.图片(img) 2.视频(video) 3.文件(file)
|
||||||
|
* @param isCustomDialog{Boolean} 是否自定义弹窗, 配置true后将不会显示上传按钮改为传v-model:visible-dialog=""来开启关闭弹窗,通过@update:v-model=""来获取最新数据
|
||||||
|
* @param isCheckConfirm{Boolean} 弹窗是否需要操作提交取消按钮
|
||||||
|
* @param limit{Number} 上传数量限制
|
||||||
|
* @param isTips{Boolean} 是否显示提示文字
|
||||||
|
* @param tipsText{String} 提示文字
|
||||||
|
* @param size{Number|String} 上传图片大小
|
||||||
|
* @param style{Number} 样式 0.默认样式 1.自定义样式1 2.自定义样式2
|
||||||
|
* @return {*} update:modelValue
|
||||||
|
*/
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'img', // img/video/file
|
||||||
|
},
|
||||||
|
isCustomDialog: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isCheckConfirm: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
default: 10,
|
||||||
|
},
|
||||||
|
isTips: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
tipsText: {
|
||||||
|
type: String,
|
||||||
|
default: '建议尺寸:690*240px',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 72,
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const modelValue = defineModel({ type: Array as PropType<uploadList[]>, default: [] });
|
||||||
|
|
||||||
|
const view_list_value = ref<uploadList[]>([]);
|
||||||
|
// 弹窗显示
|
||||||
|
// const dialogVisible = ref(props.visibleDialog);
|
||||||
|
const dialogVisible = defineModel('visibleDialog', { type: Boolean, default: false });
|
||||||
|
|
||||||
|
// 文件后缀分类
|
||||||
|
const ext_img_name_list = ref(['.png', '.jpg', '.jpeg', '.bmp', '.webp', '.gif']);
|
||||||
|
const ext_video_name_list = ref(['.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg', '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm']);
|
||||||
|
const ext_file_name_list = ref(['.png', 'jpg', 'jpeg', 'bmp', 'webp', 'gif', '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg', '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.csv', '.wav', '.mid', '.cab', '.iso', '.ofd', '.xml', '.rar', '.zip', '.tar', '.gz', '.7z', '.bz2', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf', '.txt', '.md', '.vsd']);
|
||||||
|
const ext_file_name_list_map = ref([
|
||||||
|
{ type: '.png', icon: 'error-img' },
|
||||||
|
{ type: 'jpg', icon: 'error-img' },
|
||||||
|
{ type: 'jpeg', icon: 'error-img' },
|
||||||
|
{ type: 'bmp', icon: 'error-img' },
|
||||||
|
{ type: 'webp', icon: 'error-img' },
|
||||||
|
{ type: 'gif', icon: 'error-img' },
|
||||||
|
{ type: '.flv', icon: 'video' },
|
||||||
|
{ type: '.swf', icon: 'video' },
|
||||||
|
{ type: '.mkv', icon: 'video' },
|
||||||
|
{ type: '.avi', icon: 'video' },
|
||||||
|
{ type: '.rm', icon: 'video' },
|
||||||
|
{ type: '.rmvb', icon: 'video' },
|
||||||
|
{ type: '.mpeg', icon: 'video' },
|
||||||
|
{ type: '.mpg', icon: 'video' },
|
||||||
|
{ type: '.ogg', icon: 'video' },
|
||||||
|
{ type: '.ogv', icon: 'video' },
|
||||||
|
{ type: '.mov', icon: 'video' },
|
||||||
|
{ type: '.wmv', icon: 'video' },
|
||||||
|
{ type: '.mp4', icon: 'video' },
|
||||||
|
{ type: '.webm', icon: 'video' },
|
||||||
|
{ type: '.mp3', icon: 'vf' },
|
||||||
|
{ type: '.csv', icon: 'file' },
|
||||||
|
{ type: '.wav', icon: 'file' },
|
||||||
|
{ type: '.mid', icon: 'file' },
|
||||||
|
{ type: '.cab', icon: 'file' },
|
||||||
|
{ type: '.iso', icon: 'file' },
|
||||||
|
{ type: '.ofd', icon: 'file' },
|
||||||
|
{ type: '.xml', icon: 'file' },
|
||||||
|
{ type: '.rar', icon: 'zip' },
|
||||||
|
{ type: '.zip', icon: 'zip' },
|
||||||
|
{ type: '.tar', icon: 'zip' },
|
||||||
|
{ type: '.gz', icon: 'zip' },
|
||||||
|
{ type: '.7z', icon: 'zip' },
|
||||||
|
{ type: '.bz2', icon: 'bz2' },
|
||||||
|
{ type: '.doc', icon: 'word' },
|
||||||
|
{ type: '.docx', icon: 'word' },
|
||||||
|
{ type: '.xls', icon: 'excel' },
|
||||||
|
{ type: '.xlsx', icon: 'excel' },
|
||||||
|
{ type: '.ppt', icon: 'ppt' },
|
||||||
|
{ type: '.pptx', icon: 'ppt' },
|
||||||
|
{ type: '.pdf', icon: 'pdf' },
|
||||||
|
{ type: '.txt', icon: 'txt' },
|
||||||
|
{ type: '.md', icon: 'txt' },
|
||||||
|
{ type: '.vsd', icon: 'vsd' },
|
||||||
|
]);
|
||||||
|
// 弹窗上传显示
|
||||||
|
const upload_model_visible = ref(false);
|
||||||
|
// 上传类型
|
||||||
|
const upload_type = ref(props.type);
|
||||||
|
const upload_size = computed(() => {
|
||||||
|
const size = props.size.toString();
|
||||||
|
return size.includes('%') ? size : size + 'px';
|
||||||
|
});
|
||||||
|
// 上传类型转换成name
|
||||||
|
const upload_type_name = computed(() => {
|
||||||
|
return upload_type.value === 'img' ? '图片' : upload_type.value === 'video' ? '视频' : '文件';
|
||||||
|
});
|
||||||
|
// 切换图片/视频/文件
|
||||||
|
const upload_type_change = (type: any) => {
|
||||||
|
view_list_value.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const treeRef = ref();
|
||||||
|
const defaultProps = {
|
||||||
|
children: 'children',
|
||||||
|
label: 'label',
|
||||||
|
};
|
||||||
|
// 分类查询
|
||||||
|
const search_filter = ref('');
|
||||||
|
watch(search_filter, (val) => {
|
||||||
|
treeRef.value!.filter(val);
|
||||||
|
});
|
||||||
|
// 名称查询
|
||||||
|
const search_name = ref('');
|
||||||
|
// 总页数
|
||||||
|
// const page_total = ref(0);
|
||||||
|
// 当前页
|
||||||
|
const page = ref(1);
|
||||||
|
// 总数量
|
||||||
|
const data_total = ref(0);
|
||||||
|
interface Tree {
|
||||||
|
id: number;
|
||||||
|
label: string;
|
||||||
|
children?: Tree[];
|
||||||
|
}
|
||||||
|
const filter_node = (value: string, data: any): boolean => {
|
||||||
|
if (!value) return true;
|
||||||
|
return data.label.indexOf(value) !== -1;
|
||||||
|
};
|
||||||
|
const type_data: Tree[] = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
label: '全部图片',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
label: '金刚区',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
label: '金刚区 1-1',
|
||||||
|
children: [{ id: 3, label: '金刚区 1-1-1' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// 图片/视频/文件移动至
|
||||||
|
const category_list = [
|
||||||
|
{
|
||||||
|
value: 'component',
|
||||||
|
label: 'Component',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'basic',
|
||||||
|
label: 'Basic',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'layout',
|
||||||
|
label: 'Layout',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 已上传数据的列表
|
||||||
|
const upload_list = ref<uploadList[]>([
|
||||||
|
{ id: 1, url: '/src/assets/images/layout/main/phone.png', original: '头像1', title: '头像1', ext: '.png', type: 'img' },
|
||||||
|
{ id: 2, url: '/src/assets/images/components/model-user-info/avatar.png', original: '头像2', ext: '.jpeg', type: 'img' },
|
||||||
|
{ id: 3, url: '/src/assets/images/components/model-hot/test-1.png', original: '头像3', title: '头像3', ext: '.png', type: 'img' },
|
||||||
|
{ id: 4, url: '/src/assets/images/components/model-hot/test-2.png', original: '头像4', ext: '.jpeg', type: 'img' },
|
||||||
|
{ id: 5, url: '/src/assets/movie.mp4', original: '头像5', title: '头像5', ext: '.mp4', type: 'video' },
|
||||||
|
{ id: 6, url: '/src/assets/movie.mp4', original: '头像6', title: '头像6', ext: '.docx', type: '.docx' },
|
||||||
|
]);
|
||||||
|
// 选择图片
|
||||||
|
const check_img_event = (item: any) => {
|
||||||
|
const item_id = item.id;
|
||||||
|
const index = view_list_value.value.findIndex((item: any) => item.id === item_id);
|
||||||
|
if (index !== -1) {
|
||||||
|
view_list_value.value.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
if (is_replace.value) {
|
||||||
|
view_list_value.value = [item];
|
||||||
|
} else {
|
||||||
|
if (props.limit == 1) {
|
||||||
|
view_list_value.value = [item];
|
||||||
|
} else {
|
||||||
|
view_list_value.value.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 预览开关
|
||||||
|
const preview_switch_img = ref(false);
|
||||||
|
const preview_switch_video = ref(false);
|
||||||
|
// 视频预览的路径
|
||||||
|
const preview_url = ref('');
|
||||||
|
const edit_index = ref(-1);
|
||||||
|
// 监听点击事件
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('click', video_show);
|
||||||
|
});
|
||||||
|
// 移除监听事件
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('click', video_show);
|
||||||
|
});
|
||||||
|
// 预览视频
|
||||||
|
const video_show = (event: any) => {
|
||||||
|
if (!preview_switch_video.value) return;
|
||||||
|
|
||||||
|
if (!event.target.closest('.clickable-area')) {
|
||||||
|
preview_switch_video.value = false;
|
||||||
|
preview_url.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 编辑图片/视频/文件名称
|
||||||
|
const edit_event = (item: any, index: number) => {
|
||||||
|
edit_index.value = index;
|
||||||
|
};
|
||||||
|
// 输入框 输入完成
|
||||||
|
const edit_input_change = (val: string) => {
|
||||||
|
edit_index.value = -1;
|
||||||
|
};
|
||||||
|
// 输入框失去焦点
|
||||||
|
const edit_input_blur = () => {
|
||||||
|
edit_index.value = -1;
|
||||||
|
};
|
||||||
|
// 预览图片/视频
|
||||||
|
const preview_event = (item: any, index: number) => {
|
||||||
|
preview_url.value = item.url;
|
||||||
|
if (upload_type.value == 'img') {
|
||||||
|
preview_switch_img.value = true;
|
||||||
|
} else if (upload_type.value == 'video') {
|
||||||
|
preview_switch_video.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 预览关闭
|
||||||
|
const preview_close = () => {
|
||||||
|
preview_switch_img.value = false;
|
||||||
|
};
|
||||||
|
// 删除图片/视频/文件
|
||||||
|
const del_event = (item: uploadList) => {
|
||||||
|
app?.appContext.config.globalProperties.$common.message_box('删除后不可恢复,确定继续吗?', 'warning').then(() => {
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: '删除成功!',
|
||||||
|
});
|
||||||
|
// 调用删除接口,然后,更新数据
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 打开上传弹窗
|
||||||
|
const upload_model_open = () => {
|
||||||
|
upload_model_visible.value = true;
|
||||||
|
};
|
||||||
|
// 批量删除
|
||||||
|
const mult_del_event = () => {
|
||||||
|
app?.appContext.config.globalProperties.$common.message_box('删除后不可恢复,确定继续吗?', 'warning').then(() => {
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: '删除成功!',
|
||||||
|
});
|
||||||
|
// console.log('选中的数据 = ', view_list_value.value);
|
||||||
|
// 调用删除接口,然后,更新数据
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 查询文件
|
||||||
|
const search_data = ref({
|
||||||
|
page: page.value,
|
||||||
|
type: '',
|
||||||
|
name: search_name.value,
|
||||||
|
});
|
||||||
|
// 查询文件
|
||||||
|
const get_list = () => {
|
||||||
|
console.log('查询接口', search_data);
|
||||||
|
};
|
||||||
|
// 左侧分类树结构节点点击事件
|
||||||
|
const tree_node_event = (data: any) => {
|
||||||
|
search_filter.value = data.id;
|
||||||
|
get_list();
|
||||||
|
};
|
||||||
|
// 确认
|
||||||
|
const confirm_event = () => {
|
||||||
|
dialogVisible.value = false;
|
||||||
|
if (props.limit == 1) {
|
||||||
|
modelValue.value = view_list_value.value;
|
||||||
|
} else {
|
||||||
|
if (is_replace.value) {
|
||||||
|
// 替换modelValue的replace下标下的文件
|
||||||
|
modelValue.value.splice(replace_index.value, 1, view_list_value.value[0]);
|
||||||
|
} else {
|
||||||
|
if (props.limit >= view_list_value.value.length + modelValue.value.length) {
|
||||||
|
// 数组合并
|
||||||
|
modelValue.value = modelValue.value.concat(view_list_value.value);
|
||||||
|
// view_list_value.value.forEach((item: uploadList) => {
|
||||||
|
// modelValue.value.push(item);
|
||||||
|
// });
|
||||||
|
} else {
|
||||||
|
app?.appContext.config.globalProperties.$common.alert(`最多上传 ${props.limit} 个文件!`, 'warning');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view_list_value.value = [];
|
||||||
|
search_filter.value = '';
|
||||||
|
is_replace.value = false;
|
||||||
|
replace_index.value = -1;
|
||||||
|
};
|
||||||
|
// 替换标识
|
||||||
|
const is_replace = ref(false);
|
||||||
|
// 替换的文件的下标
|
||||||
|
const replace_index = ref(-1);
|
||||||
|
// 上传回显替换文件事件
|
||||||
|
const replace_file_event = (index: number) => {
|
||||||
|
dialogVisible.value = true;
|
||||||
|
is_replace.value = true;
|
||||||
|
replace_index.value = index;
|
||||||
|
};
|
||||||
|
// 上传回显删除事件
|
||||||
|
const del_upload_event = (index: number) => {
|
||||||
|
const new_model_val = JSON.parse(JSON.stringify(modelValue.value));
|
||||||
|
new_model_val.splice(index, 1);
|
||||||
|
modelValue.value = new_model_val;
|
||||||
|
};
|
||||||
|
const handle_error = (index: number) => {
|
||||||
|
// 当视频加载失败时触发
|
||||||
|
upload_list.value[index].error = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
//#region 上传组件回调 -----------------------------------------------start
|
||||||
|
// 关闭上传弹窗回调
|
||||||
|
const close_upload_model = (data: any) => {
|
||||||
|
if (props.isCheckConfirm) {
|
||||||
|
dialogVisible.value = false;
|
||||||
|
if (data.web_image.length > 0) {
|
||||||
|
const new_web_file = {
|
||||||
|
url: data.web_image,
|
||||||
|
};
|
||||||
|
if (props.limit == 1) {
|
||||||
|
modelValue.value = [new_web_file];
|
||||||
|
} else {
|
||||||
|
if (is_replace.value) {
|
||||||
|
// 替换modelValue的replace下标下的文件
|
||||||
|
modelValue.value.splice(replace_index.value, 1, new_web_file);
|
||||||
|
} else {
|
||||||
|
if (props.limit >= view_list_value.value.length + modelValue.value.length) {
|
||||||
|
// 数组合并
|
||||||
|
modelValue.value.push(new_web_file);
|
||||||
|
} else {
|
||||||
|
app?.appContext.config.globalProperties.$common.alert(`最多上传 ${props.limit} 个文件!`, 'warning');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//#endregion 上传组件回调 -----------------------------------------------end
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'index.scss';
|
||||||
|
</style>
|
||||||
573
src/components/common/upload/upload-model.vue
Normal file
@ -0,0 +1,573 @@
|
|||||||
|
<!-- 上传组件 -->
|
||||||
|
<template>
|
||||||
|
<el-dialog v-model="dialogVisible" class="radius-lg" width="1168" append-to-body>
|
||||||
|
<template #header>
|
||||||
|
<div class="title center re">
|
||||||
|
<div class="tc size-16 fw">{{ upload_type_name }}上传</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="upload-content pa-20" @paste="handle_paste">
|
||||||
|
<el-form :model="form" label-width="75">
|
||||||
|
<el-form-item label="上传方式">
|
||||||
|
<el-radio-group v-model="form.type" @change="upload_type_change">
|
||||||
|
<el-radio value="loc">本地上传</el-radio>
|
||||||
|
<el-radio value="scan">扫码上传</el-radio>
|
||||||
|
<el-radio v-if="upload_type !== 'file'" value="web">网络上传</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="上传至分组" prop="group">
|
||||||
|
<div class="form-item-width">
|
||||||
|
<el-cascader v-model="form.group" class="w" :options="classify" placeholder="请选择" :show-all-levels="false" @change="group_change"></el-cascader>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<template v-if="form.type == 'loc'">
|
||||||
|
<div class="flex-row jc-sb align-c mt-30">
|
||||||
|
<div class="flex-row">
|
||||||
|
<el-upload ref="fileUpload1" v-model:file-list="file_list" multiple action="#" :accept="exts_text" :auto-upload="false" :show-file-list="false" :on-change="upload_change" :before-upload="before_upload" :limit="limit" :on-exceed="handle_exceed">
|
||||||
|
<template #trigger>
|
||||||
|
<el-button @click="folder_mode(false)"> 上传{{ upload_type_name }} </el-button>
|
||||||
|
<el-button @click="folder_mode(true)"> 上传文件夹 </el-button>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
<el-button @click="clear_list_event">清空列表</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="table mt-10">
|
||||||
|
<div class="table-header">
|
||||||
|
<div class="table-row">
|
||||||
|
<div class="table-cell">文件名</div>
|
||||||
|
<div class="table-cell">文件大小</div>
|
||||||
|
<div class="table-cell">上传状态</div>
|
||||||
|
<div class="table-cell-oprate">操作</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="dropzone" @dragover.prevent="handle_drag_in" @dragenter="handle_drag_in" @dragleave="handle_drag_leave" @drop.prevent="handle_drop">
|
||||||
|
<el-scrollbar v-if="!is_dragging && form.file.length > 0" height="341px">
|
||||||
|
<div class="table-body">
|
||||||
|
<div v-for="(item, index) in form.file" :key="item.file.name + item.file.size" class="table-row">
|
||||||
|
<div class="table-cell">
|
||||||
|
<el-image :src="file_to_base64(item.file)" class="preview-img radius-sm" fit="contain">
|
||||||
|
<template #error>
|
||||||
|
<div class="bg-f5 img flex-row jc-c align-c radius h w">
|
||||||
|
<icon name="error-img" size="12"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
<div class="desc">{{ item.file.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-cell">{{ annex_size_to_unit(item.file.size) }}</div>
|
||||||
|
<div class="table-cell">{{ item.status }}</div>
|
||||||
|
<div class="table-cell-oprate" @click="del_upload(index)">移除</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
<div v-show="is_dragging || form.file.length < 1" class="folder-upload mt-20" :class="is_dragging ? 'active' : ''">
|
||||||
|
<el-upload ref="fileUpload2" v-model:file-list="file_list" :accept="exts_text" multiple action="#" :auto-upload="false" :show-file-list="false" :on-change="upload_change" :before-upload="before_upload" :limit="limit" :on-exceed="handle_exceed">
|
||||||
|
<div class="flex-col jc-c align-c">
|
||||||
|
<icon name="add" size="60" color="#dbeef6"></icon>
|
||||||
|
<p class="size-18 cr-c fw">请将需要上传的文件/文件夹拖到此处或粘贴</p>
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="form.type == 'scan'">
|
||||||
|
<el-form-item label="二维码" class="mb-10">
|
||||||
|
<qrcode :src="form.qrcode" :is-mask="is_mask"></qrcode>
|
||||||
|
</el-form-item>
|
||||||
|
<div class="table">
|
||||||
|
<div class="table-header">
|
||||||
|
<div class="table-row">
|
||||||
|
<div class="table-cell">文件名</div>
|
||||||
|
<div class="table-cell">文件大小</div>
|
||||||
|
<div class="table-cell-oprate">操作</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-scrollbar height="224px">
|
||||||
|
<div class="table-body">
|
||||||
|
<div v-for="(item, index) in scan_file_list" :key="item.name + item.size" class="table-row">
|
||||||
|
<div class="table-cell">
|
||||||
|
<el-image :src="item.url" class="preview-img radius-sm" fit="contain">
|
||||||
|
<template #error>
|
||||||
|
<div class="bg-f5 img flex-row jc-c align-c radius h w">
|
||||||
|
<icon name="error-img" size="12"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
<div class="desc">{{ item.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-cell">{{ annex_size_to_unit(item.size) }}</div>
|
||||||
|
<div class="table-cell-oprate" @click="del_already_upload(index)">删除</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="form.type == 'web'">
|
||||||
|
<el-form-item label="网络图片">
|
||||||
|
<div class="flex-row align-c gap-10">
|
||||||
|
<el-input v-model="form.web_image" class="form-item-width" placeholder="请输入网络图片地址" />
|
||||||
|
<div class="c-pointer cr-primary size-12" @click="extract_images">提取图片</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button class="plr-28 ptb-10" @click="close_dialog">取消</el-button>
|
||||||
|
<el-button v-if="form.type == 'loc'" class="plr-28 ptb-10" type="primary" @click="submit">上传</el-button>
|
||||||
|
<el-button v-else-if="form.type == 'scan'" class="plr-28 ptb-10" type="primary" @click="close_dialog">返回图库</el-button>
|
||||||
|
<el-button v-else-if="form.type == 'web'" class="plr-28 ptb-10" type="primary" @click="close_all_dialog">确认</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { UploadFile, UploadFiles, UploadUserFile } from 'element-plus';
|
||||||
|
import { annex_size_to_unit, ext_name, get_math } from '@/utils';
|
||||||
|
/**
|
||||||
|
* @description: 图片执行上传弹窗
|
||||||
|
* @param close{String} 默认值
|
||||||
|
* @return {*} close
|
||||||
|
*/
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'img', // img/video/file
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
default: 10000,
|
||||||
|
},
|
||||||
|
exts: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ['.png', '.jpg', '.jpeg', '.bmp', '.webp', '.gif'],
|
||||||
|
},
|
||||||
|
fileSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 1024 * 1024,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const dialogVisible = defineModel({ type: Boolean, default: false });
|
||||||
|
// const v_model = defineModel({ type: Array, default: [] });
|
||||||
|
// const dialogVisible = ref(false);
|
||||||
|
// 上传类型
|
||||||
|
const upload_type = ref(props.type);
|
||||||
|
// 上传类型转换成name
|
||||||
|
const upload_type_name = computed(() => {
|
||||||
|
return upload_type.value === 'img' ? '图片' : upload_type.value === 'video' ? '视频' : '文件';
|
||||||
|
});
|
||||||
|
// 格式限制
|
||||||
|
const exts_text = ref(props.exts.join(','));
|
||||||
|
const file_list = ref<UploadUserFile[]>([]);
|
||||||
|
interface fileData {
|
||||||
|
file: File;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
interface formData {
|
||||||
|
type: string;
|
||||||
|
group: string;
|
||||||
|
file: fileData[];
|
||||||
|
qrcode: string;
|
||||||
|
web_image: string;
|
||||||
|
}
|
||||||
|
const form = ref<formData>({
|
||||||
|
type: 'loc',
|
||||||
|
group: '',
|
||||||
|
file: [],
|
||||||
|
qrcode: '11223344',
|
||||||
|
web_image: '',
|
||||||
|
});
|
||||||
|
// 是否给二维码加模糊效果
|
||||||
|
const is_mask = ref(true);
|
||||||
|
const timer = ref<number | null>(null);
|
||||||
|
// 上传方式
|
||||||
|
const upload_type_change = (type: any) => {
|
||||||
|
// 清除之前的定时器(如果存在)
|
||||||
|
if (timer.value) {
|
||||||
|
// 直接检查 timer.value 是否存在(不是 null 或 undefined)
|
||||||
|
clearTimeout(timer.value);
|
||||||
|
timer.value = null; // 清除引用,防止内存泄漏
|
||||||
|
}
|
||||||
|
// 如果需要设置新的定时器
|
||||||
|
if (type === 'scan') {
|
||||||
|
timer.value = setInterval(() => {
|
||||||
|
console.log('timer-----定时调用');
|
||||||
|
// 此处写定时调用接口,获取文件列表
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 选择分组
|
||||||
|
const group_change = (val: any) => {
|
||||||
|
if (val && val.length > 0) {
|
||||||
|
is_mask.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 图片/视频/文件移动至
|
||||||
|
const classify = [
|
||||||
|
{
|
||||||
|
value: 'resource',
|
||||||
|
label: 'Resource',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'resource',
|
||||||
|
label: 'Resource',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'axure',
|
||||||
|
label: 'Axure Components',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'guide',
|
||||||
|
label: 'Guide',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'disciplines',
|
||||||
|
label: 'Disciplines',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'consistency',
|
||||||
|
label: 'Consistency',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'navigation',
|
||||||
|
label: 'Navigation',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'side nav',
|
||||||
|
label: 'Side Navigation',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
//#region 本地上传 -----------------------------------------------start
|
||||||
|
|
||||||
|
// 选择文件夹
|
||||||
|
const state = reactive({
|
||||||
|
uploadEle: null as HTMLInputElement | null,
|
||||||
|
uploadList: [],
|
||||||
|
});
|
||||||
|
const folder_mode = (type: boolean) => {
|
||||||
|
if (!state.uploadEle) {
|
||||||
|
state.uploadEle = document.querySelector('.el-upload__input') as HTMLInputElement;
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
(state.uploadEle as HTMLInputElement).webkitdirectory = type;
|
||||||
|
// console.log(state.uploadEle);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
|
||||||
|
const upload_change = async (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
|
||||||
|
// console.log('文件状态改变时的钩子', uploadFile, uploadFiles);
|
||||||
|
// // 过滤已上传的文件和重复的文件
|
||||||
|
const results = uploadFiles.flat(Infinity).filter((f: any) => validExt(f.name) && validSize(f.size));
|
||||||
|
const new_upload_files = results.filter((item: UploadFile) => {
|
||||||
|
return !form.value.file.find((item2: fileData) => {
|
||||||
|
return item2.file.name === item.name && item2.file.size === item.size;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// 将过滤后的数组和form.file数组合并
|
||||||
|
new_upload_files.forEach((item: UploadFile) => {
|
||||||
|
// item.status = 'ready';
|
||||||
|
const new_file_obj = {
|
||||||
|
status: '等待上传',
|
||||||
|
file: item.raw as File,
|
||||||
|
};
|
||||||
|
form.value.file.push(new_file_obj);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 上传文件之前的钩子,参数为上传的文件, 若返回false或者返回 Promise 且被 reject,则停止上传。
|
||||||
|
const validExt = (name: string) => props.exts.includes(ext_name(name));
|
||||||
|
const validSize = (size: number) => size <= props.fileSize;
|
||||||
|
// 上传前的钩子
|
||||||
|
const before_upload = (file: any) => {
|
||||||
|
// 检查文件是否为图片
|
||||||
|
if (validExt(file.name) && validSize(file.size)) {
|
||||||
|
console.log('允许上传');
|
||||||
|
return true; // 允许上传
|
||||||
|
} else {
|
||||||
|
console.log('不允许上传');
|
||||||
|
return false; // 不允许上传
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 超出限制时的钩子
|
||||||
|
const handle_exceed = (files: any, fileList: any) => {
|
||||||
|
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
|
||||||
|
};
|
||||||
|
// 清空列表
|
||||||
|
const clear_list_event = () => {
|
||||||
|
form.value.file = [];
|
||||||
|
file_list.value = [];
|
||||||
|
};
|
||||||
|
const is_dragging = ref(false);
|
||||||
|
// 引用dropzone DOM元素以便在dragLeave中检查
|
||||||
|
const dropzone = ref<HTMLElement | null>(null);
|
||||||
|
onMounted(() => {
|
||||||
|
dropzone.value = document.getElementById('dropzone') as HTMLElement;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拖拽事件 和 列表拖动文件进入触发事件
|
||||||
|
const handle_drag_in = (e: DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
is_dragging.value = true;
|
||||||
|
};
|
||||||
|
// 列表拖动文件离开触发事件
|
||||||
|
const handle_drag_leave = (event: DragEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
// 使用一个变量来跟踪鼠标是否离开了dropzone
|
||||||
|
const leaveTimer = setTimeout(() => {
|
||||||
|
// 确保鼠标不在dropzone或其子元素中
|
||||||
|
if (!document.getElementById('dropzone')?.contains(event.relatedTarget as Node)) {
|
||||||
|
is_dragging.value = false;
|
||||||
|
}
|
||||||
|
}, 50); // 延迟50毫秒
|
||||||
|
|
||||||
|
// 当鼠标重新进入dropzone时,清除定时器
|
||||||
|
event.currentTarget?.addEventListener(
|
||||||
|
'dragenter',
|
||||||
|
() => {
|
||||||
|
clearTimeout(leaveTimer);
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// 松开拖拽的文件 获取文件信息
|
||||||
|
const handle_drop = async (event: any) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
is_dragging.value = false;
|
||||||
|
let results = await Promise.all([...event.dataTransfer.items].map((item) => handle_entry(item.webkitGetAsEntry())));
|
||||||
|
// 过滤符合条件的数据 文件后缀名,文件大小过滤
|
||||||
|
results = results.flat(Infinity).filter((f: any) => validExt(f.name) && validSize(f.size));
|
||||||
|
// 遍历过滤后的数据,过滤重复数据后并添加到上传列表中
|
||||||
|
if (results.length + form.value.file.length <= props.limit) {
|
||||||
|
results.forEach((file: any) => {
|
||||||
|
if (!form.value.file.some((item: any) => item.file.name === file.name && item.file.size === file.size)) {
|
||||||
|
const new_file_obj = {
|
||||||
|
status: '等待上传',
|
||||||
|
file: file,
|
||||||
|
};
|
||||||
|
const new_file_obj_upload = {
|
||||||
|
name: file.name,
|
||||||
|
url: 'xxx',
|
||||||
|
file: file,
|
||||||
|
};
|
||||||
|
form.value.file.push(new_file_obj);
|
||||||
|
file_list.value.push(new_file_obj_upload);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 处理文件/目录并返回一个Promise。
|
||||||
|
* 此函数旨在处理文件/目录,无论是文件还是目录。如果是文件,则直接以文件形式解析并解决Promise。如果是目录,则递归处理目录中的所有文件。
|
||||||
|
* @param {any} entry 文件/目录,可以是文件或目录。
|
||||||
|
* @returns {Promise<any[]>} 返回一个Promise,解析为一个数组,包含所有条目的文件对象。
|
||||||
|
*/
|
||||||
|
const handle_entry = (entry: any) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// 如果entry是一个文件
|
||||||
|
if (entry.isFile) {
|
||||||
|
// 直接以文件形式解析Promise
|
||||||
|
entry.file(resolve);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果entry是一个目录
|
||||||
|
const dirReader = entry.createReader();
|
||||||
|
dirReader.readEntries(async (entries: any) => {
|
||||||
|
// 递归处理目录中的所有条目,并通过Promise.all等待所有处理完成
|
||||||
|
resolve(await Promise.all(entries.map(handle_entry)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 移除文件
|
||||||
|
const del_upload = (index: number) => {
|
||||||
|
// 根据下标删除文件
|
||||||
|
form.value.file.splice(index, 1);
|
||||||
|
file_list.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
// 粘贴上传
|
||||||
|
const handle_paste = (event: any) => {
|
||||||
|
console.log(event);
|
||||||
|
// 获取粘贴板中的文件列表
|
||||||
|
const files = event.clipboardData.files;
|
||||||
|
// 过滤符合条件的数据 文件后缀名,文件大小过滤
|
||||||
|
const results = [...files].filter((f: any) => validExt(f.name) && validSize(f.size));
|
||||||
|
// 遍历过滤后的数据,过滤重复数据后并添加到上传列表中
|
||||||
|
if (results.length + form.value.file.length <= props.limit) {
|
||||||
|
console.log(results);
|
||||||
|
results.forEach((file: any) => {
|
||||||
|
// 判断是否重复
|
||||||
|
// 如果上传列表中没有与当前文件相同的文件,则添加到上传列表中
|
||||||
|
if (!form.value.file.some((item: any) => item.file.name === file.name && item.file.size === file.size)) {
|
||||||
|
const new_file_obj = {
|
||||||
|
status: '等待上传',
|
||||||
|
file: file,
|
||||||
|
};
|
||||||
|
const new_file_obj_upload = {
|
||||||
|
name: file.name,
|
||||||
|
url: 'xxx',
|
||||||
|
file: file,
|
||||||
|
};
|
||||||
|
form.value.file.push(new_file_obj);
|
||||||
|
file_list.value.push(new_file_obj_upload);
|
||||||
|
} else {
|
||||||
|
ElMessageBox.alert(`文件 ${file.name} 已存在!`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行上传
|
||||||
|
const submit = () => {};
|
||||||
|
//#endregion 本地上传 -----------------------------------------------end
|
||||||
|
|
||||||
|
//#region 扫码上传 -----------------------------------------------start
|
||||||
|
interface scanFile {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
const scan_file_list = ref<scanFile[]>([
|
||||||
|
{ name: '1', url: '1', size: 0 },
|
||||||
|
{ name: '2', url: '2', size: 0 },
|
||||||
|
{ name: '3', url: '3', size: 0 },
|
||||||
|
{ name: '4', url: '4', size: 0 },
|
||||||
|
{ name: '5', url: '5', size: 0 },
|
||||||
|
]);
|
||||||
|
// 删除已上传的文件
|
||||||
|
const del_already_upload = (index: number) => {
|
||||||
|
// 根据下标删除文件
|
||||||
|
scan_file_list.value.splice(index, 1);
|
||||||
|
// 调接口真实删除
|
||||||
|
};
|
||||||
|
//#endregion 扫码上传 -----------------------------------------------end
|
||||||
|
|
||||||
|
//#region 网络上传 -----------------------------------------------start
|
||||||
|
// 提取图片
|
||||||
|
const extract_images = () => {
|
||||||
|
// 此处调用接口获取提取后的图片更新输入框的地址,改为在线地址
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: '提取成功!',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
// 关闭所有弹窗
|
||||||
|
const close_all_dialog = () => {
|
||||||
|
const new_form = JSON.parse(JSON.stringify(form.value));
|
||||||
|
emit('close', new_form);
|
||||||
|
close_dialog();
|
||||||
|
};
|
||||||
|
//#endregion 网络上传 -----------------------------------------------end
|
||||||
|
|
||||||
|
// file转换成base64
|
||||||
|
const file_to_base64 = (file: any) => {
|
||||||
|
return URL.createObjectURL(file);
|
||||||
|
};
|
||||||
|
// 关闭弹窗
|
||||||
|
const close_dialog = () => {
|
||||||
|
dialogVisible.value = false;
|
||||||
|
form.value = {
|
||||||
|
type: 'loc',
|
||||||
|
group: '',
|
||||||
|
file: [],
|
||||||
|
qrcode: '',
|
||||||
|
web_image: '',
|
||||||
|
};
|
||||||
|
scan_file_list.value = [];
|
||||||
|
// 直接检查 timer.value 是否存在(不是 null 或 undefined)
|
||||||
|
if (timer.value !== null) {
|
||||||
|
clearTimeout(timer.value);
|
||||||
|
}
|
||||||
|
timer.value = null; // 清除引用,防止内存泄漏
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.upload-content {
|
||||||
|
height: 57.4rem;
|
||||||
|
gap: 4.5rem;
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
height: 30rem;
|
||||||
|
.table-header,
|
||||||
|
.table-body {
|
||||||
|
.table-row {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 0.1rem solid #eee;
|
||||||
|
color: #999;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
.table-cell {
|
||||||
|
flex: 1;
|
||||||
|
padding: 1rem;
|
||||||
|
color: #666;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
word-break: break-all;
|
||||||
|
gap: 1rem;
|
||||||
|
.preview-img {
|
||||||
|
width: 2.8rem;
|
||||||
|
height: 2.8rem;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
flex: 1;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.table-cell-oprate {
|
||||||
|
padding: 1rem;
|
||||||
|
width: 5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.table-body {
|
||||||
|
.table-cell,
|
||||||
|
.table-cell-oprate {
|
||||||
|
padding: 1.5rem 1rem !important;
|
||||||
|
}
|
||||||
|
.table-cell-oprate {
|
||||||
|
color: $cr-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.folder-upload {
|
||||||
|
background: #fafcff;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
border: 0.1rem dashed #afdafa;
|
||||||
|
height: 32rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
.active {
|
||||||
|
background-color: #eef5ff;
|
||||||
|
border-color: #35a9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.form-item-width {
|
||||||
|
width: 33.5rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
31
src/components/common/url-value/index.scss
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
.url-value-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 3.2rem;
|
||||||
|
line-height: 3.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
.value-input-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: 3.4rem;
|
||||||
|
z-index: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.url-value-content {
|
||||||
|
height: 57.3rem;
|
||||||
|
gap: 6rem;
|
||||||
|
.left-content {
|
||||||
|
width: 22.5rem;
|
||||||
|
.el-menu-item {
|
||||||
|
height: 4rem;
|
||||||
|
line-height: 4rem;
|
||||||
|
&.is-active {
|
||||||
|
background: var(--el-menu-hover-bg-color);
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right-content {
|
||||||
|
}
|
||||||
|
}
|
||||||
230
src/components/common/url-value/index.vue
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
<!-- 上传组件 -->
|
||||||
|
<template>
|
||||||
|
<el-dialog v-model="dialogVisible" class="radius-lg" width="1168" append-to-body @close="close_event">
|
||||||
|
<template #header>
|
||||||
|
<div class="title center re">
|
||||||
|
<div class="tc size-16 fw">选择链接</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="url-value-content pa-20 flex-row">
|
||||||
|
<div class="left-content">
|
||||||
|
<el-menu :default-active="link_select" class="w br-none" @select="handle_select">
|
||||||
|
<el-menu-item v-for="item in base_data" :key="item.type" :index="item.type" :disabled="!(custom_link_type.length == 0 || custom_link_type.includes(item.type))">
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</div>
|
||||||
|
<div class="right-content flex-1">
|
||||||
|
<template v-if="link_select == 'shop'">
|
||||||
|
<link-list v-model="link_value" :reset="reset_compontent"></link-list>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="link_select == 'goods-category'">
|
||||||
|
<link-goods-category v-model="link_value" :reset="reset_compontent"></link-goods-category>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="link_select == 'goods-search'">
|
||||||
|
<link-goods-search :reset="reset_compontent" :status="component_status" @update:link="goods_category_link" @type="goods_category_type_change" @required="required_tips"></link-goods-search>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="link_select == 'goods'">
|
||||||
|
<link-goods v-model="link_value" :reset="reset_compontent"></link-goods>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="link_select == 'articles'">
|
||||||
|
<link-articles v-model="link_value" :reset="reset_compontent"></link-articles>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="link_select == 'diy'">
|
||||||
|
<link-table v-model="link_value" :reset="reset_compontent"></link-table>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="link_select == 'design'">
|
||||||
|
<link-table v-model="link_value" :reset="reset_compontent"></link-table>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="link_select == 'custom-view'">
|
||||||
|
<link-table v-model="link_value" :reset="reset_compontent"></link-table>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="link_select == 'custom'">
|
||||||
|
<link-custom :reset="reset_compontent" :status="component_status" @update:link="custom_link" @required="required_tips"></link-custom>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="link_select == 'plugins'">
|
||||||
|
<link-list v-model="link_value" :reset="reset_compontent"></link-list>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button class="plr-28 ptb-10" @click="close_event">取消</el-button>
|
||||||
|
<el-button class="plr-28 ptb-10" type="primary" @click="confirm_event">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<div class="flex-row align-c gap-10 br-d radius-sm plr-11 url-value-input" @click="dialogVisible = true">
|
||||||
|
<div class="flex-1 flex-width size-12 text-line-1">
|
||||||
|
<text v-if="!is_obj_empty(modelValue)">{{ modelValue.name }}</text>
|
||||||
|
<text v-else class="cr-9">{{ placeholder }}</text>
|
||||||
|
</div>
|
||||||
|
<div class="value-input-icon">
|
||||||
|
<template v-if="is_obj_empty(modelValue)">
|
||||||
|
<icon name="arrow-right" size="12" color="9"></icon>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div @click.stop="clear_model_value">
|
||||||
|
<icon name="close-o" size="12" color="c"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { MenuItemClicked } from 'element-plus/es/components/menu/src/types';
|
||||||
|
import { is_obj_empty } from '@/utils';
|
||||||
|
import { PropType } from 'vue';
|
||||||
|
|
||||||
|
const app = getCurrentInstance();
|
||||||
|
/**
|
||||||
|
* @description: 页面链接
|
||||||
|
* @param modelValue{Object} 默认值
|
||||||
|
* @param dialogVisible {Boolean} 弹窗显示
|
||||||
|
* @param type{String} 链接类型为空数组则表示无限制,全部可用,传过来则表示传的值可用
|
||||||
|
* @param placeholder{String} 提示文字
|
||||||
|
* @return {*} update:modelValue
|
||||||
|
*/
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '请选择链接',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const modelValue = defineModel({ type: Object, default: {} });
|
||||||
|
const dialogVisible = defineModel('visibleDialog', { type: Boolean, default: false });
|
||||||
|
const link_value = ref({});
|
||||||
|
const reset_compontent = ref(false);
|
||||||
|
const custom_link_type = ref(props.type);
|
||||||
|
const base_data = reactive([
|
||||||
|
{
|
||||||
|
name: '商城页面',
|
||||||
|
type: 'shop',
|
||||||
|
data: [{ name: '基础链接', data: [{ name: '', page: '' }] }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '商品分类',
|
||||||
|
type: 'goods-category',
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '商品搜索',
|
||||||
|
type: 'goods-search',
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '商品页面',
|
||||||
|
type: 'goods',
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '文章页面',
|
||||||
|
type: 'articles',
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DIY页面',
|
||||||
|
type: 'diy',
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '页面设计',
|
||||||
|
type: 'design',
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '自定义页面',
|
||||||
|
type: 'custom-view',
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '自定义链接',
|
||||||
|
type: 'custom',
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '插件',
|
||||||
|
type: 'plugins',
|
||||||
|
data: [{ name: '多商户', data: [{ name: '1', page: '2' }] }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// 弹窗显示
|
||||||
|
|
||||||
|
//#region 链接回调 -----------------------------------------------start
|
||||||
|
// 菜单选中回调
|
||||||
|
const link_select = ref(props.type.length == 0 ? 'shop' : props.type[0]);
|
||||||
|
const handle_select = (index: string, indexPath: string[], item: MenuItemClicked, routeResult: any) => {
|
||||||
|
// console.log(index, indexPath, item, routeResult);
|
||||||
|
link_select.value = index;
|
||||||
|
};
|
||||||
|
//#endregion 链接回调 -----------------------------------------------end
|
||||||
|
|
||||||
|
//@region 子组件回调 -----------------------------------------------start
|
||||||
|
const component_status = ref(false);
|
||||||
|
// 商品分类选中回调
|
||||||
|
const goods_category_type = ref(0);
|
||||||
|
const goods_category_type_change = (type: number) => {
|
||||||
|
goods_category_type.value = type;
|
||||||
|
};
|
||||||
|
const goods_category_link = (data: object, type: number) => {
|
||||||
|
if (type == 2) {
|
||||||
|
modelValue.value = data;
|
||||||
|
close_event();
|
||||||
|
} else {
|
||||||
|
link_value.value = data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 自定义地址回调
|
||||||
|
const custom_link = (data: object) => {
|
||||||
|
modelValue.value = data;
|
||||||
|
close_event();
|
||||||
|
};
|
||||||
|
// 错误回调
|
||||||
|
const required_tips = () => {
|
||||||
|
ElMessage({
|
||||||
|
type: 'warning',
|
||||||
|
message: '必填项不能为空',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
//#endregion 子组件回调 -----------------------------------------------end
|
||||||
|
|
||||||
|
//#region 链接确认回调 -----------------------------------------------start
|
||||||
|
// 取消回调
|
||||||
|
const close_event = () => {
|
||||||
|
link_select.value = props.type.length == 0 ? 'shop' : props.type[0];
|
||||||
|
dialogVisible.value = false;
|
||||||
|
link_value.value = {};
|
||||||
|
reset_compontent.value = !reset_compontent.value;
|
||||||
|
};
|
||||||
|
// 确认回调
|
||||||
|
const confirm_event = () => {
|
||||||
|
// 判断是否是自定义页面和商品查询
|
||||||
|
if (link_select.value == 'custom' || (link_select.value == 'goods-search' && goods_category_type.value == 2)) {
|
||||||
|
component_status.value = !component_status.value;
|
||||||
|
} else {
|
||||||
|
if (is_obj_empty(link_value.value)) {
|
||||||
|
ElMessage({
|
||||||
|
type: 'warning',
|
||||||
|
message: '请先选择链接',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
modelValue.value = link_value.value;
|
||||||
|
close_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//#endregion 链接确认回调 -----------------------------------------------end
|
||||||
|
|
||||||
|
//#endregion 链接清空-------------------------------------------------start
|
||||||
|
const clear_model_value = () => {
|
||||||
|
modelValue.value = {};
|
||||||
|
};
|
||||||
|
//#endregion 链接清空-------------------------------------------------end
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'index.scss';
|
||||||
|
</style>
|
||||||
140
src/components/common/url-value/link-articles.vue
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 商品分类 -->
|
||||||
|
<div class="container">
|
||||||
|
<div class="flex-row jc-e gap-20 mb-20">
|
||||||
|
<el-select v-model="type" class="search-w" placeholder="请选择">
|
||||||
|
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-input v-model="search_value" placeholder="请输入搜索内容" class="search-w" @change="handle_search">
|
||||||
|
<template #suffix>
|
||||||
|
<icon name="search" size="16" color="9"></icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-table :data="tableData" class="w" :header-cell-style="{ background: '#f7f7f7' }" row-key="id" height="438" fixed @row-click="row_click">
|
||||||
|
<el-table-column label="#" width="120" type="">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-radio v-model="template_selection" :label="scope.$index + ''"> </el-radio>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="id" label="ID" width="180" type="" />
|
||||||
|
<el-table-column prop="icon" label="文章图片" width="130">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-image :src="scope.row.icon" class="img">
|
||||||
|
<template #error>
|
||||||
|
<div class="bg-f5 flex-row jc-c align-c radius h w">
|
||||||
|
<icon name="error-img" size="14" color="9"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="name" label="文章名称" />
|
||||||
|
</el-table>
|
||||||
|
<div class="mt-10 flex-row jc-e">
|
||||||
|
<el-pagination :current-page="page" :page-size="21" :pager-count="5" layout="prev, pager, next" :total="data_total" @current-change="get_list" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps({
|
||||||
|
// 重置
|
||||||
|
reset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.reset,
|
||||||
|
() => {
|
||||||
|
template_selection.value = '';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const modelValue = defineModel({ type: Object, default: {} });
|
||||||
|
interface User {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
const tableData: User[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '一级分类一级分类一级分类一级分类一级分类一级分类一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'c',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'd',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: '一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'e',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const search_value = ref('');
|
||||||
|
const handle_search = () => {
|
||||||
|
console.log(search_value.value);
|
||||||
|
};
|
||||||
|
const type = ref('');
|
||||||
|
const options = ref([
|
||||||
|
{ value: '1', label: '一级分类' },
|
||||||
|
{ value: '2', label: '二级分类' },
|
||||||
|
{ value: '3', label: '三级分类' },
|
||||||
|
]);
|
||||||
|
const template_selection = ref('');
|
||||||
|
|
||||||
|
const row_click = (row: any) => {
|
||||||
|
const new_table_data = JSON.parse(JSON.stringify(tableData));
|
||||||
|
template_selection.value = new_table_data.findIndex((item: User) => item.id == row.id).toString();
|
||||||
|
modelValue.value = row;
|
||||||
|
};
|
||||||
|
//#region 分页 -----------------------------------------------start
|
||||||
|
// 总页数
|
||||||
|
// const page_total = ref(0);
|
||||||
|
// 当前页
|
||||||
|
const page = ref(1);
|
||||||
|
// 总数量
|
||||||
|
const data_total = ref(0);
|
||||||
|
|
||||||
|
// 查询文件
|
||||||
|
const search_data = ref({
|
||||||
|
page: page.value,
|
||||||
|
type: '',
|
||||||
|
name: search_value.value,
|
||||||
|
});
|
||||||
|
// 查询文件
|
||||||
|
const get_list = () => {
|
||||||
|
console.log('查询接口', search_data);
|
||||||
|
};
|
||||||
|
//#region 分页 -----------------------------------------------end
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
.search-w {
|
||||||
|
width: 22.5rem;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
:deep(.el-table__inner-wrapper:before) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
width: 3.6rem;
|
||||||
|
height: 3.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
211
src/components/common/url-value/link-custom.vue
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 商城 -->
|
||||||
|
<div class="container">
|
||||||
|
<div class="tabs flex-row gap-10 mb-30">
|
||||||
|
<div v-for="item in custom_type" :key="item.id" class="item bg-f5 radius-sm" :class="custom_type_active == item.id ? 'active' : ''" @click="custom_type_event(item)">{{ item.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-scrollbar height="470px">
|
||||||
|
<el-form ref="ruleFormRef" :model="form" label-width="85px" status-icon>
|
||||||
|
<template v-if="custom_type_active == 0">
|
||||||
|
<el-form-item label="跳转路径" prop="link" :rules="link">
|
||||||
|
<el-input v-model="form.link" class="link-input" placeholder="请输入跳转路径" />
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="custom_type_active == 1">
|
||||||
|
<el-form-item label="APPID" prop="app_id" :rules="app_id">
|
||||||
|
<el-input v-model="form.app_id" class="link-input" placeholder="请输入小程序APPID" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="小程序路径" prop="app_link" :rules="app_link">
|
||||||
|
<el-input v-model="form.app_link" class="link-input" placeholder="请输入小程序路径" />
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="custom_type_active == 2">
|
||||||
|
<el-form-item label="电话号码" prop="phone" :rules="phone">
|
||||||
|
<el-input v-model="form.phone" class="link-input" placeholder="请输入电话号码" />
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="custom_type_active == 3">
|
||||||
|
<el-form-item label="名称" prop="name" :rules="name">
|
||||||
|
<el-input v-model="form.name" class="link-input" placeholder="请输入名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="详细地址" prop="address" :rules="address">
|
||||||
|
<el-input v-model="form.address" class="link-input" placeholder="请输入地址" type="1" @change="address_change" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="经纬度">
|
||||||
|
<maps v-model="map_address" type="4" @point="map_point"></maps>
|
||||||
|
<!-- <t-map v-model="map_address" @point="map_point"></t-map> -->
|
||||||
|
<!-- <bd-map v-model="map_address" @point="map_point"></bd-map> -->
|
||||||
|
<!-- <gd-map v-model="map_address" @point="map_point"></gd-map> -->
|
||||||
|
<!-- <tx-map v-model="map_address" @point="map_point"></tx-map> -->
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<el-button type="primary" class="hide" @click="on_submit">Create</el-button>
|
||||||
|
</el-form>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FormInstance } from 'element-plus';
|
||||||
|
const props = defineProps({
|
||||||
|
status: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// 重置
|
||||||
|
reset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.status,
|
||||||
|
(val) => {
|
||||||
|
on_submit();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => props.reset,
|
||||||
|
() => {
|
||||||
|
reset_data();
|
||||||
|
custom_type_active.value = 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
interface customType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
const custom_type = ref<customType[]>([
|
||||||
|
{ id: 0, name: '普通链接' },
|
||||||
|
{ id: 1, name: '跳转小程序' },
|
||||||
|
{ id: 2, name: '拨打电话' },
|
||||||
|
{ id: 3, name: '跳转地图' },
|
||||||
|
]);
|
||||||
|
const custom_type_active = ref(0);
|
||||||
|
const custom_type_event = (item: any) => {
|
||||||
|
custom_type_active.value = item.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const map_address = ref('');
|
||||||
|
const address_change = (val: string) => {
|
||||||
|
map_address.value = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
//#region 天地图 -----------------------------------------------start
|
||||||
|
const map_point = (lng: number, lat: number) => {
|
||||||
|
form.lng = lng;
|
||||||
|
form.lat = lat;
|
||||||
|
};
|
||||||
|
//#endregion 天地图 -----------------------------------------------end
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
link: '',
|
||||||
|
app_id: '',
|
||||||
|
app_link: '',
|
||||||
|
phone: '',
|
||||||
|
name: '',
|
||||||
|
address: '',
|
||||||
|
lng: 121.47894,
|
||||||
|
lat: 31.223,
|
||||||
|
});
|
||||||
|
const link = computed(() => {
|
||||||
|
return { trigger: 'change', message: '跳转路径不能为空', required: custom_type_active.value == 0 };
|
||||||
|
});
|
||||||
|
const app_id = computed(() => {
|
||||||
|
return { trigger: 'change', message: '跳转小程序APPID不能为空', required: custom_type_active.value == 1 };
|
||||||
|
});
|
||||||
|
const app_link = computed(() => {
|
||||||
|
return { trigger: 'change', message: '跳转小程序路径不能为空', required: custom_type_active.value == 1 };
|
||||||
|
});
|
||||||
|
const phone = computed(() => {
|
||||||
|
return { trigger: 'change', message: '电话号码不能为空', required: custom_type_active.value == 2 };
|
||||||
|
});
|
||||||
|
const name = computed(() => {
|
||||||
|
return { trigger: 'change', message: '名称不能为空', required: custom_type_active.value == 3 };
|
||||||
|
});
|
||||||
|
const address = computed(() => {
|
||||||
|
return { trigger: 'change', message: '详细地址不能为空', required: custom_type_active.value == 3 };
|
||||||
|
});
|
||||||
|
const ruleFormRef = ref<FormInstance>();
|
||||||
|
const emit = defineEmits(['update:link', 'required']);
|
||||||
|
interface formType {
|
||||||
|
id?: number;
|
||||||
|
name: string;
|
||||||
|
link: string;
|
||||||
|
lng?: number;
|
||||||
|
lat?: number;
|
||||||
|
}
|
||||||
|
const on_submit = () => {
|
||||||
|
if (!ruleFormRef.value) return;
|
||||||
|
ruleFormRef.value.validate((valid: boolean) => {
|
||||||
|
if (valid) {
|
||||||
|
let new_value: formType = {
|
||||||
|
name: '',
|
||||||
|
link: '',
|
||||||
|
};
|
||||||
|
if (custom_type_active.value == 1) {
|
||||||
|
new_value = {
|
||||||
|
name: form.app_id,
|
||||||
|
link: form.app_link,
|
||||||
|
};
|
||||||
|
} else if (custom_type_active.value == 2) {
|
||||||
|
new_value = {
|
||||||
|
name: form.phone,
|
||||||
|
link: form.phone,
|
||||||
|
};
|
||||||
|
} else if (custom_type_active.value == 3) {
|
||||||
|
new_value = {
|
||||||
|
name: form.name,
|
||||||
|
link: form.address,
|
||||||
|
lng: form.lng,
|
||||||
|
lat: form.lat,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
new_value = {
|
||||||
|
name: form.link,
|
||||||
|
link: form.link,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
emit('update:link', new_value);
|
||||||
|
} else {
|
||||||
|
emit('required');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const reset_data = () => {
|
||||||
|
form.link = '';
|
||||||
|
form.app_id = '';
|
||||||
|
form.app_link = '';
|
||||||
|
form.phone = '';
|
||||||
|
form.name = '';
|
||||||
|
form.address = '';
|
||||||
|
form.lng = 121.47894;
|
||||||
|
form.lat = 31.223;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
.tabs {
|
||||||
|
.item {
|
||||||
|
width: 8rem;
|
||||||
|
height: 3rem;
|
||||||
|
line-height: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
&:hover {
|
||||||
|
background-color: #edf4ff;
|
||||||
|
color: $cr-primary;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background: #edf4ff;
|
||||||
|
color: $cr-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.link-input {
|
||||||
|
width: 33.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
159
src/components/common/url-value/link-goods-category.vue
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 商品分类 -->
|
||||||
|
<div class="container">
|
||||||
|
<div class="flex-row jc-e gap-20 mb-20">
|
||||||
|
<el-select v-model="type" class="search-w" placeholder="请选择">
|
||||||
|
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="brand" class="search-w" placeholder="品牌">
|
||||||
|
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-input v-model="search_value" placeholder="请输入搜索内容" class="search-w" @change="handle_search">
|
||||||
|
<template #suffix>
|
||||||
|
<icon name="search" size="16" color="9"></icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-table :data="tableData" class="w" :header-cell-style="{ background: '#f7f7f7' }" row-key="id" height="438" fixed @row-click="row_click">
|
||||||
|
<el-table-column label="#" width="120" type="">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-radio v-model="template_selection" :label="scope.$index + ''"> </el-radio>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="id" label="ID" width="180" type="" />
|
||||||
|
<el-table-column prop="name" label="分类名称" />
|
||||||
|
<el-table-column prop="icon" label="分类图标">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-image :src="scope.row.icon" class="img" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="mt-10 flex-row jc-e">
|
||||||
|
<el-pagination :current-page="page" :page-size="21" :pager-count="5" layout="prev, pager, next" :total="data_total" @current-change="get_list" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps({
|
||||||
|
// 重置
|
||||||
|
reset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.reset,
|
||||||
|
() => {
|
||||||
|
template_selection.value = '';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const modelValue = defineModel({ type: Object, default: {} });
|
||||||
|
interface User {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
link: string;
|
||||||
|
hasChildren?: boolean;
|
||||||
|
children?: User[];
|
||||||
|
}
|
||||||
|
const tableData: User[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'a',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '二级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'b',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'c',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'd',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: '一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'e',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const search_value = ref('');
|
||||||
|
const handle_search = () => {
|
||||||
|
console.log(search_value.value);
|
||||||
|
};
|
||||||
|
const type = ref('');
|
||||||
|
const brand = ref('');
|
||||||
|
const options = ref([
|
||||||
|
{ value: '1', label: '一级分类' },
|
||||||
|
{ value: '2', label: '二级分类' },
|
||||||
|
{ value: '3', label: '三级分类' },
|
||||||
|
]);
|
||||||
|
const template_selection = ref('');
|
||||||
|
|
||||||
|
const row_click = (row: any) => {
|
||||||
|
let new_data: User[] = [];
|
||||||
|
// 多级数组转换一级数组
|
||||||
|
const array_conversion = (item: User[]) => {
|
||||||
|
item.forEach((item) => {
|
||||||
|
new_data.push(item);
|
||||||
|
if (item.children) {
|
||||||
|
array_conversion(item.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new_data;
|
||||||
|
};
|
||||||
|
const new_table_data = array_conversion(JSON.parse(JSON.stringify(tableData)));
|
||||||
|
template_selection.value = new_table_data.findIndex((item: User) => item.id == row.id).toString();
|
||||||
|
modelValue.value = row;
|
||||||
|
};
|
||||||
|
//#region 分页 -----------------------------------------------start
|
||||||
|
// 总页数
|
||||||
|
// const page_total = ref(0);
|
||||||
|
// 当前页
|
||||||
|
const page = ref(1);
|
||||||
|
// 总数量
|
||||||
|
const data_total = ref(0);
|
||||||
|
|
||||||
|
// 查询文件
|
||||||
|
const search_data = ref({
|
||||||
|
page: page.value,
|
||||||
|
type: '',
|
||||||
|
name: search_value.value,
|
||||||
|
});
|
||||||
|
// 查询文件
|
||||||
|
const get_list = () => {
|
||||||
|
console.log('查询接口', search_data);
|
||||||
|
};
|
||||||
|
//#region 分页 -----------------------------------------------end
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
.search-w {
|
||||||
|
width: 22.5rem;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
:deep(.el-table__inner-wrapper:before) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
width: 3.6rem;
|
||||||
|
height: 3.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
275
src/components/common/url-value/link-goods-search.vue
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 商城 -->
|
||||||
|
<div class="container">
|
||||||
|
<div class="tabs flex-row gap-10 mb-20">
|
||||||
|
<div v-for="item in custom_type" :key="item.id" class="item bg-f5 radius-sm" :class="custom_type_active === item.id ? 'active' : ''" @click="custom_type_event(item)">{{ item.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<template v-if="custom_type_active === 0">
|
||||||
|
<div class="goods-tips ptb-10 plr-14 mb-20 size-12">
|
||||||
|
<text class="cr-666">您当前选择的产品类别是:</text>
|
||||||
|
<text>{{ one_item_text }} {{ two_item_text ? '>' : '' }}</text>
|
||||||
|
<text class="pl-3">{{ two_item_text }} {{ three_item_text ? '>' : '' }}</text>
|
||||||
|
<text class="pl-3">{{ three_item_text }}</text>
|
||||||
|
</div>
|
||||||
|
<div class="goods-category flex-row gap-30">
|
||||||
|
<div v-if="goods_category_data.length > 0" class="goods-category-wdith br-d radius-xs">
|
||||||
|
<el-scrollbar height="420px">
|
||||||
|
<div v-for="(item, index) in goods_category_data" :key="item.id" class="item flex-row jc-c align-c gap-10 pa-10" :class="one_item_index === index + 1 ? 'active' : ''" @click="goods_item_click(item, 1, index)">
|
||||||
|
<text class="flex-1 flex-width text-line-1">{{ item.name }}</text>
|
||||||
|
<icon v-if="item?.children" name="arrow-right"></icon>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
<div v-if="two_item_data.length > 0" class="goods-category-wdith br-d radius-xs">
|
||||||
|
<el-scrollbar height="420px">
|
||||||
|
<div v-for="(item, index) in two_item_data" :key="item.id" class="item flex-row jc-c align-c gap-10 pa-10" :class="two_item_index === index + 1 ? 'active' : ''" @click="goods_item_click(item, 2, index)">
|
||||||
|
<text class="flex-1 flex-width text-line-1">{{ item.name }}</text>
|
||||||
|
<icon v-if="item?.children" name="arrow-right"></icon>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
<div v-if="three_item_data.length > 0" class="goods-category-wdith br-d radius-xs">
|
||||||
|
<el-scrollbar height="420px">
|
||||||
|
<div v-for="(item, index) in three_item_data" :key="item.id" class="item flex-row jc-c align-c gap-10 pa-10" :class="three_item_index === index + 1 ? 'active' : ''" @click="goods_item_click(item, 3, index)">
|
||||||
|
<text class="flex-1 flex-width text-line-1">{{ item.name }}</text>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="custom_type_active === 1">
|
||||||
|
<div class="brand">
|
||||||
|
<div class="flex-1 br-d radius-xs">
|
||||||
|
<el-scrollbar height="480px">
|
||||||
|
<div v-for="(item, index) in brand_data" :key="item.id" class="item flex-row jc-c align-c gap-10 pa-10" :class="brand_item_index === index + 1 ? 'active' : ''" @click="brand_item_click(item, index)">
|
||||||
|
<text class="flex-1 flex-width text-line-1">{{ item.name }}</text>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="custom_type_active === 2">
|
||||||
|
<el-form ref="ruleFormRef" :model="form" :rules="rules" label-width="85px" status-icon>
|
||||||
|
<el-form-item label="关键字" prop="key">
|
||||||
|
<el-input v-model="form.key" class="link-input" placeholder="请输入关键字" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-button type="primary" class="hide" @click="on_submit">Create</el-button>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus';
|
||||||
|
const props = defineProps({
|
||||||
|
status: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// 重置
|
||||||
|
reset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.status,
|
||||||
|
(val) => {
|
||||||
|
on_submit();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => props.reset,
|
||||||
|
() => {
|
||||||
|
reset_data();
|
||||||
|
custom_type_active.value = 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const emit = defineEmits(['update:link', 'required', 'type']);
|
||||||
|
interface customType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
const custom_type = ref<customType[]>([
|
||||||
|
{ id: 0, name: '商品分类' },
|
||||||
|
{ id: 1, name: '品牌' },
|
||||||
|
{ id: 2, name: '关键字' },
|
||||||
|
]);
|
||||||
|
interface baseData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
children?: baseData[];
|
||||||
|
}
|
||||||
|
const goods_category_data = reactive<baseData[]>([
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: '服饰鞋包',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '服饰',
|
||||||
|
children: [
|
||||||
|
{ id: 1, name: '上衣' },
|
||||||
|
{ id: 2, name: '裤子' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '鞋包',
|
||||||
|
children: [
|
||||||
|
{ id: 1, name: '休闲鞋' },
|
||||||
|
{ id: 2, name: '商务休闲鞋' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ id: 1, name: '家居生活', children: [{ id: 1, name: '生活用品' }] },
|
||||||
|
{ id: 2, name: '母婴' },
|
||||||
|
]);
|
||||||
|
const brand_data = reactive<baseData[]>([
|
||||||
|
{ id: 1, name: '品牌1' },
|
||||||
|
{ id: 2, name: '品牌2' },
|
||||||
|
]);
|
||||||
|
const custom_type_active = ref(0);
|
||||||
|
const custom_type_event = (item: any) => {
|
||||||
|
custom_type_active.value = item.id;
|
||||||
|
emit('type', item.id);
|
||||||
|
};
|
||||||
|
//#region 商品分类 -----------------------------------------------start
|
||||||
|
const check_data = ref({});
|
||||||
|
const two_item_data = ref<baseData[]>([]);
|
||||||
|
const three_item_data = ref<baseData[]>([]);
|
||||||
|
const one_item_index = ref(0);
|
||||||
|
const two_item_index = ref(0);
|
||||||
|
const three_item_index = ref(0);
|
||||||
|
const one_item_text = ref('');
|
||||||
|
const two_item_text = ref('');
|
||||||
|
const three_item_text = ref('');
|
||||||
|
// 商品项点击事件
|
||||||
|
const goods_item_click = (item: baseData, level: number, index: number) => {
|
||||||
|
if (level === 1) {
|
||||||
|
one_item_index.value = index + 1;
|
||||||
|
one_item_text.value = item.name;
|
||||||
|
two_item_index.value = 0;
|
||||||
|
two_item_text.value = '';
|
||||||
|
three_item_index.value = 0;
|
||||||
|
three_item_text.value = '';
|
||||||
|
two_item_data.value = [];
|
||||||
|
three_item_data.value = [];
|
||||||
|
} else if (level === 2) {
|
||||||
|
two_item_index.value = index + 1;
|
||||||
|
two_item_text.value = item.name;
|
||||||
|
three_item_index.value = 0;
|
||||||
|
three_item_text.value = '';
|
||||||
|
three_item_data.value = [];
|
||||||
|
} else {
|
||||||
|
three_item_index.value = index + 1;
|
||||||
|
three_item_text.value = item.name;
|
||||||
|
}
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
if (level === 1) {
|
||||||
|
two_item_data.value = item.children;
|
||||||
|
} else if (level === 2) {
|
||||||
|
three_item_data.value = item.children;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
check_data.value = item;
|
||||||
|
emit('update:link', item, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//#endregion 商品分类 -----------------------------------------------end
|
||||||
|
|
||||||
|
//#region 品牌 -----------------------------------------------start
|
||||||
|
const brand_item_index = ref(0);
|
||||||
|
const brand_item_click = (item: any, index: number) => {
|
||||||
|
brand_item_index.value = index + 1;
|
||||||
|
check_data.value = item;
|
||||||
|
emit('update:link', item, 1);
|
||||||
|
};
|
||||||
|
//#endregion 品牌 -----------------------------------------------end
|
||||||
|
|
||||||
|
//#region 关键字 -----------------------------------------------start
|
||||||
|
const form = reactive({
|
||||||
|
key: '',
|
||||||
|
});
|
||||||
|
const rules = ref<FormRules>({
|
||||||
|
key: [{ required: true, trigger: 'change', message: '关键词不能为空' }],
|
||||||
|
});
|
||||||
|
const ruleFormRef = ref<FormInstance>();
|
||||||
|
interface formType {
|
||||||
|
id?: number;
|
||||||
|
name: string;
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
const on_submit = () => {
|
||||||
|
if (!ruleFormRef.value) return;
|
||||||
|
ruleFormRef.value.validate((valid: boolean) => {
|
||||||
|
if (valid) {
|
||||||
|
let new_value: formType = {
|
||||||
|
name: form.key,
|
||||||
|
link: form.key,
|
||||||
|
};
|
||||||
|
emit('update:link', new_value, 2);
|
||||||
|
} else {
|
||||||
|
emit('required');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
//#endregion 关键字 -----------------------------------------------end
|
||||||
|
const reset_data = () => {
|
||||||
|
one_item_index.value = 0;
|
||||||
|
one_item_text.value = '';
|
||||||
|
two_item_index.value = 0;
|
||||||
|
two_item_text.value = '';
|
||||||
|
three_item_index.value = 0;
|
||||||
|
three_item_text.value = '';
|
||||||
|
two_item_data.value = [];
|
||||||
|
brand_item_index.value = 0;
|
||||||
|
form.key = '';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
.tabs {
|
||||||
|
.item {
|
||||||
|
width: 8rem;
|
||||||
|
height: 3rem;
|
||||||
|
line-height: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
&:hover {
|
||||||
|
background-color: #edf4ff;
|
||||||
|
color: $cr-primary;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background: #edf4ff;
|
||||||
|
color: $cr-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
.goods-tips {
|
||||||
|
background-color: #f3f2fc;
|
||||||
|
}
|
||||||
|
.goods-category,
|
||||||
|
.brand {
|
||||||
|
.item {
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
background: #edf4ff;
|
||||||
|
color: $cr-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.goods-category-wdith {
|
||||||
|
width: calc((100% - 6rem) / 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.link-input {
|
||||||
|
width: 33.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
139
src/components/common/url-value/link-goods.vue
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 商品 -->
|
||||||
|
<div class="container">
|
||||||
|
<div class="flex-row jc-e gap-20 mb-20">
|
||||||
|
<el-select v-model="type" class="search-w" placeholder="请选择">
|
||||||
|
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="brand" class="search-w" placeholder="品牌">
|
||||||
|
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-input v-model="search_value" placeholder="请输入搜索内容" class="search-w" @change="handle_search">
|
||||||
|
<template #suffix>
|
||||||
|
<icon name="search" size="16" color="9"></icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-table :data="tableData" class="w" :header-cell-style="{ background: '#f7f7f7' }" row-key="id" height="438" fixed @row-click="row_click">
|
||||||
|
<el-table-column label="#" width="120" type="">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-radio v-model="template_selection" :label="scope.$index + ''"> </el-radio>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="id" label="ID" width="180" type="" />
|
||||||
|
<el-table-column prop="icon" label="商品图片">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-image :src="scope.row.icon" class="img" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="name" label="分类名称" />
|
||||||
|
</el-table>
|
||||||
|
<div class="mt-10 flex-row jc-e">
|
||||||
|
<el-pagination :current-page="page" :page-size="21" :pager-count="5" layout="prev, pager, next" :total="data_total" @current-change="get_list" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps({
|
||||||
|
// 重置
|
||||||
|
reset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.reset,
|
||||||
|
() => {
|
||||||
|
template_selection.value = '';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const modelValue = defineModel({ type: Object, default: {} });
|
||||||
|
interface User {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
const tableData: User[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'c',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'd',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: '一级分类',
|
||||||
|
icon: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||||
|
link: 'e',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const search_value = ref('');
|
||||||
|
const handle_search = () => {
|
||||||
|
console.log(search_value.value);
|
||||||
|
};
|
||||||
|
const type = ref('');
|
||||||
|
const brand = ref('');
|
||||||
|
const options = ref([
|
||||||
|
{ value: '1', label: '一级分类' },
|
||||||
|
{ value: '2', label: '二级分类' },
|
||||||
|
{ value: '3', label: '三级分类' },
|
||||||
|
]);
|
||||||
|
const emit = defineEmits(['update:link']);
|
||||||
|
const template_selection = ref('');
|
||||||
|
|
||||||
|
const row_click = (row: any) => {
|
||||||
|
const new_table_data = JSON.parse(JSON.stringify(tableData));
|
||||||
|
template_selection.value = new_table_data.findIndex((item: User) => item.id == row.id).toString();
|
||||||
|
modelValue.value = row;
|
||||||
|
};
|
||||||
|
//#region 分页 -----------------------------------------------start
|
||||||
|
// 总页数
|
||||||
|
// const page_total = ref(0);
|
||||||
|
// 当前页
|
||||||
|
const page = ref(1);
|
||||||
|
// 总数量
|
||||||
|
const data_total = ref(0);
|
||||||
|
|
||||||
|
// 查询文件
|
||||||
|
const search_data = ref({
|
||||||
|
page: page.value,
|
||||||
|
type: '',
|
||||||
|
name: search_value.value,
|
||||||
|
});
|
||||||
|
// 查询文件
|
||||||
|
const get_list = () => {
|
||||||
|
console.log('查询接口', search_data);
|
||||||
|
};
|
||||||
|
//#region 分页 -----------------------------------------------end
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
.search-w {
|
||||||
|
width: 22.5rem;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
:deep(.el-table__inner-wrapper:before) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
width: 3.6rem;
|
||||||
|
height: 3.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
142
src/components/common/url-value/link-list.vue
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 商城 -->
|
||||||
|
<div class="container">
|
||||||
|
<div class="flex-row jc-e mb-20">
|
||||||
|
<div class="search">
|
||||||
|
<el-input v-model="search_value" placeholder="请输入搜索内容" class="" @change="handle_search">
|
||||||
|
<template #suffix>
|
||||||
|
<icon name="search" size="16" color="9"></icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-scrollbar height="480px">
|
||||||
|
<div class="flex-col gap-30">
|
||||||
|
<div v-for="item in base_data" :key="item.id">
|
||||||
|
<div class="fw mb-15">{{ item.name }}</div>
|
||||||
|
<div class="flex-row flex-wrap gap-15">
|
||||||
|
<div v-for="child in item.data" :key="child.id" class="item" :class="menu_active == item.id + '-' + child.id ? 'active' : ''" @click="menu_link_event(child, item.id)">{{ child.name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps({
|
||||||
|
// 重置
|
||||||
|
reset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.reset,
|
||||||
|
() => {
|
||||||
|
menu_active.value = '';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const modelValue = defineModel({ type: Object, default: {} });
|
||||||
|
const search_value = ref('');
|
||||||
|
interface Data {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
link?: String;
|
||||||
|
data?: Data[];
|
||||||
|
}
|
||||||
|
const base_data = ref<Data[]>([
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: '基础链接',
|
||||||
|
data: [
|
||||||
|
{ id: 0, name: '首页', link: '首页' },
|
||||||
|
{ id: 1, name: '商城分类', link: '商城分类' },
|
||||||
|
{ id: 2, name: '购物车', link: '购物车' },
|
||||||
|
{ id: 3, name: '分类商品列表', link: '分类商品列表' },
|
||||||
|
{ id: 4, name: '退款列表', link: '退款列表' },
|
||||||
|
{ id: 5, name: '我的订单', link: '我的订单' },
|
||||||
|
{ id: 6, name: '文章列表', link: '文章列表' },
|
||||||
|
{ id: 7, name: '供应商入驻', link: '供应商入驻' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '个人中心',
|
||||||
|
data: [
|
||||||
|
{ id: 0, name: '付费会员', link: '付费会员' },
|
||||||
|
{ id: 1, name: '收银页面', link: '收银页面' },
|
||||||
|
{ id: 2, name: '我的订单', link: '我的订单' },
|
||||||
|
{ id: 3, name: '我的收藏', link: '我的收藏' },
|
||||||
|
{ id: 4, name: '我的地址', link: '我的地址' },
|
||||||
|
{ id: 5, name: '我的优惠券', link: '我的优惠券' },
|
||||||
|
{ id: 6, name: '我的消息', link: '我的消息' },
|
||||||
|
{ id: 7, name: '我的资料', link: '我的资料' },
|
||||||
|
{ id: 8, name: '我的积分', link: '我的积分' },
|
||||||
|
{ id: 9, name: '我的余额', link: '我的余额' },
|
||||||
|
{ id: 10, name: '我的红包', link: '我的红包' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '分销',
|
||||||
|
data: [
|
||||||
|
{ id: 0, name: '分销中心', link: '分销中心' },
|
||||||
|
{ id: 1, name: '分销订单', link: '分销订单' },
|
||||||
|
{ id: 2, name: '分销商品', link: '分销商品' },
|
||||||
|
{ id: 3, name: '分销提现', link: '分销提现' },
|
||||||
|
{ id: 4, name: '分销佣金', link: '分销佣金' },
|
||||||
|
{ id: 5, name: '分销设置', link: '分销设置' },
|
||||||
|
{ id: 6, name: '分销关系', link: '分销关系' },
|
||||||
|
{ id: 7, name: '分销商列表', link: '分销商列表' },
|
||||||
|
{ id: 8, name: '分销商等级', link: '分销商等级' },
|
||||||
|
{ id: 9, name: '分销商统计', link: '分销商统计' },
|
||||||
|
{ id: 10, name: '分销商提现', link: '分销商提现' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const handle_search = () => {
|
||||||
|
console.log(search_value.value);
|
||||||
|
};
|
||||||
|
const menu_active = ref('');
|
||||||
|
const emit = defineEmits(['update:link']);
|
||||||
|
const menu_link_event = (item: Data, parent_id: number) => {
|
||||||
|
if (`${parent_id}-${item.id}` == menu_active.value) {
|
||||||
|
menu_active.value = '';
|
||||||
|
modelValue.value = {};
|
||||||
|
} else {
|
||||||
|
menu_active.value = `${parent_id}-${item.id}`;
|
||||||
|
modelValue.value = item;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
search {
|
||||||
|
width: 22.5rem;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
.item {
|
||||||
|
width: 10.3rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
height: 3.6rem;
|
||||||
|
line-height: 3.6rem;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
&:hover {
|
||||||
|
background-color: #edf4ff;
|
||||||
|
color: $cr-primary;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background: #edf4ff;
|
||||||
|
color: $cr-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
117
src/components/common/url-value/link-table.vue
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 商品 -->
|
||||||
|
<div class="container">
|
||||||
|
<div class="flex-row jc-e gap-20 mb-20">
|
||||||
|
<el-input v-model="search_value" placeholder="请输入搜索内容" class="search-w" @change="handle_search">
|
||||||
|
<template #suffix>
|
||||||
|
<icon name="search" size="16" color="9"></icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-table :data="tableData" class="w" :header-cell-style="{ background: '#f7f7f7' }" row-key="id" height="438" fixed @row-click="row_click">
|
||||||
|
<el-table-column label="#" width="120" type="">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-radio v-model="template_selection" :label="scope.$index + ''"> </el-radio>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="id" label="ID" width="180" type="" />
|
||||||
|
<el-table-column prop="name" label="页面名称" />
|
||||||
|
<el-table-column prop="link" label="页面链接" />
|
||||||
|
</el-table>
|
||||||
|
<div class="mt-10 flex-row jc-e">
|
||||||
|
<el-pagination :current-page="page" :page-size="21" :pager-count="5" layout="prev, pager, next" :total="data_total" @current-change="get_list" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps({
|
||||||
|
// 重置
|
||||||
|
reset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.reset,
|
||||||
|
() => {
|
||||||
|
template_selection.value = '';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const modelValue = defineModel({ type: Object, default: {} });
|
||||||
|
interface User {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
const tableData: User[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '一级分类',
|
||||||
|
link: 'a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '一级分类',
|
||||||
|
link: 'c',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '一级分类',
|
||||||
|
link: 'd',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: '一级分类',
|
||||||
|
link: 'e',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const search_value = ref('');
|
||||||
|
const handle_search = () => {
|
||||||
|
console.log(search_value.value);
|
||||||
|
};
|
||||||
|
const emit = defineEmits(['update:link']);
|
||||||
|
const template_selection = ref('');
|
||||||
|
|
||||||
|
const row_click = (row: any) => {
|
||||||
|
const new_table_data = JSON.parse(JSON.stringify(tableData));
|
||||||
|
template_selection.value = new_table_data.findIndex((item: User) => item.id == row.id).toString();
|
||||||
|
modelValue.value = row;
|
||||||
|
};
|
||||||
|
//#region 分页 -----------------------------------------------start
|
||||||
|
// 总页数
|
||||||
|
// const page_total = ref(0);
|
||||||
|
// 当前页
|
||||||
|
const page = ref(1);
|
||||||
|
// 总数量
|
||||||
|
const data_total = ref(0);
|
||||||
|
|
||||||
|
// 查询文件
|
||||||
|
const search_data = ref({
|
||||||
|
page: page.value,
|
||||||
|
type: '',
|
||||||
|
name: search_value.value,
|
||||||
|
});
|
||||||
|
// 查询文件
|
||||||
|
const get_list = () => {
|
||||||
|
console.log('查询接口', search_data);
|
||||||
|
};
|
||||||
|
//#region 分页 -----------------------------------------------end
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
.search-w {
|
||||||
|
width: 22.5rem;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
:deep(.el-table__inner-wrapper:before) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
width: 3.6rem;
|
||||||
|
height: 3.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
101
src/components/footer-nav/footer-nav-content.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<el-form :model="form" label-width="70">
|
||||||
|
<card-container class="mb-8">
|
||||||
|
<div class="mb-12">展示设置</div>
|
||||||
|
<el-form-item label="导航样式">
|
||||||
|
<el-radio-group v-model="form.nav_style" is-button @change="nav_style_change">
|
||||||
|
<el-radio value="0">图片加文字</el-radio>
|
||||||
|
<el-radio value="1">图片</el-radio>
|
||||||
|
<el-radio value="2">文字</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="导航类型">
|
||||||
|
<el-radio-group v-model="form.nav_type" is-button @change="nav_type_change">
|
||||||
|
<el-radio value="0">底部固定</el-radio>
|
||||||
|
<el-radio value="1">底部悬浮</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</card-container>
|
||||||
|
<card-container class="footer-nav-height">
|
||||||
|
<div class="mb-12">导航内容</div>
|
||||||
|
<div class="size-12 cr-c mb-20">图片建议宽高80*80;鼠标拖拽左侧圆点可调整导航顺序</div>
|
||||||
|
<div class="nav-list">
|
||||||
|
<drag :data="form.nav_content" type="card" :space-col="20" @remove="nav_content_remove">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="w">
|
||||||
|
<el-form-item label="图标" label-width="45">
|
||||||
|
<div class="flex-col jc-c align-c mr-12">
|
||||||
|
<upload v-model="row.src" :limit="1" :size="44" :styles="1"></upload>
|
||||||
|
<text class="cr-9 size-12">选中</text>
|
||||||
|
</div>
|
||||||
|
<div class="flex-col jc-c align-c">
|
||||||
|
<upload v-model="row.src_checked" :limit="1" :size="44" :styles="1"></upload>
|
||||||
|
<text class="cr-9 size-12">未选中</text>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="名称" label-width="45">
|
||||||
|
<el-input v-model="row.name" placeholder="请输入名称" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="链接" label-width="45">
|
||||||
|
<url-value v-model="row.href"></url-value>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</drag>
|
||||||
|
<el-button class="mtb-20 w" @click="add">+添加</el-button>
|
||||||
|
</div>
|
||||||
|
</card-container>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { get_math } from '@/utils';
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
nav_style: '0',
|
||||||
|
nav_type: '0',
|
||||||
|
nav_content: [
|
||||||
|
{ id: '1', name: '首页', src: [{ id: 1, url: '/src/assets/images/layout/main/phone.png', original: '头像1', title: '头像1', ext: '.png', type: 'img' }], src_checked: 'tabs', href: {} },
|
||||||
|
{ id: '2', name: '分类', src: [{ id: 1, url: '/src/assets/images/layout/main/phone.png', original: '头像1', title: '头像1', ext: '.png', type: 'img' }], src_checked: 'tabs', href: {} },
|
||||||
|
{ id: '3', name: '购物车', src: [{ id: 1, url: '/src/assets/images/layout/main/phone.png', original: '头像1', title: '头像1', ext: '.png', type: 'img' }], src_checked: 'tabs', href: {} },
|
||||||
|
{ id: '4', name: '我的', src: [{ id: 1, url: '/src/assets/images/layout/main/phone.png', original: '头像1', title: '头像1', ext: '.png', type: 'img' }], src_checked: 'tabs', href: {} },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const form = reactive(props.value);
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
const nav_style_change = (style: any) => {
|
||||||
|
form.nav_style = style;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const nav_type_change = (type: any) => {
|
||||||
|
form.nav_type = type;
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const nav_content_remove = (index: number) => {
|
||||||
|
form.nav_content.splice(index, 1);
|
||||||
|
emit('update:value', form);
|
||||||
|
};
|
||||||
|
const add = () => {
|
||||||
|
form.nav_content.push({
|
||||||
|
id: get_math(),
|
||||||
|
name: '',
|
||||||
|
src: [],
|
||||||
|
src_checked: [],
|
||||||
|
href: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.footer-nav-height {
|
||||||
|
min-height: calc(100vh - 36.8rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
45
src/components/footer-nav/footer-nav-setting.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<template v-if="type == '1'">
|
||||||
|
<footer-nav-content :value="form.content" @update:value="content_update"></footer-nav-content>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type == '2'">
|
||||||
|
<footer-nav-styles :value="form.style" @update:value="style_update"></footer-nav-styles>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: '1',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const form = reactive(props.value);
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
const content_update = (value: any) => {
|
||||||
|
form.content = value;
|
||||||
|
let new_data = form.style.common_style;
|
||||||
|
const new_arry = ['margin', 'margin_left', 'margin_right', 'margin_top', 'margin_bottom', 'padding', 'padding_left', 'padding_right', 'padding_top', 'padding_bottom', 'radius', 'radius_top_left', 'radius_top_right', 'radius_bottom_left', 'radius_bottom_right'];
|
||||||
|
if (value.nav_type == '1') {
|
||||||
|
new_arry.forEach((item) => {
|
||||||
|
if (item.indexOf('radius') !== -1) {
|
||||||
|
new_data[item] = 100;
|
||||||
|
} else {
|
||||||
|
new_data[item] = 10;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
new_arry.forEach((item) => {
|
||||||
|
new_data[item] = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const style_update = (value: any) => {
|
||||||
|
form.style = value;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
59
src/components/footer-nav/footer-nav-styles.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<el-form :model="form" label-width="70">
|
||||||
|
<card-container class="mb-8">
|
||||||
|
<div class="mb-12">颜色设置</div>
|
||||||
|
<el-form-item label="选中文本">
|
||||||
|
<color-picker v-model="form.text_color_checked" default-color="rgba(204, 204, 204, 1)" @update:value="text_color_checked_event" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="默认文本">
|
||||||
|
<color-picker v-model="form.default_text_color" default-color="rgba(0, 0, 0, 1)" @update:value="default_text_color_event" />
|
||||||
|
</el-form-item>
|
||||||
|
</card-container>
|
||||||
|
</el-form>
|
||||||
|
<common-styles :value="form.common_style" @update:value="common_styles_update" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {
|
||||||
|
text_color_checked: 'rgba(204, 204, 204, 1)',
|
||||||
|
default_text_color: 'rgba(0, 0, 0, 1)',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
const state = reactive({
|
||||||
|
form: props.value,
|
||||||
|
});
|
||||||
|
// 如果需要解构,确保使用toRefs
|
||||||
|
const { form } = toRefs(state);
|
||||||
|
const text_color_checked_event = (val: string | null) => {
|
||||||
|
form.value.text_color_checked = val;
|
||||||
|
call_back_update(form);
|
||||||
|
};
|
||||||
|
const default_text_color_event = (val: string | null) => {
|
||||||
|
form.value.default_text_color = val;
|
||||||
|
call_back_update(form);
|
||||||
|
};
|
||||||
|
const common_styles_update = (val: Object) => {
|
||||||
|
form.value.common_style = val;
|
||||||
|
call_back_update(form.value);
|
||||||
|
};
|
||||||
|
const call_back_update = (val: Object) => {
|
||||||
|
emit('update:value', val);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
.container-height {
|
||||||
|
min-height: calc(100vh - 36.8rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
83
src/components/footer-nav/index.vue
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<template>
|
||||||
|
<div class="footer-nav flex-row jc-c align-c" :class="showFooter ? 'br-2 br-primary' : ''" @click="footer_nav_event">
|
||||||
|
<div class="footer-nav-content flex-row jc-c align-c w" :style="style_container">
|
||||||
|
<ul class="flex-row jc-sa align-c w">
|
||||||
|
<li v-for="(item, index) in footerData.content.nav_content" :key="index" class="flex-1 flex-col jc-c align-c gap-5" @mouseenter="is_hover = index + 1" @mouseleave="is_hover = 0">
|
||||||
|
<div v-if="footerData.content.nav_style !== '2'" class="img re">
|
||||||
|
<img class="img-item abs radius-xs animate-linear w" :class="is_hover != index + 1 ? 'active' : ''" :src="item.src[0]?.url" width="22" height="22" />
|
||||||
|
<img class="img-item abs radius-xs animate-linear w" :class="is_hover == index + 1 ? 'active' : ''" :src="item.src_checked[0]?.url" width="22" height="22" />
|
||||||
|
</div>
|
||||||
|
<span v-if="footerData.content.nav_style !== '1'" class="animate-linear size-12" :style="is_hover == index + 1 ? text_color_checked : default_text_color">{{ item.name }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { common_styles_computer } from '@/utils';
|
||||||
|
import { footerNavCounterStore } from '@/store/modules/footer-nav-content';
|
||||||
|
const footer_nav_counter_store = footerNavCounterStore();
|
||||||
|
const props = defineProps({
|
||||||
|
showFooter: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
footerData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const style_container = ref('');
|
||||||
|
const default_text_color = ref('');
|
||||||
|
const text_color_checked = ref('');
|
||||||
|
let is_hover = ref(0);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
props.footerData,
|
||||||
|
(newVal, oldValue) => {
|
||||||
|
const new_content = newVal?.content || {};
|
||||||
|
const new_style = newVal?.style || {};
|
||||||
|
default_text_color.value = 'color:' + new_style.default_text_color || 'rgba(0, 0, 0, 1)';
|
||||||
|
text_color_checked.value = 'color:' + new_style.text_color_checked || 'rgba(204, 204, 204, 1)';
|
||||||
|
|
||||||
|
style_container.value = common_styles_computer(new_style.common_style);
|
||||||
|
let footer_height = new_style.common_style.padding_top + new_style.common_style.padding_bottom + new_style.common_style.margin_top + new_style.common_style.margin_bottom + 50;
|
||||||
|
if (footer_height >= 70) {
|
||||||
|
footer_height = footer_height;
|
||||||
|
} else {
|
||||||
|
footer_height = 70;
|
||||||
|
}
|
||||||
|
footer_nav_counter_store.padding_footer_computer(footer_height);
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
const emits = defineEmits(['footer-nav']);
|
||||||
|
const footer_nav_event = () => {
|
||||||
|
emits('footer-nav');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.footer-nav {
|
||||||
|
width: 39rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0rem;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
.footer-nav-content {
|
||||||
|
min-height: 7rem;
|
||||||
|
.img {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
.img-item {
|
||||||
|
opacity: 0;
|
||||||
|
&.active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.br-2 {
|
||||||
|
border: 0.2rem solid $cr-main;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
98
src/components/model-article-list/index.vue
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<div :style="style_container">
|
||||||
|
<div class="re" :style="style">
|
||||||
|
<div class="flex-warp gap-10" :class="article_type == '1' ? 'style1 flex-row' : article_type == '0' ? 'style2 flex-col' : 'style3 flex-col'">
|
||||||
|
<card-container class="item gap-10" padding="10px" :class="article_type == '0' ? 'flex-row' : 'flex-col'" :style="content_radius">
|
||||||
|
<img v-if="is_img_show" src="@/assets/images/components/model-video/video.png" />
|
||||||
|
<div class="flex-col jc-sb gap-8">
|
||||||
|
<div class="title text-line-2" :style="article_name">华为荣耀畅想平板换屏服务,屏幕换外屏幕主板维修华为荣耀畅想平板换屏服务,屏幕换外屏幕主板维修华为荣耀畅想平板换屏服务,屏幕换外屏幕主板维修华为荣耀畅想平板换屏服务,屏幕换外屏幕主板维修华为荣耀畅想平板换屏服务,屏幕换外屏幕主板维修</div>
|
||||||
|
<div class="flex-row jc-sb gap-8">
|
||||||
|
<div :style="article_date">{{ is_show.includes('0') ? '2020-06-05 15:20' : '' }}</div>
|
||||||
|
<icon v-show="is_show.includes('1')" name="eye" :style="article_page_view">16</icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</card-container>
|
||||||
|
<card-container class="item gap-10" padding="10px" :class="article_type == '0' ? 'flex-row' : 'flex-col'" :style="content_radius">
|
||||||
|
<img v-if="is_img_show" src="@/assets/images/components/model-video/video.png" />
|
||||||
|
<div class="flex-col jc-sb gap-8">
|
||||||
|
<div class="title text-line-2" :style="article_name">华为荣耀畅想平板换屏服务,屏幕换外屏幕主板维修华为荣耀畅想平板换屏服务,屏幕换外屏幕主板维修华为荣耀畅想平板换屏服务,屏幕换外屏幕主板维修华为荣耀畅想平板换屏服务,屏幕换外屏幕主板维修华为荣耀畅想平板换屏服务,屏幕换外屏幕主板维修华为荣耀畅想平板换屏服务,屏幕换外屏幕主板维修</div>
|
||||||
|
<div class="flex-row jc-sb gap-8">
|
||||||
|
<div :style="article_date">{{ is_show.includes('0') ? '2020-06-05 15:20' : '' }}</div>
|
||||||
|
<icon v-show="is_show.includes('1')" name="eye" :style="article_page_view">16</icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</card-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { common_styles_computer, radius_computer } from '@/utils';
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
isCommonStyle: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const style = ref('');
|
||||||
|
const style_container = ref('');
|
||||||
|
// 风格
|
||||||
|
const article_type = ref('0');
|
||||||
|
// 是否显示
|
||||||
|
const is_show = ref(['0', '1']);
|
||||||
|
// 是否显示封面图片
|
||||||
|
const is_img_show = ref(true);
|
||||||
|
// 文章
|
||||||
|
const article_name = ref('');
|
||||||
|
// 日期
|
||||||
|
const article_date = ref('');
|
||||||
|
// 浏览量
|
||||||
|
const article_page_view = ref('');
|
||||||
|
// 内容圆角
|
||||||
|
const content_radius = ref('');
|
||||||
|
watch(
|
||||||
|
props.value,
|
||||||
|
(newVal, oldValue) => {
|
||||||
|
const new_content = newVal?.content;
|
||||||
|
const new_style = newVal?.style;
|
||||||
|
// 内容
|
||||||
|
article_type.value = new_content.article_style;
|
||||||
|
is_show.value = new_content.is_show;
|
||||||
|
is_img_show.value = new_content.is_img_show;
|
||||||
|
// 样式
|
||||||
|
article_name.value = 'font-size:' + new_style.name_size + 'px;' + 'font-weight:' + new_style.name_weight + ';' + 'color:' + new_style.name_color + ';';
|
||||||
|
article_date.value = 'font-size:' + new_style.time_size + 'px;' + 'font-weight:' + new_style.time_weight + ';' + 'color:' + new_style.time_color + ';';
|
||||||
|
article_page_view.value = 'font-size:' + new_style.page_view_size + 'px;' + 'font-weight:' + new_style.page_view_weight + ';' + 'color:' + new_style.page_view_color + ';';
|
||||||
|
content_radius.value = radius_computer(new_style);
|
||||||
|
if (new_style.common_style && props.isCommonStyle) {
|
||||||
|
style_container.value = common_styles_computer(new_style.common_style);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.style1 {
|
||||||
|
.item {
|
||||||
|
width: calc(50% - 0.5rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.style2 {
|
||||||
|
.item {
|
||||||
|
width: 100%;
|
||||||
|
img {
|
||||||
|
width: 11rem;
|
||||||
|
height: 8.3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.style3 {
|
||||||
|
.item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
145
src/components/model-article-list/model-article-list-content.vue
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<div class="content">
|
||||||
|
<el-form :model="form" label-width="70" class="m-h">
|
||||||
|
<card-container class="mb-8">
|
||||||
|
<div class="mb-12">展示设置</div>
|
||||||
|
<el-form-item label="选择风格">
|
||||||
|
<el-radio-group v-model="form.article_style">
|
||||||
|
<el-radio v-for="item in base_list.article_style_list" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</card-container>
|
||||||
|
<div class="content-height bg-f">
|
||||||
|
<card-container class="card-container-br">
|
||||||
|
<div class="mb-12">文章设置</div>
|
||||||
|
<el-form-item label="读取方式">
|
||||||
|
<el-radio-group v-model="form.article_check">
|
||||||
|
<el-radio v-for="item in base_list.article_list" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<template v-if="form.article_check === '0'">
|
||||||
|
<div class="nav-list">
|
||||||
|
<drag :data="form.article_list" :space-col="20" @remove="article_list_remove" @on-sort="article_list_sort">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<upload v-model="row.new_url" :limit="1" size="40" styles="2"></upload>
|
||||||
|
<el-image :src="row.url" fit="contain" class="img">
|
||||||
|
<template #error>
|
||||||
|
<div class="bg-f5 flex-row jc-c align-c radius h w">
|
||||||
|
<icon name="error-img" size="16" color="9"></icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
<div class="flex-1 flex-width">
|
||||||
|
<url-value v-model="row.link"></url-value>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</drag>
|
||||||
|
<el-button class="mtb-20 w" @click="add">+添加</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-form-item label="文章分类">
|
||||||
|
<el-select v-model="form.article_type" multiple collapse-tags placeholder="请选择文章分类">
|
||||||
|
<el-option v-for="item in base_list.article_type_list" :key="item.value" :label="item.name" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="显示数量">
|
||||||
|
<el-input v-model="form.number" type="number" placeholder="请输入显示数量" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序类型">
|
||||||
|
<el-radio-group v-model="form.sort">
|
||||||
|
<el-radio v-for="item in base_list.sort_list" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序规则">
|
||||||
|
<el-radio-group v-model="form.sort_rules">
|
||||||
|
<el-radio v-for="item in base_list.sort_rules_list" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="封面图片">
|
||||||
|
<el-switch v-model="form.is_img_show" />
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</card-container>
|
||||||
|
<card-container>
|
||||||
|
<div class="mb-12">列表设置</div>
|
||||||
|
<el-form-item label="是否显示">
|
||||||
|
<el-checkbox-group v-model="form.is_show">
|
||||||
|
<el-checkbox v-for="item in base_list.list_show_list" :key="item.value" :value="item.value">{{ item.name }}</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-form-item>
|
||||||
|
</card-container>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { get_math } from '@/utils';
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const form = reactive(props.value);
|
||||||
|
const base_list = reactive({
|
||||||
|
article_style_list: [
|
||||||
|
{ name: '单列展示', value: '0' },
|
||||||
|
{ name: '两列展示(纵向)', value: '1' },
|
||||||
|
{ name: '大图展示', value: '2' },
|
||||||
|
{ name: '左右滑动展示', value: '3' },
|
||||||
|
],
|
||||||
|
article_list: [
|
||||||
|
{ name: '选择文章', value: '0' },
|
||||||
|
{ name: '筛选文章', value: '1' },
|
||||||
|
],
|
||||||
|
article_type_list: [
|
||||||
|
{ name: '样式一', value: '0' },
|
||||||
|
{ name: '样式二', value: '1' },
|
||||||
|
{ name: '样式三', value: '2' },
|
||||||
|
],
|
||||||
|
sort_list: [
|
||||||
|
{ name: '综合', value: '0' },
|
||||||
|
{ name: '时间', value: '1' },
|
||||||
|
{ name: '浏览量', value: '2' },
|
||||||
|
],
|
||||||
|
sort_rules_list: [
|
||||||
|
{ name: '降序(desc)', value: '0' },
|
||||||
|
{ name: '升序(asc)', value: '1' },
|
||||||
|
],
|
||||||
|
list_show_list: [
|
||||||
|
{ name: '日期时间', value: '0' },
|
||||||
|
{ name: '浏览量', value: '1' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const article_list_remove = (index: number) => {
|
||||||
|
form.article_list.splice(index, 1);
|
||||||
|
};
|
||||||
|
const article_list_sort = (item: any) => {
|
||||||
|
form.article_list = item;
|
||||||
|
};
|
||||||
|
const add = () => {
|
||||||
|
form.article_list.push({
|
||||||
|
id: get_math(),
|
||||||
|
src: '',
|
||||||
|
new_url: [],
|
||||||
|
href: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
.content-height {
|
||||||
|
min-height: calc(100vh - 31.8rem);
|
||||||
|
.card-container-br {
|
||||||
|
border-bottom: 0.8rem solid #f0f2f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<div class="setting-content">
|
||||||
|
<template v-if="type == '1'">
|
||||||
|
<model-article-list-content :value="value.content"></model-article-list-content>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type == '2'">
|
||||||
|
<model-article-list-styles :value="value.style"></model-article-list-styles>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: '1',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div class="styles">
|
||||||
|
<el-form :model="form" label-width="70">
|
||||||
|
<card-container class="mb-8">
|
||||||
|
<div class="mb-12">列表样式</div>
|
||||||
|
<el-form-item label="文章名称">
|
||||||
|
<el-radio-group v-model="form.name_weight">
|
||||||
|
<el-radio v-for="item in font_weight" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="字号">
|
||||||
|
<slider v-model="form.name_size"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="名称色值">
|
||||||
|
<color-picker v-model="form.name_color"></color-picker>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="日期时间">
|
||||||
|
<el-radio-group v-model="form.time_weight">
|
||||||
|
<el-radio v-for="item in font_weight" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="字号">
|
||||||
|
<slider v-model="form.time_size"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="日期颜色">
|
||||||
|
<color-picker v-model="form.time_color"></color-picker>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="浏览量">
|
||||||
|
<el-radio-group v-model="form.page_view_weight">
|
||||||
|
<el-radio v-for="item in font_weight" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="字号">
|
||||||
|
<slider v-model="form.page_view_size"></slider>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="浏览色值">
|
||||||
|
<color-picker v-model="form.page_view_color"></color-picker>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="内容圆角">
|
||||||
|
<radius :value="form"></radius>
|
||||||
|
</el-form-item>
|
||||||
|
</card-container>
|
||||||
|
</el-form>
|
||||||
|
<common-styles :value="form.common_style" @update:value="common_style_update" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
const font_weight = reactive([
|
||||||
|
{ name: '加粗', value: '500' },
|
||||||
|
{ name: '正常', value: '400' },
|
||||||
|
]);
|
||||||
|
// 默认值
|
||||||
|
const form = ref(props.value);
|
||||||
|
const common_style_update = (value: any) => {
|
||||||
|
form.value.common_style = value;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.styles {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
41
src/components/model-article-tabs/index.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<div :style="style_container">
|
||||||
|
<tabs-view ref="tabs" :value="article_tabs"></tabs-view>
|
||||||
|
<div class="pt-10">
|
||||||
|
<model-article-list :value="article_tabs" :is-common-style="false"></model-article-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { common_styles_computer, get_math } from '@/utils';
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const style_container = ref('');
|
||||||
|
const article_tabs = ref(props.value);
|
||||||
|
watch(
|
||||||
|
article_tabs.value,
|
||||||
|
(newVal, oldValue) => {
|
||||||
|
const new_style = newVal?.style;
|
||||||
|
let new_data = newVal;
|
||||||
|
new_data.content.article_check = new_data.content.tabs_list[0].article_check;
|
||||||
|
new_data.content.article_type = new_data.content.tabs_list[0].article_type;
|
||||||
|
new_data.content.number = new_data.content.tabs_list[0].number;
|
||||||
|
new_data.content.sort = new_data.content.tabs_list[0].sort;
|
||||||
|
new_data.content.sort_rules = new_data.content.tabs_list[0].sort_rules;
|
||||||
|
new_data.content.is_img_show = new_data.content.tabs_list[0].is_img_show;
|
||||||
|
new_data.content.article_list = new_data.content.tabs_list[0].article_list;
|
||||||
|
article_tabs.value = new_data;
|
||||||
|
style_container.value += common_styles_computer(new_style.common_style);
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.video {
|
||||||
|
height: 22rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||