@opendesign-plus-test/components 0.0.1-rc.2
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/components.cjs.js +1 -0
- package/dist/components.css +1 -0
- package/dist/components.es.js +1193 -0
- package/dist/components.umd.js +1 -0
- package/docs/design.md +27 -0
- package/docs/design_banner.md +41 -0
- package/docs/design_section.md +27 -0
- package/package.json +47 -0
- package/scripts/generate-components-index.js +81 -0
- package/src/assets/svg-icons/icon-chevron-right.svg +3 -0
- package/src/assets/svg-icons/icon-close.svg +3 -0
- package/src/assets/svg-icons/icon-delete.svg +3 -0
- package/src/assets/svg-icons/icon-header-back.svg +3 -0
- package/src/assets/svg-icons/icon-header-delete.svg +3 -0
- package/src/assets/svg-icons/icon-header-search.svg +4 -0
- package/src/assets/svg-icons/icon-moon.svg +3 -0
- package/src/assets/svg-icons/icon-sun.svg +3 -0
- package/src/components/OBanner.vue +390 -0
- package/src/components/OCookieNotice.vue +417 -0
- package/src/components/OCookieNoticeEl.vue +403 -0
- package/src/components/OHeaderSearch.vue +601 -0
- package/src/components/OPlusConfigProvider.vue +32 -0
- package/src/components/OSection.vue +178 -0
- package/src/components/OThemeSwitcher.vue +108 -0
- package/src/components/common/ClientOnlyWrapper.ts +21 -0
- package/src/components/common/ContentWrapper.vue +85 -0
- package/src/draft/Banner.vue +265 -0
- package/src/draft/ButtonCards.vue +106 -0
- package/src/draft/Feature.vue +134 -0
- package/src/draft/Footer.vue +512 -0
- package/src/draft/HorizontalAnchor.vue +165 -0
- package/src/draft/ItemSwiper.vue +133 -0
- package/src/draft/Logo.vue +141 -0
- package/src/draft/LogoCard.vue +75 -0
- package/src/draft/LogoV2.vue +19 -0
- package/src/draft/MainCard.vue +38 -0
- package/src/draft/MultiCard.vue +95 -0
- package/src/draft/MultiIconCard.vue +74 -0
- package/src/draft/OInfoCard.vue +176 -0
- package/src/draft/Process.vue +81 -0
- package/src/draft/Section.vue +167 -0
- package/src/draft/SingleTabCard.vue +85 -0
- package/src/draft/SliderCard.vue +110 -0
- package/src/env.d.ts +1 -0
- package/src/i18n/en.ts +20 -0
- package/src/i18n/index.ts +42 -0
- package/src/i18n/zh.ts +9 -0
- package/src/index.ts +34 -0
- package/src/shared/provide.ts +6 -0
- package/src/vue.d.ts +10 -0
- package/tsconfig.json +33 -0
- package/vite.config.ts +90 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { OCard, OLink } from '@opensig/opendesign';
|
|
3
|
+
import { useScreen } from '@/composables/useScreen';
|
|
4
|
+
import { ref, watch, type Component } from 'vue';
|
|
5
|
+
|
|
6
|
+
const { lePadV } = useScreen();
|
|
7
|
+
|
|
8
|
+
interface LinkT {
|
|
9
|
+
title: string;
|
|
10
|
+
target?: '_blank' | '_self';
|
|
11
|
+
href?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface BgImgT {
|
|
15
|
+
pcImg: string;
|
|
16
|
+
padImg: string;
|
|
17
|
+
mbImg: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface LinkCardItemT {
|
|
21
|
+
icon: Component;
|
|
22
|
+
title: string;
|
|
23
|
+
desc: string;
|
|
24
|
+
links: LinkT[];
|
|
25
|
+
linkLayout?: 'block' | 'inline';
|
|
26
|
+
bgImg?: BgImgT;
|
|
27
|
+
bgPosition?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const props = defineProps<{
|
|
31
|
+
item: LinkCardItemT;
|
|
32
|
+
}>();
|
|
33
|
+
|
|
34
|
+
const links = ref<LinkT[]>([]);
|
|
35
|
+
watch(
|
|
36
|
+
() => props.item,
|
|
37
|
+
() => {
|
|
38
|
+
links.value = props.item.links;
|
|
39
|
+
},
|
|
40
|
+
{ immediate: true }
|
|
41
|
+
);
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<OCard
|
|
46
|
+
:href="lePadV && links.length === 1 && links[0].href ? links[0].href : undefined"
|
|
47
|
+
class="info-card"
|
|
48
|
+
:style="[{ backgroundImage: `url(${props.item.bgImg?.pcImg})` }, props.item.bgPosition ? { backgroundPosition: props.item.bgPosition } : {}]"
|
|
49
|
+
>
|
|
50
|
+
<template #main>
|
|
51
|
+
<div class="header">
|
|
52
|
+
<div class="title">
|
|
53
|
+
<component :is="item.icon" class="title-icon"></component>
|
|
54
|
+
<span class="title-text">{{ item.title }}</span>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="desc">{{ item.desc }}</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div v-if="lePadV" class="footer" :class="item.linkLayout ? item.linkLayout : ''">
|
|
60
|
+
<OLink v-for="(link, index) in links" :key="index" hover-underline class="link-item" :href="link.href" :target="link.target" color="primary">
|
|
61
|
+
{{ link.title }}
|
|
62
|
+
</OLink>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div v-if="!lePadV && links.length > 1" class="footer" :class="item.linkLayout ? item.linkLayout : ''">
|
|
66
|
+
<OLink v-for="(link, index) in links" :key="index" hover-underline class="link-item" :href="link.href" :target="link.target" color="primary">
|
|
67
|
+
{{ link.title }}
|
|
68
|
+
</OLink>
|
|
69
|
+
</div>
|
|
70
|
+
</template>
|
|
71
|
+
</OCard>
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<style lang="scss" scoped>
|
|
75
|
+
.info-card {
|
|
76
|
+
height: 100%;
|
|
77
|
+
--bg-height: 100%;
|
|
78
|
+
background-size: 100% auto;
|
|
79
|
+
background-repeat: no-repeat;
|
|
80
|
+
position: relative;
|
|
81
|
+
@include respond-to('<=pad_v') {
|
|
82
|
+
background-size: auto var(--bg-height);
|
|
83
|
+
height: auto;
|
|
84
|
+
&.has-bg-img {
|
|
85
|
+
:deep(.o-card-main) {
|
|
86
|
+
padding-top: 48px;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
--padding-bottom: 0;
|
|
91
|
+
:deep(.o-card-main) {
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
justify-content: space-between;
|
|
95
|
+
@include respond-to('<=pad_v') {
|
|
96
|
+
padding-bottom: var(--padding-bottom);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.title {
|
|
102
|
+
@include h1;
|
|
103
|
+
color: var(--o-color-info1);
|
|
104
|
+
font-weight: 600;
|
|
105
|
+
vertical-align: vertical;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.title-icon {
|
|
109
|
+
line-height: var(--o-icon_size-l);
|
|
110
|
+
font-size: var(--o-icon_size-l);
|
|
111
|
+
margin-right: var(--o3-gap-2);
|
|
112
|
+
position: relative;
|
|
113
|
+
top: calc((var(--o-icon_size-l) - var(--line-height)) / 2);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.desc {
|
|
117
|
+
@include text1;
|
|
118
|
+
color: var(--o-color-info3);
|
|
119
|
+
margin-top: 8px;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
text-overflow: ellipsis;
|
|
122
|
+
display: -webkit-box;
|
|
123
|
+
-webkit-line-clamp: 2;
|
|
124
|
+
-webkit-box-orient: vertical;
|
|
125
|
+
line-clamp: 2;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.footer {
|
|
129
|
+
@include text1;
|
|
130
|
+
display: flex;
|
|
131
|
+
flex-wrap: wrap;
|
|
132
|
+
align-items: flex-start;
|
|
133
|
+
&.block {
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
.link-item {
|
|
136
|
+
margin-right: 0;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@include respond-to('>pad_v') {
|
|
141
|
+
:deep(.o-link-main) {
|
|
142
|
+
overflow: hidden;
|
|
143
|
+
text-overflow: ellipsis;
|
|
144
|
+
display: -webkit-box;
|
|
145
|
+
-webkit-line-clamp: 1;
|
|
146
|
+
-webkit-box-orient: vertical;
|
|
147
|
+
line-clamp: 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
margin-top: calc(-1 * var(--o3-gap-3) + var(--o3-gap-5));
|
|
151
|
+
|
|
152
|
+
.link-item {
|
|
153
|
+
margin-left: var(var(--o3-gap-7));
|
|
154
|
+
margin-top: var(--o3-gap-3);
|
|
155
|
+
}
|
|
156
|
+
:deep(.o-link) {
|
|
157
|
+
display: inline-flex;
|
|
158
|
+
flex-direction: row-reverse;
|
|
159
|
+
.o-link-prefix {
|
|
160
|
+
margin-right: 0;
|
|
161
|
+
margin-left: var(--link-gap);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@include respond-to('<=pad_v') {
|
|
167
|
+
border-top: 1px solid #edeff2;
|
|
168
|
+
margin: 8px -12 px 0 -12px;
|
|
169
|
+
padding: 0 12px;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.link-item {
|
|
174
|
+
--link-icon-size: var(--o-icon_size-m);
|
|
175
|
+
}
|
|
176
|
+
</style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import {} from '@opensig/opendesign';
|
|
4
|
+
import arrow from '@/assets/category/process/arrow.png';
|
|
5
|
+
|
|
6
|
+
interface ProcessItem {
|
|
7
|
+
title: string;
|
|
8
|
+
icon?: any;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
process: ProcessItem[];
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
const serials = computed(() => props.process.map((_, idx) => String(idx + 1).padStart(2, '0')));
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<div class="process-list">
|
|
20
|
+
<template v-for="(item, idx) in props.process" :key="idx">
|
|
21
|
+
<div class="process-item">
|
|
22
|
+
<div class="icon">
|
|
23
|
+
<component :is="item.icon" v-if="item.icon" />
|
|
24
|
+
</div>
|
|
25
|
+
<div class="serial">
|
|
26
|
+
<span class="clip-text">{{ serials[idx] }}</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="title">{{ item.title }}</div>
|
|
29
|
+
</div>
|
|
30
|
+
<img v-if="idx < props.process.length - 1" :src="arrow" alt="arrow" class="process-arrow" />
|
|
31
|
+
</template>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<style lang="scss" scoped>
|
|
36
|
+
.process-list {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: row;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: space-between;
|
|
41
|
+
width: 100%;
|
|
42
|
+
}
|
|
43
|
+
.process-item {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
align-items: center;
|
|
47
|
+
min-width: 80px;
|
|
48
|
+
flex: 1 1 0;
|
|
49
|
+
}
|
|
50
|
+
.process-arrow {
|
|
51
|
+
width: 44px;
|
|
52
|
+
height: 6px;
|
|
53
|
+
margin: 0 1rem;
|
|
54
|
+
flex-shrink: 0;
|
|
55
|
+
}
|
|
56
|
+
.icon {
|
|
57
|
+
font-size: 48px;
|
|
58
|
+
margin-bottom: 0.5rem;
|
|
59
|
+
}
|
|
60
|
+
.serial {
|
|
61
|
+
margin-bottom: 0.5rem;
|
|
62
|
+
.clip-text {
|
|
63
|
+
font-size: 48px;
|
|
64
|
+
font-weight: bold;
|
|
65
|
+
clip-path: polygon(0 0, 100% 0, 100% 54%, 0 54%);
|
|
66
|
+
-webkit-clip-path: polygon(0 0, 100% 0, 100% 54%, 0 54%);
|
|
67
|
+
color: var(--o-color-info3);
|
|
68
|
+
opacity: 0.4;
|
|
69
|
+
line-height: 1;
|
|
70
|
+
display: inline-block;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
.title {
|
|
74
|
+
@include text1;
|
|
75
|
+
font-weight: 600;
|
|
76
|
+
margin-top: 0.5rem;
|
|
77
|
+
text-align: center;
|
|
78
|
+
position: relative;
|
|
79
|
+
top: -20px;
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { isArray, OLink } from '@opensig/opendesign';
|
|
3
|
+
|
|
4
|
+
interface SectionPropsT {
|
|
5
|
+
title?: string | Array<string>;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
full?: boolean;
|
|
8
|
+
headerJustifyCenter?: boolean;
|
|
9
|
+
footer?: string;
|
|
10
|
+
footerHref?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<SectionPropsT>(), {
|
|
14
|
+
title: undefined,
|
|
15
|
+
subtitle: undefined,
|
|
16
|
+
full: false,
|
|
17
|
+
headerJustifyCenter: true,
|
|
18
|
+
footer: undefined,
|
|
19
|
+
footerHref: undefined,
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div class="app-section" :class="{ 'is-full': props.full }">
|
|
25
|
+
<div class="section-wrapper">
|
|
26
|
+
<slot name="main">
|
|
27
|
+
<!-- header -->
|
|
28
|
+
<div v-if="$slots.header || props.title || props.subtitle" class="section-header"
|
|
29
|
+
:class="{ 'is-left': !props.headerJustifyCenter }">
|
|
30
|
+
<slot name="header">
|
|
31
|
+
<template v-if="isArray(props.title)">
|
|
32
|
+
<h2 v-for="item in props.title" :key="item" class="section-title">
|
|
33
|
+
{{ item }}
|
|
34
|
+
</h2>
|
|
35
|
+
</template>
|
|
36
|
+
<h2 v-else-if="$slots.title || props.title" class="section-title">
|
|
37
|
+
<slot name="title">
|
|
38
|
+
{{ props.title }}
|
|
39
|
+
</slot>
|
|
40
|
+
</h2>
|
|
41
|
+
<p v-if="$slots.subtitle || props.subtitle" class="section-subtitle">
|
|
42
|
+
<slot name="subtitle">
|
|
43
|
+
{{ props.subtitle }}
|
|
44
|
+
</slot>
|
|
45
|
+
</p>
|
|
46
|
+
</slot>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<!-- body -->
|
|
50
|
+
<div v-if="$slots.default" class="section-body">
|
|
51
|
+
<slot></slot>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<!-- footer -->
|
|
55
|
+
<div v-if="$slots.footer || props.footer" class="section-footer">
|
|
56
|
+
<slot name="footer">
|
|
57
|
+
<OLink :href="props.footerHref" target="_blank">
|
|
58
|
+
{{ props.footer }}
|
|
59
|
+
<template #suffix>
|
|
60
|
+
<!-- <OIcon class="footer-icon"><IconChevronRight /> </OIcon> -->
|
|
61
|
+
</template>
|
|
62
|
+
</OLink>
|
|
63
|
+
</slot>
|
|
64
|
+
</div>
|
|
65
|
+
</slot>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<style lang="scss" scoped>
|
|
71
|
+
.app-section {
|
|
72
|
+
.section-wrapper {
|
|
73
|
+
max-width: 1416px;
|
|
74
|
+
margin: var(--o-gap-section) auto 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
&.is-full {
|
|
78
|
+
.section-body {
|
|
79
|
+
max-width: 100%;
|
|
80
|
+
width: 100%;
|
|
81
|
+
padding: 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
&:last-child {
|
|
86
|
+
.section-wrapper {
|
|
87
|
+
padding-bottom: var(--o-gap-section);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.section-header {
|
|
92
|
+
&.is-left {
|
|
93
|
+
|
|
94
|
+
.section-title,
|
|
95
|
+
.section-subtitle {
|
|
96
|
+
justify-content: start;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.section-title {
|
|
102
|
+
max-width: var(--layout-content-max-width);
|
|
103
|
+
padding: 0 var(--layout-content-padding);
|
|
104
|
+
margin: 0 auto;
|
|
105
|
+
|
|
106
|
+
display: flex;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
color: var(--o-color-info1);
|
|
109
|
+
text-align: center;
|
|
110
|
+
@include display3;
|
|
111
|
+
font-weight: 500;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.section-subtitle {
|
|
115
|
+
max-width: var(--layout-content-max-width);
|
|
116
|
+
padding: 0 var(--layout-content-padding);
|
|
117
|
+
margin: 0 auto;
|
|
118
|
+
|
|
119
|
+
display: flex;
|
|
120
|
+
justify-content: center;
|
|
121
|
+
margin-top: 12px;
|
|
122
|
+
color: var(--o-color-info2);
|
|
123
|
+
@include text1;
|
|
124
|
+
|
|
125
|
+
@include respond-to('pad-laptop') {
|
|
126
|
+
margin-top: 8px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@include respond-to('phone') {
|
|
130
|
+
margin-top: 12px;
|
|
131
|
+
text-align: center;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.section-body {
|
|
136
|
+
max-width: var(--layout-content-max-width);
|
|
137
|
+
padding: 0 var(--layout-content-padding);
|
|
138
|
+
margin: 0 auto;
|
|
139
|
+
|
|
140
|
+
margin-top: var(--o-gap-t2c);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.section-footer {
|
|
144
|
+
max-width: var(--layout-content-max-width);
|
|
145
|
+
padding: 0 var(--layout-content-padding);
|
|
146
|
+
margin: 0 auto;
|
|
147
|
+
|
|
148
|
+
display: flex;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
@include text1;
|
|
151
|
+
|
|
152
|
+
margin-top: 32px;
|
|
153
|
+
|
|
154
|
+
@include respond-to('<=laptop') {
|
|
155
|
+
margin-top: 16px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@include respond-to('phone') {
|
|
159
|
+
margin-top: 12px;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.footer-icon {
|
|
164
|
+
font-size: 16px;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
</style>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { type PropType } from 'vue';
|
|
3
|
+
import { OButton, OCard, OTab, OTabPane } from '@opensig/opendesign';
|
|
4
|
+
import { useScreen } from '@/composables/useScreen';
|
|
5
|
+
|
|
6
|
+
const { lePadV } = useScreen();
|
|
7
|
+
|
|
8
|
+
interface ButtonT {
|
|
9
|
+
text: string;
|
|
10
|
+
href?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface CardT {
|
|
14
|
+
title: string;
|
|
15
|
+
content: string;
|
|
16
|
+
cover?: string;
|
|
17
|
+
buttons?: ButtonT[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface TabListT {
|
|
21
|
+
title: string;
|
|
22
|
+
card: CardT;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
defineProps({
|
|
26
|
+
tabs: {
|
|
27
|
+
type: Array as PropType<TabListT[]>,
|
|
28
|
+
default: () => [],
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
</script>
|
|
32
|
+
<template>
|
|
33
|
+
<div class="wrapper">
|
|
34
|
+
<template v-if="tabs.length === 1">
|
|
35
|
+
<OCard :title="tabs[0].card.title" :detail="tabs[0].card.content" :cover="tabs[0].card.cover"
|
|
36
|
+
:layout="lePadV ? 'v' : 'hr'" :cover-ratio="lePadV ? undefined : 16 / 9" class="single-card">
|
|
37
|
+
<template #footer>
|
|
38
|
+
<div class="buttons">
|
|
39
|
+
<OButton v-for="(button, index) in tabs[0].card.buttons" :key="index" :href="button.href"
|
|
40
|
+
color="primary" round="pill" variant="outline" class="button">{{ button.text }}
|
|
41
|
+
</OButton>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
</OCard>
|
|
45
|
+
</template>
|
|
46
|
+
<template v-else>
|
|
47
|
+
<OTab :line="false" class="section-tab">
|
|
48
|
+
<OTabPane v-for="(tab, index) in tabs" :key="index" :label="tab.title" :value="tab.title"
|
|
49
|
+
class="tab-pane">
|
|
50
|
+
<OCard :title="tab.card.title" :detail="tab.card.content" :cover="tab.card.cover"
|
|
51
|
+
:layout="lePadV ? 'v' : 'hr'" :cover-ratio="lePadV ? undefined : 16 / 9" class="single-card">
|
|
52
|
+
<template #footer>
|
|
53
|
+
<div class="buttons">
|
|
54
|
+
<OButton v-for="(button, index) in tab.card.buttons" :key="index" :href="button.href"
|
|
55
|
+
color="primary" round="pill" variant="outline" class="button">{{ button.text }}
|
|
56
|
+
</OButton>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
</OCard>
|
|
60
|
+
</OTabPane>
|
|
61
|
+
</OTab>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
<style lang="scss" scoped>
|
|
67
|
+
.section-tab {
|
|
68
|
+
:deep(.o-tab-pane) {
|
|
69
|
+
padding-top: var(--o3-gap-5);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.buttons {
|
|
74
|
+
display: flex;
|
|
75
|
+
gap: var(--o3-gap-4);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.single-card {
|
|
79
|
+
@include respond-to('<=pad_v') {
|
|
80
|
+
:deep(.o-figure-img) {
|
|
81
|
+
height: 184px;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
</style>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { OCarousel, OCarouselItem, OFigure, OLink } from '@opensig/opendesign';
|
|
3
|
+
import { useScreen } from '@/composables/useScreen';
|
|
4
|
+
|
|
5
|
+
const { lePadV } = useScreen();
|
|
6
|
+
|
|
7
|
+
interface CardT {
|
|
8
|
+
title: string;
|
|
9
|
+
content: string;
|
|
10
|
+
cover: string;
|
|
11
|
+
href: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = defineProps({
|
|
15
|
+
cards: {
|
|
16
|
+
type: Array as () => CardT[],
|
|
17
|
+
default: () => [],
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<div class="wrapper">
|
|
24
|
+
<OCarousel v-if="!lePadV" class="slider-card" indicator-click :loop="true" effect="toggle">
|
|
25
|
+
<OCarouselItem v-for="(card, index) in props.cards" :key="index" class="slider-card-item">
|
|
26
|
+
<OFigure :src="card.cover" :alt="card.title" class="slider-card-cover">
|
|
27
|
+
<div class="card-content">
|
|
28
|
+
<div class="card-title">{{ card.title }}</div>
|
|
29
|
+
<div class="card-description">{{ card.content }}</div>
|
|
30
|
+
<OLink v-if="card.href" :href="card.href" class="card-link"> 了解更多 </OLink>
|
|
31
|
+
</div>
|
|
32
|
+
</OFigure>
|
|
33
|
+
</OCarouselItem>
|
|
34
|
+
</OCarousel>
|
|
35
|
+
<OCarousel v-else class="slider-card" indicator-click :loop="true" effect="toggle">
|
|
36
|
+
<OCarouselItem v-for="(card, index) in props.cards" :key="index" class="slider-card-item">
|
|
37
|
+
<OFigure :src="card.cover" :alt="card.title" class="slider-card-cover">
|
|
38
|
+
<div class="card-content">
|
|
39
|
+
<div class="card-title">{{ card.title }}</div>
|
|
40
|
+
<div class="card-description">{{ card.content }}</div>
|
|
41
|
+
<OLink v-if="card.href" :href="card.href" class="card-link"> 了解更多 </OLink>
|
|
42
|
+
</div>
|
|
43
|
+
</OFigure>
|
|
44
|
+
</OCarouselItem>
|
|
45
|
+
</OCarousel>
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<style lang="scss" scoped>
|
|
50
|
+
.slider-card {
|
|
51
|
+
--slide-width: calc(70vw - 36px);
|
|
52
|
+
--slide-gap: 16px;
|
|
53
|
+
--carousel-indicator-offset: -32px;
|
|
54
|
+
height: 360px;
|
|
55
|
+
width: 100%;
|
|
56
|
+
overflow: visible;
|
|
57
|
+
:deep(.the-slider-container) {
|
|
58
|
+
height: 100%;
|
|
59
|
+
overflow: hidden;
|
|
60
|
+
}
|
|
61
|
+
:deep(.o-carousel-indicator-bar) {
|
|
62
|
+
width: 100%;
|
|
63
|
+
&::after {
|
|
64
|
+
border-radius: 6px;
|
|
65
|
+
}
|
|
66
|
+
&::before {
|
|
67
|
+
border-radius: 6px;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
@include respond-to('pc_s') {
|
|
71
|
+
height: 300px;
|
|
72
|
+
}
|
|
73
|
+
@include respond-to('<=laptop') {
|
|
74
|
+
height: 280px;
|
|
75
|
+
}
|
|
76
|
+
@include respond-to('<=pad_v') {
|
|
77
|
+
--slide-width: calc(100vw - 64px);
|
|
78
|
+
width: calc(var(--slide-gap) + var(--slide-width));
|
|
79
|
+
height: 184px;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.slider-card-item {
|
|
84
|
+
height: 100%;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.slider-card-cover {
|
|
88
|
+
width: 100%;
|
|
89
|
+
height: 100%;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.card-content {
|
|
93
|
+
padding: 0 96px;
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
justify-content: center;
|
|
97
|
+
height: 100%;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.card-title {
|
|
101
|
+
@include h1;
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
color: var(--o-color-info1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.card-description {
|
|
107
|
+
margin-top: var(--o3-gap-3);
|
|
108
|
+
margin-bottom: var(--o3-gap-7);
|
|
109
|
+
}
|
|
110
|
+
</style>
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
package/src/i18n/en.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
search: 'Search',
|
|
3
|
+
'search.hot': 'Top search',
|
|
4
|
+
'search.placeholder': 'Please enter the content',
|
|
5
|
+
'search.expandedPlaceholder': 'Please enter the content',
|
|
6
|
+
'search.history': 'History',
|
|
7
|
+
'cookie.title': '{0} Community Respects Your Privacy',
|
|
8
|
+
'cookie.desc': 'This site uses cookies from us and our partners to improve your browsing experience and make the site work properly. By clicking "Accept All", you consent to the use of cookies. By clicking "Reject All", you disable the use of unnecessary cookies. You can manage your cookie settings by clicking "Manage Cookies". For more information or to change your cookie settings, please refer to our ',
|
|
9
|
+
'cookie.about': 'About Cookies',
|
|
10
|
+
'cookie.acceptAll': 'Accept All',
|
|
11
|
+
'cookie.rejectAll': 'Reject All',
|
|
12
|
+
'cookie.manage': 'Manage Cookies',
|
|
13
|
+
'cookie.necessaryCookie': 'Strictly Necessary Cookies',
|
|
14
|
+
'cookie.alwaysOn': 'Always active',
|
|
15
|
+
'cookie.necessaryCookieDetail': 'These cookies are necessary for the site to work properly and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as logging in or filling in forms. You can set the browser to block these cookies, but that can make parts of the site not work. These cookies do not store any personally identifiable information.',
|
|
16
|
+
'cookie.analyticalCookie': 'Analytics Cookies',
|
|
17
|
+
'cookie.analyticalCookieDetail': 'We will use these cookies only with your consent. These cookies help us make improvements by collecting statistics such as the number of visits and traffic sources.',
|
|
18
|
+
'cookie.saveSetting': 'Save Settings',
|
|
19
|
+
'cookie.setting': 'Cookie settings',
|
|
20
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { computed, inject, getCurrentInstance, ref } from 'vue';
|
|
2
|
+
|
|
3
|
+
import zh from './zh';
|
|
4
|
+
import en from './en';
|
|
5
|
+
import { configProviderInjectKey } from '@/shared/provide';
|
|
6
|
+
import { isString, isUndefined } from '@opensig/opendesign';
|
|
7
|
+
|
|
8
|
+
const i18nLangs = ref({
|
|
9
|
+
zh,
|
|
10
|
+
en,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export function useI18n() {
|
|
14
|
+
const instance = getCurrentInstance();
|
|
15
|
+
const configProvider = instance ? inject(configProviderInjectKey) : null;
|
|
16
|
+
const locale = computed(() => {
|
|
17
|
+
return configProvider?.locale || 'zh';
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const transform = (key: string, ...args: any[]): string => {
|
|
21
|
+
const lang = i18nLangs.value[locale.value];
|
|
22
|
+
const value = lang[key as keyof typeof lang];
|
|
23
|
+
|
|
24
|
+
// 处理变量替换
|
|
25
|
+
if (args.length > 0 && isString(value)) {
|
|
26
|
+
return value.replace(/{(\d+)}/g, (match, index) => {
|
|
27
|
+
return args[index] ?? match;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isUndefined(value)) {
|
|
32
|
+
console.warn(`Cannot translate the value of keypath '${key}'`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return value;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
locale,
|
|
40
|
+
t: transform,
|
|
41
|
+
};
|
|
42
|
+
}
|
package/src/i18n/zh.ts
ADDED