@ebiz/designer-components 0.0.18-beta.1 → 0.0.18-beta.10

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,139 @@
1
+ <template>
2
+ <div class="ebiz-employee-info" :style="customStyle">
3
+ <div class="employee-avatar">
4
+ <ebiz-avatar
5
+ :image="employeeInfo.avatar"
6
+ :content="avatarContent"
7
+ :size="avatarSize"
8
+ :shape="avatarShape"
9
+ :alt="employeeInfo.name"
10
+ @error="handleAvatarError"
11
+ />
12
+ </div>
13
+ <div class="employee-details">
14
+ <div class="employee-name">{{ employeeInfo.name }}</div>
15
+ <div class="employee-id" v-if="employeeInfo.id">ID: {{ employeeInfo.id }}</div>
16
+ <div class="employee-department" v-if="employeeInfo.department">{{ employeeInfo.department }}</div>
17
+ </div>
18
+ </div>
19
+ </template>
20
+
21
+ <script>
22
+ export default {
23
+ name: "EbizEmployeeInfo"
24
+ }
25
+ </script>
26
+
27
+ <script setup>
28
+ import { computed, defineProps, defineEmits } from 'vue';
29
+ import EbizAvatar from './EbizAvatar.vue';
30
+
31
+ const props = defineProps({
32
+ // 员工信息对象,包含头像、名称、工号、部门
33
+ info: {
34
+ type: Object,
35
+ required: true,
36
+ default: () => ({
37
+ avatar: '',
38
+ name: '',
39
+ id: '',
40
+ department: ''
41
+ })
42
+ },
43
+ // 头像尺寸
44
+ avatarSize: {
45
+ type: String,
46
+ default: 'medium',
47
+ validator: (val) => ['small', 'medium', 'large'].includes(val) || /^(\d+px|\d+em|\d+rem|\d+%)$/.test(val)
48
+ },
49
+ // 头像形状
50
+ avatarShape: {
51
+ type: String,
52
+ default: 'circle',
53
+ validator: (val) => ['circle', 'round'].includes(val)
54
+ },
55
+ // 自定义宽度
56
+ width: {
57
+ type: [String, Number],
58
+ default: ''
59
+ },
60
+ // 自定义高度
61
+ height: {
62
+ type: [String, Number],
63
+ default: ''
64
+ }
65
+ });
66
+
67
+ const emit = defineEmits(['avatar-error']);
68
+
69
+ // 从props获取员工信息,提供默认值
70
+ const employeeInfo = computed(() => {
71
+ return {
72
+ avatar: props.info.avatar || '',
73
+ name: props.info.name || '未知',
74
+ id: props.info.id || '',
75
+ department: props.info.department || ''
76
+ };
77
+ });
78
+
79
+ // 生成头像内容(当没有提供头像图片时使用员工姓名首字母)
80
+ const avatarContent = computed(() => {
81
+ if (employeeInfo.value.name) {
82
+ return employeeInfo.value.name.charAt(0);
83
+ }
84
+ return '';
85
+ });
86
+
87
+ // 处理头像加载错误
88
+ const handleAvatarError = (context) => {
89
+ emit('avatar-error', context);
90
+ };
91
+
92
+ // 计算自定义样式
93
+ const customStyle = computed(() => {
94
+ const style = {};
95
+ if (props.width) {
96
+ style.width = typeof props.width === 'number' ? `${props.width}px` : props.width;
97
+ }
98
+ if (props.height) {
99
+ style.height = typeof props.height === 'number' ? `${props.height}px` : props.height;
100
+ }
101
+ return style;
102
+ });
103
+ </script>
104
+
105
+ <style lang="less" scoped>
106
+ .ebiz-employee-info {
107
+ display: flex;
108
+ align-items: center;
109
+
110
+ .employee-avatar {
111
+ margin-right: 16px;
112
+ }
113
+
114
+ .employee-details {
115
+ flex: 1;
116
+ display: flex;
117
+ flex-direction: column;
118
+ justify-content: center;
119
+ }
120
+
121
+ .employee-name {
122
+ font-size: 16px;
123
+ font-weight: 600;
124
+ color: #333;
125
+ margin-bottom: 4px;
126
+ }
127
+
128
+ .employee-id {
129
+ font-size: 13px;
130
+ color: #666;
131
+ margin-bottom: 2px;
132
+ }
133
+
134
+ .employee-department {
135
+ font-size: 13px;
136
+ color: #666;
137
+ }
138
+ }
139
+ </style>
@@ -1,14 +1,15 @@
1
1
  <template>
