@xilonglab/vue-main 1.6.35 → 1.6.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/page/app.vue CHANGED
@@ -1,86 +1,86 @@
1
- <script setup>
2
- const props = defineProps({
3
- condition: {
4
- type: Boolean,
5
- default: false
6
- }
7
- })
8
- </script>
9
-
10
- <template>
11
- <div class="user-view" v-if="condition">
12
- <slot name="sidebar" />
13
- <div class="body">
14
- <slot name="topbar" />
15
- <router-view class="content" v-slot="{ Component }">
16
- <keep-alive>
17
- <component :is="Component" />
18
- </keep-alive>
19
- </router-view>
20
- </div>
21
- </div>
22
- <div class="system-view" v-else>
23
- <router-view />
24
- </div>
25
- </template>
26
-
27
- <style lang="less">
28
- body {
29
- background: radial-gradient(circle at 10% 20%, rgb(0, 93, 133) 0%, rgb(0, 181, 149) 90%) !important;
30
- }
31
-
32
- html,
33
- body,
34
- #app,
35
- .user-view,
36
- .system-view {
37
- height: 100%;
38
- width: 100%;
39
- margin: 0;
40
- padding: 0;
41
- max-width: none;
42
- text-align: left;
43
- color: #000;
44
- }
45
-
46
- .user-view {
47
- display: flex;
48
- flex-flow: row;
49
-
50
- .xl-side-bar {
51
- width: 150px;
52
-
53
- .logo-wrapper {
54
- display: flex;
55
- align-items: center;
56
- justify-content: center;
57
- padding-top: 5px;
58
- border-bottom: 1px solid #1c3b64;
59
-
60
- .logo {
61
- opacity: 0.9;
62
- }
63
- }
64
- }
65
-
66
- .body {
67
- flex: 1;
68
- height: 100%;
69
- width: 100%;
70
- margin-right:5px;
71
- display: flex;
72
- flex-flow: column;
73
- overflow-x: scroll;
74
-
75
- .content {
76
- flex: 1;
77
- display: flex;
78
- flex-flow: column;
79
- min-height: 0;
80
- border-radius: 5px;
81
- overflow: hidden;
82
- }
83
-
84
- }
85
- }
86
- </style>
1
+ <script setup>
2
+ const props = defineProps({
3
+ condition: {
4
+ type: Boolean,
5
+ default: false
6
+ }
7
+ })
8
+ </script>
9
+
10
+ <template>
11
+ <div class="user-view" v-if="condition">
12
+ <slot name="sidebar" />
13
+ <div class="body">
14
+ <slot name="topbar" />
15
+ <router-view class="content" v-slot="{ Component }">
16
+ <keep-alive>
17
+ <component :is="Component" />
18
+ </keep-alive>
19
+ </router-view>
20
+ </div>
21
+ </div>
22
+ <div class="system-view" v-else>
23
+ <router-view />
24
+ </div>
25
+ </template>
26
+
27
+ <style lang="less">
28
+ body {
29
+ background: radial-gradient(circle at 10% 20%, rgb(0, 93, 133) 0%, rgb(0, 181, 149) 90%) !important;
30
+ }
31
+
32
+ html,
33
+ body,
34
+ #app,
35
+ .user-view,
36
+ .system-view {
37
+ height: 100%;
38
+ width: 100%;
39
+ margin: 0;
40
+ padding: 0;
41
+ max-width: none;
42
+ text-align: left;
43
+ color: #000;
44
+ }
45
+
46
+ .user-view {
47
+ display: flex;
48
+ flex-flow: row;
49
+
50
+ .xl-side-bar {
51
+ width: 150px;
52
+
53
+ .logo-wrapper {
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ padding-top: 5px;
58
+ border-bottom: 1px solid #1c3b64;
59
+
60
+ .logo {
61
+ opacity: 0.9;
62
+ }
63
+ }
64
+ }
65
+
66
+ .body {
67
+ flex: 1;
68
+ height: 100%;
69
+ width: 100%;
70
+ margin-right:5px;
71
+ display: flex;
72
+ flex-flow: column;
73
+ overflow-x: scroll;
74
+
75
+ .content {
76
+ flex: 1;
77
+ display: flex;
78
+ flex-flow: column;
79
+ min-height: 0;
80
+ border-radius: 5px;
81
+ overflow: hidden;
82
+ }
83
+
84
+ }
85
+ }
86
+ </style>
@@ -1,185 +1,185 @@
1
- <script setup>
2
- import { Session } from '#/system'
3
- import { useRouter } from "vue-router"
4
- import { inject, reactive, ref } from 'vue'
5
- import gsap from 'gsap'
6
-
7
-
8
- const props = defineProps({
9
- callback: {
10
- type: Function,
11
- default: () => {}
12
- }
13
- })
14
-
15
- const router = useRouter()
16
- const store = inject('store')
17
-
18
- const refs = {
19
- form: ref(null),
20
- button: ref(null),
21
- loginBox: ref(null)
22
- }
23
-
24
- const state = reactive({
25
- isRemember: false,
26
- loading: false
27
- })
28
-
29
- const handleKeyEnter = () => refs.button.value.click()
30
-
31
- const showErrorAnimation = () => {
32
- gsap.to(refs.loginBox.value, { duration: 0.1, x: -10, yoyo: true, repeat: 3 })
33
- }
34
-
35
- const showSuccessAnimation = async () => {
36
- await gsap.to(refs.loginBox.value, { duration: 0.5, scale: 0.8, opacity: 0, ease: "power2.in" })
37
- }
38
-
39
- const handleLoginSuccess = ({ token, userData }) => {
40
- store.state.accessToken = token
41
- localStorage.setItem('accessToken', token)
42
- store.state.userData = userData
43
- router.push("/instr/list")
44
- props.callback()
45
- }
46
-
47
- const handleLoginError = (data, { Captcha, Error }) => {
48
- showErrorAnimation()
49
- Captcha.refresh()
50
- const errorMap = {
51
- '用户不存在': Error.user,
52
- '密码错误': Error.password,
53
- '验证码错误': Error.captcha
54
- }
55
- errorMap[data.msg]?.()
56
- }
57
-
58
- async function login() {
59
- state.loading = true
60
- const { form, Captcha, Cookie, Error } = refs.form.value
61
-
62
- try {
63
- if (!await refs.form.value.validate()) return
64
- Cookie.set()
65
- const data = await Session.resource.post(form, '')
66
-
67
- if (data.token) {
68
- await showSuccessAnimation()
69
- handleLoginSuccess(data)
70
- } else if (data.code === 500) {
71
- handleLoginError(data, { Captcha, Error })
72
- }
73
- } finally {
74
- state.loading = false
75
- }
76
- }
77
- </script>
78
-
79
- <template>
80
- <div class="login">
81
- <div class="login-box" ref="loginBox">
82
- <div class="title">
83
- <span class="system-title">__NAME__</span>
84
- <span class="version">V__VERSION__</span>
85
- </div>
86
- <div class="wrapper">
87
- <xl-login-form :ref="refs.form" @keyEnter="handleKeyEnter" :scope="state" />
88
- <xl-async-button class="login-btn" :ref="refs.button" :api="login" :loading="state.loading" style="width: 100%">
89
- {{ state.loading ? '登录中...' : '登录' }}
90
- </xl-async-button>
91
- <div class="tips">
92
- <div class="remember">
93
- <el-checkbox v-model="state.isRemember"></el-checkbox>
94
- <span>记住密码</span>
95
- </div>
96
- </div>
97
- </div>
98
- </div>
99
- </div>
100
- </template>
101
-
102
- <style lang="less">
103
- .login {
104
- display: flex;
105
- justify-content: center;
106
- align-items: center;
107
- width: 100%;
108
- height: 100%;
109
- position: relative;
110
- font-family: "黑体";
111
-
112
- &::before {
113
- content: '';
114
- position: absolute;
115
- inset: 0;
116
- backdrop-filter: blur(5px);
117
- }
118
-
119
- .login-box {
120
- position: relative;
121
- width: 400px;
122
- background: rgba(255, 255, 255, 0.9);
123
- padding: 30px;
124
- border-radius: 15px;
125
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
126
- backdrop-filter: blur(10px);
127
- border: 1px solid rgba(255, 255, 255, 0.2);
128
- transition: transform 0.3s ease;
129
-
130
- &:hover {
131
- transform: translateY(-5px);
132
- }
133
-
134
- .title {
135
- text-align: center;
136
- padding-bottom: 20px;
137
- .system-title {
138
- font-size: 32px;
139
- font-weight: 600;
140
- background: linear-gradient(45deg, #51c7f9, #3a5df7);
141
- -webkit-background-clip: text;
142
- -webkit-text-fill-color: transparent;
143
- display: block;
144
- }
145
- .version {
146
- font-size: 14px;
147
- color: #666;
148
- opacity: 0.8;
149
- }
150
- }
151
-
152
- .login-btn {
153
- background: linear-gradient(45deg, #51c7f9, #3a5df7) !important;
154
- font-size: 18px;
155
- height: 42px;
156
- color: #fff;
157
- border-radius: 21px;
158
- border: none;
159
- transition: all 0.3s ease;
160
-
161
- &:hover {
162
- transform: translateY(-2px);
163
- box-shadow: 0 5px 15px rgba(58,93,247,0.3);
164
- }
165
- }
166
-
167
- .tips {
168
- width: 100%;
169
- padding-top: 20px;
170
- display: flex;
171
- justify-content: space-between;
172
- align-items: center;
173
-
174
- .remember {
175
- cursor: pointer;
176
- color: #666;
177
- font-size: 14px;
178
- display: flex;
179
- align-items: center;
180
- gap: 5px;
181
- }
182
- }
183
- }
184
- }
185
- </style>
1
+ <script setup>
2
+ import { Session } from '#/system'
3
+ import { useRouter } from "vue-router"
4
+ import { inject, reactive, ref } from 'vue'
5
+ import gsap from 'gsap'
6
+
7
+
8
+ const props = defineProps({
9
+ callback: {
10
+ type: Function,
11
+ default: () => {}
12
+ }
13
+ })
14
+
15
+ const router = useRouter()
16
+ const store = inject('store')
17
+
18
+ const refs = {
19
+ form: ref(null),
20
+ button: ref(null),
21
+ loginBox: ref(null)
22
+ }
23
+
24
+ const state = reactive({
25
+ isRemember: false,
26
+ loading: false
27
+ })
28
+
29
+ const handleKeyEnter = () => refs.button.value.click()
30
+
31
+ const showErrorAnimation = () => {
32
+ gsap.to(refs.loginBox.value, { duration: 0.1, x: -10, yoyo: true, repeat: 3 })
33
+ }
34
+
35
+ const showSuccessAnimation = async () => {
36
+ await gsap.to(refs.loginBox.value, { duration: 0.5, scale: 0.8, opacity: 0, ease: "power2.in" })
37
+ }
38
+
39
+ const handleLoginSuccess = ({ token, userData }) => {
40
+ store.state.accessToken = token
41
+ localStorage.setItem('accessToken', token)
42
+ store.state.userData = userData
43
+ router.push("/instr/list")
44
+ props.callback()
45
+ }
46
+
47
+ const handleLoginError = (data, { Captcha, Error }) => {
48
+ showErrorAnimation()
49
+ Captcha.refresh()
50
+ const errorMap = {
51
+ '用户不存在': Error.user,
52
+ '密码错误': Error.password,
53
+ '验证码错误': Error.captcha
54
+ }
55
+ errorMap[data.msg]?.()
56
+ }
57
+
58
+ async function login() {
59
+ state.loading = true
60
+ const { form, Captcha, Cookie, Error } = refs.form.value
61
+
62
+ try {
63
+ if (!await refs.form.value.validate()) return
64
+ Cookie.set()
65
+ const data = await Session.resource.post(form, '')
66
+
67
+ if (data.token) {
68
+ await showSuccessAnimation()
69
+ handleLoginSuccess(data)
70
+ } else if (data.code === 500) {
71
+ handleLoginError(data, { Captcha, Error })
72
+ }
73
+ } finally {
74
+ state.loading = false
75
+ }
76
+ }
77
+ </script>
78
+
79
+ <template>
80
+ <div class="login">
81
+ <div class="login-box" ref="loginBox">
82
+ <div class="title">
83
+ <span class="system-title">__NAME__</span>
84
+ <span class="version">V__VERSION__</span>
85
+ </div>
86
+ <div class="wrapper">
87
+ <xl-login-form :ref="refs.form" @keyEnter="handleKeyEnter" :scope="state" />
88
+ <xl-async-button class="login-btn" :ref="refs.button" :api="login" :loading="state.loading" style="width: 100%">
89
+ {{ state.loading ? '登录中...' : '登录' }}
90
+ </xl-async-button>
91
+ <div class="tips">
92
+ <div class="remember">
93
+ <el-checkbox v-model="state.isRemember"></el-checkbox>
94
+ <span>记住密码</span>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </template>
101
+
102
+ <style lang="less">
103
+ .login {
104
+ display: flex;
105
+ justify-content: center;
106
+ align-items: center;
107
+ width: 100%;
108
+ height: 100%;
109
+ position: relative;
110
+ font-family: "黑体";
111
+
112
+ &::before {
113
+ content: '';
114
+ position: absolute;
115
+ inset: 0;
116
+ backdrop-filter: blur(5px);
117
+ }
118
+
119
+ .login-box {
120
+ position: relative;
121
+ width: 400px;
122
+ background: rgba(255, 255, 255, 0.9);
123
+ padding: 30px;
124
+ border-radius: 15px;
125
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
126
+ backdrop-filter: blur(10px);
127
+ border: 1px solid rgba(255, 255, 255, 0.2);
128
+ transition: transform 0.3s ease;
129
+
130
+ &:hover {
131
+ transform: translateY(-5px);
132
+ }
133
+
134
+ .title {
135
+ text-align: center;
136
+ padding-bottom: 20px;
137
+ .system-title {
138
+ font-size: 32px;
139
+ font-weight: 600;
140
+ background: linear-gradient(45deg, #51c7f9, #3a5df7);
141
+ -webkit-background-clip: text;
142
+ -webkit-text-fill-color: transparent;
143
+ display: block;
144
+ }
145
+ .version {
146
+ font-size: 14px;
147
+ color: #666;
148
+ opacity: 0.8;
149
+ }
150
+ }
151
+
152
+ .login-btn {
153
+ background: linear-gradient(45deg, #51c7f9, #3a5df7) !important;
154
+ font-size: 18px;
155
+ height: 42px;
156
+ color: #fff;
157
+ border-radius: 21px;
158
+ border: none;
159
+ transition: all 0.3s ease;
160
+
161
+ &:hover {
162
+ transform: translateY(-2px);
163
+ box-shadow: 0 5px 15px rgba(58,93,247,0.3);
164
+ }
165
+ }
166
+
167
+ .tips {
168
+ width: 100%;
169
+ padding-top: 20px;
170
+ display: flex;
171
+ justify-content: space-between;
172
+ align-items: center;
173
+
174
+ .remember {
175
+ cursor: pointer;
176
+ color: #666;
177
+ font-size: 14px;
178
+ display: flex;
179
+ align-items: center;
180
+ gap: 5px;
181
+ }
182
+ }
183
+ }
184
+ }
185
+ </style>
@@ -1,87 +1,87 @@
1
- <script setup>
2
- import User from '#/user'
3
- import { ref, reactive, inject } from 'vue'
4
-
5
- const store = inject('store')
6
- const refs = { form: ref(null) }
7
- const data = reactive({ oldPassword: '', newPassword: '', repeatPassword: '' })
8
- const error = reactive({ oldPassword: '', newPassword: '', repeatPassword: '' })
9
-
10
- const validateNewPassword = (rule, value, callback) => {
11
- if (!value || value.length < 6) {
12
- callback(new Error('密码长度必须大于等于6位'))
13
- } else if (/^\d+$/.test(value)) {
14
- callback(new Error('密码不能为纯数字'))
15
- } else if (/^[a-zA-Z]+$/.test(value)) {
16
- callback(new Error('密码不能为纯字母'))
17
- } else {
18
- callback()
19
- }
20
- }
21
-
22
- const rules = {
23
- oldPassword: [{ required: true, message: "请输入原密码", trigger: "blur" }],
24
- newPassword: [
25
- { required: true, message: "请输入新密码", trigger: "blur" },
26
- { validator: validateNewPassword, trigger: "blur" }
27
- ],
28
- repeatPassword: [{ required: true, message: "请再确认密码", trigger: "blur" }],
29
- }
30
-
31
- const confirm = async () => {
32
- const isValid = await refs.form.value.validate()
33
- if (!isValid) return
34
-
35
- if (data.newPassword !== data.repeatPassword) {
36
- error.repeatPassword = '两次输入不一致'
37
- } else {
38
- const { oldPassword, newPassword } = data
39
- const rsp = await User.Password.resource.put({
40
- userId: store.state.userData.id,
41
- oldPassword,
42
- newPassword
43
- })
44
- if (rsp?.code === 500 && rsp.msg === '原密码错误') {
45
- error.oldPassword = '原密码错误'
46
- } else {
47
- Object.keys(data).forEach(key => data[key] = '')
48
- }
49
- }
50
- }
51
- </script>
52
-
53
- <template>
54
- <div class="setting">
55
- <div class="wrapper">
56
- <el-form class="form" :ref="refs.form" :model="data" :rules="rules">
57
- <el-form-item label="原密码" prop="oldPassword" :error="error.oldPassword">
58
- <xl-input type="password" v-model="data.oldPassword" placeholder="" style="width:100%"/>
59
- </el-form-item>
60
- <el-form-item label="新密码" prop="newPassword" :error="error.newPassword">
61
- <xl-input type="password" v-model="data.newPassword" placeholder="" style="width:100%"/>
62
- </el-form-item>
63
- <el-form-item label="请确认" prop="repeatPassword" :error="error.repeatPassword">
64
- <xl-input type="password" v-model="data.repeatPassword" placeholder="" style="width:100%"/>
65
- </el-form-item>
66
- <xl-button l="确定" type="primary" @click="confirm"/>
67
- </el-form>
68
- </div>
69
- </div>
70
- </template>
71
-
72
- <style lang="less">
73
- .setting {
74
- background: #fff;
75
- .wrapper {
76
- height: 100%;
77
- display: flex;
78
- align-items: center;
79
- justify-content: center;
80
- .form {
81
- width: 360px;
82
- height: 300px;
83
- text-align: center;
84
- }
85
- }
86
- }
1
+ <script setup>
2
+ import User from '#/user'
3
+ import { ref, reactive, inject } from 'vue'
4
+
5
+ const store = inject('store')
6
+ const refs = { form: ref(null) }
7
+ const data = reactive({ oldPassword: '', newPassword: '', repeatPassword: '' })
8
+ const error = reactive({ oldPassword: '', newPassword: '', repeatPassword: '' })
9
+
10
+ const validateNewPassword = (rule, value, callback) => {
11
+ if (!value || value.length < 6) {
12
+ callback(new Error('密码长度必须大于等于6位'))
13
+ } else if (/^\d+$/.test(value)) {
14
+ callback(new Error('密码不能为纯数字'))
15
+ } else if (/^[a-zA-Z]+$/.test(value)) {
16
+ callback(new Error('密码不能为纯字母'))
17
+ } else {
18
+ callback()
19
+ }
20
+ }
21
+
22
+ const rules = {
23
+ oldPassword: [{ required: true, message: "请输入原密码", trigger: "blur" }],
24
+ newPassword: [
25
+ { required: true, message: "请输入新密码", trigger: "blur" },
26
+ { validator: validateNewPassword, trigger: "blur" }
27
+ ],
28
+ repeatPassword: [{ required: true, message: "请再确认密码", trigger: "blur" }],
29
+ }
30
+
31
+ const confirm = async () => {
32
+ const isValid = await refs.form.value.validate()
33
+ if (!isValid) return
34
+
35
+ if (data.newPassword !== data.repeatPassword) {
36
+ error.repeatPassword = '两次输入不一致'
37
+ } else {
38
+ const { oldPassword, newPassword } = data
39
+ const rsp = await User.Password.resource.put({
40
+ userId: store.state.userData.id,
41
+ oldPassword,
42
+ newPassword
43
+ })
44
+ if (rsp?.code === 500 && rsp.msg === '原密码错误') {
45
+ error.oldPassword = '原密码错误'
46
+ } else {
47
+ Object.keys(data).forEach(key => data[key] = '')
48
+ }
49
+ }
50
+ }
51
+ </script>
52
+
53
+ <template>
54
+ <div class="setting">
55
+ <div class="wrapper">
56
+ <el-form class="form" :ref="refs.form" :model="data" :rules="rules">
57
+ <el-form-item label="原密码" prop="oldPassword" :error="error.oldPassword">
58
+ <xl-input type="password" v-model="data.oldPassword" placeholder="" style="width:100%"/>
59
+ </el-form-item>
60
+ <el-form-item label="新密码" prop="newPassword" :error="error.newPassword">
61
+ <xl-input type="password" v-model="data.newPassword" placeholder="" style="width:100%"/>
62
+ </el-form-item>
63
+ <el-form-item label="请确认" prop="repeatPassword" :error="error.repeatPassword">
64
+ <xl-input type="password" v-model="data.repeatPassword" placeholder="" style="width:100%"/>
65
+ </el-form-item>
66
+ <xl-button l="确定" type="primary" @click="confirm"/>
67
+ </el-form>
68
+ </div>
69
+ </div>
70
+ </template>
71
+
72
+ <style lang="less">
73
+ .setting {
74
+ background: #fff;
75
+ .wrapper {
76
+ height: 100%;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ .form {
81
+ width: 360px;
82
+ height: 300px;
83
+ text-align: center;
84
+ }
85
+ }
86
+ }
87
87
  </style>
@@ -1,5 +1,5 @@
1
- /* Element 弹窗打开时会给 body 增加右侧补偿,当前布局下会出现可见空隙 */
2
- body.el-popup-parent--hidden {
3
- width: 100% !important;
4
- padding-right: 0 !important;
1
+ /* Element 弹窗打开时会给 body 增加右侧补偿,当前布局下会出现可见空隙 */
2
+ body.el-popup-parent--hidden {
3
+ width: 100% !important;
4
+ padding-right: 0 !important;
5
5
  }
@@ -1,43 +1,43 @@
1
- @import './element.css';
2
-
3
- :root {
4
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
5
- line-height: 1.5;
6
- font-weight: 400;
7
-
8
- color-scheme: light dark;
9
- color: rgba(255, 255, 255, 0.87);
10
- background-color: #242424;
11
-
12
- font-synthesis: none;
13
- text-rendering: optimizeLegibility;
14
- -webkit-font-smoothing: antialiased;
15
- -moz-osx-font-smoothing: grayscale;
16
- -webkit-text-size-adjust: 100%;
17
- }
18
-
19
- html,
20
- body,
21
- #app {
22
- height: 100%;
23
- width: 100%;
24
- margin: 0;
25
- padding: 0;
26
- }
27
-
28
- input[type=number]::-webkit-inner-spin-button,
29
- input[type=number]::-webkit-outer-spin-button {
30
- -webkit-appearance: none;
31
- appearance: none;
32
- margin: 0;
33
- }
34
-
35
- ::-webkit-scrollbar {
36
- width: 10px;
37
- height: 10px;
38
- }
39
-
40
- ::-webkit-scrollbar-thumb {
41
- background-color: #e7ebf5;
42
- border-radius: 10px;
1
+ @import './element.css';
2
+
3
+ :root {
4
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
5
+ line-height: 1.5;
6
+ font-weight: 400;
7
+
8
+ color-scheme: light dark;
9
+ color: rgba(255, 255, 255, 0.87);
10
+ background-color: #242424;
11
+
12
+ font-synthesis: none;
13
+ text-rendering: optimizeLegibility;
14
+ -webkit-font-smoothing: antialiased;
15
+ -moz-osx-font-smoothing: grayscale;
16
+ -webkit-text-size-adjust: 100%;
17
+ }
18
+
19
+ html,
20
+ body,
21
+ #app {
22
+ height: 100%;
23
+ width: 100%;
24
+ margin: 0;
25
+ padding: 0;
26
+ }
27
+
28
+ input[type=number]::-webkit-inner-spin-button,
29
+ input[type=number]::-webkit-outer-spin-button {
30
+ -webkit-appearance: none;
31
+ appearance: none;
32
+ margin: 0;
33
+ }
34
+
35
+ ::-webkit-scrollbar {
36
+ width: 10px;
37
+ height: 10px;
38
+ }
39
+
40
+ ::-webkit-scrollbar-thumb {
41
+ background-color: #e7ebf5;
42
+ border-radius: 10px;
43
43
  }
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
- {
2
- "name": "@xilonglab/vue-main",
3
- "version": "1.6.35",
4
- "description": "xilong vue main",
5
- "main": "packages/index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
9
- "author": "xilonglab",
10
- "license": "ISC",
11
- "dependencies": {
12
- "@imengyu/vue3-context-menu": "^1.3.3",
13
- "element-plus": "2.3.6",
14
- "image-conversion": "^2.1.1",
15
- "vue-router": "^4.2.2"
16
- }
1
+ {
2
+ "name": "@xilonglab/vue-main",
3
+ "version": "1.6.37",
4
+ "description": "xilong vue main",
5
+ "main": "packages/index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "xilonglab",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "@imengyu/vue3-context-menu": "^1.3.3",
13
+ "element-plus": "2.3.6",
14
+ "image-conversion": "^2.1.1",
15
+ "vue-router": "^4.2.2"
16
+ }
17
17
  }
@@ -1,206 +1,206 @@
1
- <script setup>
2
- defineOptions({ name: "XlImagesInput" })
3
-
4
- import { Close } from '@element-plus/icons-vue'
5
- import { ref } from 'vue'
6
-
7
- const props = defineProps({
8
- modelValue: {
9
- type: Array,
10
- required: true
11
- },
12
- api: {
13
- type: Function,
14
- default: () => ({})
15
- },
16
- callback: {
17
- type: Function,
18
- default: () => ({})
19
- }
20
- })
21
-
22
- const emits = defineEmits(['update:modelValue'])
23
-
24
- const tempImage = ref(null)
25
-
26
- const handlers = {
27
- removeImage: (index) => {
28
- emits('update:modelValue', props.modelValue.filter((_, i) => i !== index))
29
- },
30
- handleImageUpload: (image) => {
31
- emits('update:modelValue', [...props.modelValue, image])
32
- tempImage.value = null
33
- }
34
- }
35
- </script>
36
-
37
-
38
- <template>
39
- <div class="xl-images-input">
40
- <div
41
- v-for="(image, index) in modelValue"
42
- :key="index"
43
- class="image-card"
44
- >
45
- <div class="image-wrapper">
46
- <el-image
47
- class="thumb-image"
48
- :src="`/storage/${image.uri}`"
49
- :preview-src-list="modelValue.map((i) => `/storage/${i.uri}`)"
50
- :initial-index="index"
51
- fit="contain"
52
- :preview-teleported="true"
53
- />
54
- <span class="delete-btn" @click.stop="handlers.removeImage(index)" title="删除">
55
- <el-icon class="delete-btn__icon" :size="16"><Close /></el-icon>
56
- </span>
57
- </div>
58
- </div>
59
- <div class="image-card upload-card">
60
- <xl-image-input
61
- v-model="tempImage"
62
- :api="api"
63
- :callback="handlers.handleImageUpload"
64
- />
65
- </div>
66
- </div>
67
- </template>
68
-
69
-
70
- <style lang="less">
71
- .xl-images-input {
72
- --xl-image-card-size: 200px;
73
- display: grid;
74
- grid-template-columns: repeat(auto-fill, minmax(var(--xl-image-card-size), 1fr));
75
- gap: 20px;
76
- align-items: start;
77
-
78
- @media (max-width: 768px) {
79
- --xl-image-card-size: 180px;
80
- grid-template-columns: repeat(auto-fill, minmax(var(--xl-image-card-size), 1fr));
81
- gap: 16px;
82
- }
83
-
84
- .delete-btn {
85
- position: absolute;
86
- top: 8px;
87
- right: 8px;
88
- width: 32px;
89
- height: 32px;
90
- background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%);
91
- color: white;
92
- border-radius: 50%;
93
- display: inline-flex;
94
- align-items: center;
95
- justify-content: center;
96
- padding: 0;
97
- cursor: pointer;
98
- box-shadow: 0 3px 10px rgba(245, 108, 108, 0.4);
99
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
100
- z-index: 10;
101
- border: 2px solid #ffffff;
102
- box-sizing: border-box;
103
-
104
- .delete-btn__icon {
105
- margin: 0;
106
- flex-shrink: 0;
107
- }
108
-
109
- &:hover {
110
- background: linear-gradient(135deg, #f78989 0%, #fa9a9a 100%);
111
- transform: scale(1.15) rotate(90deg);
112
- box-shadow: 0 4px 16px rgba(245, 108, 108, 0.6);
113
- }
114
-
115
- &:active {
116
- transform: scale(0.9) rotate(90deg);
117
- }
118
- }
119
-
120
- .image-card {
121
- position: relative;
122
- width: var(--xl-image-card-size);
123
- height: var(--xl-image-card-size);
124
- box-sizing: border-box;
125
- justify-self: start;
126
- border-radius: 10px;
127
- overflow: hidden;
128
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
129
- background-color: #ffffff;
130
- border: 1px solid #e4e7ed;
131
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
132
-
133
- &:hover {
134
- transform: translateY(-6px);
135
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
136
- border-color: #b3d8ff;
137
- }
138
-
139
- &.upload-card {
140
- border: 2px dashed #c0c4cc;
141
- background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
142
- display: flex;
143
- align-items: center;
144
- justify-content: center;
145
- transition: all 0.3s ease;
146
-
147
- &:hover {
148
- border-color: #409eff;
149
- background: linear-gradient(135deg, #f0f9ff 0%, #e1f3ff 100%);
150
- transform: translateY(-4px);
151
- box-shadow: 0 6px 20px rgba(64, 158, 255, 0.2);
152
- }
153
-
154
- :deep(.xl-image-input) {
155
- display: block;
156
- width: 100%;
157
- height: 100%;
158
- }
159
-
160
- :deep(.xl-image-input .el-upload) {
161
- width: 100%;
162
- height: 100%;
163
- min-height: 0;
164
- border: none;
165
- box-sizing: border-box;
166
- display: flex;
167
- align-items: center;
168
- justify-content: center;
169
- }
170
-
171
- :deep(.xl-image-input .el-icon.icon) {
172
- width: 100% !important;
173
- height: 100% !important;
174
- max-width: 100%;
175
- max-height: 100%;
176
- box-sizing: border-box;
177
- }
178
- }
179
- }
180
-
181
- .image-wrapper {
182
- position: relative;
183
- width: 100%;
184
- height: 100%;
185
- min-height: 0;
186
- padding: 0;
187
- box-sizing: border-box;
188
- background: #fafbfc;
189
- }
190
-
191
- .image-wrapper .thumb-image {
192
- display: block;
193
- width: 100%;
194
- height: 100%;
195
- }
196
-
197
- .image-wrapper .thumb-image :deep(.el-image__inner) {
198
- transition: transform 0.3s ease;
199
- }
200
-
201
- .image-wrapper:hover .thumb-image :deep(.el-image__inner) {
202
- transform: scale(1.02);
203
- }
204
- }
205
- </style>
206
-
1
+ <script setup>
2
+ defineOptions({ name: "XlImagesInput" })
3
+
4
+ import { Close } from '@element-plus/icons-vue'
5
+ import { ref } from 'vue'
6
+
7
+ const props = defineProps({
8
+ modelValue: {
9
+ type: Array,
10
+ required: true
11
+ },
12
+ api: {
13
+ type: Function,
14
+ default: () => ({})
15
+ },
16
+ callback: {
17
+ type: Function,
18
+ default: () => ({})
19
+ }
20
+ })
21
+
22
+ const emits = defineEmits(['update:modelValue'])
23
+
24
+ const tempImage = ref(null)
25
+
26
+ const handlers = {
27
+ removeImage: (index) => {
28
+ emits('update:modelValue', props.modelValue.filter((_, i) => i !== index))
29
+ },
30
+ handleImageUpload: (image) => {
31
+ emits('update:modelValue', [...props.modelValue, image])
32
+ tempImage.value = null
33
+ }
34
+ }
35
+ </script>
36
+
37
+
38
+ <template>
39
+ <div class="xl-images-input">
40
+ <div
41
+ v-for="(image, index) in modelValue"
42
+ :key="index"
43
+ class="image-card"
44
+ >
45
+ <div class="image-wrapper">
46
+ <el-image
47
+ class="thumb-image"
48
+ :src="`/storage/${image.uri}`"
49
+ :preview-src-list="modelValue.map((i) => `/storage/${i.uri}`)"
50
+ :initial-index="index"
51
+ fit="contain"
52
+ :preview-teleported="true"
53
+ />
54
+ <span class="delete-btn" @click.stop="handlers.removeImage(index)" title="删除">
55
+ <el-icon class="delete-btn__icon" :size="16"><Close /></el-icon>
56
+ </span>
57
+ </div>
58
+ </div>
59
+ <div class="image-card upload-card">
60
+ <xl-image-input
61
+ v-model="tempImage"
62
+ :api="api"
63
+ :callback="handlers.handleImageUpload"
64
+ />
65
+ </div>
66
+ </div>
67
+ </template>
68
+
69
+
70
+ <style lang="less">
71
+ .xl-images-input {
72
+ --xl-image-card-size: 200px;
73
+ display: grid;
74
+ grid-template-columns: repeat(auto-fill, minmax(var(--xl-image-card-size), 1fr));
75
+ gap: 20px;
76
+ align-items: start;
77
+
78
+ @media (max-width: 768px) {
79
+ --xl-image-card-size: 180px;
80
+ grid-template-columns: repeat(auto-fill, minmax(var(--xl-image-card-size), 1fr));
81
+ gap: 16px;
82
+ }
83
+
84
+ .delete-btn {
85
+ position: absolute;
86
+ top: 8px;
87
+ right: 8px;
88
+ width: 32px;
89
+ height: 32px;
90
+ background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%);
91
+ color: white;
92
+ border-radius: 50%;
93
+ display: inline-flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ padding: 0;
97
+ cursor: pointer;
98
+ box-shadow: 0 3px 10px rgba(245, 108, 108, 0.4);
99
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
100
+ z-index: 10;
101
+ border: 2px solid #ffffff;
102
+ box-sizing: border-box;
103
+
104
+ .delete-btn__icon {
105
+ margin: 0;
106
+ flex-shrink: 0;
107
+ }
108
+
109
+ &:hover {
110
+ background: linear-gradient(135deg, #f78989 0%, #fa9a9a 100%);
111
+ transform: scale(1.15) rotate(90deg);
112
+ box-shadow: 0 4px 16px rgba(245, 108, 108, 0.6);
113
+ }
114
+
115
+ &:active {
116
+ transform: scale(0.9) rotate(90deg);
117
+ }
118
+ }
119
+
120
+ .image-card {
121
+ position: relative;
122
+ width: var(--xl-image-card-size);
123
+ height: var(--xl-image-card-size);
124
+ box-sizing: border-box;
125
+ justify-self: start;
126
+ border-radius: 10px;
127
+ overflow: hidden;
128
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
129
+ background-color: #ffffff;
130
+ border: 1px solid #e4e7ed;
131
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
132
+
133
+ &:hover {
134
+ transform: translateY(-6px);
135
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
136
+ border-color: #b3d8ff;
137
+ }
138
+
139
+ &.upload-card {
140
+ border: 2px dashed #c0c4cc;
141
+ background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
142
+ display: flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ transition: all 0.3s ease;
146
+
147
+ &:hover {
148
+ border-color: #409eff;
149
+ background: linear-gradient(135deg, #f0f9ff 0%, #e1f3ff 100%);
150
+ transform: translateY(-4px);
151
+ box-shadow: 0 6px 20px rgba(64, 158, 255, 0.2);
152
+ }
153
+
154
+ :deep(.xl-image-input) {
155
+ display: block;
156
+ width: 100%;
157
+ height: 100%;
158
+ }
159
+
160
+ :deep(.xl-image-input .el-upload) {
161
+ width: 100%;
162
+ height: 100%;
163
+ min-height: 0;
164
+ border: none;
165
+ box-sizing: border-box;
166
+ display: flex;
167
+ align-items: center;
168
+ justify-content: center;
169
+ }
170
+
171
+ :deep(.xl-image-input .el-icon.icon) {
172
+ width: 100% !important;
173
+ height: 100% !important;
174
+ max-width: 100%;
175
+ max-height: 100%;
176
+ box-sizing: border-box;
177
+ }
178
+ }
179
+ }
180
+
181
+ .image-wrapper {
182
+ position: relative;
183
+ width: 100%;
184
+ height: 100%;
185
+ min-height: 0;
186
+ padding: 0;
187
+ box-sizing: border-box;
188
+ background: #fafbfc;
189
+ }
190
+
191
+ .image-wrapper .thumb-image {
192
+ display: block;
193
+ width: 100%;
194
+ height: 100%;
195
+ }
196
+
197
+ .image-wrapper .thumb-image :deep(.el-image__inner) {
198
+ transition: transform 0.3s ease;
199
+ }
200
+
201
+ .image-wrapper:hover .thumb-image :deep(.el-image__inner) {
202
+ transform: scale(1.02);
203
+ }
204
+ }
205
+ </style>
206
+
@@ -106,6 +106,10 @@ const props = defineProps({
106
106
  type: Function,
107
107
  default: () => {},
108
108
  },
109
+ filterChange: {
110
+ type: Function,
111
+ default: () => {},
112
+ },
109
113
  });
110
114
 
111
115
  const { refs, api, params, obj, chartOptions, total } = inject('injections')
@@ -162,6 +166,14 @@ const { refs, api, params, obj, chartOptions, total } = inject('injections')
162
166
  :width="col.width"
163
167
  :map="col.map"
164
168
  />
169
+ <xl-dual-map-col
170
+ v-else-if="col.type === 'dual-map'"
171
+ :l="col.label"
172
+ :p="col.prop"
173
+ :width="col.width"
174
+ :map1="col.map1"
175
+ :map2="col.map2"
176
+ />
165
177
  <xl-status-col
166
178
  v-else-if="col.type === 'status'"
167
179
  :l="col.label"