Procházet zdrojové kódy

使用 useModel 绑定

icssoa před 1 rokem
rodič
revize
8381fe5e0b

+ 9 - 14
packages/crud/src/App.vue

@@ -6,6 +6,10 @@
 			<cl-row>
 				<cl-add-btn />
 				<cl-adv-btn />
+
+				<cl-flex1 />
+
+				<cl-search-key v-model="v1" @change="onChange" refreshOnInput></cl-search-key>
 			</cl-row>
 
 			<cl-row>
@@ -20,10 +24,6 @@
 			<cl-upsert ref="Upsert"></cl-upsert>
 			<cl-adv-search ref="AdvSearch"></cl-adv-search>
 		</cl-crud>
-
-		<cl-dialog v-model="visible" height="20vh">
-			<div style="height: 50px" v-for="d in 100" :key="d">{{ d }}</div>
-		</cl-dialog>
 	</div>
 </template>
 
@@ -38,7 +38,11 @@ interface Data {
 	[key: string]: any;
 }
 
-const visible = ref(true);
+const v1 = ref();
+
+function onChange() {
+	console.log(1111);
+}
 
 const Upsert = useUpsert<Data>({
 	items: [
@@ -152,15 +156,6 @@ const Crud = useCrud(
 );
 
 const Form = useForm<Data>();
-
-Form.value?.open({
-	items: [
-		{
-			type: "tabs",
-			prop: "age"
-		}
-	]
-});
 </script>
 
 <style scoped>

+ 27 - 28
packages/crud/src/components/search-key/index.tsx

@@ -1,6 +1,7 @@
-import { defineComponent, ref, watch, computed, PropType } from "vue";
+import { defineComponent, ref, computed, type PropType, useModel } from "vue";
 import { useConfig, useCore } from "../../hooks";
 import { parsePx } from "../../utils";
+import { debounce } from "lodash-es";
 
 export default defineComponent({
 	name: "cl-search-key",
@@ -26,7 +27,9 @@ export default defineComponent({
 		width: {
 			type: [String, Number],
 			default: 300
-		}
+		},
+		// 是否实时刷新
+		refreshOnInput: Boolean
 	},
 
 	emits: ["update:modelValue", "change", "field-change"],
@@ -57,17 +60,7 @@ export default defineComponent({
 		});
 
 		// 搜索内容
-		const value = ref("");
-
-		watch(
-			() => props.modelValue,
-			(val) => {
-				value.value = val || "";
-			},
-			{
-				immediate: true
-			}
-		);
+		const value = useModel(props, "modelValue");
 
 		// 锁
 		let lock = false;
@@ -109,27 +102,33 @@ export default defineComponent({
 			}
 		}
 
-		// 监听输入
-		function onInput(val: string) {
-			emit("update:modelValue", val);
-			emit("change", val);
-		}
-
 		// 监听变化
-		function onChange() {
-			search();
-			lock = true;
+		function onChange(val: string) {
+			if (!props.refreshOnInput) {
+				search();
+				lock = true;
+
+				setTimeout(() => {
+					lock = false;
+				}, 300);
 
-			setTimeout(() => {
-				lock = false;
-			}, 300);
+				emit("change", val);
+			}
 		}
 
+		// 监听输入
+		const onInput = debounce((val: string) => {
+			emit("change", val);
+
+			if (props.refreshOnInput) {
+				search();
+			}
+		}, 300);
+
 		// 监听字段选择
 		function onFieldChange() {
 			emit("field-change", selectField.value);
-			onInput("");
-			value.value = "";
+			value.value = undefined;
 		}
 
 		expose({
@@ -157,8 +156,8 @@ export default defineComponent({
 							size={style.size}
 							placeholder={placeholder.value}
 							onKeydown={onKeydown}
-							onInput={onInput}
 							onChange={onChange}
+							onInput={onInput}
 							clearable
 						/>
 

+ 2 - 12
src/modules/base/components/dept/select.vue

@@ -21,7 +21,7 @@
 
 <script lang="ts" name="cl-dept-select" setup>
 import { ElMessage } from "element-plus";
-import { onMounted, ref, watch } from "vue";
+import { onMounted, ref, useModel } from "vue";
 import { useCool } from "/@/cool";
 import { deepTree } from "/@/cool/utils";
 
@@ -38,7 +38,7 @@ const emit = defineEmits(["update:modelValue", "change"]);
 
 const { service } = useCool();
 
-const value = ref();
+const value = useModel(props, "modelValue");
 
 const list = ref();
 
@@ -69,16 +69,6 @@ function refresh() {
 		});
 }
 
-watch(
-	() => props.modelValue,
-	(val) => {
-		value.value = val;
-	},
-	{
-		immediate: true
-	}
-);
-
 onMounted(() => {
 	refresh();
 });

+ 3 - 18
src/modules/base/components/menu/icon.vue

@@ -1,5 +1,5 @@
 <template>
-	<el-select filterable v-model="name" fit-input-width @change="onChange">
+	<el-select filterable v-model="value" fit-input-width>
 		<div class="cl-menu-icon">
 			<el-option :value="item" v-for="item in list" :key="item">
 				<cl-svg :name="item" />
@@ -9,7 +9,7 @@
 </template>
 
 <script lang="ts" name="cl-menu-icon" setup>
-import { ref, watch } from "vue";
+import { ref, useModel } from "vue";
 import { basename } from "/@/cool/utils";
 
 // svg 图标加载
@@ -42,22 +42,7 @@ function iconList() {
 const list = ref(iconList());
 
 // 已选图标
-const name = ref<string>();
-
-// 监听改变
-function onChange(val: string) {
-	emit("update:modelValue", val);
-}
-
-watch(
-	() => props.modelValue,
-	(val) => {
-		name.value = val;
-	},
-	{
-		immediate: true
-	}
-);
+const value = useModel(props, "modelValue");
 </script>
 
 <style lang="scss" scoped>

+ 20 - 27
src/modules/base/components/menu/select.vue

@@ -15,15 +15,14 @@
 			filterable
 			:size="size"
 			:placeholder="placeholder"
-			@change="onChange"
-		></el-tree-select>
+		/>
 	</div>
 </template>
 
 <script lang="ts" name="cl-menu-select" setup>
-import { useForm } from "@cool-vue/crud";
+import { useForm, useUpsert } from "@cool-vue/crud";
 import { cloneDeep } from "lodash-es";
-import { computed, onMounted, ref, watch } from "vue";
+import { computed, ref, useModel } from "vue";
 import { useCool } from "/@/cool";
 import { deepTree } from "/@/cool/utils";
 
@@ -43,42 +42,36 @@ const { service } = useCool();
 const Form = useForm();
 
 // 绑定值
-const value = ref();
+const value = useModel(props, "modelValue", {
+	get(val) {
+		return val ? Number(val) : val;
+	}
+});
 
 // 菜单列表
 const list = ref<any[]>([]);
 
 // 树形列表
 const tree = computed(() => {
-	return deepTree(
-		cloneDeep(list.value).filter((e) => (props.type === 0 ? e.type == 0 : props.type > e.type))
-	);
+	return deepTree(cloneDeep(list.value)).filter((e) => !e.parentId);
 });
 
 // 刷新列表
-function refresh() {
-	service.base.sys.menu.list().then((res) => {
-		list.value = res.filter((e) => e.id != Form.value?.form.id);
+async function refresh() {
+	return service.base.sys.menu.list().then((res) => {
+		// 过滤掉自己和下级的数据
+		list.value = res.filter(
+			(e) =>
+				e.id != Form.value?.form.id &&
+				(props.type === 0 ? e.type == 0 : props.type > e.type!)
+		);
 	});
 }
 
-// 绑定值回调
-function onChange(id: number) {
-	emit("update:modelValue", id);
-}
-
-watch(
-	() => props.modelValue,
-	(val) => {
-		value.value = val ? Number(val) : val;
-	},
-	{
-		immediate: true
+useUpsert({
+	onOpened() {
+		refresh();
 	}
-);
-
-onMounted(function () {
-	refresh();
 });
 </script>
 

+ 10 - 1
src/modules/demo/views/crud/components/crud/all.vue

@@ -30,9 +30,12 @@
 						<!-- 字典 -->
 						<cl-filter label="工作(字典)">
 							<cl-select
+								tree
 								:options="dict.get('occupation')"
 								prop="occupation"
-								:width="120"
+								:width="140"
+								check-strictly
+								@change="onChange"
 							/>
 						</cl-filter>
 
@@ -124,6 +127,8 @@ import { reactive, ref } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import { useCool } from "/@/cool";
 
+const v = ref();
+
 // 基础
 const { service, refs, setRefs } = useCool();
 
@@ -575,4 +580,8 @@ const visible = ref(false);
 function open() {
 	visible.value = true;
 }
+
+function onChange(val: any) {
+	console.log(val);
+}
 </script>

+ 8 - 4
src/modules/demo/views/crud/components/form/component/index.vue

@@ -12,7 +12,8 @@
 					'form/component/index.vue',
 					'form/component/select-labels.vue',
 					'form/component/select-status.vue',
-					'form/component/select-work.vue'
+					'form/component/select-work.vue',
+					'form/component/select-work2.vue'
 				]"
 			/>
 
@@ -35,7 +36,7 @@
 <script setup lang="ts">
 import { useForm } from "@cool-vue/crud";
 import { ElMessage } from "element-plus";
-import SelectWork from "./select-work.vue";
+import SelectWork from "./select-work2.vue";
 import SelectLabels from "./select-labels.vue";
 import SelectStatus from "./select-status.vue";
 
@@ -48,8 +49,9 @@ function open() {
 		items: [
 			{
 				label: "昵称",
-				prop: "nickname",
+				prop: "name",
 				// 组件配置方式1:标签名(方便,但是不建议组件全局注册)
+				value: "神仙",
 				component: {
 					// 必须是“全局注册”的组件名,如 element-plus 的 el-input、el-date-picker 等
 					name: "el-input"
@@ -59,6 +61,7 @@ function open() {
 				label: "年龄",
 				prop: "age",
 				// 组件配置方式2:插槽(万能,就是代码多写点)
+				value: 18,
 				component: {
 					// 必须是 "slot-" 开头
 					name: "slot-age"
@@ -68,6 +71,7 @@ function open() {
 			{
 				label: "工作",
 				prop: "work",
+				value: "设计",
 				component: {
 					// 双向绑定
 					vm: SelectWork
@@ -76,7 +80,7 @@ function open() {
 			{
 				label: "标签",
 				prop: "labels",
-				value: [],
+				value: ["多金", "深情"],
 				component: {
 					// scope[prop]绑定
 					vm: SelectLabels

+ 6 - 6
src/modules/demo/views/crud/components/form/component/select-labels.vue

@@ -23,16 +23,16 @@ const props = defineProps({
 // 选项列表
 const list = ref<{ label: string; value: string }[]>([
 	{
-		label: "倒茶",
-		value: "倒茶" // 测试直接使用label,真实情况可能是1,2,3,4或者id
+		label: "帅气",
+		value: "帅气" // 测试直接使用label,真实情况可能是1,2,3,4或者id
 	},
 	{
-		label: "设计",
-		value: "设计"
+		label: "多金",
+		value: "多金"
 	},
 	{
-		label: "开发",
-		value: "开发"
+		label: "深情",
+		value: "深情"
 	}
 ]);
 </script>

+ 1 - 1
src/modules/demo/views/crud/components/form/component/select-work.vue

@@ -14,7 +14,7 @@
 import { ref, watch } from "vue";
 
 const props = defineProps({
-	modelValue: Number
+	modelValue: String
 });
 
 const emit = defineEmits(["update:modelValue", "change"]);

+ 38 - 0
src/modules/demo/views/crud/components/form/component/select-work2.vue

@@ -0,0 +1,38 @@
+<template>
+	<el-select v-model="active">
+		<el-option
+			v-for="(item, index) in list"
+			:key="index"
+			:label="item.label"
+			:value="item.label"
+		/>
+	</el-select>
+</template>
+
+<!-- 【很重要】必须要有name,避免注册后和其他冲突 -->
+<script setup lang="ts" name="select-work2">
+import { ref, useModel } from "vue";
+
+const props = defineProps({
+	modelValue: String
+});
+
+//【很重要】绑定值,使用 useModel 的方式双向绑定
+const active = useModel(props, "modelValue");
+
+// 选项列表
+const list = ref<{ label: string; value: string }[]>([
+	{
+		label: "倒茶",
+		value: "倒茶" // 测试直接使用label,真实情况可能是1,2,3,4或者id
+	},
+	{
+		label: "设计",
+		value: "设计"
+	},
+	{
+		label: "开发",
+		value: "开发"
+	}
+]);
+</script>

+ 1 - 1
src/modules/demo/views/crud/index.vue

@@ -24,7 +24,7 @@
 </template>
 
 <script lang="ts" setup name="demo-crud">
-import { ref, onActivated } from "vue";
+import { ref, onActivated, getCurrentScope, toValue } from "vue";
 
 import CrudBase from "./components/crud/base.vue";
 import CrudAll from "./components/crud/all.vue";

+ 4 - 0
src/modules/demo/views/demo.vue

@@ -50,6 +50,10 @@ const list = [ContextMenu, ClForm, Crud, Upload, Editor, Svg, Copy, File, Design
 			height: 50px;
 			box-sizing: border-box;
 
+			a {
+				font-size: 12px;
+			}
+
 			&._svg {
 				.cl-svg {
 					margin-right: 15px;

+ 3 - 13
src/plugins/crud/components/date/picker.vue

@@ -22,10 +22,10 @@
 <script lang="ts" setup name="cl-date-picker">
 import { useCrud } from "@cool-vue/crud";
 import dayjs from "dayjs";
-import { type PropType, computed, ref, watch } from "vue";
+import { type PropType, computed, ref, useModel } from "vue";
 
 const props = defineProps({
-	modelValue: Array,
+	modelValue: null,
 	// 日期类型
 	type: {
 		type: String as PropType<
@@ -72,7 +72,7 @@ const defaultTime = ref<any>(
 );
 
 // 日期
-const date = ref();
+const date = useModel(props, "modelValue");
 
 // 按钮类型
 const quickType = ref(props.defaultQuickType);
@@ -157,16 +157,6 @@ function init() {
 	onQuickTypeChange();
 }
 
-watch(
-	() => props.modelValue,
-	(val) => {
-		date.value = val;
-	},
-	{
-		immediate: true
-	}
-);
-
 defineExpose({
 	init
 });

+ 8 - 15
src/plugins/crud/components/select/index.tsx

@@ -1,8 +1,8 @@
 import { useCrud } from "@cool-vue/crud";
 import { isEmpty, isString } from "lodash-es";
-import { computed, defineComponent, isRef, type PropType, type Ref, ref, watch } from "vue";
+import { computed, defineComponent, type PropType, type Ref, toValue, useModel } from "vue";
 import { parsePx } from "/@/cool/utils";
-import { Dict } from "/$/dict/types";
+import type { Dict } from "/$/dict/types";
 
 export default defineComponent({
 	name: "cl-select",
@@ -27,7 +27,9 @@ export default defineComponent({
 		// 是否树形
 		tree: Boolean,
 		// 是否返回选中层级下的所有值
-		allLevelsId: Boolean
+		allLevelsId: Boolean,
+		// 是否父子不互相关联
+		checkStrictly: Boolean
 	},
 
 	emits: ["update:modelValue", "change"],
@@ -37,11 +39,11 @@ export default defineComponent({
 		const Crud = useCrud();
 
 		// 选中值
-		const value = ref();
+		const value = useModel(props, "modelValue");
 
 		// 列表
 		const list = computed(() => {
-			return (isRef(props.options) ? props.options.value : props.options) || [];
+			return toValue(props.options) || [];
 		});
 
 		// 获取值
@@ -84,16 +86,6 @@ export default defineComponent({
 			}
 		}
 
-		watch(
-			() => props.modelValue,
-			(val) => {
-				value.value = val;
-			},
-			{
-				immediate: true
-			}
-		);
-
 		return () => {
 			// 样式
 			const style = {
@@ -111,6 +103,7 @@ export default defineComponent({
 					filterable
 					placeholder={placeholder}
 					data={list.value}
+					checkStrictly={props.allLevelsId || props.checkStrictly}
 					props={{
 						label: props.labelKey,
 						value: props.valueKey