2
- <tiny-select ref="selectRef" v-model="selectedValue" :options="options" :loading="loading" :remote="true"
3
- :remote-method="handleRemoteSearch" :multiple="multiple" :placeholder="placeholder" :clearable="clearable"
4
- :disabled="disabled" @change="handleChange" @focus="handleRemoteSearch">
5
- <template #prefix>
2
+ <t-select ref="selectRef" v-model="selectedValue" :options="options" :loading="loading" :filterable="true"
3
+ @search="handleRemoteSearch" :multiple="multiple" :placeholder="placeholder" :clearable="clearable"
4
+ :disabled="disabled" :size="size" :empty="emptyText" :popupProps="popupProps" @change="handleChange"
5
+ @focus="handleFocus" @blur="handleBlur">
6
+ <template #prefixIcon v-if="$slots.prefix">
6
7
  <slot name="prefix"></slot>
7
8
  </template>
8
- <template #suffix>
9
+ <template #suffixIcon v-if="$slots.suffix">
9
10
  <slot name="suffix"></slot>
10
11
  </template>
11
- </tiny-select>
12
+ </t-select>
12
13
  </template>
13
14
 
14
15
  <script>
@@ -19,8 +20,8 @@ export default {
19
20
 
20
21
  <script setup>
21
22
  import { ref, onMounted, toRefs, computed } from 'vue';
22
- import { Select as TinySelect } from '@opentiny/vue';
23
- import dataService from "../apiService/simpleDataService";
23
+ import { Select as TSelect } from 'tdesign-vue-next';
24
+ import dataService from '../apiService/simpleDataServiceAppData';
24
25
 
25
26
  const props = defineProps({
26
27
  /**
@@ -30,6 +31,7 @@ const props = defineProps({
30
31
  type: Object,
31
32
  required: true,
32
33
  default: () => ({
34
+ key: null,
33
35
  apiId: null,
34
36
  apiType: ''
35
37
  })
@@ -39,7 +41,7 @@ const props = defineProps({
39
41
  */
40
42
  queryParams: {
41
43
  type: Object,
42
- default: {}
44
+ default: () => ({})
43
45
  },
44
46
  /**
45
47
  * 是否多选
@@ -73,21 +75,54 @@ const props = defineProps({
73
75
  * 默认值
74
76
  */
75
77
  modelValue: {
76
- type: [String, Number],
78
+ type: [String, Number, Array],
77
79
  default: ''
78
80
  },
81
+ /**
82
+ * 选项配置
83
+ */
79
84
  optionsConfig: {
80
- labelField: '',//作为label的字段
81
- valueField: ''//作为value的字段
85
+ type: Object,
86
+ default: () => ({
87
+ labelField: '',
88
+ valueField: ''
89
+ })
90
+ },
91
+ /**
92
+ * 组件尺寸
93
+ */
94
+ size: {
95
+ type: String,
96
+ default: 'medium',
97
+ validator: (val) => ['small', 'medium', 'large'].includes(val)
98
+ },
99
+ /**
100
+ * 空数据文本
101
+ */
102
+ emptyText: {
103
+ type: String,
104
+ default: '暂无数据'
105
+ },
106
+ /**
107
+ * 弹出层配置
108
+ */
109
+ popupProps: {
110
+ type: Object,
111
+ default: () => ({})
82
112
  }
83
113
  });
84
114
 
85
115
  const { modelValue, apiConfig, queryParams } = toRefs(props)
86
116
 
87
- const emit = defineEmits(['update:modelValue', 'change']);
117
+ const emit = defineEmits(['update:modelValue', 'change', 'focus', 'blur']);
88
118
 
89
119
  // 选中的值
90
- const selectedValue = computed(() => modelValue.value)
120
+ const selectedValue = computed({
121
+ get: () => modelValue.value,
122
+ set: (val) => {
123
+ emit('update:modelValue', val);
124
+ }
125
+ });
91
126
 
92
127
  // 选项列表
93
128
  const options = ref([]);
@@ -104,37 +139,55 @@ const focus = () => {
104
139
  };
105
140
 
106
141
  defineExpose({
107
- focus
142
+ focus,
143
+ selectRef
108
144
  });
109
145
 
110
146
  // 远程搜索处理函数
111
- const handleRemoteSearch = async () => {
112
- //if (options.value?.length > 0) return
113
-
147
+ const handleRemoteSearch = async (keyword) => {
114
148
  loading.value = true;
115
- if (apiConfig.value.apiId && apiConfig.value.apiType >= 0) {
116
- try {
117
- const params = {
118
- queryParams: queryParams.value
119
- };
120
- const res = await dataService.fetch(params, { apiId: props.apiConfig.apiId, apiType: 'MULTIPLE_DATA_SEARCH' });
121
- options.value = res.data.map(item => ({
122
- label: item.label || item.name,
123
- value: item.value || item.id
124
- }));
125
- } catch (error) {
126
- console.error('远程搜索失败:', error);
127
- options.value = [];
128
- } finally {
129
- loading.value = false;
130
- }
149
+ console.log('handleRemoteSearch', keyword);
150
+ try {
151
+ const params = {
152
+ queryParams: {
153
+ ...queryParams.value,
154
+ keyword
155
+ }
156
+ };
157
+ const res = await dataService.fetch(params, props.apiConfig);
158
+ const { labelField, valueField } = props.optionsConfig;
159
+
160
+ options.value = res.data.map(item => ({
161
+ label: labelField ? item[labelField] : (item.label || item.name),
162
+ value: valueField ? item[valueField] : (item.value || item.id)
163
+ }));
164
+ } catch (error) {
165
+ console.error('远程搜索失败:', error);
166
+ options.value = [];
167
+ } finally {
168
+ loading.value = false;
169
+ }
170
+
171
+ };
172
+
173
+ // 处理获取焦点事件
174
+ const handleFocus = (value, context) => {
175
+ emit('focus', value, context);
176
+ // 当选项为空时,初始加载数据
177
+ if (options.value.length === 0) {
178
+ handleRemoteSearch('');
131
179
  }
132
180
  };
133
181
 
182
+ // 处理失去焦点事件
183
+ const handleBlur = (value, context) => {
184
+ emit('blur', value, context);
185
+ };
186
+
134
187
  // 值变化处理函数
135
- const handleChange = (value) => {
188
+ const handleChange = (value, context) => {
136
189
  emit('update:modelValue', value);
137
- emit('change', value);
190
+ emit('change', value, context);
138
191
  };
139
192
 
140
193
  // 组件挂载时,如果有默认值则加载对应的选项
@@ -145,10 +198,16 @@ onMounted(async () => {
145
198
  const params = {
146
199
  queryParams: queryParams.value
147
200
  };
148
- const res = await dataService.fetch(params, { apiId: props.apiConfig.apiId, apiType: 'MULTIPLE_DATA_SEARCH' });
201
+ const res = await dataService.fetch(params, {
202
+ key: props.apiConfig.key,
203
+ apiId: props.apiConfig.apiId,
204
+ apiType: 'MULTIPLE_DATA_SEARCH'
205
+ });
206
+ const { labelField, valueField } = props.optionsConfig;
207
+
149
208
  options.value = res.data.map(item => ({
150
- label: item.label || item.name,
151
- value: item.value || item.id
209
+ label: labelField ? item[labelField] : (item.label || item.name),
210
+ value: valueField ? item[valueField] : (item.value || item.id)
152
211
  }));
153
212
  } catch (error) {
154
213
  console.error('加载默认选项失败:', error);
@@ -156,12 +215,11 @@ onMounted(async () => {
156
215
  loading.value = false;
157
216
  }
158
217
  }
159
-
160
218
  });
161
219
  </script>
162
220
 
163
221
  <style scoped>
164
- .tiny-select {
222
+ .t-select {
165
223
  width: 100%;
166
224
  }
167
225
  </style>
@@ -0,0 +1,150 @@
1
+ <template>
2
+ <t-statistic
3
+ ref="statisticRef"
4
+ :animation="animation"
5
+ :animation-start="animationStart"
6
+ :color="color"
7
+ :decimalPlaces="decimalPlaces"
8
+ :loading="loading"
9
+ :prefix="prefix"
10
+ :suffix="suffix"
11
+ :title="title"
12
+ :trend="trend"
13
+ :trend-placement="trendPlacement"
14
+ :unit="unit"
15
+ :value="value"
16
+ @click="handleClick"
17
+ >
18
+ <!-- 加载中状态插槽 -->
19
+ <template v-if="$slots.loading" #loading>
20
+ <slot name="loading"></slot>
21
+ </template>
22
+
23
+ <!-- 前缀插槽 -->
24
+ <template v-if="$slots.prefix" #prefix>
25
+ <slot name="prefix"></slot>
26
+ </template>
27
+
28
+ <!-- 后缀插槽 -->
29
+ <template v-if="$slots.suffix" #suffix>
30
+ <slot name="suffix"></slot>
31
+ </template>
32
+
33
+ <!-- 标题插槽 -->
34
+ <template v-if="$slots.title" #title>
35
+ <slot name="title"></slot>
36
+ </template>
37
+
38
+ <!-- 趋势插槽 -->
39
+ <template v-if="$slots.trend" #trend>
40
+ <slot name="trend"></slot>
41
+ </template>
42
+
43
+ <!-- 单位插槽 -->
44
+ <template v-if="$slots.unit" #unit>
45
+ <slot name="unit"></slot>
46
+ </template>
47
+
48
+ <!-- 默认插槽 -->
49
+ <slot></slot>
50
+ </t-statistic>
51
+ </template>
52
+
53
+ <script>
54
+ export default {
55
+ name: "EbizStatistic"
56
+ }
57
+ </script>
58
+
59
+ <script setup>
60
+ import { defineProps, defineEmits, ref, onMounted } from 'vue';
61
+ import { Statistic as TStatistic } from 'tdesign-vue-next';
62
+
63
+ const props = defineProps({
64
+ // 数值动画配置
65
+ animation: {
66
+ type: Object,
67
+ default: () => ({}),
68
+ },
69
+ // 数值动画开始时间,单位:毫秒
70
+ animationStart: {
71
+ type: Boolean,
72
+ default: false,
73
+ },
74
+ // 颜色
75
+ color: {
76
+ type: String,
77
+ default: '',
78
+ },
79
+ // 小数位数
80
+ decimalPlaces: {
81
+ type: Number,
82
+ default: 0,
83
+ },
84
+ // 加载中状态
85
+ loading: {
86
+ type: Boolean,
87
+ default: false,
88
+ },
89
+ // 前缀内容
90
+ prefix: {
91
+ type: [String, Function],
92
+ default: '',
93
+ },
94
+ // 后缀内容
95
+ suffix: {
96
+ type: [String, Function],
97
+ default: '',
98
+ },
99
+ // 数值显示的标题
100
+ title: {
101
+ type: [String, Function],
102
+ default: '',
103
+ },
104
+ // 趋势
105
+ trend: {
106
+ type: String,
107
+ default: '',
108
+ validator: (val) => ['', 'increase', 'decrease'].includes(val)
109
+ },
110
+ // 趋势展示位置
111
+ trendPlacement: {
112
+ type: String,
113
+ default: 'left',
114
+ validator: (val) => ['left', 'right'].includes(val)
115
+ },
116
+ // 单位内容
117
+ unit: {
118
+ type: [String, Function],
119
+ default: '',
120
+ },
121
+ // 数值
122
+ value: {
123
+ type: Number,
124
+ default: 0,
125
+ },
126
+ });
127
+
128
+ const emit = defineEmits(['click']);
129
+
130
+ // 引用实例,用于调用组件的start方法
131
+ const statisticRef = ref(null);
132
+
133
+ // 点击事件
134
+ const handleClick = (e) => {
135
+ emit('click', e);
136
+ };
137
+
138
+ // 暴露start方法,用于手动开始动画
139
+ defineExpose({
140
+ start: () => {
141
+ if (statisticRef.value) {
142
+ statisticRef.value.start();
143
+ }
144
+ }
145
+ });
146
+ </script>
147
+
148
+ <style lang="less" scoped>
149
+ /* 自定义样式 */
150
+ </style>
@@ -1,12 +1,7 @@
1
1
  <template>
2
2
  <tiny-tabs v-model="showTab" :tab-style="props.tabStyle" activeColor="#5e7ce0" @click="tabChange">
3
- <tiny-tab-item
4
- activeColor="#5e7ce0"
5
- v-for="i in showTabs"
6
- :key="i.value"
7
- :title="i.label"
8
- :name="i.value.toString()"
9
- />
3
+ <tiny-tab-item activeColor="#5e7ce0" v-for="i in showTabs" :key="i.value" :title="i.label"
4
+ :name="i.value.toString()" />
10
5
  </tiny-tabs>
11
6
  </template>
12
7
 
@@ -22,6 +17,7 @@ const props = defineProps({
22
17
  apiConfig: {
23
18
  type: Object,
24
19
  default: () => ({
20
+ key: null,
25
21
  apiId: null,
26
22
  apiType: 'MULTIPLE_DATA_SEARCH',
27
23
  }),
@@ -63,7 +59,7 @@ const remoteTabs = ref([])
63
59
 
64
60
  // 展示的tabs
65
61
  const showTabs = computed(() => {
66
- console.log(props.tabs,66, 110)
62
+ console.log(props.tabs, 66, 110)
67
63
  if (remoteTabs.value.length) return [{ label: '全部', value: '0' }, ...remoteTabs.value];
68
64
  if (props.tabs.length) return props.tabs
69
65
  return [{ label: '全部', value: '0' }]
@@ -87,7 +83,7 @@ watch(() => showTabs.value, () => {
87
83
 
88
84
  watch(() => props.apiConfig, (nVal) => {
89
85
  if (!nVal?.apiId) return
90
- dataService.fetch({ }, { apiId: nVal.apiId, apiType: 'MULTIPLE_DATA_SEARCH' }).then(res => {
86
+ dataService.fetch({}, { key: nVal.key, apiId: nVal.apiId, apiType: 'MULTIPLE_DATA_SEARCH' }).then(res => {
91
87
  remoteTabs.value = (res.data ?? []).map(i => ({
92
88
  label: i[props.labelKey],
93
89
  value: i[props.valueKey],
@@ -146,4 +142,4 @@ watch(() => props.apiConfig, (nVal) => {
146
142
  overflow: hidden;
147
143
  white-space: nowrap;
148
144
  }
149
- </style>
145
+ </style>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <tab-panel
3
+ :value="value"
4
+ :label="label"
5
+ :disabled="disabled"
6
+ :removable="removable"
7
+ :destroyOnHide="destroyOnHide"
8
+ :draggable="draggable"
9
+ @remove="handleRemove"
10
+ >
11
+ <slot></slot>
12
+ </tab-panel>
13
+ </template>
14
+
15
+ <script>
16
+ import { TabPanel } from 'tdesign-vue-next';
17
+
18
+ export default TabPanel;
19
+ </script>
20
+
21
+ <style lang="less" scoped>
22
+ /* 自定义样式 */
23
+ </style>