2023-04-16 00:50:36 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
|
2024-01-18 17:18:17 +08:00
|
|
|
|
<el-table size="small" border :data="hashValues" height="500" min-height="300" stripe>
|
2023-07-06 20:59:22 +08:00
|
|
|
|
<el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100"> </el-table-column>
|
|
|
|
|
|
<el-table-column resizable sortable prop="field" label="field" show-overflow-tooltip min-width="100"> </el-table-column>
|
|
|
|
|
|
<el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200"> </el-table-column>
|
2023-04-16 00:50:36 +08:00
|
|
|
|
<el-table-column label="操作">
|
|
|
|
|
|
<template #header>
|
2023-07-06 20:59:22 +08:00
|
|
|
|
<el-input
|
|
|
|
|
|
class="key-detail-filter-value"
|
|
|
|
|
|
v-model="state.filterValue"
|
|
|
|
|
|
@keyup.enter="hscan(true, true)"
|
2024-01-18 17:18:17 +08:00
|
|
|
|
placeholder="关键词回车搜索"
|
2023-07-06 20:59:22 +08:00
|
|
|
|
clearable
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
/>
|
2023-04-16 00:50:36 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
<template #default="scope">
|
2023-07-06 20:59:22 +08:00
|
|
|
|
<el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit" plain></el-link>
|
2023-04-16 00:50:36 +08:00
|
|
|
|
<el-popconfirm title="确定删除?" @confirm="hdel(scope.row.field, scope.$index)">
|
|
|
|
|
|
<template #reference>
|
2023-07-06 20:59:22 +08:00
|
|
|
|
<el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small" plain class="ml5"></el-link>
|
2023-04-16 00:50:36 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</el-popconfirm>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
<!-- load more content -->
|
2023-07-06 20:59:22 +08:00
|
|
|
|
<div class="content-more-container">
|
|
|
|
|
|
<el-button size="small" @click="hscan()" :disabled="loadMoreDisable" class="content-more-btn"> 加载更多 </el-button>
|
2023-04-16 00:50:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2023-07-06 20:59:22 +08:00
|
|
|
|
<el-dialog title="添加新行" v-model="editDialog.visible" width="600px" :destroy-on-close="true" :close-on-click-modal="false">
|
2023-04-16 00:50:36 +08:00
|
|
|
|
<el-form>
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
|
<el-input v-model="editDialog.field" placeholder="field" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
|
<format-viewer class="w100" ref="formatViewerRef" :content="editDialog.value"></format-viewer>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<div class="dialog-footer">
|
|
|
|
|
|
<el-button @click="editDialog.visible = false">取 消</el-button>
|
|
|
|
|
|
<el-button v-auth="'redis:data:save'" type="primary" @click="confirmEditData">确 定</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
2024-01-18 17:18:17 +08:00
|
|
|
|
import { ref, onMounted, reactive, toRefs } from 'vue';
|
2023-04-16 00:50:36 +08:00
|
|
|
|
import { ElMessage } from 'element-plus';
|
|
|
|
|
|
import { notBlank } from '@/common/assert';
|
|
|
|
|
|
import FormatViewer from './FormatViewer.vue';
|
2024-03-02 19:08:19 +08:00
|
|
|
|
import { RedisInst } from './redis';
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
2024-03-02 19:08:19 +08:00
|
|
|
|
redis: {
|
|
|
|
|
|
type: RedisInst,
|
|
|
|
|
|
required: true,
|
2023-04-16 00:50:36 +08:00
|
|
|
|
},
|
|
|
|
|
|
keyInfo: {
|
|
|
|
|
|
type: [Object],
|
|
|
|
|
|
},
|
2023-07-06 20:59:22 +08:00
|
|
|
|
});
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
|
|
|
|
|
const formatViewerRef = ref(null) as any;
|
|
|
|
|
|
|
|
|
|
|
|
const state = reactive({
|
|
|
|
|
|
key: '',
|
|
|
|
|
|
scanParam: {
|
|
|
|
|
|
cursor: 0,
|
|
|
|
|
|
count: 50,
|
|
|
|
|
|
},
|
|
|
|
|
|
filterValue: '',
|
|
|
|
|
|
hashValues: [] as any,
|
|
|
|
|
|
total: 0,
|
|
|
|
|
|
loadMoreDisable: false,
|
|
|
|
|
|
editDialog: {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
field: '',
|
|
|
|
|
|
value: '',
|
|
|
|
|
|
dataRow: null as any,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2023-07-06 20:59:22 +08:00
|
|
|
|
const { hashValues, total, loadMoreDisable, editDialog } = toRefs(state);
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
state.key = props.keyInfo?.key;
|
|
|
|
|
|
initData();
|
2023-07-06 20:59:22 +08:00
|
|
|
|
});
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
|
|
|
|
|
const initData = () => {
|
|
|
|
|
|
state.filterValue = '';
|
|
|
|
|
|
hscan(true, true);
|
2023-07-06 20:59:22 +08:00
|
|
|
|
};
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
|
|
|
|
|
const getScanMatch = () => {
|
|
|
|
|
|
return state.filterValue ? `*${state.filterValue}*` : '*';
|
2023-07-06 20:59:22 +08:00
|
|
|
|
};
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
|
|
|
|
|
const hscan = async (resetTableData = false, resetCursor = false) => {
|
|
|
|
|
|
if (resetCursor) {
|
|
|
|
|
|
state.scanParam.cursor = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-02 19:08:19 +08:00
|
|
|
|
props.redis.runCmd(['HLEN', state.key]).then((res) => (state.total = res));
|
|
|
|
|
|
|
|
|
|
|
|
// HSCAN key cursor [MATCH pattern] [COUNT count]
|
|
|
|
|
|
// 返回值 [coursor, keys:[]]
|
|
|
|
|
|
let scanRes = await props.redis.runCmd(['HSCAN', state.key, state.scanParam.cursor, 'MATCH', getScanMatch(), 'COUNT', state.scanParam.count]);
|
|
|
|
|
|
state.scanParam.cursor = scanRes[0];
|
|
|
|
|
|
state.loadMoreDisable = state.scanParam.cursor == 0;
|
|
|
|
|
|
const keys = scanRes[1];
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
|
|
|
|
|
const hashValue = [];
|
|
|
|
|
|
const fieldCount = keys.length / 2;
|
|
|
|
|
|
let nextFieldIndex = 0;
|
|
|
|
|
|
for (let i = 0; i < fieldCount; i++) {
|
|
|
|
|
|
hashValue.push({ field: keys[nextFieldIndex++], value: keys[nextFieldIndex++] });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (resetTableData) {
|
|
|
|
|
|
state.hashValues = hashValue;
|
|
|
|
|
|
} else {
|
2023-07-06 20:59:22 +08:00
|
|
|
|
state.hashValues.push(...hashValue);
|
2023-04-16 00:50:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const hdel = async (field: any, index: any) => {
|
2024-03-02 19:08:19 +08:00
|
|
|
|
await props.redis.runCmd(['HDEL', state.key, field]);
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
|
|
|
|
|
ElMessage.success('删除成功');
|
2023-07-06 20:59:22 +08:00
|
|
|
|
state.hashValues.splice(index, 1);
|
2023-04-16 00:50:36 +08:00
|
|
|
|
state.total--;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const showEditDialog = (row: any) => {
|
|
|
|
|
|
state.editDialog.dataRow = row;
|
|
|
|
|
|
state.editDialog.field = row ? row.field : '';
|
|
|
|
|
|
state.editDialog.value = row ? row.value : '';
|
|
|
|
|
|
state.editDialog.visible = true;
|
2023-07-06 20:59:22 +08:00
|
|
|
|
};
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
|
|
|
|
|
const confirmEditData = async () => {
|
|
|
|
|
|
const field = state.editDialog.field;
|
2023-07-06 20:59:22 +08:00
|
|
|
|
notBlank(field, 'field不能为空');
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取hash value内容并新增
|
2023-07-06 20:59:22 +08:00
|
|
|
|
const value = formatViewerRef.value.getContent();
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
2024-03-02 19:08:19 +08:00
|
|
|
|
const res = await props.redis.runCmd(['HSET', state.key, field, value]);
|
2023-07-06 20:59:22 +08:00
|
|
|
|
ElMessage.success('保存成功');
|
2024-03-02 19:08:19 +08:00
|
|
|
|
// 响应0则为被覆盖,则重新scan
|
|
|
|
|
|
if (res == 0) {
|
|
|
|
|
|
hscan(true, true);
|
2023-04-16 00:50:36 +08:00
|
|
|
|
} else {
|
2024-03-02 19:08:19 +08:00
|
|
|
|
state.hashValues.unshift({ value, field });
|
|
|
|
|
|
state.total++;
|
2023-04-16 00:50:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
state.editDialog.visible = false;
|
|
|
|
|
|
state.editDialog.dataRow = null;
|
2023-07-06 20:59:22 +08:00
|
|
|
|
};
|
2023-04-16 00:50:36 +08:00
|
|
|
|
|
2023-07-06 20:59:22 +08:00
|
|
|
|
defineExpose({ initData });
|
2023-04-16 00:50:36 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
|
#string-value-text {
|
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
|
|
.text-type-select {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
z-index: 2;
|
|
|
|
|
|
right: 10px;
|
|
|
|
|
|
top: 10px;
|
|
|
|
|
|
max-width: 70px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|