joy-admin-components 0.1.18 → 0.1.20

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/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ v18.18.0
package/README.md CHANGED
@@ -24,120 +24,3 @@ app.use(ElementPlus)
24
24
  app.use(JoyAdminComponents)
25
25
  app.mount('#app')
26
26
  ```
27
-
28
- ### 按需引入
29
-
30
- ```js
31
- import { JoyTable, JoyForm, JoySearch } from 'joy-admin-components'
32
-
33
- export default {
34
- components: {
35
- JoyTable,
36
- JoyForm,
37
- JoySearch
38
- }
39
- }
40
- ```
41
-
42
- ## 组件列表
43
-
44
- ### JoyTable
45
-
46
- 增强的表格组件,集成了分页功能。
47
-
48
- ```vue
49
- <template>
50
- <joy-table
51
- :data="tableData"
52
- :current-page="page"
53
- :page-size="limit"
54
- :total="total"
55
- @current-change="handleCurrentChange"
56
- @size-change="handleSizeChange"
57
- >
58
- <el-table-column prop="name" label="姓名" />
59
- <el-table-column prop="age" label="年龄" />
60
- </joy-table>
61
- </template>
62
- ```
63
-
64
- ### JoyForm
65
-
66
- 动态表单组件,支持多种表单项类型。
67
-
68
- ```vue
69
- <template>
70
- <joy-form
71
- :model="form"
72
- :rules="rules"
73
- :form-items="formItems"
74
- @submit="handleSubmit"
75
- @reset="handleReset"
76
- />
77
- </template>
78
-
79
- <script setup>
80
- const form = reactive({
81
- name: '',
82
- age: '',
83
- gender: ''
84
- })
85
-
86
- const formItems = [
87
- { label: '姓名', prop: 'name', type: 'input' },
88
- { label: '年龄', prop: 'age', type: 'input' },
89
- {
90
- label: '性别',
91
- prop: 'gender',
92
- type: 'select',
93
- options: [
94
- { label: '男', value: 'male' },
95
- { label: '女', value: 'female' }
96
- ]
97
- }
98
- ]
99
- </script>
100
- ```
101
-
102
- ### JoySearch
103
-
104
- 搜索表单组件,适用于列表页面的筛选功能。
105
-
106
- ```vue
107
- <template>
108
- <joy-search
109
- :search-items="searchItems"
110
- :initial-values="initialValues"
111
- @search="handleSearch"
112
- @reset="handleReset"
113
- />
114
- </template>
115
-
116
- <script setup>
117
- const searchItems = [
118
- { label: '姓名', prop: 'name', type: 'input' },
119
- { label: '状态', prop: 'status', type: 'select', options: [
120
- { label: '启用', value: 1 },
121
- { label: '禁用', value: 0 }
122
- ]}
123
- ]
124
-
125
- const initialValues = {
126
- name: '',
127
- status: ''
128
- }
129
- </script>
130
- ```
131
-
132
- ## 开发
133
-
134
- ```bash
135
- # 安装依赖
136
- npm install
137
-
138
- # 启动开发服务器
139
- npm run dev
140
-
141
- # 构建库
142
- npm run build
143
- ```
package/index.html ADDED
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" href="/favicon.ico" />
6
+ <link rel="stylesheet" href="//at.alicdn.com/t/font_2570680_gkyjimtz1d.css" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <!-- <script src="https://cdn.staticfile.net/translate.js/3.1.2/translate.js"></script> -->
9
+ <title></title>
10
+ </head>
11
+ <body>
12
+ <div id="app"></div>
13
+ <script type="module" src="/src/main.js"></script>
14
+ <!-- <script>
15
+ translate.language.setLocal('chinese_simplified'); //设置本地语种(当前网页的语种)。如果不设置,默认自动识别当前网页显示文字的语种。 可填写如 'english'、'chinese_simplified' 等,具体参见文档下方关于此的说明。
16
+ translate.service.use('client.edge'); //设置机器翻译服务通道,直接客户端本身,不依赖服务端 。相关说明参考 http://translate.zvo.cn/43086.html
17
+ translate.execute(); //进行翻译
18
+ document.body.addEventListener('click', () => {
19
+ translate.execute(); //进行翻译
20
+ });
21
+ </script> -->
22
+ </body>
23
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joy-admin-components",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "main": "./src/index.js",
5
5
  "scripts": {
6
6
  "dev": "vite",
@@ -10,9 +10,6 @@
10
10
  "pub-minor": "npm version minor && npm publish",
11
11
  "pub-major": "npm version major && npm publish"
12
12
  },
13
- "files": [
14
- "dist"
15
- ],
16
13
  "dependencies": {},
17
14
  "peerDependencies": {
18
15
  "vue": "^3.3.4",
package/src/App.vue ADDED
@@ -0,0 +1,12 @@
1
+ <template>
2
+ <div id="app">
3
+ <JoyForm>
4
+ <el-form-item label="用户名" prop="username" :span="4">
5
+ <el-input placeholder="请输入用户名"></el-input>
6
+ </el-form-item>
7
+ </JoyForm>
8
+ </div>
9
+ </template>
10
+ <script>
11
+
12
+ </script>
@@ -0,0 +1,162 @@
1
+ <template>
2
+ <el-select
3
+ v-model="model"
4
+ v-bind="$attrs"
5
+ filterable
6
+ collapse-tags
7
+ collapse-tags-tooltip
8
+ clearable
9
+ @clear="onChange"
10
+ @change="onChange"
11
+ >
12
+ <slot
13
+ name="header"
14
+ v-if="$attrs.multiple != undefined && $attrs.multiple != false && showCheckAll"
15
+ >
16
+ <el-checkbox class="mgl20" v-model="checkAll" @change="checkAllHandle">
17
+ {{ $t('quan-xuan') }}
18
+ </el-checkbox>
19
+ </slot>
20
+ <el-option
21
+ v-for="(item, index) in data ? data : selectionData"
22
+ :key="index"
23
+ :disabled="getDisabled(item)"
24
+ :label="cmpLabel(item)"
25
+ @click.stop="optionClickHanlde(item)"
26
+ :value="$attrs['value-key'] ? item : item[labelValue.value]"
27
+ ></el-option>
28
+ </el-select>
29
+ </template>
30
+
31
+ <script setup>
32
+ import { difference } from 'lodash';
33
+ import { computed, nextTick, ref, useAttrs, watch } from 'vue';
34
+ import { useI18n } from 'vue-i18n';
35
+ const props = defineProps({
36
+ api: {
37
+ type: Function,
38
+ },
39
+ showCheckAll: {
40
+ type: Boolean,
41
+ default: true,
42
+ },
43
+ optionClick: {
44
+ type: Function,
45
+ },
46
+ maxLimit: {
47
+ type: Number,
48
+ default: undefined,
49
+ },
50
+ minLimit: {
51
+ type: Number,
52
+ default: undefined,
53
+ },
54
+ data: {
55
+ type: Array,
56
+ },
57
+ labelValue: {
58
+ type: Object,
59
+ default: () => ({
60
+ label: 'label',
61
+ labelEn: 'labelEn',
62
+ value: 'value',
63
+ }),
64
+ },
65
+ changeLocal: {
66
+ type: Boolean,
67
+ default: false,
68
+ },
69
+ });
70
+ const $attrs = useAttrs();
71
+ const { t, locale } = useI18n();
72
+ const model = defineModel();
73
+ const checkAll = computed({
74
+ get() {
75
+ if ($attrs.multiple == undefined) return false;
76
+ if (model.value == undefined) return false;
77
+ let data = props.data ? props.data : selectionData.value;
78
+ let dif =
79
+ difference(
80
+ data.map((item) => ($attrs['value-key'] ? item : item[props.labelValue.value])),
81
+ model.value,
82
+ ).length == 0;
83
+ return model.value.length == data.length && dif;
84
+ },
85
+ set(val) {
86
+ return val;
87
+ },
88
+ });
89
+ const emits = defineEmits(['success', 'change']);
90
+ const selectionData = ref([]);
91
+ const apiGetDaata = async () => {
92
+ const { data } = await props.api();
93
+ selectionData.value = [...data];
94
+ emits('success', selectionData);
95
+ };
96
+ if (props.api) {
97
+ apiGetDaata();
98
+ }
99
+
100
+ const labels = props.labelValue.label.split('-');
101
+ // label 可以用-拼接多个属性
102
+ const cmpLabel = (row) => {
103
+ if (labels.length == 1) {
104
+ // props.changeLocal 使用本地翻译
105
+ if (props.changeLocal) {
106
+ return t(row[props.labelValue.label]);
107
+ }
108
+
109
+ // 取设置的labelEn
110
+ let label =
111
+ locale.value == 'en_us'
112
+ ? row[props.labelValue.labelEn || 'i18nName']
113
+ : row[props.labelValue.label];
114
+ // 中文兜底
115
+ label = label || row[props.labelValue.label];
116
+ return label;
117
+ } else {
118
+ let res = [];
119
+ labels.forEach((label) => {
120
+ res.push(row[label]);
121
+ });
122
+ return res.join('-');
123
+ }
124
+ };
125
+ function checkAllHandle(val) {
126
+ let data = props.data ? props.data : selectionData.value;
127
+ model.value = val
128
+ ? data.map((item) => ($attrs['value-key'] ? item : item[props.labelValue.value]))
129
+ : [];
130
+ nextTick(() => {
131
+ emits('change', model.value);
132
+ });
133
+ }
134
+ function optionClickHanlde(item) {
135
+ props.optionClick && props.optionClick(item);
136
+ props.optionClick && emits('change', $attrs['value-key'] ? item : item[props.labelValue.value]);
137
+ }
138
+ function onChange(val) {
139
+ emits('change', val);
140
+ }
141
+ function getDisabled(item) {
142
+ // 多选 最大选择数
143
+ if ($attrs.multiple != undefined && $attrs.multiple != false && props.maxLimit > 0) {
144
+ return (
145
+ model.value.length >= props.maxLimit && !model.value.includes(item[props.labelValue.value])
146
+ );
147
+ }
148
+ // 多选 最小选择数
149
+ if ($attrs.multiple != undefined && $attrs.multiple != false && props.minLimit > 0) {
150
+ return (
151
+ model.value.length <= props.minLimit && model.value.includes(item[props.labelValue.value])
152
+ );
153
+ }
154
+ return false;
155
+ }
156
+ </script>
157
+
158
+ <style lang="scss" scoped>
159
+ .cmp-icon {
160
+ padding: 0 !important;
161
+ }
162
+ </style>
@@ -0,0 +1,30 @@
1
+ <!--
2
+ * @Author: zhouyunjie 531671820@qq.com
3
+ * @Date: 2023-11-16 14:00:32
4
+ * @LastEditors: zhouyunjie 531671820@qq.com
5
+ * @LastEditTime: 2023-11-16 16:28:19
6
+ * @FilePath: /warehouse-reservation-web/src/components/ConfrimButton/index.vue
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ -->
9
+ <template>
10
+ <el-popconfirm v-bind="$attrs" @confirm="confirmHandle" @cancel="cancelHandle">
11
+ <template #reference>
12
+ <el-link underline="never" :type="$attrs.type">
13
+ <slot></slot>
14
+ </el-link>
15
+ </template>
16
+ </el-popconfirm>
17
+ </template>
18
+
19
+ <script setup>
20
+ import { debounce } from 'lodash';
21
+ const emits = defineEmits(['ok', 'no']);
22
+ const confirmHandle = debounce(() => {
23
+ emits('ok');
24
+ }, 500);
25
+ function cancelHandle() {
26
+ emits('no');
27
+ }
28
+ </script>
29
+
30
+ <style lang="scss" scoped></style>
@@ -0,0 +1,50 @@
1
+ <!--
2
+ * @Author: zhouyunjie 531671820@qq.com
3
+ * @Date: 2023-11-17 11:06:06
4
+ * @LastEditors: zhouyunjie bernie.zhou@joy-group.com
5
+ * @LastEditTime: 2025-07-02 19:39:47
6
+ * @FilePath: /warehouse-reservation-web/src/components/CmpIcon/index.vue
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ -->
9
+ <template>
10
+ <span>
11
+ <el-button :loading="loading" class="mgr10" @click="input.click()">
12
+ {{ $t('dao-ru') }}
13
+ </el-button>
14
+ <input
15
+ @change="fileChange"
16
+ ref="input"
17
+ :accept="accept"
18
+ style="position: absolute; width: 0px; height: 0px; opacity: 0"
19
+ type="file"
20
+ />
21
+ </span>
22
+ </template>
23
+
24
+ <script setup>
25
+ import { ref } from 'vue';
26
+
27
+ const props = defineProps({
28
+ accept: {
29
+ type: String,
30
+ default: '.xlsx,.xls',
31
+ },
32
+ loading: {
33
+ type: Boolean,
34
+ default: false,
35
+ },
36
+ });
37
+ const emits = defineEmits(['fileChange']);
38
+ const input = ref(null);
39
+ function fileChange(e) {
40
+ let file = e.target.files[0];
41
+ emits('fileChange', file);
42
+ input.value.value = null;
43
+ }
44
+ </script>
45
+
46
+ <style lang="scss" scoped>
47
+ .cmp-icon {
48
+ padding: 0 !important;
49
+ }
50
+ </style>
@@ -0,0 +1,10 @@
1
+ import JoyForm from './index.vue';
2
+
3
+ export { JoyForm };
4
+
5
+ // 导出Vue插件
6
+ export default {
7
+ install(app) {
8
+ app.component(JoyForm.name, JoyForm);
9
+ },
10
+ };
@@ -0,0 +1,41 @@
1
+
2
+ <script>
3
+ import { ElCol, ElForm, ElRow } from 'element-plus';
4
+ import { defineComponent, h, onMounted, ref } from 'vue';
5
+
6
+ export default defineComponent({
7
+ name: 'JoyForm',
8
+ setup(props, { slots, attrs, emit }) {
9
+ const formRef = ref(null);
10
+ // 给表单项添加栅格 方便多列表单布局
11
+ const modifiedSlot = () => {
12
+ if (!slots.default) return null;
13
+ return slots
14
+ .default()
15
+ .filter((item) => item.type !== Symbol.for('v-cmt')) //过滤注释节点
16
+ .map((item) => (item.type === Symbol.for('v-fgt') ? item.children : item)) //如果是fragment节点则取子节点
17
+ .flat()
18
+ .map((vnode) =>
19
+ h(
20
+ ElCol,
21
+ { span: vnode.props?.span || 24 },
22
+ {
23
+ default: () => {
24
+ return vnode;
25
+ },
26
+ },
27
+ ),
28
+ );
29
+ };
30
+ onMounted(() => {
31
+ emit('ref', formRef.value);
32
+ });
33
+ function render() {
34
+ return h(ElForm, { ...attrs, ref: formRef }, () =>
35
+ h(ElRow, { gutter: attrs.gutter ?? 20 }, { default: () => modifiedSlot() }),
36
+ );
37
+ }
38
+ return render;
39
+ },
40
+ });
41
+ </script>
@@ -0,0 +1,258 @@
1
+ <template>
2
+ <div :class="{ box: searchForm.showShadow }">
3
+ <SearchBar v-if="searchForm.showSearch" :form="searchForm" @reset="reset" @confirm="getList()">
4
+ <template #btn>
5
+ <slot name="search-bar-btn"></slot>
6
+ <el-button round @click="tableRef.openCustom()"><CmpIcon name="Tools" /></el-button>
7
+ </template>
8
+ </SearchBar>
9
+ <slot name="table-header-left"></slot>
10
+ <div ref="tableContentRef">
11
+ <VxeTable
12
+ :id="id"
13
+ ref="tableRef"
14
+ :loading="loading || apiLoading"
15
+ :data="api ? tableData : data"
16
+ v-bind="tableConfigCmptd"
17
+ :height="computedTableHeight"
18
+ @checkbox-change="checkboxChangeHandle"
19
+ @checkbox-all="checkboxChangeHandle"
20
+ border
21
+ >
22
+ <vxe-column
23
+ v-if="searchForm.showCheckBox"
24
+ type="checkbox"
25
+ :width="locale == 'zh_cn' ? 80 : 150"
26
+ fixed="left"
27
+ :title="$t('xu-hao')"
28
+ >
29
+ <template #checkbox="{ rowIndex, row, checked, disabled, indeterminate }">
30
+ <div class="center">
31
+ <!-- :checked="checked" 绑定checked值时 全选不会更新状态 -->
32
+ <el-checkbox
33
+ v-if="checked"
34
+ :checked="true"
35
+ :disabled="disabled"
36
+ size="default"
37
+ @click.stop="!disabled && toggleCheckboxEvent(row)"
38
+ />
39
+ <el-checkbox
40
+ v-else
41
+ :disabled="disabled"
42
+ :checked="false"
43
+ size="default"
44
+ @click.stop="!disabled && toggleCheckboxEvent(row)"
45
+ />
46
+ <el-text type="info" class="mgl5">{{ rowIndex + 1 }}</el-text>
47
+ </div>
48
+ </template>
49
+ </vxe-column>
50
+ <slot></slot>
51
+ </VxeTable>
52
+ </div>
53
+
54
+ <el-pagination
55
+ v-if="searchForm.showPage"
56
+ v-model:current-page="pageData.pageNo"
57
+ class="myPagination mgt10"
58
+ background
59
+ layout="total, sizes, prev, pager, next"
60
+ :total="pageData.total"
61
+ :page-size="pageData.pageSize"
62
+ :page-sizes="[10, 50, 100, 500]"
63
+ @current-change="handleCurrentChange"
64
+ @size-change="handleSizeChange"
65
+ />
66
+ </div>
67
+ </template>
68
+
69
+ <script setup>
70
+ import SearchBar from '@/components/SearchBar/index.vue';
71
+ import CmpIcon from '@/components/CmpIcon/index.vue';
72
+ import { computed, reactive, ref, onMounted, onUnmounted, nextTick } from 'vue';
73
+ import { formatFieldValue } from '../SearchBar/tools';
74
+ import { cloneDeep, debounce } from 'lodash';
75
+ import { useI18n } from 'vue-i18n';
76
+ const { t, locale } = useI18n();
77
+ const props = defineProps({
78
+ // 表格id 本地存储列设置时使用
79
+ id: {
80
+ type: String,
81
+ required: true,
82
+ },
83
+ loading: {
84
+ type: Boolean,
85
+ default: false,
86
+ },
87
+ // 是否立即请求
88
+ immediate: {
89
+ type: Boolean,
90
+ default: true,
91
+ },
92
+ searchForm: {
93
+ type: Object,
94
+ default: () => {
95
+ return {
96
+ items: [],
97
+ selections: [],
98
+ showSearch: false,
99
+ showPage: false,
100
+ showShadow: false,
101
+ showCheckBox: false,
102
+ };
103
+ },
104
+ },
105
+ api: {
106
+ type: Function,
107
+ },
108
+ data: {
109
+ type: Array,
110
+ },
111
+ tableConfig: {
112
+ type: Object,
113
+ default: () => ({}),
114
+ },
115
+ });
116
+ const apiLoading = ref(false);
117
+ const tableData = ref([]);
118
+ const resetData = cloneDeep(props.searchForm.items);
119
+ const pageData = reactive({
120
+ pageNo: 1,
121
+ pageSize: 10,
122
+ total: 0,
123
+ });
124
+ const tableContentRef = ref(null);
125
+ const tableHeight = ref(450);
126
+ // 计算表格高度
127
+ const calculateTableHeight = debounce(() => {
128
+ nextTick(() => {
129
+ if (!tableContentRef.value) return;
130
+ const container = tableContentRef.value;
131
+ const containerRect = container.getBoundingClientRect();
132
+ const windowHeight = window.innerHeight;
133
+ // 计算容器顶部到视窗顶部的距离
134
+ const containerTop = containerRect.top;
135
+ const bottomReserved = 100;
136
+
137
+ // 计算表格可用高度
138
+ const availableHeight = windowHeight - containerTop - bottomReserved;
139
+
140
+ // 设置最小高度
141
+ const minHeight = 200;
142
+ tableHeight.value = Math.max(availableHeight, minHeight);
143
+ });
144
+ }, 100);
145
+
146
+ // 监听窗口大小变化
147
+ const handleResize = () => {
148
+ calculateTableHeight();
149
+ };
150
+
151
+ onMounted(() => {
152
+ calculateTableHeight();
153
+ window.addEventListener('resize', handleResize);
154
+ });
155
+
156
+ onUnmounted(() => {
157
+ window.removeEventListener('resize', handleResize);
158
+ });
159
+ // 计算表格高度,优先使用配置的高度,否则使用自适应高度
160
+ const computedTableHeight = computed(() => {
161
+ return tableConfigCmptd.value.height || tableHeight.value;
162
+ });
163
+ async function getListHandle(prm) {
164
+ const { dataFormat } = props.tableConfig;
165
+ apiLoading.value = true;
166
+ const { code, data = {} } = await props.api(prm).finally(() => {
167
+ apiLoading.value = false;
168
+ props.searchForm.selections = [];
169
+ });
170
+ if (code == 200) {
171
+ let resData = data.rows || [];
172
+ tableData.value = dataFormat ? dataFormat(resData) : resData;
173
+ pageData.total = data.totalRows || 0;
174
+ }
175
+ }
176
+
177
+ function getList(prm = {}) {
178
+ getListHandle({ ...pageData, ...formatFieldValue(props.searchForm.items), ...prm });
179
+ }
180
+ async function reset() {
181
+ const { reset } = props.tableConfig;
182
+ props.searchForm.items.forEach((item) => {
183
+ const resetItem = resetData.find((item2) => item2.key == item.key);
184
+ if (resetItem) {
185
+ item.value = resetItem.value;
186
+ }
187
+ });
188
+ reset && (await reset());
189
+ getList();
190
+ }
191
+ if (props.api && props.immediate) {
192
+ getList();
193
+ }
194
+ function getPrm() {
195
+ return { ...pageData, ...formatFieldValue(props.searchForm.items) };
196
+ }
197
+ const tableRef = ref(null);
198
+ defineExpose({ tableRef, getList, getPrm, calculateTableHeight });
199
+
200
+ const customConfig = reactive({
201
+ storage: true,
202
+ });
203
+ const columnConfig = reactive({
204
+ drag: true,
205
+ resizable: true,
206
+ maxFixedSize: 0,
207
+ });
208
+ const tableConfigCmptd = computed(() => {
209
+ const {
210
+ customConfig: prppCustomConfig,
211
+ columnConfig: propColumnConfig,
212
+ ...other
213
+ } = props.tableConfig;
214
+ return {
215
+ headerAlign: 'left',
216
+ align: 'center',
217
+ customConfig: { ...customConfig, ...prppCustomConfig },
218
+ 'column-config': { ...columnConfig, ...propColumnConfig },
219
+ // 列过多时 自动宽度 虚拟滚动会闪屏 关闭虚拟滚动
220
+ 'virtual-x-config': {
221
+ enabled: false,
222
+ scrollToLeftOnChange: true,
223
+ },
224
+ // 纵向虚拟滚动 大于100条开启
225
+ 'virtual-y-config': { enabled: true, gt: 100 },
226
+ ...other,
227
+ };
228
+ });
229
+ // 分页相关:监听页码切换事件
230
+ const handleCurrentChange = (val) => {
231
+ pageData.pageNo = val;
232
+ getListHandle({ ...pageData, ...formatFieldValue(props.searchForm.items) });
233
+ };
234
+ // 分页相关:监听单页显示数量切换事件
235
+ const handleSizeChange = (val) => {
236
+ pageData.pageSize = val;
237
+ pageData.pageNo = 1;
238
+ getListHandle({ ...pageData, ...formatFieldValue(props.searchForm.items) });
239
+ };
240
+ function checkboxChangeHandle() {
241
+ const sles = tableRef.value.getCheckboxRecords();
242
+ props.searchForm.selections = sles;
243
+ }
244
+ const toggleCheckboxEvent = (row) => {
245
+ const $table = tableRef.value;
246
+ if ($table) {
247
+ $table.toggleCheckboxRow(row);
248
+ const sles = tableRef.value.getCheckboxRecords();
249
+ props.searchForm.selections = sles;
250
+ }
251
+ };
252
+ </script>
253
+
254
+ <style lang="scss" scoped>
255
+ :deep(.vxe-table-custom-wrapper.placement--top-right.is--active) {
256
+ max-height: 400px !important;
257
+ }
258
+ </style>
@@ -0,0 +1,112 @@
1
+ <template>
2
+ <div class="searchBar-container mgt10 mgb10" @keydown.enter="onSearch">
3
+ <!-- 搜索条件 -->
4
+ <div class="searchBar" :style="{ height: open ? 'auto' : '42px' }">
5
+ <el-form inline :model="form.data">
6
+ <template v-for="item in form.items.filter((item) => !item.hidden)" :key="item.key">
7
+ <br v-if="item.type == 'br'" />
8
+ <el-form-item v-else :label="item.name">
9
+ <el-input
10
+ v-if="item.type === 'input'"
11
+ v-model="item.value"
12
+ :class="item.option?.class || 'w150'"
13
+ :placeholder="$t('qing-shu-ru')"
14
+ clearable
15
+ />
16
+ <component v-else-if="item.type === 'custom'" :is="item.render" />
17
+ <CmpDictionary
18
+ v-if="item.type === 'select'"
19
+ :class="item.option?.class || 'w150'"
20
+ v-model="item.value"
21
+ v-bind="item.option"
22
+ @change="onSearch"
23
+ @clear="onSearch"
24
+ />
25
+ <el-date-picker
26
+ v-if="item.type === 'date'"
27
+ :class="{ datetimerange: 'w300', daterange: 'w200' }[item.dateType] || 'w100'"
28
+ v-model="item.value"
29
+ :value-format="
30
+ { datetimerange: 'YYYY-MM-DD HH:mm:ss', daterange: 'YYYY-MM-DD' }[item.dateType] ||
31
+ 'YYYY-MM-DD'
32
+ "
33
+ :type="item.dateType || 'date'"
34
+ @focus="currDatePicker = item"
35
+ @change="onDateChange(item)"
36
+ :placeholder="$t('qing-xuan-ze')"
37
+ :start-placeholder="$t('kai-shi-shi-jian')"
38
+ :end-placeholder="$t('jie-shu-shi-jian')"
39
+ />
40
+ </el-form-item>
41
+ </template>
42
+ </el-form>
43
+ </div>
44
+ <div class="btns">
45
+ <div class="left">
46
+ <slot name="table-header-left"></slot>
47
+ </div>
48
+ <div class="right">
49
+ <el-link underline="never" class="mgr10" @click="open = !open">
50
+ <cmpIcon :name="open ? `ArrowUp` : `ArrowDown`" /> {{ openText }}
51
+ </el-link>
52
+ <el-button type="primary" @click="onSearch">{{ $t('shai-xuan') }}</el-button>
53
+ <el-button type="primary" @click="onReset">{{ $t('chong-zhi') }}</el-button>
54
+ <!-- 插槽填入自定义按钮 -->
55
+ <slot name="btn"></slot>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </template>
60
+
61
+ <script setup>
62
+ import { computed, defineComponent, ref, toValue } from 'vue';
63
+ import cmpIcon from '@/components/CmpIcon/index.vue';
64
+ import CmpDictionary from '@/components/CmpDictionary/index.vue';
65
+ import { useI18n } from 'vue-i18n';
66
+ const props = defineProps({
67
+ form: {
68
+ type: Object,
69
+ required: true,
70
+ },
71
+ });
72
+ const emits = defineEmits(['confirm', 'reset']);
73
+ const open = ref(true);
74
+ const currDatePicker = ref(null);
75
+ const { t } = useI18n();
76
+ const openText = computed(() => (open.value ? t('guan-bi') : t('zhan-kai')));
77
+ function onSearch() {
78
+ emits('confirm');
79
+ }
80
+ function onReset() {
81
+ emits('reset');
82
+ }
83
+ function onDateChange() {
84
+ emits('confirm');
85
+ }
86
+ </script>
87
+
88
+ <style lang="scss" scoped>
89
+ .searchBar-container {
90
+ .searchBar {
91
+ overflow: hidden;
92
+ transition: all 0.3s;
93
+ }
94
+
95
+ .btns {
96
+ display: flex;
97
+ justify-content: space-between;
98
+ align-items: center;
99
+ // margin-top: -10px;
100
+ .left {
101
+ display: flex;
102
+ align-items: center;
103
+ :deep(.el-tabs__header) {
104
+ margin: 0;
105
+ }
106
+ }
107
+ .right {
108
+ text-align: right;
109
+ }
110
+ }
111
+ }
112
+ </style>
@@ -0,0 +1,39 @@
1
+ /*
2
+ * @Author: zhouyunjie bernie.zhou@joy-group.com
3
+ * @Date: 2025-05-09 17:43:52
4
+ * @LastEditors: zhouyunjie bernie.zhou@joy-group.com
5
+ * @LastEditTime: 2025-05-19 16:58:25
6
+ * @FilePath: /plm-system-web/src/components/SearchBar/tools.js
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ */
9
+ import { stringToArray } from "@/utils/tools/tools";
10
+ import { dayjs } from "element-plus";
11
+
12
+ // 处理查询表单数据
13
+ export function formatFieldValue(items) {
14
+ let resData = {};
15
+ items.forEach(field => {
16
+ const { key, type, option = {}, value, dateType, toArr } = field
17
+ if (type == 'input' && option?.multiple) {
18
+ resData[key] = stringToArray(value);
19
+ } else if (type == 'date') {
20
+ if (dateType.includes('range')) {
21
+ const [start, end] = key
22
+ let [startVal, endVal] = value || []
23
+ if (startVal && dateType == 'daterange') {
24
+ startVal = dayjs(startVal).format('YYYY-MM-DD 00:00:00')
25
+ endVal = dayjs(endVal).format('YYYY-MM-DD 23:59:59')
26
+ }
27
+ resData[start] = startVal;
28
+ resData[end] = endVal;
29
+ } else {
30
+ resData[key] = value;
31
+ }
32
+ } else if (type == 'select') {
33
+ resData[key] = toArr ? [value] : value;
34
+ } else {
35
+ resData[key] = value;
36
+ }
37
+ });
38
+ return resData
39
+ }
@@ -0,0 +1,54 @@
1
+ /*
2
+ * @Author: zhouyunjie bernie.zhou@joy-group.com
3
+ * @Date: 2025-06-11 14:43:19
4
+ * @LastEditors: zhouyunjie bernie.zhou@joy-group.com
5
+ * @LastEditTime: 2025-07-02 17:46:48
6
+ * @FilePath: /mdm-system-web/src/components/VxeTable/index.jsx
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ */
9
+ import { VxeUI } from 'vxe-pc-ui'
10
+ import EnumRender from './render/EnumRender.vue'
11
+ import { ElInputNumber, ElText } from 'element-plus'
12
+ import { getValueBykey } from '@/utils/tools/tools';
13
+ import { useI18n } from 'vue-i18n';
14
+ // renderOpts, params
15
+ // renderOpts cellRenderer 传入的参数
16
+ // params 行列参数
17
+
18
+
19
+ // 枚举值渲染器
20
+ VxeUI.renderer.add('Enum', {
21
+ // 默认显示模板
22
+ renderTableDefault(renderOpts, params) {
23
+ if ([null, undefined].includes(params.row[params.column.field])) return '-'
24
+ return <EnumRender renderOpts={renderOpts} params={params} />
25
+ }
26
+ })
27
+ // 枚举值渲染器
28
+ VxeUI.renderer.add('InputNumber', {
29
+ // 默认显示模板
30
+ renderTableDefault(renderOpts, params) {
31
+ return <ElInputNumber v-model={params.row[params.column.field]} {...renderOpts.props} step-strictly value-on-clear={0} />
32
+ }
33
+ })
34
+ // 真假值渲染器
35
+ VxeUI.renderer.add('TrueFalse', {
36
+ // 默认显示模板
37
+ renderTableDefault(renderOpts, params) {
38
+ return <ElText type={params.row[params.column.field] ? 'success' : 'danger'}>{params.row[params.column.field] ? '是' : '否'}</ElText>
39
+ }
40
+ })
41
+
42
+ // 真假值渲染器
43
+ VxeUI.renderer.add('I18n', {
44
+ // 默认显示模板
45
+ renderTableDefault(renderOpts, params) {
46
+ const { locale } = useI18n()
47
+ let key = locale.value == 'zh_cn' ? params.column.field : renderOpts.fieldEn
48
+ // 链式字符串访问
49
+ if (key.includes('.')) {
50
+ return getValueBykey(params.row, key) || params.row[params.column.field]
51
+ }
52
+ return params.row[key] || params.row[params.column.field]
53
+ }
54
+ })
@@ -0,0 +1,33 @@
1
+ <!--
2
+ * @Author: zhouyunjie bernie.zhou@joy-group.com
3
+ * @Date: 2025-05-15 18:26:42
4
+ * @LastEditors: zhouyunjie bernie.zhou@joy-group.com
5
+ * @LastEditTime: 2025-05-19 11:21:11
6
+ * @FilePath: /plm-system-web/src/components/VxeTable/render/EnumRender.vue
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ -->
9
+ <template>
10
+ <el-text v-if="renderOpts.text">
11
+ {{ renderOpts.enum.getName(cellValue) }}
12
+ </el-text>
13
+ <el-text v-else :type="renderOpts.enum.getTag(cellValue)">
14
+ {{ renderOpts.enum.getName(cellValue) }}
15
+ </el-text>
16
+ </template>
17
+
18
+ <script setup>
19
+ const props = defineProps({
20
+ renderOpts: {
21
+ type: Object,
22
+ default: () => ({
23
+ enum: {},
24
+ }),
25
+ },
26
+ params: {
27
+ type: Object,
28
+ },
29
+ });
30
+ const cellValue = props.params.row[props.params.column.field];
31
+ </script>
32
+
33
+ <style></style>
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import {JoyForm} from './components/JoyForm/index.js';
2
+
3
+ // 导出组件
4
+ export { JoyForm }
5
+
6
+ // 默认导出所有组件
7
+ export default {
8
+ install: (app) => {
9
+ app.component(JoyForm.name, JoyForm);
10
+ }
11
+ };
package/src/main.js ADDED
@@ -0,0 +1,16 @@
1
+ /*
2
+ * @Date: 2022-05-22 20:44:25
3
+ * @Description:
4
+ */
5
+ import { createApp } from 'vue';
6
+ import App from './App.vue';
7
+ import JoyAdminComponents from './index.js';
8
+ import ElementPlus from "element-plus";
9
+ import "element-plus/theme-chalk/display.css"; // 引入基于断点的隐藏类
10
+ import "element-plus/dist/index.css";
11
+
12
+ const app = createApp(App);
13
+ app.use(JoyAdminComponents);
14
+ app.use(ElementPlus);
15
+
16
+ app.mount("#app");
package/vite.config.js ADDED
@@ -0,0 +1,38 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [vue()],
7
+ define: {
8
+ // enable hydration mismatch details in production build
9
+ __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "true",
10
+ },
11
+ base: "./",
12
+ build: {
13
+ lib: {
14
+ entry: "./src/index.js",
15
+ name: "JoyAdminComponents",
16
+ fileName: (format) => `joy-admin-components.${format}.js`,
17
+ },
18
+ rollupOptions: {
19
+ external: ["vue", "element-plus"],
20
+ output: {
21
+ globals: {
22
+ vue: "Vue",
23
+ "element-plus": "ElementPlus",
24
+ },
25
+ },
26
+ },
27
+ },
28
+ server: {
29
+ watch: {
30
+ usePolling: true, // 修复HMR热更新失效
31
+ overlay: true,
32
+ },
33
+ port: 3008,
34
+ host: "0.0.0.0",
35
+ hmr: true,
36
+ open: true,
37
+ },
38
+ });
@@ -1,38 +0,0 @@
1
- import { ElForm as d, ElRow as i, ElCol as s } from "element-plus";
2
- import { defineComponent as c, ref as y, onMounted as g, h as r } from "vue";
3
- const u = c({
4
- name: "JoyForm",
5
- setup(o, { slots: t, attrs: n, emit: m }) {
6
- const f = y(null), a = () => t.default ? t.default().filter((e) => e.type !== Symbol.for("v-cmt")).map((e) => e.type === Symbol.for("v-fgt") ? e.children : e).flat().map(
7
- (e) => {
8
- var l;
9
- return r(
10
- s,
11
- { span: ((l = e.props) == null ? void 0 : l.span) || 24 },
12
- {
13
- default: () => e
14
- }
15
- );
16
- }
17
- ) : null;
18
- g(() => {
19
- m("ref", f.value);
20
- });
21
- function p() {
22
- return r(
23
- d,
24
- { ...n, ref: f },
25
- () => r(i, { gutter: n.gutter ?? 20 }, { default: () => a() })
26
- );
27
- }
28
- return p;
29
- }
30
- }), S = {
31
- install: (o) => {
32
- o.component(u.name, u);
33
- }
34
- };
35
- export {
36
- u as JoyForm,
37
- S as default
38
- };
@@ -1 +0,0 @@
1
- (function(e,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("element-plus"),require("vue")):typeof define=="function"&&define.amd?define(["exports","element-plus","vue"],t):(e=typeof globalThis<"u"?globalThis:e||self,t(e.JoyAdminComponents={},e.ElementPlus,e.Vue))})(this,function(e,t,o){"use strict";const r=o.defineComponent({name:"JoyForm",setup(f,{slots:u,attrs:i,emit:m}){const l=o.ref(null),s=()=>u.default?u.default().filter(n=>n.type!==Symbol.for("v-cmt")).map(n=>n.type===Symbol.for("v-fgt")?n.children:n).flat().map(n=>{var d;return o.h(t.ElCol,{span:((d=n.props)==null?void 0:d.span)||24},{default:()=>n})}):null;o.onMounted(()=>{m("ref",l.value)});function a(){return o.h(t.ElForm,{...i,ref:l},()=>o.h(t.ElRow,{gutter:i.gutter??20},{default:()=>s()}))}return a}}),p={install:f=>{f.component(r.name,r)}};e.JoyForm=r,e.default=p,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});