@hostlink/nuxt-light 1.16.0 → 1.17.1
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/module.json +1 -1
- package/dist/runtime/components/L/System/Setting/developer.vue +10 -7
- package/dist/runtime/components/L/System/Setting/forget-password.vue +14 -9
- package/dist/runtime/components/L/System/Setting/mail.vue +1 -1
- package/dist/runtime/components/L/System/Setting/security.vue +22 -10
- package/dist/runtime/components/l-app.vue +12 -4
- package/dist/runtime/components/l-login.vue +39 -4
- package/dist/runtime/index.d.ts +13 -4
- package/dist/runtime/pages/System/mailtest.vue +10 -7
- package/dist/runtime/pages/User/_user_id/view.vue +35 -1
- package/dist/runtime/pages/User/setting/two-factor-auth.vue +35 -3
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -4,11 +4,14 @@ import { useQuasar } from 'quasar'
|
|
|
4
4
|
|
|
5
5
|
const $q = useQuasar()
|
|
6
6
|
|
|
7
|
-
const modelValue = defineModel<{
|
|
8
|
-
mode: string
|
|
9
|
-
}>()
|
|
10
7
|
|
|
8
|
+
export interface LSystemSettingDeveloperProps {
|
|
9
|
+
mode: string
|
|
10
|
+
}
|
|
11
11
|
|
|
12
|
+
const modelValue = defineModel<LSystemSettingDeveloperProps>({
|
|
13
|
+
required: true,
|
|
14
|
+
})
|
|
12
15
|
|
|
13
16
|
if (modelValue.value.mode != 'prod') {
|
|
14
17
|
modelValue.value.mode = 'dev'
|
|
@@ -16,18 +19,18 @@ if (modelValue.value.mode != 'prod') {
|
|
|
16
19
|
modelValue.value.mode = 'prod'
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
const onSubmit = async (d
|
|
20
|
-
let data = [];
|
|
22
|
+
const onSubmit = async (d: LSystemSettingDeveloperProps) => {
|
|
23
|
+
let data: { name: string, value: string }[] = [];
|
|
21
24
|
Object.keys(d).forEach((key) => {
|
|
22
25
|
data.push({
|
|
23
26
|
name: key,
|
|
24
|
-
value: d[key]
|
|
27
|
+
value: d[key as keyof LSystemSettingDeveloperProps]
|
|
25
28
|
})
|
|
26
29
|
})
|
|
27
30
|
await m("updateAppConfigs", { data })
|
|
28
31
|
//update the modelValue
|
|
29
32
|
Object.keys(d).forEach((key) => {
|
|
30
|
-
modelValue.value[key] = d[key]
|
|
33
|
+
modelValue.value[key as keyof LSystemSettingDeveloperProps] = d[key as keyof LSystemSettingDeveloperProps]
|
|
31
34
|
})
|
|
32
35
|
|
|
33
36
|
$q.notify({ message: "Settings saved", color: "positive" })
|
|
@@ -4,13 +4,17 @@ import { q, m } from '#imports'
|
|
|
4
4
|
import { useQuasar } from 'quasar'
|
|
5
5
|
const $q = useQuasar()
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
export type LSystemSettingForgetPasswordProps = {
|
|
8
8
|
forget_password_enabled: string,
|
|
9
9
|
forget_password_subject: string,
|
|
10
10
|
forget_password_template: string,
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const modelValue = defineModel<LSystemSettingForgetPasswordProps>({
|
|
14
|
+
required: true,
|
|
15
|
+
})
|
|
12
16
|
|
|
13
|
-
if (
|
|
17
|
+
if (modelValue.value.forget_password_enabled != "0") {
|
|
14
18
|
modelValue.value.forget_password_enabled = "1"
|
|
15
19
|
}
|
|
16
20
|
|
|
@@ -23,8 +27,8 @@ if (!modelValue.value.forget_password_template) {
|
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
|
|
26
|
-
const onSubmit = async (d) => {
|
|
27
|
-
let data = [];
|
|
30
|
+
const onSubmit = async (d: any) => {
|
|
31
|
+
let data: any = [];
|
|
28
32
|
Object.keys(d).forEach((key) => {
|
|
29
33
|
if (d[key] === undefined) {
|
|
30
34
|
d[key] = ""
|
|
@@ -36,10 +40,11 @@ const onSubmit = async (d) => {
|
|
|
36
40
|
})
|
|
37
41
|
})
|
|
38
42
|
await m("updateAppConfigs", { data })
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
|
|
44
|
+
modelValue.value.forget_password_enabled = d.forget_password_enabled
|
|
45
|
+
modelValue.value.forget_password_subject = d.forget_password_subject
|
|
46
|
+
modelValue.value.forget_password_template = d.forget_password_template
|
|
47
|
+
|
|
43
48
|
$q.notify({ message: "Settings saved", color: "positive" })
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -74,7 +74,7 @@ const onSubmit = async (d) => {
|
|
|
74
74
|
<FormKit type="l-input" label="SMTP Password" name="mail_password"></FormKit>
|
|
75
75
|
|
|
76
76
|
<FormKit type="l-select" label="SMTP Encryption" name="mail_encryption" :options="[
|
|
77
|
-
{ label: 'None', value: '
|
|
77
|
+
{ label: 'None', value: '' },
|
|
78
78
|
{ label: 'SSL', value: 'ssl' },
|
|
79
79
|
{ label: 'TLS', value: 'tls' },
|
|
80
80
|
]"></FormKit>
|
|
@@ -4,7 +4,7 @@ import { useQuasar } from 'quasar'
|
|
|
4
4
|
|
|
5
5
|
const $q = useQuasar()
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
export type LSystemSettingSecurityProps = {
|
|
8
8
|
password_contains_uppercase: string
|
|
9
9
|
password_contains_lowercase: string
|
|
10
10
|
password_contains_numeric: string
|
|
@@ -14,20 +14,25 @@ const modelValue = defineModel<{
|
|
|
14
14
|
auth_lockout_duration: string
|
|
15
15
|
auth_lockout_attempts: string
|
|
16
16
|
access_token_expire: string
|
|
17
|
-
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const modelValue = defineModel<LSystemSettingSecurityProps>({
|
|
20
|
+
required: true
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const onSubmit = async (d: LSystemSettingSecurityProps) => {
|
|
24
|
+
let data: { name: string, value: string }[] = []
|
|
18
25
|
|
|
19
|
-
const onSubmit = async (d, form) => {
|
|
20
|
-
let data = [];
|
|
21
26
|
Object.keys(d).forEach((key) => {
|
|
22
27
|
data.push({
|
|
23
28
|
name: key,
|
|
24
|
-
value: d[key]
|
|
29
|
+
value: d[key as keyof LSystemSettingSecurityProps]
|
|
25
30
|
})
|
|
26
31
|
})
|
|
27
32
|
await m("updateAppConfigs", { data })
|
|
28
33
|
//update the modelValue
|
|
29
34
|
Object.keys(d).forEach((key) => {
|
|
30
|
-
modelValue.value[key] = d[key]
|
|
35
|
+
modelValue.value[key as keyof LSystemSettingSecurityProps] = d[key as keyof LSystemSettingSecurityProps]
|
|
31
36
|
})
|
|
32
37
|
|
|
33
38
|
$q.notify({ message: "Settings saved", color: "positive" })
|
|
@@ -46,7 +51,7 @@ const onSubmit = async (d, form) => {
|
|
|
46
51
|
two_factor_authentication: modelValue.two_factor_authentication,
|
|
47
52
|
auth_lockout_duration: modelValue.auth_lockout_duration,
|
|
48
53
|
auth_lockout_attempts: modelValue.auth_lockout_attempts,
|
|
49
|
-
access_token_expire: modelValue.access_token_expire
|
|
54
|
+
access_token_expire: modelValue.access_token_expire,
|
|
50
55
|
|
|
51
56
|
}" @submit="onSubmit">
|
|
52
57
|
<q-field label="Password policy" stack-label :color="$light.color">
|
|
@@ -63,9 +68,8 @@ const onSubmit = async (d, form) => {
|
|
|
63
68
|
</FormKit>
|
|
64
69
|
|
|
65
70
|
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
</q-field>
|
|
71
|
+
<FormKit type="l-checkbox" name="two_factor_authentication" true-value="1" false-value="0"
|
|
72
|
+
label="Two factor authentication" />
|
|
69
73
|
|
|
70
74
|
<FormKit label="Auth lockout duration" type="l-input" name="auth_lockout_duration"
|
|
71
75
|
hint="The number of minutes the user is locked out after the maximum number of failed login attempts. Default is 15 minutes."
|
|
@@ -78,5 +82,13 @@ const onSubmit = async (d, form) => {
|
|
|
78
82
|
<FormKit label="Access token expiration" type="l-input" name="access_token_expire"
|
|
79
83
|
hint="The access token expiration time in seconds. Default is 28800 seconds (8 hours)."
|
|
80
84
|
validation="required" />
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
<!-- form-kit label="Password expiration" true-value="1" false-value="0" name="password_expiration"
|
|
88
|
+
type="l-checkbox"></form-kit>
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
<form-kit label="Password expiration duration" name="password_expiration_duration" type="l-input"
|
|
92
|
+
hint="The number of days before the password expires. Default is 90 days." validation="required"></form-kit -->
|
|
81
93
|
</FormKit>
|
|
82
94
|
</template>
|
|
@@ -18,7 +18,16 @@ watch(route, (to, from) => {
|
|
|
18
18
|
|
|
19
19
|
let app = null
|
|
20
20
|
try {
|
|
21
|
-
app = (await q({
|
|
21
|
+
app = (await q({
|
|
22
|
+
app: {
|
|
23
|
+
company: true,
|
|
24
|
+
companyLogo: true,
|
|
25
|
+
logged: true,
|
|
26
|
+
twoFactorAuthentication: true,
|
|
27
|
+
googleClientId: true,
|
|
28
|
+
forgetPasswordEnabled: true
|
|
29
|
+
}
|
|
30
|
+
})).app;
|
|
22
31
|
light.setCompany(app.company);
|
|
23
32
|
light.setCompanyLogo(app.companyLogo);
|
|
24
33
|
} catch (e) {
|
|
@@ -41,8 +50,7 @@ provide('color', color)
|
|
|
41
50
|
<q-layout v-if="!app.logged">
|
|
42
51
|
<q-page-container class="bg-grey-2" style="color:#1f1f1f">
|
|
43
52
|
<q-page padding>
|
|
44
|
-
<l-login
|
|
45
|
-
:google-client-id="app.googleClientId"></l-login>
|
|
53
|
+
<l-login v-bind="app"></l-login>
|
|
46
54
|
</q-page>
|
|
47
55
|
</q-page-container>
|
|
48
56
|
</q-layout>
|
|
@@ -53,7 +61,7 @@ provide('color', color)
|
|
|
53
61
|
<slot name="header"></slot>
|
|
54
62
|
</template>
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
|
|
57
65
|
|
|
58
66
|
|
|
59
67
|
|
|
@@ -3,14 +3,15 @@ import { useLight } from "#imports";
|
|
|
3
3
|
import { ref, reactive, onMounted } from 'vue'
|
|
4
4
|
import { useQuasar } from 'quasar';
|
|
5
5
|
import { useI18n } from 'vue-i18n';
|
|
6
|
-
import { m,
|
|
6
|
+
import { m, api } from '#imports';
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
const light = useLight();
|
|
10
10
|
|
|
11
11
|
const props = defineProps({
|
|
12
12
|
twoFactorAuthentication: Boolean,
|
|
13
|
-
googleClientId: String
|
|
13
|
+
googleClientId: String,
|
|
14
|
+
forgetPasswordEnabled: Boolean
|
|
14
15
|
})
|
|
15
16
|
|
|
16
17
|
const { t } = useI18n();
|
|
@@ -20,8 +21,28 @@ const data = reactive({
|
|
|
20
21
|
username: "", password: "", code: ""
|
|
21
22
|
});
|
|
22
23
|
|
|
24
|
+
|
|
23
25
|
const $q = useQuasar()
|
|
24
26
|
|
|
27
|
+
const loginWithCode = (username, password) => {
|
|
28
|
+
$q.dialog({
|
|
29
|
+
title: "Enter your code",
|
|
30
|
+
message: "Please enter your two factor authentication code (If you lost your authenticator, please contact your administrator)",
|
|
31
|
+
prompt: {
|
|
32
|
+
type: "text",
|
|
33
|
+
required: true
|
|
34
|
+
},
|
|
35
|
+
cancel: true,
|
|
36
|
+
persistent: true
|
|
37
|
+
}).onOk(async code => {
|
|
38
|
+
data.username = username;
|
|
39
|
+
data.password = password;
|
|
40
|
+
data.code = code;
|
|
41
|
+
submit();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
25
46
|
const submit = async () => {
|
|
26
47
|
if (await form1.value.validate()) {
|
|
27
48
|
|
|
@@ -29,7 +50,20 @@ const submit = async () => {
|
|
|
29
50
|
await api.auth.login(data.username, data.password, data.code)
|
|
30
51
|
window.self.location.reload();
|
|
31
52
|
} catch (e) {
|
|
32
|
-
|
|
53
|
+
data.code = "";
|
|
54
|
+
|
|
55
|
+
if (e.message == "two factor authentication code is required") {
|
|
56
|
+
|
|
57
|
+
loginWithCode(data.username, data.password);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
$q.notify({
|
|
62
|
+
message: e.message,
|
|
63
|
+
color: "negative",
|
|
64
|
+
icon: "sym_o_error",
|
|
65
|
+
});
|
|
66
|
+
|
|
33
67
|
|
|
34
68
|
}
|
|
35
69
|
}
|
|
@@ -192,7 +226,8 @@ onMounted(() => {
|
|
|
192
226
|
<q-card-actions>
|
|
193
227
|
<l-btn label="Login" outline rounded color="primary" icon="sym_o_login" @click="submit" />
|
|
194
228
|
<l-btn v-if="hasBioLogin" outline rounded color="primary" icon="sym_o_fingerprint" @click="bioLogin" />
|
|
195
|
-
<l-btn label="Forget password" outline rounded color="primary" icon="sym_o_lock_reset" @click="forgetPassword"
|
|
229
|
+
<l-btn label="Forget password" outline rounded color="primary" icon="sym_o_lock_reset" @click="forgetPassword"
|
|
230
|
+
v-if="forgetPasswordEnabled" />
|
|
196
231
|
</q-card-actions>
|
|
197
232
|
<q-card-actions v-if="props.googleClientId">
|
|
198
233
|
<div>
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -64,21 +64,30 @@ declare module '@formkit/inputs' {
|
|
|
64
64
|
number?: string;
|
|
65
65
|
dense?: boolean;
|
|
66
66
|
};
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
lCheckbox: {
|
|
68
|
+
type: 'l-checkbox';
|
|
69
|
+
name: string;
|
|
70
|
+
label: string;
|
|
71
|
+
dense?: boolean;
|
|
72
|
+
};
|
|
69
73
|
lDatePicker: {
|
|
70
74
|
type: 'l-date-picker';
|
|
71
75
|
name: string;
|
|
72
76
|
label: string;
|
|
73
77
|
dense?: boolean;
|
|
74
78
|
};
|
|
75
|
-
}
|
|
76
|
-
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
|
|
77
79
|
lForm: {
|
|
78
80
|
type: 'l-form';
|
|
79
81
|
gutter?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
80
82
|
bordered?: boolean;
|
|
81
83
|
};
|
|
84
|
+
lSelect: {
|
|
85
|
+
type: 'l-select';
|
|
86
|
+
name: string;
|
|
87
|
+
label: string;
|
|
88
|
+
options: Array<any>;
|
|
89
|
+
dense?: boolean;
|
|
90
|
+
};
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
export declare const useLight: (options?: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { m } from '
|
|
2
|
+
import { m } from '#imports'
|
|
3
3
|
import { useQuasar } from "quasar"
|
|
4
4
|
const quasar = useQuasar();
|
|
5
5
|
|
|
@@ -25,11 +25,14 @@ const onSubmit = async (data) => {
|
|
|
25
25
|
</script>
|
|
26
26
|
<template>
|
|
27
27
|
<l-page>
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
<form-kit type="l-form" @submit="onSubmit" :value="{
|
|
29
|
+
subject: 'Test email',
|
|
30
|
+
content: 'Hello, this is a test email'
|
|
31
|
+
}">
|
|
32
|
+
<form-kit type="l-input" label="Email" name="email" validation="required|email"></form-kit>
|
|
33
|
+
<form-kit type="l-input" label="Subject" name="subject" validation="required"></form-kit>
|
|
34
|
+
<form-kit type="l-input" input-type="textarea" label="Content" name="content" validation="required">
|
|
35
|
+
</form-kit>
|
|
36
|
+
</form-kit>
|
|
34
37
|
</l-page>
|
|
35
38
|
</template>
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { useLight, getObject } from "#imports";
|
|
2
|
+
import { useLight, getObject, m } from "#imports";
|
|
3
3
|
import { useRoute } from "vue-router"
|
|
4
4
|
import { ref } from 'vue';
|
|
5
|
+
import { useQuasar } from 'quasar';
|
|
6
|
+
|
|
7
|
+
const $q = useQuasar();
|
|
5
8
|
const route = useRoute();
|
|
6
9
|
|
|
7
10
|
const obj = await getObject(["canUpdate"]);
|
|
@@ -11,6 +14,36 @@ const light = useLight();
|
|
|
11
14
|
const tab = ref('overview');
|
|
12
15
|
const id = route.params.user_id;
|
|
13
16
|
|
|
17
|
+
const reset2fa = async () => {
|
|
18
|
+
|
|
19
|
+
//confirm
|
|
20
|
+
await $q.dialog({
|
|
21
|
+
title: "Reset 2FA",
|
|
22
|
+
message: "Are you sure you want to reset 2FA?",
|
|
23
|
+
color: "negative",
|
|
24
|
+
ok: true,
|
|
25
|
+
cancel: true
|
|
26
|
+
}).onOk(async () => {
|
|
27
|
+
try {
|
|
28
|
+
await m('reset2FA', { id: parseInt(id) });
|
|
29
|
+
$q.notify({
|
|
30
|
+
type: 'positive',
|
|
31
|
+
message: '2FA reset successfully'
|
|
32
|
+
});
|
|
33
|
+
} catch (e) {
|
|
34
|
+
$q.notify({
|
|
35
|
+
type: 'negative',
|
|
36
|
+
message: e.message
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
}
|
|
14
47
|
|
|
15
48
|
</script>
|
|
16
49
|
|
|
@@ -21,6 +54,7 @@ const id = route.params.user_id;
|
|
|
21
54
|
<l-btn to="change-password" icon="sym_o_key" permission="user.changePassword"
|
|
22
55
|
label="Change password"></l-btn>
|
|
23
56
|
<l-btn to="update-role" icon="sym_o_people" permission="user.role.add" label="Update role"></l-btn>
|
|
57
|
+
<l-btn label="Reset 2FA" icon="sym_o_key" permission="user.reset2fa" @click="reset2fa"></l-btn>
|
|
24
58
|
</template>
|
|
25
59
|
|
|
26
60
|
<q-card flat bordered>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, reactive } from "vue"
|
|
3
|
+
import { useQuasar } from "quasar"
|
|
3
4
|
import { q, m, notify } from '#imports'
|
|
5
|
+
const $q = useQuasar()
|
|
4
6
|
|
|
5
7
|
const my = await q("my", ["twoFactorEnabled"])
|
|
6
8
|
const my2FA = await m("my2FA", [])
|
|
@@ -25,6 +27,21 @@ if (my.twoFactorEnabled) {
|
|
|
25
27
|
show.value = false;
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
const onCopy = () => {
|
|
31
|
+
navigator.clipboard.writeText(my2FA.secret).then(() => {
|
|
32
|
+
$q.notify({
|
|
33
|
+
message: "Secret copied",
|
|
34
|
+
color: "green",
|
|
35
|
+
})
|
|
36
|
+
}, () => {
|
|
37
|
+
$q.notify({
|
|
38
|
+
message: "Failed to copy",
|
|
39
|
+
color: "red",
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
|
|
29
46
|
</script>
|
|
30
47
|
<template>
|
|
@@ -37,18 +54,33 @@ if (my.twoFactorEnabled) {
|
|
|
37
54
|
<p>
|
|
38
55
|
For Android user, install
|
|
39
56
|
<a type="primary" target="_blank"
|
|
40
|
-
href="https://play.google.com/store/apps/details?id=com.azure.authenticator">
|
|
57
|
+
href="https://play.google.com/store/apps/details?id=com.azure.authenticator">Microsoft
|
|
58
|
+
Authenticator</a>
|
|
59
|
+
|
|
60
|
+
or
|
|
61
|
+
<a type="primary" target="_blank"
|
|
62
|
+
href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">Google
|
|
63
|
+
Authenticator</a>
|
|
64
|
+
|
|
41
65
|
</p>
|
|
42
66
|
|
|
43
67
|
<p>
|
|
44
68
|
For iOS user, install
|
|
45
69
|
<a type="primary" target="_blank"
|
|
46
|
-
href="https://apps.apple.com/us/app/microsoft-authenticator/id983156458">
|
|
70
|
+
href="https://apps.apple.com/us/app/microsoft-authenticator/id983156458">Microsoft
|
|
71
|
+
Authenticator</a>
|
|
72
|
+
or
|
|
73
|
+
<a type="primary" target="_blank"
|
|
74
|
+
href="https://apps.apple.com/us/app/google-authenticator/id388497605">Google
|
|
75
|
+
Authenticator</a>
|
|
76
|
+
|
|
47
77
|
</p>
|
|
48
78
|
</div>
|
|
49
79
|
<q-img :src="my2FA.image" width="250px" />
|
|
50
80
|
<p>
|
|
51
|
-
Secret : {{ my2FA.secret }}
|
|
81
|
+
Secret : <strong>{{ my2FA.secret }}</strong>
|
|
82
|
+
|
|
83
|
+
<q-btn flat round dense icon="sym_o_content_copy" @click="onCopy" />
|
|
52
84
|
</p>
|
|
53
85
|
|
|
54
86
|
<l-input v-model="obj.code" label="Code"
|