@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.
Files changed (53) hide show
  1. package/dist/page/app.vue +86 -0
  2. package/dist/page/login.vue +185 -0
  3. package/dist/page/setting.vue +72 -0
  4. package/dist/style/app.less +58 -0
  5. package/dist/style/reset.css +32 -0
  6. package/package.json +15 -0
  7. package/packages/XlBreadcrumb.vue +85 -0
  8. package/packages/XlControlBar.vue +64 -0
  9. package/packages/XlSideBar.vue +135 -0
  10. package/packages/button/XlAsyncButton.vue +67 -0
  11. package/packages/button/XlButton.vue +25 -0
  12. package/packages/button/XlDeleteButton.vue +22 -0
  13. package/packages/button/XlEditButton.vue +22 -0
  14. package/packages/button/XlIconButton.vue +22 -0
  15. package/packages/button/XlUploadButton.vue +109 -0
  16. package/packages/dialog/XlDialog.vue +116 -0
  17. package/packages/dialog/XlEditReviewDialog.vue +81 -0
  18. package/packages/dialog/XlFormDialog.vue +79 -0
  19. package/packages/dialog/XlImagePreviewDialog.vue +40 -0
  20. package/packages/dialog/XlMessageDialog.vue +74 -0
  21. package/packages/dialog/XlReviewDialog.vue +115 -0
  22. package/packages/dialog/XlStateDialog.vue +21 -0
  23. package/packages/form/XlCascader.vue +46 -0
  24. package/packages/form/XlCheckbox.vue +45 -0
  25. package/packages/form/XlDate.vue +54 -0
  26. package/packages/form/XlFormCol.vue +19 -0
  27. package/packages/form/XlFormRow.vue +20 -0
  28. package/packages/form/XlImageInput.vue +127 -0
  29. package/packages/form/XlInput.vue +53 -0
  30. package/packages/form/XlMapSelect.vue +72 -0
  31. package/packages/form/XlNumber.vue +11 -0
  32. package/packages/form/XlRadio.vue +42 -0
  33. package/packages/form/XlRawSelect.vue +71 -0
  34. package/packages/form/XlRegion.vue +51 -0
  35. package/packages/form/XlSearchSelect.vue +85 -0
  36. package/packages/form/XlSelect.vue +77 -0
  37. package/packages/form/XlSwitch.vue +33 -0
  38. package/packages/form/XlTabRadio.vue +43 -0
  39. package/packages/form/XlTags.vue +105 -0
  40. package/packages/form/XlTextarea.vue +48 -0
  41. package/packages/form/XlTime.vue +50 -0
  42. package/packages/form/data/areas.json +1 -0
  43. package/packages/index.js +130 -0
  44. package/packages/main/XlAutoSaver.vue +75 -0
  45. package/packages/main/XlDataView.vue +212 -0
  46. package/packages/main/XlFormDialog2.vue +80 -0
  47. package/packages/main/XlLoginForm.vue +192 -0
  48. package/packages/main/XlNavBar.vue +89 -0
  49. package/packages/main/XlStatusIndicator.vue +36 -0
  50. package/packages/main/XlTabView.vue +81 -0
  51. package/packages/main/XlToolBar.vue +132 -0
  52. package/packages/main/XlUpdateIndicator.vue +40 -0
  53. package/packages/main/XlVerticalMenu.vue +72 -0
