n20-project-component 1.0.0

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.
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <el-button
3
+ :type="type"
4
+ :size="size"
5
+ :disabled="disabled"
6
+ :loading="loading"
7
+ :icon="icon"
8
+ class="n20-demo-button"
9
+ @click="handleClick"
10
+ >
11
+ <slot>{{ text }}</slot>
12
+ </el-button>
13
+ </template>
14
+
15
+ <script>
16
+ /**
17
+ * DemoButton - 示例按钮组件
18
+ *
19
+ * 用于验证组件库构建流程。实际开发时可删除此组件,替换为业务组件。
20
+ *
21
+ * @example
22
+ * <DemoButton type="primary" text="提交" @click="handleSubmit" />
23
+ */
24
+ export default {
25
+ name: 'N20DemoButton',
26
+ props: {
27
+ /** 按钮文本 */
28
+ text: {
29
+ type: String,
30
+ default: '按钮',
31
+ },
32
+ /** 按钮类型:primary / success / warning / danger / info / text */
33
+ type: {
34
+ type: String,
35
+ default: 'primary',
36
+ },
37
+ /** 按钮尺寸:medium / small / mini */
38
+ size: {
39
+ type: String,
40
+ default: '',
41
+ },
42
+ /** 是否禁用 */
43
+ disabled: {
44
+ type: Boolean,
45
+ default: false,
46
+ },
47
+ /** 是否加载中 */
48
+ loading: {
49
+ type: Boolean,
50
+ default: false,
51
+ },
52
+ /** 图标类名 */
53
+ icon: {
54
+ type: String,
55
+ default: '',
56
+ },
57
+ },
58
+ methods: {
59
+ handleClick(e) {
60
+ this.$emit('click', e)
61
+ },
62
+ },
63
+ }
64
+ </script>
65
+
66
+ <style lang="scss" scoped>
67
+ @import '~@/styles/variables.scss';
68
+
69
+ .n20-demo-button {
70
+ // 自定义样式示例 —— 在 Element UI 基础上扩展
71
+ border-radius: $--n20-border-radius;
72
+ font-size: $--n20-font-size-base;
73
+ }
74
+ </style>
@@ -0,0 +1,171 @@
1
+ <template>
2
+ <span class="n20-copy-text" @click.stop="handleCopy">
3
+ <span class="n20-copy-text__content" ref="content">
4
+ <slot>{{ text }}</slot>
5
+ </span>
6
+ <i
7
+ v-if="showIcon"
8
+ :class="['n20-copy-text__icon', copied ? 'el-icon-check' : 'el-icon-document-copy']"
9
+ :style="{ order: iconPosition === 'left' ? -1 : 1 }"
10
+ />
11
+ <transition name="n20-copy-fade">
12
+ <span v-if="copied" class="n20-copy-text__tip">{{ successTip }}</span>
13
+ </transition>
14
+ </span>
15
+ </template>
16
+
17
+ <script>
18
+ /**
19
+ * N20CopyText - 点击复制文本组件
20
+ *
21
+ * 点击后将指定文本复制到剪贴板,显示成功反馈。
22
+ *
23
+ * @example
24
+ * <N20CopyText :text="row.billNo">{{ row.billNo }}</N20CopyText>
25
+ *
26
+ * @example
27
+ * <!-- 自动获取 slot 内容 -->
28
+ * <N20CopyText>BILL-2024-001</N20CopyText>
29
+ */
30
+ export default {
31
+ name: 'N20CopyText',
32
+ props: {
33
+ /** 要复制的文本(默认取 slot 内文本) */
34
+ text: {
35
+ type: [String, Number],
36
+ default: ''
37
+ },
38
+ /** 复制成功提示文字 */
39
+ successTip: {
40
+ type: String,
41
+ default: '已复制'
42
+ },
43
+ /** 是否显示复制图标 */
44
+ showIcon: {
45
+ type: Boolean,
46
+ default: true
47
+ },
48
+ /** 图标位置 */
49
+ iconPosition: {
50
+ type: String,
51
+ default: 'right',
52
+ validator: v => ['left', 'right'].includes(v)
53
+ },
54
+ /** 是否禁用 */
55
+ disabled: {
56
+ type: Boolean,
57
+ default: false
58
+ }
59
+ },
60
+ data() {
61
+ return {
62
+ copied: false,
63
+ timer: null
64
+ }
65
+ },
66
+ beforeDestroy() {
67
+ clearTimeout(this.timer)
68
+ },
69
+ methods: {
70
+ async handleCopy() {
71
+ if (this.disabled || this.copied) return
72
+
73
+ const copyText = this.text || (this.$refs.content && this.$refs.content.innerText) || ''
74
+ if (!copyText) return
75
+
76
+ try {
77
+ if (navigator.clipboard && window.isSecureContext) {
78
+ await navigator.clipboard.writeText(String(copyText))
79
+ } else {
80
+ // fallback for HTTP or older browsers
81
+ const textarea = document.createElement('textarea')
82
+ textarea.value = String(copyText)
83
+ textarea.style.position = 'fixed'
84
+ textarea.style.left = '-9999px'
85
+ document.body.appendChild(textarea)
86
+ textarea.select()
87
+ document.execCommand('copy')
88
+ document.body.removeChild(textarea)
89
+ }
90
+
91
+ this.copied = true
92
+ this.$emit('copy', String(copyText))
93
+
94
+ this.timer = setTimeout(() => {
95
+ this.copied = false
96
+ }, 2000)
97
+ } catch (err) {
98
+ this.$emit('error', err)
99
+ }
100
+ }
101
+ }
102
+ }
103
+ </script>
104
+
105
+ <style lang="scss" scoped>
106
+ .n20-copy-text {
107
+ display: inline-flex;
108
+ align-items: center;
109
+ gap: 4px;
110
+ cursor: pointer;
111
+ position: relative;
112
+
113
+ &:hover {
114
+ .n20-copy-text__icon {
115
+ opacity: 1;
116
+ }
117
+ }
118
+
119
+ &__content {
120
+ display: inline;
121
+ }
122
+
123
+ &__icon {
124
+ font-size: 14px;
125
+ color: #909399;
126
+ opacity: 0.4;
127
+ transition: all 0.2s ease;
128
+
129
+ &.el-icon-check {
130
+ color: #67C23A;
131
+ opacity: 1;
132
+ }
133
+ }
134
+
135
+ &__tip {
136
+ position: absolute;
137
+ top: -28px;
138
+ left: 50%;
139
+ transform: translateX(-50%);
140
+ background: #67C23A;
141
+ color: #fff;
142
+ font-size: 12px;
143
+ padding: 2px 8px;
144
+ border-radius: 4px;
145
+ white-space: nowrap;
146
+ pointer-events: none;
147
+
148
+ &::after {
149
+ content: '';
150
+ position: absolute;
151
+ bottom: -4px;
152
+ left: 50%;
153
+ transform: translateX(-50%);
154
+ border-left: 4px solid transparent;
155
+ border-right: 4px solid transparent;
156
+ border-top: 4px solid #67C23A;
157
+ }
158
+ }
159
+ }
160
+
161
+ .n20-copy-fade-enter-active,
162
+ .n20-copy-fade-leave-active {
163
+ transition: opacity 0.3s ease, transform 0.3s ease;
164
+ }
165
+
166
+ .n20-copy-fade-enter,
167
+ .n20-copy-fade-leave-to {
168
+ opacity: 0;
169
+ transform: translateX(-50%) translateY(4px);
170
+ }
171
+ </style>
package/src/index.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * n20-project-component
3
+ * PC 端 Vue 2 + Element UI 组件库
4
+ */
5
+
6
+ // ========== 组件导入 ==========
7
+ import DemoButton from './components/DemoButton/index.vue'
8
+ import N20CopyText from './components/N20CopyText/index.vue'
9
+
10
+ // ========== 组件列表 ==========
11
+ const components = {
12
+ DemoButton,
13
+ N20CopyText,
14
+ }
15
+
16
+ // ========== 按需导出 ==========
17
+ export {
18
+ DemoButton,
19
+ N20CopyText,
20
+ }
21
+
22
+ // ========== 全量注册(Vue.use)==========
23
+ const install = (Vue) => {
24
+ if (install.installed) return
25
+ install.installed = true
26
+ Object.values(components).forEach((component) => {
27
+ Vue.component(component.name, component)
28
+ })
29
+ }
30
+
31
+ // 支持通过 <script> 标签直接引入时自动注册
32
+ if (typeof window !== 'undefined' && window.Vue) {
33
+ install(window.Vue)
34
+ }
35
+
36
+ export default {
37
+ install,
38
+ ...components,
39
+ }
@@ -0,0 +1,30 @@
1
+ // ========== n20-project-component 共享变量 ==========
2
+
3
+ // 主色
4
+ $--n20-primary-color: #409EFF;
5
+ $--n20-success-color: #67C23A;
6
+ $--n20-warning-color: #E6A23C;
7
+ $--n20-danger-color: #F56C6C;
8
+ $--n20-info-color: #909399;
9
+
10
+ // 文字颜色
11
+ $--n20-text-primary: #303133;
12
+ $--n20-text-regular: #606266;
13
+ $--n20-text-secondary: #909399;
14
+ $--n20-text-placeholder: #C0C4CC;
15
+
16
+ // 边框
17
+ $--n20-border-color: #DCDFE6;
18
+ $--n20-border-radius: 4px;
19
+
20
+ // 间距
21
+ $--n20-spacing-xs: 4px;
22
+ $--n20-spacing-sm: 8px;
23
+ $--n20-spacing-md: 16px;
24
+ $--n20-spacing-lg: 24px;
25
+
26
+ // 字体
27
+ $--n20-font-size-sm: 12px;
28
+ $--n20-font-size-base: 14px;
29
+ $--n20-font-size-lg: 16px;
30
+ $--n20-font-size-xl: 18px;