widget.qw 1.0.43 → 1.0.45

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "widget.qw",
3
3
  "private": false,
4
- "version": "1.0.43",
4
+ "version": "1.0.45",
5
5
  "description": "marqstree Vue3组件库",
6
6
  "main": "build/widget.qw.es.js",
7
7
  "keywords": [
@@ -111,7 +111,8 @@ watch(() => JSON.stringify(modelValue.value), async (n, o) => {
111
111
  data.selectedNodeId = modelValue.value
112
112
  } else {
113
113
  data.selectedNodes = []
114
- modelValue.value.forEach(async(id) => {
114
+ let ids = modelValue.value || []
115
+ ids.forEach(async(id) => {
115
116
  let params = {}
116
117
  params.id = id
117
118
  params.isReturnParent = false
@@ -0,0 +1,254 @@
1
+ <template>
2
+ <div class="widget">
3
+ <div class="option" v-for="(item, index) in modelValue" :key="index">
4
+ <div style=" display: flex;align-items: center;">
5
+ <van-icon name="arrow-left" @click.stop="onForward(index)" />
6
+ <div style="padding: 5px 5px;">
7
+ <div v-for="(k, j) in visiableKeys" :key="j" style="padding:2px 2px;">
8
+ {{ `${key2name(k)}:${key2value(k, item)}` }}
9
+ </div>
10
+ </div>
11
+
12
+ <div style="display: flex;flex-direction:column;cursor: pointer;">
13
+ <van-icon name="edit" v-if="props.schema.auth != 'readonly'" @click.stop="onEdit(index)" />
14
+
15
+ <van-icon name="arrow" @click.stop="onBack(index)" style="margin:8px 0" />
16
+
17
+ <van-icon name="cross" v-if="props.schema.auth != 'readonly'" @click.stop="onDelete(index)" />
18
+ </div>
19
+ </div>
20
+ </div>
21
+
22
+ <van-icon name="plus" v-if="props.isShowAdd && !data.isShowInput" @click="onNewOption" />
23
+
24
+ <van-form v-if="data.isShowInput" :model="data.inputValue">
25
+ <van-field v-for="(key, i) in keys" :key="i" :label="props.schema[key].label"
26
+ v-show="props.schema[key].auth != 'gone'" :required="props.schema[key].auth === 'required'"
27
+ :disabled="props.schema[key].auth === 'readonly'">
28
+
29
+ <template #input>
30
+ <van-field v-if="props.schema[key].type == 'String' && !props.schema[key]?.schema?.options"
31
+ v-model="data.inputValue[key]" :placeholder="`请输入${key2name(key)}`" type="text" />
32
+
33
+ <van-field v-if="props.schema[key].type == 'Number' && !props.schema[key]?.schema?.options"
34
+ v-model.number="data.inputValue[key]" :placeholder="`请输入${key2name(key)}`" type="number" />
35
+
36
+ <van-switch v-if="props.schema[key].type == 'Boolean'" v-model="data.inputValue[key]" />
37
+
38
+ <DatetimePicker v-if="props.schema[key].type == 'Datetime'" v-model="data.inputValue[key]"
39
+ placeholder="选择时间" />
40
+
41
+ <DateSelector v-if="props.schema[key]?.schema?.options"
42
+ v-model="data.inputValue[key]"
43
+ :options="props.schema[key]?.schema?.options"
44
+ :placeholder="`请输入${key2name(key)}`" :columnsFieldNames="{
45
+ text: (props.schema[key]?.schema?.options || []).length > 0 ? Object.keys(props.schema[key].schema.options[0])[0] : '',
46
+ value: (props.schema[key]?.schema?.options || []).length > 0 ? Object.keys(props.schema[key].schema.options[0])[1] : ''
47
+ }" @select="onChangeOption(key)" />
48
+
49
+ <ObjsEditor v-if="props.schema[key].type == 'Array' && props.schema[key].auth != 'gone'"
50
+ v-model="data.inputValue[key]" :schema="props.schema[key].schema" />
51
+ </template>
52
+ </van-field>
53
+
54
+ <div style="margin: 16px; display: flex;align-items: center;justify-content: center;">
55
+ <van-button v-if="data.isShowInput" @click="onInputConfirm" type="primary" size="mini">保存</van-button>
56
+ <van-button v-if="data.isShowInput" @click="onInputCancel" type="primary" size="mini">取消</van-button>
57
+ </div>
58
+ </van-form>
59
+
60
+ </div>
61
+ </template>
62
+
63
+ <script setup>
64
+ import { nextTick, ref, onMounted, defineProps, defineEmits, reactive, computed } from 'vue'
65
+ import { useVModel } from '@vueuse/core'
66
+ import util from '@/util'
67
+ import DatetimePicker from './DatetimePicker/index.vue'
68
+ import DateSelector from './data_selector.vue'
69
+
70
+ const props = defineProps({
71
+ modelValue: {
72
+ type: Array,
73
+ default: []
74
+ },
75
+ isShowAdd: {
76
+ type: Boolean,
77
+ default: true
78
+ },
79
+ schema: {
80
+ type: Object,
81
+ default: {
82
+ type: "String",
83
+ label: '名称',
84
+ default: '',
85
+ //权限取值范围: required 必填 option 选填 readonly 只读 gone 隐藏
86
+ auth: 'option'
87
+ }
88
+ }
89
+ })
90
+
91
+ const emit = defineEmits(['update:modelValue'])
92
+ const modelValue = useVModel(props, 'modelValue', emit)
93
+
94
+ const data = reactive({
95
+ inputValue: {},
96
+ isShowInput: false,
97
+ selectIndex: -1
98
+ })
99
+ const keys = Object.keys(props.schema)
100
+ const visiableKeys = keys.filter(k => props.schema[k].auth != 'gone')
101
+
102
+ onMounted(() => {
103
+ init()
104
+ })
105
+
106
+ const init = () => {
107
+ if (modelValue.value)
108
+ return
109
+
110
+ modelValue.value = []
111
+ }
112
+
113
+ const makeDefaultValue = () => {
114
+ let res = {}
115
+
116
+ keys.forEach(key => {
117
+ // 复制默认值,防止对象类型的默认值被修改
118
+ if (props.schema[key].default === 'uuid')
119
+ res[key] = util.uuid8()
120
+ else
121
+ res[key] = JSON.parse(JSON.stringify(props.schema[key].default))
122
+ })
123
+
124
+ return res
125
+ }
126
+
127
+ const onForward = (index) => {
128
+ util.arySwapPrev(modelValue.value, index)
129
+ }
130
+
131
+ const onBack = (index) => {
132
+ util.arySwapNext(modelValue.value, index)
133
+ }
134
+
135
+ const onDelete = (index) => {
136
+ modelValue.value.splice(index, 1)
137
+ }
138
+
139
+ const key2name = (key) => {
140
+ return props.schema[key].label
141
+ }
142
+
143
+ const key2value = (key, item) => {
144
+ console.log('key2value', key, item[key], item)
145
+
146
+ let v = item[key]
147
+ if (Array.isArray(v)) {
148
+ let formatValues = v.map(e => {
149
+ let kvs = Object.keys(e).map(k => {
150
+ let label = props.schema[key].schema[k].label
151
+ let formatValue = e[k]
152
+
153
+ //下拉框选项,用选项的第二个属性value找选中的选项
154
+ //再取选项的第一个属性name作为标题
155
+ if (props.schema[key].schema[k]?.schema?.options) {
156
+ let one = props.schema[key].schema[k]?.schema?.options.find(item => item[Object.keys(item)[1]] === formatValue)
157
+ formatValue = one[Object.keys(one)[0]]
158
+ }
159
+ return `${label}:${formatValue}`
160
+ })
161
+ return kvs.join(',')
162
+ })
163
+
164
+ return formatValues.join(' > ')
165
+ }
166
+ //下拉框选项,用选项的第二个属性value找选中的选项
167
+ //再取选项的第一个属性name作为标题
168
+ else if (props.schema[key]?.schema?.options) {
169
+ let one = props.schema[key]?.schema?.options.find(item => item[Object.keys(item)[1]] === v)
170
+ return one[Object.keys(one)[0]]
171
+ }
172
+ else {
173
+ return v
174
+ }
175
+ }
176
+
177
+ const onEdit = (index) => {
178
+ data.isShowInput = true
179
+ data.selectIndex = index
180
+ data.inputValue = modelValue.value[data.selectIndex]
181
+ }
182
+
183
+ const onNewOption = () => {
184
+ data.selectIndex = -1
185
+ data.inputValue = makeDefaultValue()
186
+ data.isShowInput = true
187
+ }
188
+
189
+ const onInputConfirm = () => {
190
+ let idx = modelValue.value.indexOf(item => JSON.stringify(item) === JSON.stringify(data.inputValue))
191
+ if (idx > -1 && idx != data.selectIndex) {
192
+ util.warnToast('重复添加')
193
+ return
194
+ }
195
+
196
+ if (!data.inputValue) {
197
+ data.isShowInput = false
198
+ data.inputValue = makeDefaultValue()
199
+ return
200
+ }
201
+
202
+ if (data.selectIndex < 0) {
203
+ modelValue.value.push(data.inputValue)
204
+ } else {
205
+ modelValue.value[data.selectIndex] = data.inputValue
206
+ }
207
+
208
+ data.isShowInput = false
209
+ data.inputValue = makeDefaultValue()
210
+ }
211
+
212
+ const onInputCancel = () => {
213
+ data.isShowInput = false
214
+ data.inputValue = makeDefaultValue()
215
+ }
216
+
217
+ const onChangeOption = (key) => {
218
+ console.log('onChangeOption', key, data.inputValue[key], props.schema[key]?.schema?.options)
219
+ let val = data.inputValue[key]
220
+ let options = props.schema[key].schema.options
221
+
222
+ //用选中对象给输入项的同名属性值赋值
223
+ let option = options.find(item => item[Object.keys(item)[1]] === val)
224
+ Object.keys(option).forEach(k => {
225
+ if (props.schema[k])
226
+ data.inputValue[k] = option[k]
227
+ })
228
+ }
229
+ </script>
230
+
231
+ <style lang="scss" scoped>
232
+ .widget {
233
+ background: #fff;
234
+ box-sizing: border-box;
235
+ height: 100%;
236
+ text-align: left;
237
+
238
+ .option {
239
+ margin: 8px 8px;
240
+ padding: 5px 5px;
241
+ border-radius: 4px;
242
+ background-color: rgb(217, 236, 255);
243
+ color: #409eff;
244
+ display: inline-block;
245
+ position: relative;
246
+ }
247
+
248
+ }
249
+
250
+ :deep(.van-field__label) {
251
+ margin:auto;
252
+
253
+ }
254
+ </style>
@@ -83,6 +83,13 @@ const props = defineProps({
83
83
  modelValue: {
84
84
  type: [Array, String, Number]
85
85
  },
86
+ columnsFieldNames:{
87
+ type:Object,
88
+ default:{
89
+ text:'text',
90
+ value:'value'
91
+ }
92
+ },
86
93
  rules: {
87
94
  type: Array,
88
95
  default: () => []
@@ -118,6 +125,17 @@ const searchKeyword = ref(null);
118
125
  const checkboxRefs = ref([]);
119
126
  const checkedValue = ref([]);
120
127
  const isAllChecked = ref(false);
128
+ const formatOptions = computed(()=>{
129
+ if(!props.options)
130
+ return []
131
+
132
+ return props.options.map(item=>{
133
+ return {
134
+ text:item[props.columnsFieldNames.text],
135
+ value:item[props.columnsFieldNames.value]
136
+ }
137
+ })
138
+ })
121
139
 
122
140
  const findTextsByValues = (valuesToFind, data) => {
123
141
  return valuesToFind.map((value) => {
@@ -127,20 +145,20 @@ const findTextsByValues = (valuesToFind, data) => {
127
145
  };
128
146
 
129
147
  const values2items = (values)=>{
130
- if(!values || values.length<1 || !props.options || props.options.length<1)
148
+ if(!values || values.length<1 || !formatOptions.value || formatOptions.value.length<1)
131
149
  return []
132
150
 
133
- let res = props.options.filter(option=>values.includes(option.value))
151
+ let res = formatOptions.value.filter(option=>values.includes(option.value))
134
152
  return res
135
153
  }
136
154
 
137
155
  const reShow = () => {
138
156
  if (props.modelValue) {
139
- if (Array.isArray(props.options)) {
157
+ if (Array.isArray(formatOptions.value)) {
140
158
  if (props.multiple) {
141
- selectedItems.value = findTextsByValues(props.modelValue, props.options);
159
+ selectedItems.value = findTextsByValues(props.modelValue, formatOptions.value);
142
160
  } else {
143
- const foundItem = props.options.find((item) => item.value === props.modelValue);
161
+ const foundItem = formatOptions.value.find((item) => item.value === props.modelValue);
144
162
  selectedItems.value = foundItem ? [foundItem.text] : [];
145
163
  }
146
164
  }
@@ -180,11 +198,11 @@ const cancelSelection = () => {
180
198
 
181
199
  const confirmSelection = () => {
182
200
  if (props.multiple) {
183
- selectedItems.value = findTextsByValues(checkedValue.value, props.options);
201
+ selectedItems.value = findTextsByValues(checkedValue.value, formatOptions.value);
184
202
  emit("update:modelValue", checkedValue.value);
185
203
  emit("select",values2items(checkedValue.value))
186
204
  } else {
187
- const foundItem = props.options.find((item) => item.value === checkedValue.value);
205
+ const foundItem = formatOptions.value.find((item) => item.value === checkedValue.value);
188
206
  selectedItems.value = foundItem ? [foundItem.text] : [];
189
207
  emit("update:modelValue", checkedValue.value);
190
208
  emit("select", foundItem)
@@ -205,7 +223,7 @@ const radioToggle = (value) => {
205
223
  };
206
224
 
207
225
  const filteredColumns = computed(() => {
208
- return props.options.filter((item) => {
226
+ return formatOptions.value.filter((item) => {
209
227
  return item.text.includes(searchKeyword.value) || item.value.includes(searchKeyword.value);
210
228
  });
211
229
  });
@@ -228,7 +246,7 @@ watch(() => props.modelValue, (newVal) => {
228
246
  }, { deep: true, immediate: true });
229
247
 
230
248
  // 在options变化时也需要更新显示
231
- watch(() => props.options, () => {
249
+ watch(() => formatOptions.value, () => {
232
250
  reShow();
233
251
  }, { deep: true });
234
252
  </script>
@@ -27,6 +27,7 @@ import TreePicker from './TreePicker.vue'
27
27
  import CascaderPop from './CascaderPop.vue'
28
28
  import CascaderPicker from './CascaderPicker.vue'
29
29
  import SingleApiPicker from "./SingleApiPicker.vue";
30
+ import ObjsEditor from './ObjsEditor.vue'
30
31
 
31
32
  const components = [
32
33
  {
@@ -137,6 +138,10 @@ const components = [
137
138
  name: 'WidgetQwSingleApiPicker',
138
139
  widget: SingleApiPicker
139
140
  },
141
+ {
142
+ name: "WidgetQwObjsEditor",
143
+ widget: ObjsEditor,
144
+ },
140
145
  ];
141
146
  // 导出组件
142
147
  export const WidgetQw = {
@@ -149,7 +154,7 @@ export const WidgetQw = {
149
154
 
150
155
  // 导出初始化方法
151
156
  export function setup(params) {
152
- Object.keys(params).forEach(key=>{
157
+ Object.keys(params).forEach(key => {
153
158
  vm[key] = params[key]
154
159
  })
155
160
  }
package/src/env.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- /// <reference types="vite/client" />
2
-
3
- declare module '*.vue' {
4
- import type { DefineComponent } from 'vue'
5
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6
- const component: DefineComponent<{}, {}, any>
7
- export default component
8
- }
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module '*.vue' {
4
+ import type { DefineComponent } from 'vue'
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6
+ const component: DefineComponent<{}, {}, any>
7
+ export default component
8
+ }
@@ -149,6 +149,11 @@ const routes: Array<RouteRecordRaw> = [
149
149
  path: "/singleapipicker",
150
150
  name: "singleapipicker",
151
151
  component: () => import("../views/singleapipicker/index.vue"),
152
+ },
153
+ {
154
+ path: "/objseditor",
155
+ name: "objseditor",
156
+ component: () => import("../views/objseditor/index.vue"),
152
157
  }
153
158
  ]
154
159
 
package/src/util/index.js CHANGED
@@ -10,6 +10,7 @@ import * as ToastUtil from './toast_util'
10
10
  import * as EvalUtil from './eval_util'
11
11
  import * as ImageUtil from './image_util'
12
12
  import * as TreeUtil from './tree_util'
13
+ import * as UuidUtil from './uuid_util'
13
14
 
14
15
  export default {
15
16
  ...AuthUtil,
@@ -23,6 +24,7 @@ export default {
23
24
  ...EvalUtil,
24
25
  ...ImageUtil,
25
26
  ...TreeUtil,
27
+ ...UuidUtil,
26
28
 
27
29
  href(url) {
28
30
  window.location.href = url
@@ -0,0 +1,9 @@
1
+ export function uuid8() {
2
+ // 获取当前时间戳的后 8 位十六进制
3
+ let hex = Date.now().toString(16).slice(-8);
4
+ // 如果不足 8 位则补全
5
+ if (hex.length < 8) {
6
+ hex = '0'.repeat(8 - hex.length) + hex;
7
+ }
8
+ return hex;
9
+ }
Binary file
@@ -0,0 +1,180 @@
1
+ <template>
2
+ <div class="page">
3
+ <widget-qw-objs-editor v-model="data.datas" :schema="data.schema" />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup>
8
+ import { onMounted, reactive, watch } from "vue";
9
+
10
+ const data = reactive({
11
+ schema: {
12
+ id:{
13
+ label: 'id',
14
+ type: 'String',
15
+ // 自动生成8位uuid默认值
16
+ default: 'uuid',
17
+ //此字段是隐藏值
18
+ auth: 'gone',
19
+ },
20
+ name: {
21
+ label: '房号',
22
+ type: 'String',
23
+ default: '',
24
+ auth:'required'
25
+ },
26
+ finishQuantity:{
27
+ label: '完成数量',
28
+ type: 'Number',
29
+ default: 0,
30
+ auth: 'readonly'
31
+ },
32
+ quantity:{
33
+ label: '每天最多完成数量',
34
+ type: 'Number',
35
+ default: 0,
36
+ auth: 'option'
37
+ },
38
+ parts: {
39
+ label: '空调',
40
+ type: 'Array',
41
+ default: [],
42
+ auth:'required',
43
+ schema: {
44
+ partStandardId: {
45
+ label: '名称',
46
+ type: 'String',
47
+ default: '',
48
+ schema: {
49
+ options: [{
50
+ name:'内机',
51
+ value: 'nj',
52
+ title: '内机'
53
+ }]
54
+ }
55
+ },
56
+ quantity: {
57
+ label: '数量',
58
+ type: 'Number',
59
+ default: 1,
60
+ }
61
+ }
62
+ }
63
+ },
64
+ // schema: {
65
+ // title: {
66
+ // label: '标题',
67
+ // type: 'String',
68
+ // default: ''
69
+ // },
70
+ // time: {
71
+ // label: '时间',
72
+ // type: 'Datetime',
73
+ // default: ''
74
+ // },
75
+ // quantity: {
76
+ // label: '数量',
77
+ // type: 'Number',
78
+ // default: 1
79
+ // },
80
+ // isFinish: {
81
+ // label: '完成',
82
+ // type: 'Boolean',
83
+ // default: true
84
+ // },
85
+ // stages: {
86
+ // label: '工序',
87
+ // type: 'Array',
88
+ // default: [],
89
+ // schema: {
90
+ // title: {
91
+ // label: '名称',
92
+ // type: 'String',
93
+ // default: ''
94
+ // },
95
+ // hour: {
96
+ // label: '工时',
97
+ // type: 'Number',
98
+ // default: '1'
99
+ // },
100
+ // }
101
+ // },
102
+ // permission: {
103
+ // label: '权限',
104
+ // type: 'String',
105
+ // default: '',
106
+ // schema: {
107
+ // options: [
108
+ // {
109
+ // name: '查询所有工资单',
110
+ // value: 'salary_query_total',
111
+ // title: '查询所有工资单',
112
+
113
+ // },
114
+ // {
115
+ // name: '查询项目利润',
116
+ // value: 'project_profit',
117
+ // title: '查询项目利润'
118
+ // }
119
+ // ]
120
+ // }
121
+ // },
122
+ // role: {
123
+ // label: "角色",
124
+ // type: "String",
125
+ // default: ""
126
+ // },
127
+ // permissions: {
128
+ // label: "权限列表",
129
+ // type: "Array",
130
+ // default: [],
131
+ // schema: {
132
+ // title: {
133
+ // label: "名称",
134
+ // type: "String",
135
+ // default: ""
136
+ // },
137
+ // value: {
138
+ // label: "值",
139
+ // type: "String",
140
+ // default: "",
141
+ // // 注意:选项列表一定要放在值属性下面
142
+ // schema: {
143
+ // options: [
144
+ // {
145
+ // title: "查询所有工资单",
146
+ // value: "salary_query_total"
147
+ // },
148
+ // {
149
+ // title: "查询项目利润",
150
+ // value: "project_profit"
151
+ // }
152
+ // ]
153
+ // }
154
+ // }
155
+ // }
156
+ // }
157
+ //},
158
+ datas: [],
159
+ })
160
+
161
+ watch(() => data.datas, (n, o) => {
162
+ console.log('>>>>', n)
163
+ }, {
164
+ immediate: true
165
+ })
166
+ </script>
167
+
168
+ <style lang="scss" scoped>
169
+ .page {
170
+ height: 100vh;
171
+ background: #fff;
172
+ text-align: center;
173
+ background-size: cover;
174
+ display: flex;
175
+ flex-direction: column;
176
+ justify-content: flex-start;
177
+ padding: 80px 15px 0 15px;
178
+ box-sizing: border-box;
179
+ }
180
+ </style>
package/tsconfig.json CHANGED
@@ -1,19 +1,19 @@
1
- {
2
- "compilerOptions": {
3
- "target": "esnext",
4
- "useDefineForClassFields": true,
5
- "module": "esnext",
6
- "moduleResolution": "node",
7
- "strict": true,
8
- "jsx": "preserve",
9
- "sourceMap": true,
10
- "resolveJsonModule": true,
11
- "isolatedModules": true,
12
- "esModuleInterop": true,
13
- "lib": ["esnext", "dom"],
14
- "skipLibCheck": true,
15
- "allowJs": true,
16
- },
17
- "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
18
- "references": [{ "path": "./tsconfig.node.json" }]
19
- }
1
+ {
2
+ "compilerOptions": {
3
+ "target": "esnext",
4
+ "useDefineForClassFields": true,
5
+ "module": "esnext",
6
+ "moduleResolution": "node",
7
+ "strict": true,
8
+ "jsx": "preserve",
9
+ "sourceMap": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "esModuleInterop": true,
13
+ "lib": ["esnext", "dom"],
14
+ "skipLibCheck": true,
15
+ "allowJs": true,
16
+ },
17
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
18
+ "references": [{ "path": "./tsconfig.node.json" }]
19
+ }
@@ -1,8 +1,8 @@
1
- {
2
- "compilerOptions": {
3
- "composite": true,
4
- "module": "esnext",
5
- "moduleResolution": "node"
6
- },
7
- "include": ["vite.config.ts"]
8
- }
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "module": "esnext",
5
+ "moduleResolution": "node"
6
+ },
7
+ "include": ["vite.config.ts"]
8
+ }