@@ -0,0 +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>
@@ -0,0 +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("/dashboard")
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>
@@ -0,0 +1,72 @@
1
+ <script setup>
2
+ import { User } from '#/people'
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 rules = {
11
+ oldPassword: [{ required: true, message: "请输入原密码", trigger: "blur" }],
12
+ newPassword: [{ required: true, message: "请输入新密码", trigger: "blur" }],
13
+ repeatPassword: [{ required: true, message: "请再确认密码", trigger: "blur" }],
14
+ }
15
+
16
+ const confirm = async () => {
17
+ const isValid = await refs.form.value.validate()
18
+ if (!isValid) return
19
+
20
+ if (data.newPassword !== data.repeatPassword) {
21
+ error.repeatPassword = '两次输入不一致'
22
+ } else {
23
+ const { oldPassword, newPassword } = data
24
+ const rsp = await User.Password.resource.put({
25
+ userId: store.state.userData.id,
26
+ oldPassword,
27
+ newPassword
28
+ })
29
+ if (rsp?.code === 500 && rsp.msg === '原密码错误') {
30
+ error.oldPassword = '原密码错误'
31
+ } else {
32
+ Object.keys(data).forEach(key => data[key] = '')
33
+ }
34
+ }
35
+ }
36
+ </script>
37
+
38
+ <template>
39
+ <div class="setting">
40
+ <div class="wrapper">
41
+ <el-form class="form" :ref="refs.form" :model="data" :rules="rules">
42
+ <el-form-item label="原密码" prop="oldPassword" :error="error.oldPassword">
43
+ <xl-input type="password" v-model="data.oldPassword" placeholder="" style="width:100%"/>
44
+ </el-form-item>
45
+ <el-form-item label="新密码" prop="newPassword" :error="error.newPassword">
46
+ <xl-input type="password" v-model="data.newPassword" placeholder="" style="width:100%"/>
47
+ </el-form-item>
48
+ <el-form-item label="请确认" prop="repeatPassword" :error="error.repeatPassword">
49
+ <xl-input type="password" v-model="data.repeatPassword" placeholder="" style="width:100%"/>
50
+ </el-form-item>
51
+ <xl-button type="primary" @click="confirm">确定</xl-button>
52
+ </el-form>
53
+ </div>
54
+ </div>
55
+ </template>
56
+
57
+ <style lang="less">
58
+ .setting {
59
+ background: #fff;
60
+ .wrapper {
61
+ height: 100%;
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ .form {
66
+ width: 360px;
67
+ height: 300px;
68
+ text-align: center;
69
+ }
70
+ }
71
+ }
72
+ </style>
@@ -0,0 +1,58 @@
1
+ body {
2
+ background: radial-gradient(circle at 10% 20%, rgb(0, 93, 133) 0%, rgb(0, 181, 149) 90%) !important;
3
+ }
4
+
5
+ html,
6
+ body,
7
+ #app,
8
+ .user-view,
9
+ .system-view {
10
+ height: 100%;
11
+ width: 100%;
12
+ margin: 0;
13
+ padding: 0;
14
+ max-width: none;
15
+ text-align: left;
16
+ color: #000;
17
+ }
18
+
19
+ .user-view {
20
+ display: flex;
21
+ flex-flow: row;
22
+
23
+ .xl-side-bar {
24
+ width: 150px;
25
+
26
+ .logo-wrapper {
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ padding-top: 5px;
31
+ border-bottom: 1px solid #1c3b64;
32
+
33
+ .logo {
34
+ opacity: 0.9;
35
+ }
36
+ }
37
+ }
38
+
39
+ .body {
40
+ flex: 1;
41
+ height: 100%;
42
+ width: 100%;
43
+ margin-right:5px;
44
+ display: flex;
45
+ flex-flow: column;
46
+ overflow-x: scroll;
47
+
48
+ .content {
49
+ flex: 1;
50
+ display: flex;
51
+ flex-flow: column;
52
+ min-height: 0;
53
+ border-radius: 5px;
54
+ overflow: hidden;
55
+ }
56
+
57
+ }
58
+ }
@@ -0,0 +1,32 @@
1
+ :root {
2
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color-scheme: light dark;
7
+ color: rgba(255, 255, 255, 0.87);
8
+ background-color: #242424;
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ -webkit-text-size-adjust: 100%;
15
+ }
16
+
17
+ input[type=number]::-webkit-inner-spin-button,
18
+ input[type=number]::-webkit-outer-spin-button {
19
+ -webkit-appearance: none;
20
+ appearance: none;
21
+ margin: 0;
22
+ }
23
+
24
+ ::-webkit-scrollbar {
25
+ width: 10px;
26
+ height: 10px;
27
+ }
28
+
29
+ ::-webkit-scrollbar-thumb {
30
+ background-color: #e7ebf5;
31
+ border-radius: 10px;
32
+ }
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@xilonglab/vue-main",
3
+ "version": "0.7.8",
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
+ "element-plus": "2.3.6",
13
+ "image-conversion": "^2.1.1"
14
+ }
15
+ }
@@ -0,0 +1,85 @@
1
+ <script setup>
2
+ defineOptions({ name: "XlBreadcrumb" })
3
+
4
+ import { ref, watch, onMounted } from 'vue';
5
+ import { useRoute, useRouter } from 'vue-router';
6
+
7
+ const route = useRoute();
8
+ const router = useRouter();
9
+ const breadcrumbData = ref([]);
10
+
11
+ // 更新面包屑数�?
12
+ const updateBreadcrumb = () => {
13
+ // 过滤有name和title属性的有效路由
14
+ breadcrumbData.value = route.matched
15
+ .filter(item => item.name && item.meta?.title)
16
+ .map(item => ({
17
+ title: item.meta.title,
18
+ path: item.path,
19
+ isClickable: item.path !== route.path // 当前页面不可点击
20
+ }));
21
+ };
22
+
23
+ // 处理面包屑点�?
24
+ const handleClick = (path) => {
25
+ if (path === route.path) return; // 当前页面不跳�?
26
+
27
+ // 查找对应的路由配�?
28
+ const findRoute = (routes, targetPath) => {
29
+ for (const r of routes) {
30
+ if (r.path === targetPath) return r;
31
+ if (r.children) {
32
+ const found = findRoute(r.children, targetPath);
33
+ if (found) return found;
34
+ }
35
+ }
36
+ return null;
37
+ };
38
+
39
+ const targetRoute = findRoute(router.options.routes, path);
40
+
41
+ // 如果有子路由,跳转到第一个子路由
42
+ if (targetRoute?.children?.length) {
43
+ const firstChild = targetRoute.children[0];
44
+ const childPath = path + '/' + firstChild.path;
45
+ router.push(childPath);
46
+ } else {
47
+ router.push(path);
48
+ }
49
+ };
50
+
51
+ // 监听路由变化
52
+ watch(() => route.path, updateBreadcrumb, { immediate: true });
53
+
54
+ onMounted(updateBreadcrumb);
55
+ </script>
56
+
57
+ <template>
58
+ <el-breadcrumb v-if="breadcrumbData.length" class="xl-breadcrumb" separator="/">
59
+ <el-breadcrumb-item
60
+ v-for="item in breadcrumbData"
61
+ :key="item.path"
62
+ :class="{ 'current-page': !item.isClickable }"
63
+ @click="handleClick(item.path)"
64
+ >
65
+ {{ item.title }}
66
+ </el-breadcrumb-item>
67
+ </el-breadcrumb>
68
+ </template>
69
+
70
+ <style scoped>
71
+ .xl-breadcrumb {
72
+ padding: 8px 0;
73
+ font-size: 14px;
74
+ user-select: none;
75
+ }
76
+
77
+ .el-breadcrumb__item {
78
+ cursor: pointer;
79
+ }
80
+
81
+ .el-breadcrumb__item.current-page {
82
+ cursor: default;
83
+ opacity: 0.8;
84
+ }
85
+ </style>
@@ -0,0 +1,64 @@
1
+ <script setup>
2
+ defineOptions({ name: "XlControlBar" })
3
+
4
+ const props = defineProps({
5
+ leftFlex: {
6
+ type: Number,
7
+ default: 1,
8
+ },
9
+ rightFlex: {
10
+ type: Number,
11
+ default: 3,
12
+ },
13
+ })
14
+ </script>
15
+
16
+
17
+ <template>
18
+ <div class="xl-control-bar">
19
+ <div class="wrapper1" :style="`flex: ${props.leftFlex};`">
20
+ <div class="left">
21
+ <slot name="left" />
22
+ </div>
23
+ </div>
24
+ <div class="wrapper2" :style="`flex: ${props.rightFlex};`">
25
+ <div class="right">
26
+ <slot name="right" />
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+
33
+ <style lang="less">
34
+ .xl-control-bar {
35
+ display: flex;
36
+ position: absolute;
37
+ top: 3px;
38
+ right: 0;
39
+ width: 100%;
40
+ z-index: 1;
41
+ background: #fff;
42
+
43
+ .left {
44
+ display: flex;
45
+ align-items: center;
46
+ }
47
+
48
+ .right {
49
+ float: right;
50
+ display: flex;
51
+ align-items: center;
52
+ padding-right: 40px;
53
+
54
+ .xl-radio-button {
55
+ margin-right: 7px;
56
+ }
57
+
58
+ .xl-button,
59
+ .ant-btn-group {
60
+ margin-left: 3px;
61
+ }
62
+ }
63
+ }
64
+ </style>