Commit 4b56b9ee authored by hucy's avatar hucy

fix:表单样式修改

parent 589b9c5c
interface DialogLayoutProps {
width?: number;
height?: number;
[proppName: string]: any;
}
export type { DialogLayoutProps };
export interface AnyType {
[proppName: string]: any;
}
export * from './dialog-layout-props';
......@@ -23,4 +23,5 @@ export {
replace,
repeat,
uniqueId,
omit,
} from 'lodash-es';
......@@ -20,6 +20,9 @@
{{ item.label }}
</div>
<q-input
:class="{
'form-label-padding-bottom': isEmpty(item.bind?.rules),
}"
:type="item.isPwd ? 'password' : 'text'"
v-model="formValue[item.fild]"
v-bind="item.bind"
......@@ -56,6 +59,9 @@
{{ item.label }}
</div>
<date-time-pick
:class="{
'form-label-padding-bottom': isEmpty(item.bind?.rules),
}"
:dense="
item.bind.dense === undefined ? props.dense : item.bind.dense
"
......@@ -86,6 +92,9 @@
{{ item.label }}
</div>
<time-pick
:class="{
'form-label-padding-bottom': isEmpty(item.bind?.rules),
}"
format24h
v-model="formValue[item.fild]"
:config="item.bind"
......@@ -114,6 +123,9 @@
{{ item.label }}
</div>
<date-multiple
:class="{
'form-label-padding-bottom': isEmpty(item.bind?.rules),
}"
v-model="formValue[item.fild]"
:config="item.bind"
:dense="
......@@ -141,6 +153,9 @@
{{ item.label }}
</div>
<date-range
:class="{
'form-label-padding-bottom': isEmpty(item.bind?.rules),
}"
v-model="formValue[item.fild]"
:config="item.bind"
:dense="
......@@ -168,6 +183,9 @@
{{ item.label }}
</div>
<color-pick
:class="{
'form-label-padding-bottom': isEmpty(item.bind?.rules),
}"
v-model="formValue[item.fild]"
:config="item.bind"
:dense="
......@@ -194,7 +212,11 @@
>
{{ item.label }}
</div>
<q-input
:class="{
'form-label-padding-bottom': isEmpty(item.bind?.rules),
}"
v-if="item.type !== 'select'"
:type="item.type"
v-model="formValue[item.fild]"
......@@ -215,6 +237,9 @@
/>
<q-select
class="my-select"
:class="{
'form-label-padding-bottom': isEmpty(item.bind?.rules),
}"
v-if="item.type === 'select'"
v-bind="item.bind"
v-model="formValue[item.fild]"
......@@ -245,7 +270,7 @@ import TimePick from './TimePick.vue';
import DateMultiple from './DateMultiple.vue';
import DateRange from './DateRange.vue';
import ColorPick from './ColorPick.vue';
import { cloneDeep, isObjEqual } from 'src/common/utils';
import { cloneDeep, isObjEqual, isEmpty } from 'src/common/utils';
interface Props {
config: any;
......@@ -334,6 +359,9 @@ function reset() {
<style lang="scss" scoped>
.item {
padding: $padding-sm;
padding-bottom: 0;
padding-top: 0;
// border: 1px solid red;
}
.item-content {
height: 100%;
......@@ -350,14 +378,17 @@ function reset() {
margin-bottom: 2px;
display: inline-block;
}
:deep(.my-select
> :nth-child(1)
> :nth-child(1)
> :nth-child(1)
> :nth-child(1)
> span) {
white-space: nowrap;
overflow: hidden;
.form-label-padding-bottom {
padding-bottom: 20px;
}
// :deep(.my-select
// > :nth-child(1)
// > :nth-child(1)
// > :nth-child(1)
// > :nth-child(1)
// > span) {
// white-space: nowrap;
// overflow: hidden;
// }
</style>
<script setup lang="ts">
import { reactive, computed } from 'vue';
interface IDetailProps {
title?: any;
width?: number;
maxWidth?: number;
minWidth?: number;
height?: number;
maxHeight?: number;
minHeight?: number;
}
const props = withDefaults(defineProps<IDetailProps>(), {
title: '',
width: 1000,
maxWidth: 1000,
minWidth: 500,
height: 600,
maxHeight: 900,
minHeight: 500,
});
const emit = defineEmits<{
(e: 'onOk'): void;
(e: 'onCancel'): void;
}>();
const myCardStyle = computed(() => {
return {
width: props.width + 'px',
maxWidth: props.maxWidth + 'px',
minWidth: props.minWidth + 'px',
height: props.height + 'px',
maxHeight: props.maxHeight + 'px',
minHeight: props.minHeight + 'px',
};
});
const thumbStyle = reactive({
right: '4px',
borderRadius: '5px',
backgroundColor: '#64b3f4',
width: '5px',
opacity: '1',
});
const barStyle = reactive({
right: '2px',
borderRadius: '9px',
backgroundColor: '#027be3',
width: '9px',
opacity: '0.2',
});
function onCancel() {
emit('onCancel');
}
function onSave() {
emit('onOk');
}
</script>
<template>
<q-card class="my-card" :style="myCardStyle">
<div class="card-content">
<div class="title">{{ title }}</div>
<div class="content">
<q-scroll-area
style="height: 100%; max-width: 100%"
:thumb-style="thumbStyle"
:bar-style="barStyle"
>
<slot></slot>
</q-scroll-area>
</div>
<div class="bottom-action">
<q-btn flat style="color: #64b3f4" label="取消" @click="onCancel" />
<q-btn
unelevated
style="background: #64b3f4; color: white"
label="确定"
@click="onSave()"
/>
</div>
</div>
</q-card>
</template>
<style lang="scss" scoped>
.my-card {
padding: 20px;
background-image: linear-gradient(60deg, #c2e59c 0%, #64b3f4 100%);
box-sizing: border-box;
display: flex;
}
.card-content {
width: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
overflow: hidden;
.title {
height: 40px;
padding: 8px;
font-size: 16px;
font-weight: bolder;
color: #c2e59c;
display: flex;
align-items: center;
}
.content {
flex: 1;
padding: 8px;
position: relative;
}
.bottom-action {
padding: 8px;
display: flex;
flex-direction: row;
column-gap: 4px;
justify-content: flex-end;
align-items: center;
}
}
</style>
......@@ -15,6 +15,7 @@ import AgGridNoRowsComponent from './ag-grid/NoRowsOverlayComponent.vue';
import AgGridLoadingOverlayComponent from './ag-grid/LoadingOverlayComponent.vue';
import AgGridDateComponent from './ag-grid/DateComponent.vue';
import DialogTip from './dialog-tip/DialogTip.vue';
import DialogLayout from './dialog-layout/DialogLayout.vue';
export {
DateTimePick as ComDateTimePick,
......@@ -33,6 +34,7 @@ export {
AgGridLoadingOverlayComponent as ComAgGridLoadingOverlayComponent,
AgGridDateComponent as ComAgGridDateComponent,
DialogTip as ComDialogTip,
DialogLayout as ComDialogLayout,
};
export default {
......@@ -52,4 +54,5 @@ export default {
AgGridLoadingOverlayComponent,
AgGridDateComponent,
DialogTip,
DialogLayout,
};
......@@ -7,45 +7,45 @@ export const MenuList = [
active: false,
},
{
title: '',
caption: '日历',
title: '图表',
caption: 'chart虚拟滚动',
icon: require('./menuListIcons/page1.svg'),
link: '/page1',
active: false,
},
{
title: '',
title: 'canvas',
caption: 'canvas小车动画',
icon: require('./menuListIcons/page4.svg'),
link: '/page4',
active: false,
},
{
title: '',
caption: '动画',
title: '动画',
caption: '',
icon: require('./menuListIcons/page8.svg'),
link: '/page8',
active: false,
},
{
title: '',
title: '表格',
caption: '动画2&表格',
icon: require('./menuListIcons/page9.svg'),
link: '/page9',
active: false,
},
{
title: '',
caption: '设计',
title: '设计',
caption: '一言',
icon: require('./menuListIcons/page10.svg'),
link: '/page10',
active: false,
},
{
title: 'tree',
title: 'Tree',
caption: '树',
icon: require('./menuListIcons/page10.svg'),
icon: require('./menuListIcons/tree.svg'),
link: '/tree',
active: false,
},
......@@ -56,8 +56,8 @@ export const MenuList = [
active: false,
children: [
{
title: '',
caption: '表单',
title: '表单',
caption: '',
icon: require('./menuListIcons/page2.svg'),
link: '/page2',
active: false,
......@@ -85,14 +85,14 @@ export const MenuList = [
active: false,
},
{
title: '',
title: '防抖节流',
caption: '表格&防抖节流',
icon: require('./menuListIcons/page5.svg'),
link: '/page5',
active: false,
},
{
title: '',
title: 'JS',
caption: '一些js练习',
icon: require('./menuListIcons/page6.png'),
link: '/js-page6',
......@@ -107,8 +107,8 @@ export const MenuList = [
active: false,
children: [
{
title: '',
caption: '疫情防控',
title: '疫情防控',
caption: '',
icon: require('./menuListIcons/page7.svg'),
link: '/page7',
active: false,
......
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1674021701598" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1097" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M165.8 224.4l333.3 217.5M487.3 450.6L154 233.1l23.6-17.3L511 433.2zM874.7 224.4L541.4 441.9M553.2 450.6l333.4-217.5-23.7-17.3-333.4 217.4z" fill="#65C8D0" p-id="1098"></path><path d="M485.8 412.2h64.3v545.2h-64.3z" fill="#65C8D0" p-id="1099"></path><path d="M312.3 159.7a43.1 35.6 0 1 0 86.2 0 43.1 35.6 0 1 0-86.2 0Z" fill="#65C8D0" p-id="1100"></path><path d="M467.9 297.6a43.1 35.6 0 1 0 86.2 0 43.1 35.6 0 1 0-86.2 0Z" fill="#65C8D0" p-id="1101"></path><path d="M648.4 207.6a43.1 35.6 0 1 0 86.2 0 43.1 35.6 0 1 0-86.2 0Z" fill="#65C8D0" p-id="1102"></path><path d="M495.3 124.1a43.1 35.6 0 1 0 86.2 0 43.1 35.6 0 1 0-86.2 0Z" fill="#65C8D0" p-id="1103"></path></svg>
\ No newline at end of file
......@@ -43,11 +43,16 @@ function getData() {
function clickAdd() {
formDialogRef.value.openDialog();
}
function viewData() {
console.log('查看', state.rowData);
}
</script>
<template>
<div class="box">
<div class="actions">
<q-btn color="primary" label="添加" @click="clickAdd" />
<q-btn unelevated color="primary" label="添加" @click="clickAdd" />
<q-btn unelevated color="primary" label="查看" @click="viewData" />
</div>
<div class="ag-table">
<ag-grid-vue
......
......@@ -21,7 +21,6 @@ export const form_config = [
bind: {
filled: true,
lazyRules: true,
hideBottomSpace: true,
rules: [(val: any) => !isEmpty(val) || '必填'],
},
},
......@@ -34,7 +33,6 @@ export const form_config = [
bind: {
filled: true,
lazyRules: true,
hideBottomSpace: true,
rules: [(val: any) => !isEmpty(val) || '必填'],
},
},
......@@ -47,7 +45,6 @@ export const form_config = [
bind: {
filled: true,
lazyRules: true,
hideBottomSpace: true,
rules: [
(val: any) => !isEmpty(val) || '必填',
(val: any) => (val > 0 && val < 100) || '请输入真实年龄',
......@@ -55,29 +52,8 @@ export const form_config = [
},
},
{
fild: 'sex',
label: '性别',
solt: 'sex',
col: 'col-4',
type: 'select',
required: true,
bind: {
filled: true,
lazyRules: true,
hideBottomSpace: true,
rules: [(val: any) => !isEmpty(val) || '必填'],
emitValue: true,
mapOptions: true,
options: [
{
label: '男',
value: 1,
},
{
label: '女',
value: 0,
},
],
},
},
{
fild: 'dateRange',
......@@ -122,6 +98,10 @@ export const form_config = [
label: 'Facebook',
value: 'Facebook',
},
{
label: 'AAA',
value: 'aaa',
},
],
},
},
......
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { useMessage } from 'src/common/hooks';
import Com from 'src/components';
import { isEmpty } from 'src/common/utils';
import { form_config } from '../config';
import type { DialogLayoutProps } from 'src/common/types';
defineExpose({
openDialog,
closeDialog,
});
const { warn } = useMessage();
const formRef = ref<any>(null);
const show = ref(false);
......@@ -19,6 +24,21 @@ const state = reactive({
formData: {} as any,
});
const pagestate = reactive<DialogLayoutProps>({
title: '添加',
});
const sexOpt = reactive([
{
label: '男',
value: 1,
},
{
label: '女',
value: 0,
},
]);
onMounted(() => {
initFormConfig();
});
......@@ -40,89 +60,73 @@ function closeDialog() {
initDialog();
}
function onCancel() {
closeDialog();
}
function onSave() {
const { formData } = state;
formRef.value.validate().then((suc: any) => {
if (!suc) return;
console.log('formData >>>>', state.formData);
if (isEmpty(formData.sex)) return warn('性别必填');
console.log('formData >>>>', formData);
closeDialog();
});
}
function onCancel() {
closeDialog();
}
</script>
<template>
<q-dialog v-model="show" persistent>
<q-card class="my-card">
<div class="card-content">
<div class="title">添加</div>
<div class="content">
<Com.MyForm
ref="formRef"
dense
:readonly="state.readonly"
:config="state.formConfig"
v-model="state.formData"
></Com.MyForm>
</div>
<div class="bottom-action">
<q-btn flat style="color: #64b3f4" label="取消" @click="onCancel" />
<q-btn
unelevated
style="background: #64b3f4; color: white"
label="确定"
@click="onSave()"
/>
</div>
</div>
</q-card>
<Com.DialogLayout v-bind="pagestate" @onOk="onSave" @onCancel="onCancel">
<Com.MyForm
ref="formRef"
dense
:readonly="state.readonly"
:config="state.formConfig"
v-model="state.formData"
>
<template #sex>
<div class="item-content">
<div class="label-title text-required">性别</div>
<div class="label-content">
<q-option-group
v-model="state.formData.sex"
:options="sexOpt"
color="primary"
inline
:disable="state.readonly"
/>
</div>
</div>
</template>
</Com.MyForm>
</Com.DialogLayout>
</q-dialog>
</template>
<style lang="scss" scoped>
.my-card {
width: 1000px;
max-width: 1000px;
min-height: 500px;
padding: 20px;
background-image: linear-gradient(60deg, #c2e59c 0%, #64b3f4 100%);
box-sizing: border-box;
display: flex;
}
.card-content {
.item-content {
height: 40px;
width: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
.title {
height: 40px;
padding: 8px;
font-size: 16px;
font-weight: bolder;
color: #c2e59c;
display: flex;
align-items: center;
// background-color: #ee9ca7;
}
.content {
flex: 1;
padding: 8px;
}
.bottom-action {
// background-color: #209cff;
padding: 8px;
display: flex;
flex-direction: row;
column-gap: 4px;
justify-content: flex-end;
align-items: center;
}
flex-wrap: nowrap;
}
.label-title {
height: 22px;
font-size: 14px;
line-height: 22px;
color: rgba(0, 0, 0, 0.871);
margin-bottom: 2px;
display: inline-block;
}
.label-content {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
background-color: rgba(0, 0, 0, 0.05);
border-radius: 4px 4px 0 0;
}
</style>
......@@ -4,7 +4,7 @@ export default [
name: 'FORM_TEST',
component: () => import('./IndexPage.vue'),
meta: {
title: '表单&表格',
title: '信息',
permission: ['*'],
keepalive: true,
},
......
......@@ -265,7 +265,7 @@ function onResetForm() {
border: 1px solid $border-color;
}
.item-content {
height: 100%;
height: calc(100% - 20px);
width: 100%;
display: flex;
flex-direction: column;
......
......@@ -9,7 +9,6 @@ export const form = [
readonly: true,
filled: true,
lazyRules: true,
hideBottomSpace: true,
rules: [(val: any) => (val && val.length > 0) || '必填'],
},
},
......@@ -19,11 +18,11 @@ export const form = [
col: 'col-6',
type: 'password',
isPwd: true,
required: true,
bind: {
filled: true,
// lazyRules: true,
// hideBottomSpace: true,
// rules: [(val: any) => (val && val.length > 0) || '必填'],
lazyRules: true,
rules: [(val: any) => (val && val.length > 0) || '必填'],
},
},
{
......@@ -59,7 +58,6 @@ export const form = [
},
],
// lazyRules: true,
// hideBottomSpace: true,
// rules: [(val: any) => (val && val.length > 0) || '必填'],
clearable: true,
},
......
<script setup lang="ts">
import { reactive, onMounted } from 'vue';
import { cloneDeep, isEmpty } from 'src/common/utils';
import nestedDraggable from './infra/nested.vue';
import { useQuasar } from 'quasar';
import { cloneDeep, isEmpty, omit } from 'src/common/utils';
import nestedDraggable from './element/nested.vue';
import { ComDialogTip } from 'src/components';
const $q = useQuasar();
const SpcGroupItem = {
show: true,
title: null,
children: [],
};
const data = [
{
name: 'task 1',
show: true,
tasks: [
title: '1',
children: [
{
name: 'task 2',
show: true,
tasks: [],
title: '1-1',
children: [],
},
{
title: '1-2',
children: [],
},
],
key: '1',
},
{
name: 'task 3',
show: true,
tasks: [
{
name: 'task 4',
show: true,
tasks: [],
title: '1-3',
children: [],
},
],
key: '2',
},
{
name: 'task 5',
show: true,
tasks: [],
key: '3',
title: '2',
children: [],
},
];
const state = reactive({
list: [] as any[],
originalData: [] as any[],
list: [] as any[],
itemKey: 'key',
childrenKey: 'children',
labelKey: 'title',
dataStacks: [] as any,
});
onMounted(() => {
state.originalData = cloneDeep(data);
handleData(data);
state.list = data;
initData(data);
});
function handleData(data: any, key = 'tasks', before = 0) {
function initData(data: any) {
state.originalData = data;
const _data = cloneDeep(data);
handleData(_data);
state.list = _data;
pushDataToStacks();
// console.log('state.list', state.list);
}
function pushDataToStacks() {
state.dataStacks.push({
data: cloneDeep(state.list),
});
}
function handleData(data: any, key = state.childrenKey, before = 0) {
let index = 0;
data.map((item: any) => {
let indexstr;
if (before) {
indexstr = before + '-' + String(index);
indexstr = String(before) + '-' + String(index);
} else {
indexstr = String(index);
}
if (isEmpty(item.show)) {
item.show = true;
}
item['test_index'] = indexstr;
item[state.itemKey] = indexstr;
if (data.length - 1 === index) {
item.lastChild = true;
} else {
item.lastChild = false;
}
index++;
if (!isEmpty(item[key])) {
......@@ -69,61 +94,93 @@ function handleData(data: any, key = 'tasks', before = 0) {
});
}
function viewData() {
console.log('查看 >>>>', state.list);
function handelDel(listdata: any[]) {
const chiledKey = state.childrenKey;
let newArr = [] as any[];
listdata.map((item) => {
if (!isEmpty(item[chiledKey])) {
item[chiledKey] = handelDel(item[chiledKey]);
}
if (!item['test_delete']) {
newArr.push(item);
}
});
return newArr;
}
function onReset() {
const data = cloneDeep(state.originalData);
handleData(data);
state.list = data;
function getTarget(list: any, index: number) {
const target = list[index];
const parent = list;
return { target, parent };
}
function delTestKey(listdata: any[]) {
const chiledKey = state.childrenKey;
let seq = 1;
return listdata.map((item: any) => {
if (!isEmpty(item[chiledKey])) {
item[chiledKey] = delTestKey(item[chiledKey]);
}
const newObj = omit(item, ['key', 'test_index', 'show', 'lastChild']);
newObj.seq = seq;
seq++;
return newObj;
});
}
// 查看
function viewData() {
const data = cloneDeep(state.list);
const resdata = delTestKey(data);
console.log('最后处理格式', resdata);
}
// 重置
function onReset() {
state.dataStacks = [];
initData(cloneDeep(state.originalData));
}
// 拖拽结束
function dragEnd() {
handleData(state.list);
console.log('拖拽结束2', state.list);
pushDataToStacks();
}
// 添加
function onAdd() {
let obj: any = {
name: null,
tasks: [],
key: Date.now(),
};
let obj = { ...SpcGroupItem } as any;
obj[state.itemKey] = Date.now();
state.list.push(obj);
handleData(state.list);
pushDataToStacks();
}
// 删除
function delItem(data: any) {
console.log('删除', data);
data['test_delete'] = true;
let res = handelDel(state.list);
handleData(res);
state.list = res;
}
function handelDel(listdata: any[]) {
const chiledKey = 'tasks';
let newArr = [] as any[];
listdata.map((item) => {
if (!isEmpty(item[chiledKey])) {
item[chiledKey] = handelDel(item[chiledKey]);
}
if (!item['test_delete']) {
newArr.push(item);
}
let msg = '';
if (!isEmpty(data.children)) {
msg = '确定删除 ' + <string>data.title + '及其所有子项?';
} else {
msg = '确定删除 ' + <string>data.title + '?';
}
$q.dialog({
component: ComDialogTip,
componentProps: {
persistent: true,
text: msg,
color: 'negative',
},
}).onOk(() => {
data['test_delete'] = true;
let res = handelDel(state.list);
handleData(res);
state.list = res;
pushDataToStacks();
});
return newArr;
}
// 同级添加
function addItem(data: any) {
console.log('同级添加', data);
const key = 'tasks';
const key = state.childrenKey;
const indexList = data['test_index'].split('-');
let re: any;
......@@ -136,21 +193,16 @@ function addItem(data: any) {
}
}
let obj: any = {
name: null,
show: true,
key: Date.now(),
};
obj[key] = [];
let obj = { ...SpcGroupItem } as any;
obj[state.itemKey] = Date.now();
re.parent.push(obj);
handleData(state.list);
pushDataToStacks();
}
// 子级添加
function addNest(data: any) {
const key = 'tasks';
console.log('子级添加', data);
const key = state.childrenKey;
const indexList = data['test_index'].split('-');
let re: any;
......@@ -163,53 +215,41 @@ function addNest(data: any) {
}
}
let obj: any = {
name: null,
show: true,
key: Date.now(),
};
obj[key] = [];
let obj = { ...SpcGroupItem } as any;
obj[state.itemKey] = Date.now();
re.target[key].push(obj);
handleData(state.list);
pushDataToStacks();
}
function getTarget(list: any, index: number) {
const target = list[index];
const parent = list;
return { target, parent };
}
function getParamData() {
//
let data = cloneDeep(state.list);
let res = delTestKey(data);
console.log('原来的格式', res);
}
function delTestKey(listdata: any[], delKey = 'test_index') {
const chiledKey = 'tasks';
return listdata.map((item: any) => {
if (!isEmpty(item[chiledKey])) {
item[chiledKey] = delTestKey(item[chiledKey], delKey);
}
delete item[delKey];
return item;
});
// 撤销
function onUndo() {
const lastindex = state.dataStacks.length - 2;
const lastdata = state.dataStacks[lastindex];
state.list = cloneDeep(lastdata.data);
state.dataStacks.splice(lastindex + 1, 1);
}
</script>
<template>
<div class="box">
<div class="actions">
<q-btn color="primary" label="查看" @click="viewData" />
<q-btn color="primary" label="原来的格式" @click="getParamData" />
<q-btn color="primary" label="重置" @click="onReset" />
<q-btn color="primary" label="添加" @click="onAdd" />
<q-btn unelevated color="primary" label="查看" @click="viewData" />
<q-btn unelevated color="primary" label="重置" @click="onReset" />
<q-btn unelevated color="primary" label="添加" @click="onAdd" />
<q-btn
unelevated
color="primary"
label="撤销"
@click="onUndo"
:disable="state.dataStacks.length < 2"
/>
</div>
<div class="tree-box">
<nested-draggable
:tasks="state.list"
:item-key="state.itemKey"
:children-key="state.childrenKey"
:label-key="state.labelKey"
@onEnd="dragEnd"
@addItem="addItem"
@addNest="addNest"
......@@ -221,7 +261,7 @@ function delTestKey(listdata: any[], delKey = 'test_index') {
<style lang="scss" scoped>
.actions {
padding: 10px;
padding: 20px 10px 10px 20px;
display: flex;
flex-direction: row;
justify-content: flex-start;
......@@ -229,13 +269,6 @@ function delTestKey(listdata: any[], delKey = 'test_index') {
column-gap: 10px;
}
.tree-box {
// width: 600px;
height: 500px;
box-sizing: border-box;
// border: 1px solid brown;
overflow: auto;
> :nth-child(1) {
// padding-right: 10px;
}
transform: translateX(-20px);
}
</style>
<template>
<div class="row">
<div class="col-1">
<button class="btn btn-secondary button" @click="add">Add</button>
</div>
<div class="col-7">
<h3>Draggable {{ draggingInfo }}</h3>
<draggable
tag="ul"
:list="list"
class="list-group"
handle=".handle"
item-key="name"
>
<template #item="{ element, index }">
<li class="list-group-item">
<i class="fa fa-align-justify handle"></i>
<span class="text">{{ element.name }} </span>
<input type="text" class="form-control" v-model="element.text" />
<i class="fa fa-times close" @click="removeAt(index)"></i>
</li>
</template>
</draggable>
</div>
<rawDisplayer class="col-3" :value="list" title="List" />
</div>
</template>
<script>
let id = 3;
import draggable from 'vuedraggable';
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'handle',
display: 'Handle',
instruction: 'Drag using the handle icon',
order: 5,
components: {
draggable,
},
data() {
return {
list: [
{ name: 'John', text: '', id: 0 },
{ name: 'Joao', text: '', id: 1 },
{ name: 'Jean', text: '', id: 2 },
],
dragging: false,
};
},
computed: {
draggingInfo() {
return this.dragging ? 'under drag' : '';
},
},
methods: {
removeAt(idx) {
this.list.splice(idx, 1);
},
add: function () {
id++;
this.list.push({ name: 'Juan ' + id, id, text: '' });
},
},
};
</script>
<style scoped>
.button {
margin-top: 35px;
}
.handle {
float: left;
padding-top: 8px;
padding-bottom: 8px;
}
.close {
float: right;
padding-top: 8px;
padding-bottom: 8px;
}
input {
display: inline-block;
width: 50%;
}
.text {
margin: 20px;
}
</style>
<template>
<draggable
class="drag-area"
tag="div"
:scroll="true"
:list="tasks"
:group="{ name: 'g1' }"
:item-key="itemKey"
:onEnd="onEnd"
:animation="150"
>
<template #item="{ element }">
<div
:class="[
'drag-area-item',
`dragareaitem-${element.test_index}`,
{ 'has-left-line': hasLeftLine(element.test_index) },
]"
>
<span
class="has-left-vertical-line"
:class="{ 'has-left-vertical-line-last': element.lastChild }"
></span>
<div
class="drag-area-item-conten"
:style="{
paddingLeft: isEmpty(element[childrenKey]) ? '6px' : '0px',
}"
>
<div class="drag-area-item-conten-left">
<q-icon
v-if="!isEmpty(element[childrenKey])"
@click="element.show = !element.show"
color="grey"
name="bi-caret-down-fill"
class="handle"
:style="{
transform: element.show ? 'rotate(0deg)' : 'rotate(-90deg)',
}"
/>
<input
type="text"
class="item-conten-input"
v-model="element[labelKey]"
/>
</div>
<div class="actions">
<q-btn
color="grey"
dense
flat
icon="bi-hdd-stack-fill"
@click="addItem(element)"
>
<q-tooltip>同级添加</q-tooltip>
</q-btn>
<q-btn
color="grey"
dense
flat
icon="bi-hdd-network-fill"
@click="addNest(element)"
>
<q-tooltip>子级添加</q-tooltip>
</q-btn>
<q-btn
color="grey"
dense
flat
icon="bi-trash"
@click="delItem(element)"
>
<q-tooltip>删除</q-tooltip>
</q-btn>
</div>
</div>
<q-slide-transition>
<nested-draggable
v-show="element.show"
:tasks="element[childrenKey]"
:item-key="itemKey"
:children-key="childrenKey"
:label-key="labelKey"
@onEnd="onEnd"
@addItem="addItem"
@addNest="addNest"
@delItem="delItem"
/>
</q-slide-transition>
</div>
</template>
</draggable>
</template>
<script>
import { computed } from 'vue';
import { isEmpty } from 'src/common/utils';
import draggable from 'vuedraggable';
export default {
name: 'nested-draggable',
props: {
tasks: {
required: true,
type: Array,
},
itemKey: {
required: true,
type: String,
},
childrenKey: {
required: true,
type: String,
},
labelKey: {
required: true,
type: String,
},
},
components: {
draggable,
},
setup(props, ctx) {
const addItem = (data) => {
ctx.emit('addItem', data);
};
const addNest = (data) => {
ctx.emit('addNest', data);
};
const delItem = (data) => {
ctx.emit('delItem', data);
};
//
const onEnd = (data) => {
ctx.emit('onEnd', data);
};
const hasLeftLine = computed(() => (str) => {
let falg;
const leng = str.split('-');
if (leng.length > 1) {
falg = true;
} else {
falg = false;
}
return falg;
});
return {
isEmpty,
addItem,
addNest,
delItem,
onEnd,
hasLeftLine,
};
},
};
</script>
<style scoped lang="scss">
.drag-area {
// min-width: 600px;
// min-height: 20px;
overflow: auto;
/* outline: 1px dashed; */
// border: 1px solid red;
padding-left: 40px;
& > :nth-child(1) {
margin-top: 10px;
}
.drag-area-item {
position: relative;
// min-width: 600px;
// outline: 1px solid rgba(0, 0, 0, 0.125);
margin-bottom: 10px;
// background-color: pink;
.drag-area-item-conten {
height: 40px;
box-sizing: border-box;
background-color: rgba(0, 0, 0, 0.075);
// background-color: pink;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
.drag-area-item-conten-left {
// border: 1px solid purple;
// background-color: purple;
display: flex;
flex-direction: row;
align-items: center;
}
.item-conten-input {
// display: inline-block;
// background-color: #fff;
// display: flex;
// min-width: 180px;
// align-items: center;
outline-style: none;
border: 1px solid rgba(0, 0, 0, 0.075);
border-radius: 3px;
height: 32px;
padding: 0 8px;
&:focus {
// width: fit-content;
border-color: $primary;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 8px $primary-1;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px $primary-1;
}
}
.actions {
padding: 10px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
column-gap: 10px;
}
}
}
.has-left-line {
&::before {
content: '';
position: absolute;
top: 18px;
left: -40px;
box-sizing: border-box;
width: 36px;
border-top: 1px solid rgba(0, 0, 0, 0.125);
}
}
.has-left-vertical-line {
display: inline-block;
position: absolute;
box-sizing: border-box;
// width: 50px;
height: calc(100% + 10px);
left: -40px;
top: -10px;
border-left: 1px solid rgba(0, 0, 0, 0.125);
}
.has-left-vertical-line-last {
// border-color: green;
height: 30px;
}
.handle {
float: left;
padding: 4px 8px;
font-size: 16px;
cursor: pointer;
// border: 1px solid red;
transition: transform 0.3s;
}
}
</style>
<template>
<draggable
class="drag-area"
tag="div"
:list="tasks"
:group="{ name: 'g1' }"
handle=".handle-icon"
item-key="key"
:onEnd="onEnd"
:animation="150"
>
<template #item="{ element }">
<div class="item">
<div>
<div :class="['toggle-box', { 'toggle-box-btn': !element.show }]">
<div class="toggle-left-content">
<q-icon name="fa-solid fa-bars" class="handle-icon" />
<input type="text" class="input-control" v-model="element.name" />
</div>
<div class="toggle-right-content">
<q-btn flat dense label="同级添加" @click="addItem(element)" />
<q-btn flat dense label="子级添加" @click="addNest(element)" />
<q-btn
flat
dense
label="删除"
style="margin-right: 20px"
@click="delItem(element)"
/>
<q-icon
v-if="!isEmpty(element.tasks)"
name="bi-chevron-down"
:class="[
'expansion-icon',
{ 'expansion-icon-hide': !element.show },
]"
@click="element.show = !element.show"
/>
</div>
</div>
<q-slide-transition>
<div v-show="element.show" class="content">
<div
:class="[
'placeholder',
{ 'placeholder-last': isEmpty(element.tasks) },
]"
></div>
<nested-draggable
v-show="element.show"
:tasks="element.tasks"
@onEnd="onEnd"
@addItem="addItem"
@addNest="addNest"
@delItem="delItem"
/>
</div>
</q-slide-transition>
</div>
</div>
</template>
</draggable>
</template>
<script>
// import { ref } from 'vue';
import { isEmpty } from 'src/common/utils';
import draggable from 'vuedraggable';
export default {
name: 'nested-draggable',
props: {
tasks: {
required: true,
type: Array,
},
},
components: {
draggable,
},
setup(props, ctx) {
const addItem = (data) => {
ctx.emit('addItem', data);
};
const addNest = (data) => {
ctx.emit('addNest', data);
};
const delItem = (data) => {
ctx.emit('delItem', data);
};
// 拖拽结束
const onEnd = (data) => {
ctx.emit('onEnd', data);
};
return {
isEmpty,
addItem,
addNest,
delItem,
onEnd,
};
},
};
</script>
<style scoped lang="scss">
.drag-area {
// border: 1px solid red;
}
.item {
// outline: 1px solid rgba(0, 0, 0, 0.035);
// background-color: pink;
background-color: rgba(0, 0, 0, 0.035);
margin: 10px 0;
padding-left: 10px;
margin-left: 20px;
margin-top: 10px;
transition: height 0.3s;
}
.toggle-box {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding-top: 8px;
// padding-bottom: 6px;
padding-right: 10px;
}
.toggle-box-btn {
padding-bottom: 6px;
}
.toggle-left-content {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
column-gap: 10px;
}
.toggle-right-content {
display: grid;
grid-template-columns: 70px 70px 60px 30px;
align-items: center;
}
.handle-icon {
font-size: 18px;
box-sizing: border-box;
display: inline-block;
// border: 1px solid red;
cursor: grab;
}
.expansion-icon {
font-size: 16px;
cursor: pointer;
transform: rotate(180deg);
transition: transform 0.3s;
}
.expansion-icon-hide {
transform: rotate(0);
transition: transform 0.3s;
}
.content {
// height: 46px;
// background-color: pink;
}
.placeholder {
height: 20px;
// background-color: pink;
}
.placeholder-last {
height: 6px;
// background-color: pink;
}
.input-control {
outline-style: none;
border: 1px solid rgba(0, 0, 0, 0.035);
border-radius: 3px;
height: 32px;
padding: 0 6px;
&:focus {
border-color: $primary;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px $primary-1;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px $primary-1;
}
}
</style>
<template>
<draggable
class="drag-area"
tag="div"
:list="tasks"
handle=".handle"
:group="{ name: 'g1' }"
item-key="key"
:onEnd="onEnd"
:animation="150"
>
<template #item="{ element }">
<div class="drag-area-item">
<div class="drag-area-item-conten">
<!-- <div class="text">{{ element.name }}</div> -->
<div>
<i class="fa fa-align-justify handle"></i>
<input type="text" class="form-control" v-model="element.name" />
</div>
<div class="actions">
<q-btn color="primary" icon="bi-list" @click="addItem(element)" />
<q-btn
color="primary"
icon="bi-list-nested"
@click="addNest(element)"
/>
<q-btn color="primary" label="删除" @click="delItem(element)" />
</div>
</div>
<nested-draggable
:tasks="element.tasks"
@onEnd="onEnd"
@addItem="addItem"
@addNest="addNest"
@delItem="delItem"
/>
</div>
</template>
</draggable>
</template>
<script>
import { isEmpty } from 'src/common/utils';
import draggable from 'vuedraggable';
export default {
name: 'nested-draggable',
props: {
tasks: {
required: true,
type: Array,
},
},
components: {
draggable,
},
setup(props, ctx) {
const addItem = (data) => {
ctx.emit('addItem', data);
};
const addNest = (data) => {
ctx.emit('addNest', data);
};
const delItem = (data) => {
ctx.emit('delItem', data);
};
// 拖拽结束
const onEnd = (data) => {
ctx.emit('onEnd', data);
};
return {
isEmpty,
addItem,
addNest,
delItem,
onEnd,
};
},
};
</script>
<style scoped lang="scss">
.drag-area {
min-width: 450px;
// min-height: 20px;
overflow: auto;
/* outline: 1px dashed; */
border: 1px solid red;
padding-left: 40px;
& > :nth-child(1) {
margin-top: 10px;
}
.drag-area-item {
min-width: 450px;
outline: 1px solid #1ee3cf;
margin-bottom: 10px;
.drag-area-item-conten {
height: 40px;
box-sizing: border-box;
background-color: #f9bcdd;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
.actions {
padding: 10px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
column-gap: 10px;
}
}
}
.handle {
float: left;
padding: 8px;
}
}
</style>
......@@ -4,7 +4,7 @@ export default [
name: 'TREE',
component: () => import('./IndexPage.vue'),
meta: {
title: '',
title: 'Tree',
permission: ['*'],
keepalive: true,
},
......
......@@ -30,7 +30,7 @@ const routes: RouteRecordRaw[] = [
name: 'PAGE1',
component: () => import('../modules/page1/IndexPage.vue'),
meta: {
title: '页面1',
title: '图表',
permission: ['*'],
keepalive: true,
},
......@@ -40,7 +40,7 @@ const routes: RouteRecordRaw[] = [
name: 'PAGE2',
component: () => import('../modules/page2/IndexPage.vue'),
meta: {
title: '页面2',
title: '表单',
permission: ['*'],
keepalive: true,
},
......@@ -50,7 +50,7 @@ const routes: RouteRecordRaw[] = [
name: 'PAGE4',
component: () => import('../modules/page4/IndexPage.vue'),
meta: {
title: '页面4',
title: 'canvas',
permission: ['*'],
keepalive: true,
},
......@@ -60,7 +60,7 @@ const routes: RouteRecordRaw[] = [
name: 'PAGE5',
component: () => import('../modules/page5/IndexPage.vue'),
meta: {
title: '页面5',
title: '防抖节流',
permission: ['*'],
keepalive: true,
},
......@@ -70,7 +70,7 @@ const routes: RouteRecordRaw[] = [
name: 'PAGE7',
component: () => import('../modules/page7/IndexPage.vue'),
meta: {
title: '页面7',
title: '疫情防控',
permission: ['*'],
keepalive: true,
},
......@@ -90,7 +90,7 @@ const routes: RouteRecordRaw[] = [
name: 'PAGE9',
component: () => import('../modules/page9/IndexPage.vue'),
meta: {
title: '动画2&表格',
title: '表格',
permission: ['*'],
keepalive: true,
},
......@@ -110,7 +110,7 @@ const routes: RouteRecordRaw[] = [
name: 'JS_PAGE3',
component: () => import('../modules/page3/IndexPage.vue'),
meta: {
title: '页面3',
title: '链表',
permission: ['*'],
keepalive: true,
},
......@@ -120,7 +120,7 @@ const routes: RouteRecordRaw[] = [
name: 'JS_PAGE6',
component: () => import('../modules/page6/IndexPage.vue'),
meta: {
title: '一些js练习',
title: 'JS',
permission: ['*'],
keepalive: true,
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment