Skip to content

Commit

Permalink
feat: 新增远程搜索下拉、分片上传、动态行组件,修复TableFooter未检测auth属性的问题
Browse files Browse the repository at this point in the history
  • Loading branch information
Joyboo committed Apr 27, 2024
1 parent d345c8c commit 800a181
Show file tree
Hide file tree
Showing 5 changed files with 516 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/components/Joyboo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import DynamicLine from './src/DynamicLine.vue';
import RemoteSelect from './src/RemoteSelect.vue';
import UploadPart from './src/UploadPart.vue';

export { DynamicLine, RemoteSelect, UploadPart };
217 changes: 217 additions & 0 deletions src/components/Joyboo/src/DynamicLine.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<template>
<Space direction="vertical" size="small" class="w-full">
<Row :gutter="[20, 20]" v-for="(item, idx) in dataSource" :key="idx">
<Col :span="colProps.left">
<Input v-model:value="item[keyName]" :placeholder="keyText" />
</Col>
<Col :span="colProps.right">
<Input v-model:value="item[valueName]" :placeholder="valueText" />
</Col>
<Col :span="2">
<Tooltip title="删除此行" placement="right">
<a-button
ghost
color="warning"
preIcon="ant-design:delete-outlined"
@click="delLine(idx)"
/>
</Tooltip>
</Col>
</Row>
<div class="flex justify-between items-center">
<div>
<Tooltip title="添加一行">
<a-button ghost color="success" preIcon="ant-design:plus-outlined" @click="addLine" />
</Tooltip>
<Tooltip title="提交">
<a-button
v-if="showSubmit"
ghost
color="success"
class="ml-2"
:loading="loadingRef"
preIcon="ant-design:check-outlined"
@click="saveKeyValue"
/>
</Tooltip>
</div>

<div>
<a-button
type="dashed"
preIcon="bytesize:download"
:iconSize="16"
@click="handleDownloadTemplate"
>
下载模板
</a-button>

<Tooltip>
<template #title>
<div>Excel上传</div>
<div>第一行为表头,会被忽略</div>
<div>第一列为{{ keyText }},第二列为{{ valueText }}</div>
</template>
<ImpExcel class="import-excel" @success="uploadSuccess" @error="uploadError">
<a-button
color="warning"
class="ml-2"
:iconSize="16"
preIcon="ant-design:file-excel-outlined"
>
Excel上传
</a-button>
</ImpExcel>
</Tooltip>
</div>
</div>
</Space>
</template>

<script lang="ts" setup name="DynamicLine">
import { ref, computed, PropType } from 'vue';
import { Space, Row, Col, Tooltip, Input } from 'ant-design-vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { ImpExcel, jsonToSheetXlsx, ExcelData } from '/@/components/Excel';
type ColPropsType = { left: number; right: number };
type apiParamType = { id: number; name: string; kv: Array<Recordable> };
const emit = defineEmits(['update-data', 'submit']);
const props = defineProps({
id: {
type: Number,
default: 0,
},
extKey: {
type: String,
default: '',
},
data: {
type: Array as PropType<Array<Recordable>>,
default: () => [],
},
keyName: {
type: String,
default: 'Key',
},
keyText: {
type: String,
default: 'Key',
},
valueName: {
type: String,
default: 'Value',
},
valueText: {
type: String,
default: 'Value',
},
ifSubmit: {
type: Boolean,
default: false,
},
// 左右Input栅格分配
colProps: {
type: Object as PropType<ColPropsType>,
default: () => ({ left: 11, right: 11 }),
// 累加 = 22, 留2格给按钮
validator: (value: ColPropsType) => value.left + value.right === 22,
},
// 下载模板文件名
downTplName: {
type: String,
default: () => new Date().getTime() + '.xlsx',
},
submitApi: {
type: Function as PropType<PromiseFn<apiParamType>>,
default: null,
},
});
const { createMessage } = useMessage();
const dataSource = computed({
get: () => props.data,
set: (val) => emit('update-data', val),
});
const showSubmit = computed(() => {
// 后端saveKeyValue方法中非KeyValue格式会被过滤
return props.ifSubmit && props.id > 0 && props.keyName === 'Key' && props.valueName === 'Value';
});
const loadingRef = ref(false);
function delLine(index: number) {
dataSource.value.splice(index, 1);
if (dataSource.value.length === 0) {
addLine();
}
}
function addLine() {
dataSource.value = [...dataSource.value, { [props.keyName]: '', [props.valueName]: '' }];
}
function saveKeyValue() {
if (props.id <= 0) {
return;
}
loadingRef.value = true;
props
.submitApi({
id: props.id,
name: props.extKey,
kv: props.data,
})
.then(() => {
createMessage.success('保存成功');
})
.catch((_) => {})
.finally(() => {
loadingRef.value = false;
});
emit('submit', props.id, props.extKey, props.data);
}
function uploadSuccess(excelData: ExcelData[]) {
const { keyName, valueName } = props;
// 只读第一个Sheet
const [data] = excelData;
// 第一列做Key,第二列做Value
const [key, value] = data.header;
const add: Array<Recordable> = [];
data.results.forEach((item) => {
add.push({ [keyName]: item[key], [valueName]: item[value] });
});
dataSource.value = [...dataSource.value, ...add].filter(
(item) => item[keyName] !== '' || item[valueName] !== '',
);
}
function uploadError() {
createMessage.error('Excel上传失败');
}
function handleDownloadTemplate() {
const { keyText, valueText, downTplName, keyName, valueName } = props;
const header = { [keyName]: keyText, [valueName]: valueText };
const cols = Object.keys(header).map((_) => ({ wpx: 200 }));
jsonToSheetXlsx({
data: [],
header,
filename: `${downTplName}-模板.xlsx`,
json2sheetOpts: { header: Object.keys(header) },
workSheetOpts: { '!cols': cols, '!rows': [{ hpx: 20 }] },
});
}
</script>

<style lang="less" scoped>
.import-excel {
display: inline-block;
}
</style>
87 changes: 87 additions & 0 deletions src/components/Joyboo/src/RemoteSelect.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<template>
<Select
:value="value"
:filter-option="false"
:allow-clear="true"
show-search
:getPopupContainer="(triggerNode) => triggerNode.parentNode || document.body"
:not-found-content="searchState.fetching ? undefined : null"
:options="searchState.options"
@search="fetch"
@change="handleChange"
v-bind="$attrs"
>
<template v-if="searchState.fetching" #notFoundContent>
<Spin size="small" />
</template>
</Select>
</template>

<script setup lang="ts">
import { PropType, reactive, watch } from 'vue';
import { Select, Spin } from 'ant-design-vue';
import { debounce } from 'lodash-es';
const emit = defineEmits(['change']);
const props = defineProps({
api: {
type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
required: true,
},
value: {
type: [Array, Object, String, Number],
},
params: {
type: Object as PropType<Recordable>,
default: () => ({}),
},
});
let lastFetchId = 0;
const searchState = reactive<{
options: OptionsItem[];
fetching: boolean;
}>({
options: [],
fetching: false,
});
// 依赖数据一旦变化,则清空列表,value清空需要在外部处理
watch(
() => props.params,
(newVal) => {
console.log('--watch params ', newVal);
searchState.options = [];
},
{ deep: true },
);
const fetch = debounce((value) => {
if (!value || value === '') {
return;
}
lastFetchId += 1;
const fetchId = lastFetchId;
searchState.options = [];
searchState.fetching = true;
const params = Object.assign({}, props.params, { value });
props
.api(params)
.then((result) => {
if (fetchId !== lastFetchId) {
return;
}
searchState.options = result;
searchState.fetching = false;
})
.catch((e) => {
console.error(e);
});
}, 300);
function handleChange(value) {
emit('change', value);
}
</script>

<style scoped lang="less"></style>
Loading

0 comments on commit 800a181

Please sign in to comment.