@xilonglab/vue-main 0.7.8
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 +86 -0
- package/dist/page/login.vue +185 -0
- package/dist/page/setting.vue +72 -0
- package/dist/style/app.less +58 -0
- package/dist/style/reset.css +32 -0
- package/package.json +15 -0
- package/packages/XlBreadcrumb.vue +85 -0
- package/packages/XlControlBar.vue +64 -0
- package/packages/XlSideBar.vue +135 -0
- package/packages/button/XlAsyncButton.vue +67 -0
- package/packages/button/XlButton.vue +25 -0
- package/packages/button/XlDeleteButton.vue +22 -0
- package/packages/button/XlEditButton.vue +22 -0
- package/packages/button/XlIconButton.vue +22 -0
- package/packages/button/XlUploadButton.vue +109 -0
- package/packages/dialog/XlDialog.vue +116 -0
- package/packages/dialog/XlEditReviewDialog.vue +81 -0
- package/packages/dialog/XlFormDialog.vue +79 -0
- package/packages/dialog/XlImagePreviewDialog.vue +40 -0
- package/packages/dialog/XlMessageDialog.vue +74 -0
- package/packages/dialog/XlReviewDialog.vue +115 -0
- package/packages/dialog/XlStateDialog.vue +21 -0
- package/packages/form/XlCascader.vue +46 -0
- package/packages/form/XlCheckbox.vue +45 -0
- package/packages/form/XlDate.vue +54 -0
- package/packages/form/XlFormCol.vue +19 -0
- package/packages/form/XlFormRow.vue +20 -0
- package/packages/form/XlImageInput.vue +127 -0
- package/packages/form/XlInput.vue +53 -0
- package/packages/form/XlMapSelect.vue +72 -0
- package/packages/form/XlNumber.vue +11 -0
- package/packages/form/XlRadio.vue +42 -0
- package/packages/form/XlRawSelect.vue +71 -0
- package/packages/form/XlRegion.vue +51 -0
- package/packages/form/XlSearchSelect.vue +85 -0
- package/packages/form/XlSelect.vue +77 -0
- package/packages/form/XlSwitch.vue +33 -0
- package/packages/form/XlTabRadio.vue +43 -0
- package/packages/form/XlTags.vue +105 -0
- package/packages/form/XlTextarea.vue +48 -0
- package/packages/form/XlTime.vue +50 -0
- package/packages/form/data/areas.json +1 -0
- package/packages/index.js +130 -0
- package/packages/main/XlAutoSaver.vue +75 -0
- package/packages/main/XlDataView.vue +212 -0
- package/packages/main/XlFormDialog2.vue +80 -0
- package/packages/main/XlLoginForm.vue +192 -0
- package/packages/main/XlNavBar.vue +89 -0
- package/packages/main/XlStatusIndicator.vue +36 -0
- package/packages/main/XlTabView.vue +81 -0
- package/packages/main/XlToolBar.vue +132 -0
- package/packages/main/XlUpdateIndicator.vue +40 -0
- package/packages/main/XlVerticalMenu.vue +72 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import XlSideBar from './XlSideBar.vue'
|
|
2
|
+
import XlControlBar from './XlControlBar.vue'
|
|
3
|
+
import XlBreadcrumb from './XlBreadcrumb.vue'
|
|
4
|
+
|
|
5
|
+
// Button Components
|
|
6
|
+
import XlAsyncButton from './button/XlAsyncButton.vue'
|
|
7
|
+
import XlButton from './button/XlButton.vue'
|
|
8
|
+
import XlDeleteButton from './button/XlDeleteButton.vue'
|
|
9
|
+
import XlEditButton from './button/XlEditButton.vue'
|
|
10
|
+
import XlIconButton from './button/XlIconButton.vue'
|
|
11
|
+
import XlUploadButton from './button/XlUploadButton.vue'
|
|
12
|
+
|
|
13
|
+
// Form Components
|
|
14
|
+
import XlCascader from './form/XlCascader.vue'
|
|
15
|
+
import XlCheckbox from './form/XlCheckbox.vue'
|
|
16
|
+
import XlDate from './form/XlDate.vue'
|
|
17
|
+
import XlFormCol from './form/XlFormCol.vue'
|
|
18
|
+
import XlFormRow from './form/XlFormRow.vue'
|
|
19
|
+
import XlImageInput from './form/XlImageInput.vue'
|
|
20
|
+
import XlInput from './form/XlInput.vue'
|
|
21
|
+
import XlMapSelect from './form/XlMapSelect.vue'
|
|
22
|
+
import XlNumber from './form/XlNumber.vue'
|
|
23
|
+
import XlRadio from './form/XlRadio.vue'
|
|
24
|
+
import XlRawSelect from './form/XlRawSelect.vue'
|
|
25
|
+
import XlRegion from './form/XlRegion.vue'
|
|
26
|
+
import XlSearchSelect from './form/XlSearchSelect.vue'
|
|
27
|
+
import XlSelect from './form/XlSelect.vue'
|
|
28
|
+
import XlSwitch from './form/XlSwitch.vue'
|
|
29
|
+
import XlTabRadio from './form/XlTabRadio.vue'
|
|
30
|
+
import XlTags from './form/XlTags.vue'
|
|
31
|
+
import XlTextarea from './form/XlTextarea.vue'
|
|
32
|
+
import XlTime from './form/XlTime.vue'
|
|
33
|
+
|
|
34
|
+
// Dialog Components
|
|
35
|
+
import XlDialog from './dialog/XlDialog.vue'
|
|
36
|
+
import XlFormDialog from './dialog/XlFormDialog.vue'
|
|
37
|
+
import XlStateDialog from './dialog/XlStateDialog.vue'
|
|
38
|
+
import XlEditReviewDialog from './dialog/XlEditReviewDialog.vue'
|
|
39
|
+
import XlImagePreviewDialog from './dialog/XlImagePreviewDialog.vue'
|
|
40
|
+
import XlMessageDialog from './dialog/XlMessageDialog.vue'
|
|
41
|
+
import XlReviewDialog from './dialog/XlReviewDialog.vue'
|
|
42
|
+
|
|
43
|
+
// Main Components
|
|
44
|
+
import XlDataView from './main/XlDataView.vue'
|
|
45
|
+
import XlFormDialog2 from './main/XlFormDialog2.vue'
|
|
46
|
+
import XlNavBar from './main/XlNavBar.vue'
|
|
47
|
+
import XlTabView from './main/XlTabView.vue'
|
|
48
|
+
import XlToolBar from './main/XlToolBar.vue'
|
|
49
|
+
import XlVerticalMenu from './main/XlVerticalMenu.vue'
|
|
50
|
+
import XlStatusIndicator from './main/XlStatusIndicator.vue'
|
|
51
|
+
import XlUpdateIndicator from './main/XlUpdateIndicator.vue'
|
|
52
|
+
import XlAutoSaver from './main/XlAutoSaver.vue'
|
|
53
|
+
import XlLoginForm from './main/XlLoginForm.vue'
|
|
54
|
+
|
|
55
|
+
const components = [
|
|
56
|
+
XlSideBar,
|
|
57
|
+
XlControlBar,
|
|
58
|
+
XlBreadcrumb,
|
|
59
|
+
// Buttons
|
|
60
|
+
XlAsyncButton,
|
|
61
|
+
XlButton,
|
|
62
|
+
XlDeleteButton,
|
|
63
|
+
XlEditButton,
|
|
64
|
+
XlIconButton,
|
|
65
|
+
XlUploadButton,
|
|
66
|
+
// Forms
|
|
67
|
+
XlCascader,
|
|
68
|
+
XlCheckbox,
|
|
69
|
+
XlDate,
|
|
70
|
+
XlFormCol,
|
|
71
|
+
XlFormRow,
|
|
72
|
+
XlImageInput,
|
|
73
|
+
XlInput,
|
|
74
|
+
XlMapSelect,
|
|
75
|
+
XlNumber,
|
|
76
|
+
XlRadio,
|
|
77
|
+
XlRawSelect,
|
|
78
|
+
XlRegion,
|
|
79
|
+
XlSearchSelect,
|
|
80
|
+
XlSelect,
|
|
81
|
+
XlSwitch,
|
|
82
|
+
XlTabRadio,
|
|
83
|
+
XlTags,
|
|
84
|
+
XlTextarea,
|
|
85
|
+
XlTime,
|
|
86
|
+
// Dialogs
|
|
87
|
+
XlDialog,
|
|
88
|
+
XlFormDialog,
|
|
89
|
+
XlStateDialog,
|
|
90
|
+
XlEditReviewDialog,
|
|
91
|
+
XlImagePreviewDialog,
|
|
92
|
+
XlMessageDialog,
|
|
93
|
+
XlReviewDialog,
|
|
94
|
+
// Main Components
|
|
95
|
+
XlDataView,
|
|
96
|
+
XlFormDialog2,
|
|
97
|
+
XlNavBar,
|
|
98
|
+
XlTabView,
|
|
99
|
+
XlToolBar,
|
|
100
|
+
XlVerticalMenu,
|
|
101
|
+
XlStatusIndicator,
|
|
102
|
+
XlUpdateIndicator,
|
|
103
|
+
XlAutoSaver,
|
|
104
|
+
XlLoginForm
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const install = (app) => {
|
|
108
|
+
components.forEach((component) => {
|
|
109
|
+
app.component(component.name, component);
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
let componentsJson = {}
|
|
114
|
+
components.forEach(component => {
|
|
115
|
+
const componentName = component.name
|
|
116
|
+
component.install = function (app) {
|
|
117
|
+
app.component(componentName, component)
|
|
118
|
+
}
|
|
119
|
+
componentsJson[componentName] = component
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if (typeof window !== 'undefined' && window.Vue) {
|
|
124
|
+
install(window.Vue)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default {
|
|
128
|
+
install,
|
|
129
|
+
...componentsJson
|
|
130
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineOptions({ name: "XlAutoSaver" })
|
|
3
|
+
|
|
4
|
+
import { reactive } from 'vue'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
api: {
|
|
9
|
+
type: Function,
|
|
10
|
+
default: async () => { }
|
|
11
|
+
},
|
|
12
|
+
disabled: {
|
|
13
|
+
type: Boolean,
|
|
14
|
+
default: false,
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const state = reactive({
|
|
19
|
+
saving: false,
|
|
20
|
+
lastUpdateTime: null
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
async function onBlur() {
|
|
24
|
+
let { api, disabled } = props;
|
|
25
|
+
if (disabled) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
state.saving = true;
|
|
29
|
+
const code = await api();
|
|
30
|
+
state.saving = false;
|
|
31
|
+
if (code == 1) {
|
|
32
|
+
state.lastUpdateTime = new Date();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function onClick() {
|
|
37
|
+
const { api, disabled } = props;
|
|
38
|
+
if (disabled) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let now = new Date();
|
|
42
|
+
let lapse = state.lastUpdateTime ? now - state.lastUpdateTime : 3500;
|
|
43
|
+
if (lapse > 3000 && !state.saving) {
|
|
44
|
+
state.saving = true;
|
|
45
|
+
try {
|
|
46
|
+
const code = await api();
|
|
47
|
+
if (code == 1) {
|
|
48
|
+
state.lastUpdateTime = new Date();
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Auto save failed:', error);
|
|
52
|
+
} finally {
|
|
53
|
+
state.saving = false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
defineExpose({
|
|
60
|
+
state
|
|
61
|
+
})
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template>
|
|
65
|
+
<div class="xl-auto-saver" @mouseleave="onBlur" @click="onClick">
|
|
66
|
+
<slot />
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<style lang="less">
|
|
71
|
+
.xl-auto-saver {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-flow: column;
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineOptions({ name: "XlDataView" })
|
|
3
|
+
|
|
4
|
+
import { inject, computed } from 'vue'
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
selectable: {
|
|
8
|
+
type: Boolean,
|
|
9
|
+
default: false,
|
|
10
|
+
},
|
|
11
|
+
columns: {
|
|
12
|
+
type: Array,
|
|
13
|
+
default: () => [],
|
|
14
|
+
},
|
|
15
|
+
showSummary: {
|
|
16
|
+
type: Boolean,
|
|
17
|
+
default: true,
|
|
18
|
+
},
|
|
19
|
+
rowClick: {
|
|
20
|
+
type: Function,
|
|
21
|
+
default: () => { },
|
|
22
|
+
},
|
|
23
|
+
summaryMethod: {
|
|
24
|
+
type: Function,
|
|
25
|
+
},
|
|
26
|
+
showIndex: {
|
|
27
|
+
type: Boolean,
|
|
28
|
+
default: false,
|
|
29
|
+
},
|
|
30
|
+
disableAdd: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: false,
|
|
33
|
+
},
|
|
34
|
+
pagination: {
|
|
35
|
+
type: Object,
|
|
36
|
+
default() {
|
|
37
|
+
return {
|
|
38
|
+
pageNum: 1,
|
|
39
|
+
pageSize: 20,
|
|
40
|
+
total: 0,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
spanMethod: {
|
|
45
|
+
type: Function,
|
|
46
|
+
default: () => { },
|
|
47
|
+
},
|
|
48
|
+
headerCellStyle: {
|
|
49
|
+
type: Function,
|
|
50
|
+
default: () => { },
|
|
51
|
+
},
|
|
52
|
+
rowClassName: {
|
|
53
|
+
type: Function,
|
|
54
|
+
default: () => { },
|
|
55
|
+
},
|
|
56
|
+
rowKey: {
|
|
57
|
+
type: Function,
|
|
58
|
+
default: () => { },
|
|
59
|
+
},
|
|
60
|
+
rowDblClick: {
|
|
61
|
+
type: Function,
|
|
62
|
+
default: () => { },
|
|
63
|
+
},
|
|
64
|
+
selectionChange: {
|
|
65
|
+
type: Function,
|
|
66
|
+
default: () => { },
|
|
67
|
+
},
|
|
68
|
+
layout: {
|
|
69
|
+
type: String,
|
|
70
|
+
default: 'prev, pager, next, jumper'
|
|
71
|
+
},
|
|
72
|
+
small: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
default: false
|
|
75
|
+
},
|
|
76
|
+
chinese: {
|
|
77
|
+
type: String,
|
|
78
|
+
default: ''
|
|
79
|
+
},
|
|
80
|
+
english: {},
|
|
81
|
+
type: {
|
|
82
|
+
type: String,
|
|
83
|
+
default: ''
|
|
84
|
+
},
|
|
85
|
+
width: {
|
|
86
|
+
default: 70
|
|
87
|
+
},
|
|
88
|
+
labelWidth: {
|
|
89
|
+
default: 90
|
|
90
|
+
},
|
|
91
|
+
callback: {
|
|
92
|
+
type: Function,
|
|
93
|
+
default: () => { },
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const { refs, api, params, obj, chartOptions, total } = inject('injections')
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
<template>
|
|
102
|
+
<div :class="['xl-data-view', english]">
|
|
103
|
+
<xl-tool-bar :type="type" :table="refs.table" :params="params" @query="() => api.stat()">
|
|
104
|
+
<template #inputs>
|
|
105
|
+
<template v-for="col in columns" :key="col.prop">
|
|
106
|
+
<template v-if="col.search">
|
|
107
|
+
<component
|
|
108
|
+
:is="`xl-${col.search.type || 'input'}`"
|
|
109
|
+
:placeholder="col.label"
|
|
110
|
+
v-model="params[col.prop]"
|
|
111
|
+
v-bind="col.search || {}"
|
|
112
|
+
/>
|
|
113
|
+
</template>
|
|
114
|
+
</template>
|
|
115
|
+
<slot name="inputs" />
|
|
116
|
+
</template>
|
|
117
|
+
<template #buttons>
|
|
118
|
+
<slot name="buttons" />
|
|
119
|
+
<el-button class="add-btn" v-if="!disableAdd" type="success" @click="() => api.onAdd()">新增</el-button>
|
|
120
|
+
</template>
|
|
121
|
+
</xl-tool-bar>
|
|
122
|
+
<xl-query-page-table v-show="params.view != 'chart'" :ref="refs.table" :api="api" :params="params"
|
|
123
|
+
v-bind="$props" :summary-method="summaryMethod?summaryMethod:()=>({0:total})" :sort-change="(data) => api.sort(data)">
|
|
124
|
+
<template v-for="col in columns" :key="col.prop">
|
|
125
|
+
<template v-if="!col.hidden">
|
|
126
|
+
<xl-datetime-col
|
|
127
|
+
v-if="col.type === 'datetime'"
|
|
128
|
+
:l="col.label"
|
|
129
|
+
:p="col.prop"
|
|
130
|
+
/>
|
|
131
|
+
<xl-map-col
|
|
132
|
+
v-else-if="col.type === 'map'"
|
|
133
|
+
:l="col.label"
|
|
134
|
+
:p="col.prop"
|
|
135
|
+
:width="col.width"
|
|
136
|
+
:map="col.map"
|
|
137
|
+
/>
|
|
138
|
+
<xl-status-col
|
|
139
|
+
v-else-if="col.type === 'status'"
|
|
140
|
+
:l="col.label"
|
|
141
|
+
:p="col.prop"
|
|
142
|
+
:map="col.map"
|
|
143
|
+
/>
|
|
144
|
+
<xl-review-col
|
|
145
|
+
v-else-if="col.type === 'review'"
|
|
146
|
+
:l="col.label"
|
|
147
|
+
:p="col.prop"
|
|
148
|
+
/>
|
|
149
|
+
<xl-clamp-col
|
|
150
|
+
v-else-if="col.type === 'clamp'"
|
|
151
|
+
:l="col.label"
|
|
152
|
+
:p="col.prop"
|
|
153
|
+
:width="col.width"
|
|
154
|
+
/>
|
|
155
|
+
<xl-bool-col
|
|
156
|
+
v-else-if="col.type === 'bool'"
|
|
157
|
+
:l="col.label"
|
|
158
|
+
:p="col.prop"
|
|
159
|
+
:width="col.width"
|
|
160
|
+
/>
|
|
161
|
+
<xl-col
|
|
162
|
+
v-else
|
|
163
|
+
:l="col.label"
|
|
164
|
+
:p="col.prop"
|
|
165
|
+
:width="col.width"
|
|
166
|
+
/>
|
|
167
|
+
</template>
|
|
168
|
+
</template>
|
|
169
|
+
<slot name="columns" />
|
|
170
|
+
</xl-query-page-table>
|
|
171
|
+
<xl-chart v-show="params.view == 'chart'" :options="chartOptions" />
|
|
172
|
+
<xl-form-dialog-2
|
|
173
|
+
:ref="refs.editDialog"
|
|
174
|
+
:title="chinese"
|
|
175
|
+
:width="width"
|
|
176
|
+
:label-width="labelWidth"
|
|
177
|
+
:data="obj"
|
|
178
|
+
:columns="columns"
|
|
179
|
+
:callback="callback"
|
|
180
|
+
>
|
|
181
|
+
<el-row>
|
|
182
|
+
<template v-for="col in columns" :key="col.prop">
|
|
183
|
+
<xl-form-col
|
|
184
|
+
v-if="col.form"
|
|
185
|
+
:span="col.form.span"
|
|
186
|
+
:l="col.label"
|
|
187
|
+
:p="col.prop"
|
|
188
|
+
>
|
|
189
|
+
<component
|
|
190
|
+
:is="`xl-${col.form.type || 'input'}`"
|
|
191
|
+
v-model="obj[col.prop]"
|
|
192
|
+
v-bind="col.form || {}"
|
|
193
|
+
/>
|
|
194
|
+
</xl-form-col>
|
|
195
|
+
</template>
|
|
196
|
+
</el-row>
|
|
197
|
+
<slot name="items" />
|
|
198
|
+
</xl-form-dialog-2>
|
|
199
|
+
<slot name="others" />
|
|
200
|
+
<xl-message-dialog :ref="refs.deleteDialog" message="确认删除?" @confirm="() => api.delete()" />
|
|
201
|
+
</div>
|
|
202
|
+
</template>
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
<style lang="less">
|
|
206
|
+
.xl-data-view {
|
|
207
|
+
div.cell {
|
|
208
|
+
padding: 0 !important;
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
</style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineOptions({ name: "XlFormDialog2" })
|
|
3
|
+
|
|
4
|
+
import { ref, computed } from 'vue'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const emits = defineEmits(['confirm', 'finish'])
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
title: {},
|
|
11
|
+
width: {},
|
|
12
|
+
data: {},
|
|
13
|
+
columns: {
|
|
14
|
+
default() {
|
|
15
|
+
return []
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
labelWidth: {
|
|
19
|
+
default: 70
|
|
20
|
+
},
|
|
21
|
+
callback: {
|
|
22
|
+
type: Function,
|
|
23
|
+
default: () => { },
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const refs = {
|
|
28
|
+
dialog: ref(null),
|
|
29
|
+
form: ref(null),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const convertColumnsToRules = (columns) => {
|
|
33
|
+
const rules = {};
|
|
34
|
+
columns.forEach(col => {
|
|
35
|
+
if (col.form?.required) {
|
|
36
|
+
rules[col.prop] = [{
|
|
37
|
+
required: true,
|
|
38
|
+
message: `请输入${col.label}`,
|
|
39
|
+
trigger: "blur"
|
|
40
|
+
}];
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return rules;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const rules = computed(() => convertColumnsToRules(props.columns));
|
|
47
|
+
|
|
48
|
+
async function validate() {
|
|
49
|
+
let flag = 0
|
|
50
|
+
await refs.form.value.validate(function (valid) {
|
|
51
|
+
flag = valid
|
|
52
|
+
})
|
|
53
|
+
return flag
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
defineExpose({
|
|
57
|
+
show() {
|
|
58
|
+
refs.dialog.value.show()
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
<template>
|
|
65
|
+
<xl-dialog class="xl-edit-dialog" :ref="refs.dialog" :title="title" :width="width" :validate="validate"
|
|
66
|
+
:callback="callback" @finish="emits('finish')">
|
|
67
|
+
<el-form :ref="refs.form" :model="data" :rules="rules" :label-width="`${labelWidth}px`">
|
|
68
|
+
<slot />
|
|
69
|
+
</el-form>
|
|
70
|
+
</xl-dialog>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
<style lang="less">
|
|
75
|
+
.xl-edit-dialog {
|
|
76
|
+
.xl-form-item {
|
|
77
|
+
width: 100% !important;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
</style>
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineOptions({ name: "XlLoginForm" })
|
|
3
|
+
|
|
4
|
+
import { ref, reactive, computed, onMounted } from 'vue'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
scope: {
|
|
9
|
+
type: Object,
|
|
10
|
+
default() {
|
|
11
|
+
return {}
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const emits = defineEmits(['login', 'keyEnter'])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const rules = {
|
|
20
|
+
name: [
|
|
21
|
+
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
|
22
|
+
],
|
|
23
|
+
password: [
|
|
24
|
+
{ required: true, message: '请输入密码', trigger: 'blur' }
|
|
25
|
+
],
|
|
26
|
+
captcha: [
|
|
27
|
+
{ required: true, message: "请输入验证码", trigger: "blur" },
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
const loginForm = ref(null)
|
|
33
|
+
|
|
34
|
+
const form = reactive({
|
|
35
|
+
name: "",
|
|
36
|
+
password: "",
|
|
37
|
+
captcha: "",
|
|
38
|
+
uuid: "",
|
|
39
|
+
})
|
|
40
|
+
const nameError = ref('')
|
|
41
|
+
const passwordError = ref('')
|
|
42
|
+
const captchaError = ref('')
|
|
43
|
+
const captchaUrl = computed(() => {
|
|
44
|
+
return `/api/system/captcha?uuid=${form.uuid}`
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
function getUUID() {
|
|
48
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
49
|
+
return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function validate() {
|
|
54
|
+
let flag = true
|
|
55
|
+
await loginForm.value.validate(valid => {
|
|
56
|
+
flag = valid
|
|
57
|
+
})
|
|
58
|
+
return flag
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const Captcha = {
|
|
62
|
+
refresh() {
|
|
63
|
+
form.uuid = getUUID();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const Cookie = {
|
|
68
|
+
get() {
|
|
69
|
+
if (document.cookie.length > 0) {
|
|
70
|
+
let arr = document.cookie.split("; ");
|
|
71
|
+
for (let i = 0; i < arr.length; i++) {
|
|
72
|
+
let arr2 = arr[i].split("=");
|
|
73
|
+
if (arr2[0] == "name") {
|
|
74
|
+
form.name = arr2[1];
|
|
75
|
+
} else if (arr2[0] == "password") {
|
|
76
|
+
form.password = arr2[1];
|
|
77
|
+
props.scope.isRemember = true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
delete(cookiename) {
|
|
83
|
+
let d = new Date();
|
|
84
|
+
d.setDate(d.getDate() - 1);
|
|
85
|
+
let expires = ";expires=" + d;
|
|
86
|
+
let name = cookiename;
|
|
87
|
+
let value = "";
|
|
88
|
+
document.cookie = name + "=" + value + expires + "; ";
|
|
89
|
+
},
|
|
90
|
+
set() {
|
|
91
|
+
let { name, password } = form;
|
|
92
|
+
if (props.scope.isRemember) {
|
|
93
|
+
document.cookie = `name=${name}`;
|
|
94
|
+
document.cookie = `password=${password}`;
|
|
95
|
+
} else {
|
|
96
|
+
Cookie.delete("password");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
const Error = {
|
|
103
|
+
reset() {
|
|
104
|
+
nameError.value = "";
|
|
105
|
+
passwordError.value = "";
|
|
106
|
+
captchaError.value = "";
|
|
107
|
+
},
|
|
108
|
+
user() {
|
|
109
|
+
this.reset()
|
|
110
|
+
nameError.value = "用户不存在";
|
|
111
|
+
},
|
|
112
|
+
password() {
|
|
113
|
+
this.reset()
|
|
114
|
+
passwordError.value = "密码错误";
|
|
115
|
+
},
|
|
116
|
+
captcha() {
|
|
117
|
+
this.reset()
|
|
118
|
+
captchaError.value = "验证码错误";
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
onMounted(() => {
|
|
123
|
+
Cookie.get()
|
|
124
|
+
Captcha.refresh()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
defineExpose({ form, Captcha, Cookie, Error, validate })
|
|
128
|
+
</script>
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
<template>
|
|
132
|
+
<el-form class="xl-login-form" ref="loginForm" :model="form" :rules="rules" @keyup.enter="emits('keyEnter')">
|
|
133
|
+
<el-form-item prop="name" :error="nameError">
|
|
134
|
+
<el-input v-model="form.name" placeholder="用户名">
|
|
135
|
+
<template #prepend>
|
|
136
|
+
<el-icon>
|
|
137
|
+
<User />
|
|
138
|
+
</el-icon>
|
|
139
|
+
</template>
|
|
140
|
+
</el-input>
|
|
141
|
+
</el-form-item>
|
|
142
|
+
<el-form-item prop="password" :error="passwordError">
|
|
143
|
+
<el-input type="password" v-model="form.password" placeholder="密码">
|
|
144
|
+
<template #prepend>
|
|
145
|
+
<el-icon>
|
|
146
|
+
<Lock />
|
|
147
|
+
</el-icon>
|
|
148
|
+
</template>
|
|
149
|
+
</el-input>
|
|
150
|
+
</el-form-item>
|
|
151
|
+
<el-form-item class="captcha" prop="captcha" :error="captchaError">
|
|
152
|
+
<el-input v-model="form.captcha" type="number" size="large" placeholder="验证码">
|
|
153
|
+
<template #prepend>
|
|
154
|
+
<el-icon>
|
|
155
|
+
<CircleCheck />
|
|
156
|
+
</el-icon>
|
|
157
|
+
</template>
|
|
158
|
+
</el-input>
|
|
159
|
+
<img :src="captchaUrl" @click="Captcha.refresh" alt="验证码" />
|
|
160
|
+
</el-form-item>
|
|
161
|
+
</el-form>
|
|
162
|
+
</template>
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
<style lang="less">
|
|
166
|
+
.xl-login-form {
|
|
167
|
+
.el-input{
|
|
168
|
+
height:40px;
|
|
169
|
+
}
|
|
170
|
+
.captcha {
|
|
171
|
+
width: 100%;
|
|
172
|
+
|
|
173
|
+
.el-form-item__content {
|
|
174
|
+
width: 100%;
|
|
175
|
+
display: flex;
|
|
176
|
+
|
|
177
|
+
.el-input {
|
|
178
|
+
flex: 3;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
img {
|
|
182
|
+
flex: 2;
|
|
183
|
+
border: 1px solid rgb(220, 220, 220);
|
|
184
|
+
width: 152px;
|
|
185
|
+
height: 40px;
|
|
186
|
+
margin-left: 10px;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
</style>
|