mirror of https://github.com/certd/certd
perf: 流水线支持批量修改分组,批量删除
parent
0772d3b3fd
commit
a847e66c4f
|
@ -1,6 +1,4 @@
|
||||||
import { ValidateException } from './exception/index.js';
|
import { PermissionException, ValidateException } from './exception/index.js';
|
||||||
import * as _ from 'lodash-es';
|
|
||||||
import { PermissionException } from './exception/index.js';
|
|
||||||
import { In, Repository, SelectQueryBuilder } from 'typeorm';
|
import { In, Repository, SelectQueryBuilder } from 'typeorm';
|
||||||
import { Inject } from '@midwayjs/core';
|
import { Inject } from '@midwayjs/core';
|
||||||
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
@ -164,28 +162,27 @@ export abstract class BaseService<T> {
|
||||||
private buildListQuery(listReq: ListReq<T>) {
|
private buildListQuery(listReq: ListReq<T>) {
|
||||||
const { query, sort, buildQuery } = listReq;
|
const { query, sort, buildQuery } = listReq;
|
||||||
const qb = this.getRepository().createQueryBuilder('main');
|
const qb = this.getRepository().createQueryBuilder('main');
|
||||||
|
if (query) {
|
||||||
|
const keys = Object.keys(query);
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = query[key];
|
||||||
|
if (value == null) {
|
||||||
|
delete query[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qb.where(query);
|
||||||
|
}
|
||||||
if (sort && sort.prop) {
|
if (sort && sort.prop) {
|
||||||
|
const found = this.getRepository().metadata.columns.find(column => {
|
||||||
|
if (column.propertyName === sort.prop) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (found) {
|
||||||
qb.addOrderBy('main.' + sort.prop, sort.asc ? 'ASC' : 'DESC');
|
qb.addOrderBy('main.' + sort.prop, sort.asc ? 'ASC' : 'DESC');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
qb.addOrderBy('id', 'DESC');
|
qb.addOrderBy('id', 'DESC');
|
||||||
//根据bean query
|
|
||||||
if (query) {
|
|
||||||
let whereSql = '';
|
|
||||||
let index = 0;
|
|
||||||
_.forEach(query, (value, key) => {
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (index !== 0) {
|
|
||||||
whereSql += ' and ';
|
|
||||||
}
|
|
||||||
whereSql += ` main.${key} = :${key} `;
|
|
||||||
index++;
|
|
||||||
});
|
|
||||||
if (index > 0) {
|
|
||||||
qb.andWhere(whereSql, query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//自定义query
|
//自定义query
|
||||||
if (buildQuery) {
|
if (buildQuery) {
|
||||||
buildQuery(qb);
|
buildQuery(qb);
|
||||||
|
|
|
@ -7,5 +7,9 @@
|
||||||
.menu-item-title{
|
.menu-item-title{
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
.fs-icon{
|
||||||
|
font-size: 16px !important;
|
||||||
|
min-width: 16px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ export default defineComponent({
|
||||||
const title: any = () => {
|
const title: any = () => {
|
||||||
const icon = sub.icon || sub?.meta?.icon;
|
const icon = sub.icon || sub?.meta?.icon;
|
||||||
if (icon) {
|
if (icon) {
|
||||||
// @ts-ignore
|
// @ts-ignore , anticon必须要有,不然不能折叠
|
||||||
return (
|
return (
|
||||||
<div class={"menu-item-title"}>
|
<div class={"menu-item-title"}>
|
||||||
<fsIcon class={"anticon"} icon={icon} />
|
<fsIcon class={"anticon"} icon={icon} />
|
||||||
|
|
|
@ -10,13 +10,14 @@
|
||||||
@tab-click="handleClick"
|
@tab-click="handleClick"
|
||||||
@edit="handleTabEdit"
|
@edit="handleTabEdit"
|
||||||
>
|
>
|
||||||
<a-tab-pane
|
<a-tab-pane v-for="item in page.getOpened" :key="item.fullPath" :name="item.fullPath" :closable="isTabClosable(item)">
|
||||||
v-for="item in page.getOpened"
|
<template #tab>
|
||||||
:key="item.fullPath"
|
<span class="flex-o">
|
||||||
:tab="item.meta?.title || '未命名'"
|
<fs-icon :icon="item.meta.icon" class="mr-5"></fs-icon>
|
||||||
:name="item.fullPath"
|
{{ item.meta?.title || "未命名" }}
|
||||||
:closable="isTabClosable(item)"
|
</span>
|
||||||
/>
|
</template>
|
||||||
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
<!-- <fs-contextmenu v-model:open="contextmenuFlag" :x="contentmenuX" :y="contentmenuY">-->
|
<!-- <fs-contextmenu v-model:open="contextmenuFlag" :x="contentmenuX" :y="contentmenuY">-->
|
||||||
<!-- <fs-contextmenu-list-->
|
<!-- <fs-contextmenu-list-->
|
||||||
|
|
|
@ -70,6 +70,16 @@ export const certdResources = [
|
||||||
auth: true
|
auth: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "分组管理",
|
||||||
|
name: "PipelineGroupManager",
|
||||||
|
path: "/certd/pipeline/group",
|
||||||
|
component: "/certd/pipeline/group/index.vue",
|
||||||
|
meta: {
|
||||||
|
icon: "mdi:format-list-group",
|
||||||
|
auth: true
|
||||||
|
}
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// title: "邮箱设置",
|
// title: "邮箱设置",
|
||||||
// name: "EmailSetting",
|
// name: "EmailSetting",
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { ref } from "vue";
|
||||||
import { CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
|
import { CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
|
||||||
import * as api from "/@/views/certd/mine/api";
|
import * as api from "/@/views/certd/mine/api";
|
||||||
import { notification } from "ant-design-vue";
|
import { notification } from "ant-design-vue";
|
||||||
|
import { useUserStore } from "/@/store/modules/user";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
showButton: boolean;
|
showButton: boolean;
|
||||||
|
@ -33,6 +34,7 @@ const validatePass2 = async (rule: any, value: any) => {
|
||||||
throw new Error("两次输入密码不一致!");
|
throw new Error("两次输入密码不一致!");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const userStore = useUserStore();
|
||||||
const { openDialog } = useFormWrapper();
|
const { openDialog } = useFormWrapper();
|
||||||
const { buildFormOptions } = useColumns();
|
const { buildFormOptions } = useColumns();
|
||||||
const passwordFormOptions: CrudOptions = {
|
const passwordFormOptions: CrudOptions = {
|
||||||
|
@ -46,6 +48,8 @@ const passwordFormOptions: CrudOptions = {
|
||||||
},
|
},
|
||||||
async doSubmit({ form }) {
|
async doSubmit({ form }) {
|
||||||
await api.changePassword(form);
|
await api.changePassword(form);
|
||||||
|
//重新加载用户信息
|
||||||
|
await userStore.loadUserInfo();
|
||||||
},
|
},
|
||||||
async afterSubmit() {
|
async afterSubmit() {
|
||||||
notification.success({ message: "修改成功" });
|
notification.success({ message: "修改成功" });
|
||||||
|
|
|
@ -76,6 +76,22 @@ export async function Cancel(historyId: any) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function BatchUpdateGroup(pipelineIds: number[], groupId: number): Promise<CertInfo> {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/batchUpdateGroup",
|
||||||
|
method: "post",
|
||||||
|
data: { ids: pipelineIds, groupId }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function BatchDelete(pipelineIds: number[]): Promise<CertInfo> {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/batchDelete",
|
||||||
|
method: "post",
|
||||||
|
data: { ids: pipelineIds }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function GetFiles(pipelineId: number) {
|
export async function GetFiles(pipelineId: number) {
|
||||||
return await request({
|
return await request({
|
||||||
url: historyApiPrefix + "/files",
|
url: historyApiPrefix + "/files",
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
<template>
|
||||||
|
<fs-button icon="mdi:format-list-group" type="link" text="修改分组" @click="openGroupSelectDialog"></fs-button>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import * as api from "../api";
|
||||||
|
import { notification } from "ant-design-vue";
|
||||||
|
import { dict, useFormWrapper } from "@fast-crud/fast-crud";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
selectedRowKeys: any[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
change: any;
|
||||||
|
}>();
|
||||||
|
async function batchUpdateGroupRequest(groupId: number) {
|
||||||
|
await api.BatchUpdateGroup(props.selectedRowKeys, groupId);
|
||||||
|
emit("change");
|
||||||
|
}
|
||||||
|
|
||||||
|
const pipelineGroupDictRef = dict({
|
||||||
|
url: "/pi/pipeline/group/all",
|
||||||
|
value: "id",
|
||||||
|
label: "name"
|
||||||
|
});
|
||||||
|
const { openCrudFormDialog } = useFormWrapper();
|
||||||
|
|
||||||
|
async function openGroupSelectDialog() {
|
||||||
|
const crudOptions: any = {
|
||||||
|
columns: {
|
||||||
|
groupId: {
|
||||||
|
title: "分组",
|
||||||
|
type: "dict-select",
|
||||||
|
dict: pipelineGroupDictRef,
|
||||||
|
form: {
|
||||||
|
rules: [{ required: true, message: "请选择分组" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
mode: "edit",
|
||||||
|
//@ts-ignore
|
||||||
|
async doSubmit({ form }) {
|
||||||
|
await batchUpdateGroupRequest(form.groupId);
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
span: 22
|
||||||
|
},
|
||||||
|
labelCol: {
|
||||||
|
style: {
|
||||||
|
width: "100px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wrapper: {
|
||||||
|
title: "批量修改分组",
|
||||||
|
width: 600
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
await openCrudFormDialog({ crudOptions });
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -15,7 +15,7 @@ import { useModal } from "/@/use/use-modal";
|
||||||
import CertView from "./cert-view.vue";
|
import CertView from "./cert-view.vue";
|
||||||
import { eachStages } from "./utils";
|
import { eachStages } from "./utils";
|
||||||
import { createApi as createNotificationApi } from "../notification/api";
|
import { createApi as createNotificationApi } from "../notification/api";
|
||||||
export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export default function ({ crudExpose, context: { certdFormRef, groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const lastResRef = ref();
|
const lastResRef = ref();
|
||||||
|
@ -198,6 +198,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
};
|
};
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
request: {
|
request: {
|
||||||
|
@ -206,6 +207,26 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
editRequest,
|
editRequest,
|
||||||
delRequest
|
delRequest
|
||||||
},
|
},
|
||||||
|
settings: {
|
||||||
|
plugins: {
|
||||||
|
//行选择插件,内置插件
|
||||||
|
rowSelection: {
|
||||||
|
//是否启用本插件
|
||||||
|
enabled: true,
|
||||||
|
order: -2,
|
||||||
|
//合并在用户配置crudOptions之前还是之后
|
||||||
|
before: true,
|
||||||
|
props: {
|
||||||
|
multiple: true,
|
||||||
|
crossPage: false,
|
||||||
|
selectedRowKeys,
|
||||||
|
onSelectedChanged(selected) {
|
||||||
|
console.log("已选择变化:", selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
actionbar: {
|
actionbar: {
|
||||||
buttons: {
|
buttons: {
|
||||||
add: {
|
add: {
|
||||||
|
@ -232,9 +253,16 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
table: {
|
table: {
|
||||||
scroll: { x: 1500 }
|
scroll: { x: 1500 }
|
||||||
},
|
},
|
||||||
|
tabs: {
|
||||||
|
name: "groupId",
|
||||||
|
show: true
|
||||||
|
},
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
width: 300,
|
width: 200,
|
||||||
fixed: "right",
|
fixed: "right",
|
||||||
|
dropdown: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
buttons: {
|
buttons: {
|
||||||
play: {
|
play: {
|
||||||
order: -999,
|
order: -999,
|
||||||
|
@ -275,8 +303,9 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
order: 1,
|
order: 1,
|
||||||
title: "修改流水线内容",
|
title: "编辑流水线",
|
||||||
type: "link",
|
type: "link",
|
||||||
|
dropdown: true,
|
||||||
icon: "ant-design:edit-outlined",
|
icon: "ant-design:edit-outlined",
|
||||||
click({ row }) {
|
click({ row }) {
|
||||||
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "true" } });
|
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "true" } });
|
||||||
|
@ -284,8 +313,9 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
order: 2,
|
order: 2,
|
||||||
title: "修改流水线运行配置",
|
title: "修改配置/分组",
|
||||||
icon: "ant-design:setting-outlined"
|
icon: "ant-design:setting-outlined",
|
||||||
|
dropdown: true
|
||||||
},
|
},
|
||||||
viewCert: {
|
viewCert: {
|
||||||
order: 3,
|
order: 3,
|
||||||
|
@ -293,7 +323,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
type: "link",
|
type: "link",
|
||||||
icon: "ph:certificate",
|
icon: "ph:certificate",
|
||||||
async click({ row }) {
|
async click({ row }) {
|
||||||
viewCert(row);
|
await viewCert(row);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
download: {
|
download: {
|
||||||
|
@ -302,11 +332,12 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
title: "下载证书",
|
title: "下载证书",
|
||||||
icon: "ant-design:download-outlined",
|
icon: "ant-design:download-outlined",
|
||||||
async click({ row }) {
|
async click({ row }) {
|
||||||
downloadCert(row);
|
await downloadCert(row);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
order: 5
|
order: 5,
|
||||||
|
dropdown: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -495,17 +526,19 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
groupId: {
|
||||||
keepHistoryCount: {
|
title: "分组",
|
||||||
title: "历史记录保持数",
|
type: "dict-select",
|
||||||
type: "number",
|
search: {
|
||||||
form: {
|
show: true
|
||||||
value: 20,
|
|
||||||
helper: "历史记录保持条数,多余的会被删除"
|
|
||||||
},
|
},
|
||||||
|
dict: groupDictRef,
|
||||||
column: {
|
column: {
|
||||||
width: 130,
|
width: 130,
|
||||||
show: false
|
align: "center",
|
||||||
|
component: {
|
||||||
|
color: "auto"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
order: {
|
order: {
|
||||||
|
@ -520,6 +553,18 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
value: 0
|
value: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
keepHistoryCount: {
|
||||||
|
title: "历史记录保持数",
|
||||||
|
type: "number",
|
||||||
|
form: {
|
||||||
|
value: 20,
|
||||||
|
helper: "历史记录保持条数,多余的会被删除"
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 130,
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
createTime: {
|
createTime: {
|
||||||
title: "创建时间",
|
title: "创建时间",
|
||||||
type: "datetime",
|
type: "datetime",
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { request } from "/src/api/service";
|
||||||
|
|
||||||
|
export function createApi() {
|
||||||
|
const apiPrefix = "/pi/pipeline/group";
|
||||||
|
return {
|
||||||
|
async GetList(query: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/page",
|
||||||
|
method: "post",
|
||||||
|
data: query
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async AddObj(obj: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/add",
|
||||||
|
method: "post",
|
||||||
|
data: obj
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async UpdateObj(obj: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/update",
|
||||||
|
method: "post",
|
||||||
|
data: obj
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async DelObj(id: number) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/delete",
|
||||||
|
method: "post",
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async GetObj(id: number) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/info",
|
||||||
|
method: "post",
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async ListAll() {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/all",
|
||||||
|
method: "post"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pipelineGroupApi = createApi();
|
|
@ -0,0 +1,124 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
|
import { pipelineGroupApi } from "./api";
|
||||||
|
|
||||||
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const api = pipelineGroupApi;
|
||||||
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
return await api.GetList(query);
|
||||||
|
};
|
||||||
|
const editRequest = async (req: EditReq) => {
|
||||||
|
const { form, row } = req;
|
||||||
|
form.id = row.id;
|
||||||
|
const res = await api.UpdateObj(form);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
const delRequest = async (req: DelReq) => {
|
||||||
|
const { row } = req;
|
||||||
|
return await api.DelObj(row.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRequest = async (req: AddReq) => {
|
||||||
|
const { form } = req;
|
||||||
|
const res = await api.AddObj(form);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
request: {
|
||||||
|
pageRequest,
|
||||||
|
addRequest,
|
||||||
|
editRequest,
|
||||||
|
delRequest
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
labelCol: {
|
||||||
|
//固定label宽度
|
||||||
|
span: null,
|
||||||
|
style: {
|
||||||
|
width: "100px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
span: 22
|
||||||
|
},
|
||||||
|
wrapper: {
|
||||||
|
width: 600
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowHandle: {
|
||||||
|
width: 200,
|
||||||
|
group: {
|
||||||
|
editable: {
|
||||||
|
edit: {
|
||||||
|
text: "编辑",
|
||||||
|
order: -1,
|
||||||
|
type: "primary",
|
||||||
|
click({ row, index }) {
|
||||||
|
crudExpose.openEdit({
|
||||||
|
index,
|
||||||
|
row
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
editable: {
|
||||||
|
enabled: true,
|
||||||
|
mode: "cell",
|
||||||
|
exclusive: true,
|
||||||
|
//排他式激活效果,将其他行的编辑状态触发保存
|
||||||
|
exclusiveEffect: "save", //自动保存其他行编辑状态,cancel = 自动关闭其他行编辑状态
|
||||||
|
async updateCell(opts) {
|
||||||
|
const { row, key, value } = opts;
|
||||||
|
//如果是添加,需要返回{[rowKey]:xxx},比如:{id:2}
|
||||||
|
return await api.UpdateObj({ id: row.id, [key]: value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
id: {
|
||||||
|
title: "ID",
|
||||||
|
key: "id",
|
||||||
|
type: "number",
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
editable: {
|
||||||
|
disabled: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
title: "分组名称",
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入分组名称"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 400
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<template>
|
||||||
|
<fs-page>
|
||||||
|
<template #header>
|
||||||
|
<div class="title">
|
||||||
|
分组管理
|
||||||
|
<span class="sub">管理流水线分组</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, onActivated, onMounted } from "vue";
|
||||||
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
|
import createCrudOptions from "./crud";
|
||||||
|
import { createApi } from "./api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "PipelineGroupManager",
|
||||||
|
setup() {
|
||||||
|
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
|
||||||
|
|
||||||
|
// 页面打开后获取列表数据
|
||||||
|
onMounted(() => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
onActivated(() => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudBinding,
|
||||||
|
crudRef
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -4,6 +4,13 @@
|
||||||
<div class="title">我的流水线</div>
|
<div class="title">我的流水线</div>
|
||||||
</template>
|
</template>
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||||
|
<div v-if="selectedRowKeys.length > 0" class="batch-actions">
|
||||||
|
<div class="batch-actions-inner">
|
||||||
|
<span> 已选择 {{ selectedRowKeys.length }} 项 </span>
|
||||||
|
<fs-button icon="ion:trash-outline" type="link" text="批量删除" @click="batchDelete"></fs-button>
|
||||||
|
<change-group :selected-row-keys="selectedRowKeys" @change="groupChanged"></change-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<template #actionbar-right> </template>
|
<template #actionbar-right> </template>
|
||||||
<template #form-bottom>
|
<template #form-bottom>
|
||||||
<div>申请证书</div>
|
<div>申请证书</div>
|
||||||
|
@ -13,19 +20,29 @@
|
||||||
</fs-page>
|
</fs-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, onMounted, onActivated } from "vue";
|
import { onActivated, onMounted, ref } from "vue";
|
||||||
import { useCrud, useFs } from "@fast-crud/fast-crud";
|
import { dict, useFs } from "@fast-crud/fast-crud";
|
||||||
import createCrudOptions from "./crud";
|
import createCrudOptions from "./crud";
|
||||||
import { useExpose } from "@fast-crud/fast-crud";
|
|
||||||
import PiCertdForm from "./certd-form/index.vue";
|
import PiCertdForm from "./certd-form/index.vue";
|
||||||
export default defineComponent({
|
import ChangeGroup from "./components/change-group.vue";
|
||||||
name: "PipelineManager",
|
import { Modal, notification } from "ant-design-vue";
|
||||||
components: { PiCertdForm },
|
import * as api from "./api";
|
||||||
setup() {
|
defineOptions({
|
||||||
|
name: "PipelineManager"
|
||||||
|
});
|
||||||
|
|
||||||
const certdFormRef = ref();
|
const certdFormRef = ref();
|
||||||
|
const groupDictRef = dict({
|
||||||
|
url: "/pi/pipeline/group/all",
|
||||||
|
value: "id",
|
||||||
|
label: "name"
|
||||||
|
});
|
||||||
|
const selectedRowKeys = ref([]);
|
||||||
const context: any = {
|
const context: any = {
|
||||||
certdFormRef
|
certdFormRef,
|
||||||
|
groupDictRef,
|
||||||
|
selectedRowKeys
|
||||||
};
|
};
|
||||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
|
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
|
||||||
|
|
||||||
|
@ -34,16 +51,49 @@ export default defineComponent({
|
||||||
crudExpose.doRefresh();
|
crudExpose.doRefresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(async () => {
|
||||||
crudExpose.doRefresh();
|
await groupDictRef.reloadDict();
|
||||||
|
await crudExpose.doRefresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
function groupChanged() {
|
||||||
crudBinding,
|
crudExpose.doRefresh();
|
||||||
crudRef,
|
}
|
||||||
certdFormRef
|
|
||||||
};
|
function batchDelete() {
|
||||||
|
Modal.confirm({
|
||||||
|
title: "确认删除",
|
||||||
|
content: "确定要删除选中的数据吗?",
|
||||||
|
async onOk() {
|
||||||
|
await api.BatchDelete(selectedRowKeys.value);
|
||||||
|
notification.success({ message: "删除成功" });
|
||||||
|
await crudExpose.doRefresh();
|
||||||
|
selectedRowKeys.value = [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less"></style>
|
<style lang="less">
|
||||||
|
.batch-actions {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
line-height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 37.86px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 1px;
|
||||||
|
padding-left: 48px;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.batch-actions-inner {
|
||||||
|
pointer-events: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fafafa;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -37,6 +37,7 @@ function transform() {
|
||||||
let pgSql = sqliteSql.replace(/AUTOINCREMENT/g, 'GENERATED BY DEFAULT AS IDENTITY');
|
let pgSql = sqliteSql.replace(/AUTOINCREMENT/g, 'GENERATED BY DEFAULT AS IDENTITY');
|
||||||
pgSql = pgSql.replace(/datetime/g, 'timestamp');
|
pgSql = pgSql.replace(/datetime/g, 'timestamp');
|
||||||
pgSql = pgSql.replace(/boolean DEFAULT \(0\)/g, 'boolean DEFAULT (false)');
|
pgSql = pgSql.replace(/boolean DEFAULT \(0\)/g, 'boolean DEFAULT (false)');
|
||||||
|
pgSql = pgSql.replace(/boolean.*NOT NULL DEFAULT \(0\)/g, 'boolean NOT NULL DEFAULT (false)');
|
||||||
pgSql = pgSql.replace(/integer/g, 'bigint');
|
pgSql = pgSql.replace(/integer/g, 'bigint');
|
||||||
pgSql = pgSql.replace(/last_insert_rowid\(\)/g, 'LASTVAL()');
|
pgSql = pgSql.replace(/last_insert_rowid\(\)/g, 'LASTVAL()');
|
||||||
fs.writeFileSync(`./migration-pg/${notFile}`, pgSql);
|
fs.writeFileSync(`./migration-pg/${notFile}`, pgSql);
|
||||||
|
|
|
@ -53,6 +53,7 @@ export class CnameRecordController extends CrudController<CnameRecordService> {
|
||||||
@Post('/update', { summary: Constants.per.authOnly })
|
@Post('/update', { summary: Constants.per.authOnly })
|
||||||
async update(@Body(ALL) bean: any) {
|
async update(@Body(ALL) bean: any) {
|
||||||
await this.service.checkUserId(bean.id, this.getUserId());
|
await this.service.checkUserId(bean.id, this.getUserId());
|
||||||
|
delete bean.userId;
|
||||||
return super.update(bean);
|
return super.update(bean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ export class UserSettingsController extends CrudController<UserSettingsService>
|
||||||
@Post('/update', { summary: Constants.per.authOnly })
|
@Post('/update', { summary: Constants.per.authOnly })
|
||||||
async update(@Body(ALL) bean) {
|
async update(@Body(ALL) bean) {
|
||||||
await this.service.checkUserId(bean.id, this.getUserId());
|
await this.service.checkUserId(bean.id, this.getUserId());
|
||||||
|
delete bean.userId;
|
||||||
return super.update(bean);
|
return super.update(bean);
|
||||||
}
|
}
|
||||||
@Post('/info', { summary: Constants.per.authOnly })
|
@Post('/info', { summary: Constants.per.authOnly })
|
||||||
|
|
|
@ -50,6 +50,7 @@ export class AccessController extends CrudController<AccessService> {
|
||||||
@Post('/update', { summary: Constants.per.authOnly })
|
@Post('/update', { summary: Constants.per.authOnly })
|
||||||
async update(@Body(ALL) bean) {
|
async update(@Body(ALL) bean) {
|
||||||
await this.service.checkUserId(bean.id, this.getUserId());
|
await this.service.checkUserId(bean.id, this.getUserId());
|
||||||
|
delete bean.userId;
|
||||||
return super.update(bean);
|
return super.update(bean);
|
||||||
}
|
}
|
||||||
@Post('/info', { summary: Constants.per.authOnly })
|
@Post('/info', { summary: Constants.per.authOnly })
|
||||||
|
|
|
@ -104,6 +104,7 @@ export class HistoryController extends CrudController<HistoryService> {
|
||||||
@Post('/update', { summary: Constants.per.authOnly })
|
@Post('/update', { summary: Constants.per.authOnly })
|
||||||
async update(@Body(ALL) bean) {
|
async update(@Body(ALL) bean) {
|
||||||
await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
|
await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
|
||||||
|
delete bean.userId;
|
||||||
return super.update(bean);
|
return super.update(bean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ export class NotificationController extends CrudController<NotificationService>
|
||||||
checkPlus();
|
checkPlus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
delete bean.userId;
|
||||||
return super.update(bean);
|
return super.update(bean);
|
||||||
}
|
}
|
||||||
@Post('/info', { summary: Constants.per.authOnly })
|
@Post('/info', { summary: Constants.per.authOnly })
|
||||||
|
|
|
@ -62,6 +62,7 @@ export class PipelineController extends CrudController<PipelineService> {
|
||||||
@Post('/update', { summary: Constants.per.authOnly })
|
@Post('/update', { summary: Constants.per.authOnly })
|
||||||
async update(@Body(ALL) bean) {
|
async update(@Body(ALL) bean) {
|
||||||
await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
|
await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
|
||||||
|
delete bean.userId;
|
||||||
return super.update(bean);
|
return super.update(bean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,4 +110,16 @@ export class PipelineController extends CrudController<PipelineService> {
|
||||||
const count = await this.service.count({ userId: this.getUserId() });
|
const count = await this.service.count({ userId: this.getUserId() });
|
||||||
return this.ok({ count });
|
return this.ok({ count });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('/batchDelete', { summary: Constants.per.authOnly })
|
||||||
|
async batchDelete(@Body('ids') ids: number[]) {
|
||||||
|
await this.service.batchDelete(ids, this.getUserId());
|
||||||
|
return this.ok({});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/batchUpdateGroup', { summary: Constants.per.authOnly })
|
||||||
|
async batchUpdateGroup(@Body('ids') ids: number[], @Body('groupId') groupId: number) {
|
||||||
|
await this.service.batchUpdateGroup(ids, groupId, this.getUserId());
|
||||||
|
return this.ok({});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||||
|
import { Constants, CrudController } from '@certd/lib-server';
|
||||||
|
import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
|
||||||
|
import { PipelineGroupService } from '../../modules/pipeline/service/pipeline-group-service.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Controller('/api/pi/pipeline/group')
|
||||||
|
export class PipelineGroupController extends CrudController<PipelineGroupService> {
|
||||||
|
@Inject()
|
||||||
|
service: PipelineGroupService;
|
||||||
|
@Inject()
|
||||||
|
authService: AuthService;
|
||||||
|
|
||||||
|
getService(): PipelineGroupService {
|
||||||
|
return this.service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/page', { summary: Constants.per.authOnly })
|
||||||
|
async page(@Body(ALL) body: any) {
|
||||||
|
body.query = body.query ?? {};
|
||||||
|
delete body.query.userId;
|
||||||
|
const buildQuery = qb => {
|
||||||
|
qb.andWhere('user_id = :userId', { userId: this.getUserId() });
|
||||||
|
};
|
||||||
|
const res = await this.service.page({
|
||||||
|
query: body.query,
|
||||||
|
page: body.page,
|
||||||
|
sort: body.sort,
|
||||||
|
buildQuery,
|
||||||
|
});
|
||||||
|
return this.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/list', { summary: Constants.per.authOnly })
|
||||||
|
async list(@Body(ALL) body: any) {
|
||||||
|
body.userId = this.getUserId();
|
||||||
|
return await super.list(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/add', { summary: Constants.per.authOnly })
|
||||||
|
async add(@Body(ALL) bean: any) {
|
||||||
|
bean.userId = this.getUserId();
|
||||||
|
return await super.add(bean);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/update', { summary: Constants.per.authOnly })
|
||||||
|
async update(@Body(ALL) bean) {
|
||||||
|
await this.service.checkUserId(bean.id, this.getUserId());
|
||||||
|
delete bean.userId;
|
||||||
|
return await super.update(bean);
|
||||||
|
}
|
||||||
|
@Post('/info', { summary: Constants.per.authOnly })
|
||||||
|
async info(@Query('id') id: number) {
|
||||||
|
await this.service.checkUserId(id, this.getUserId());
|
||||||
|
return await super.info(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/delete', { summary: Constants.per.authOnly })
|
||||||
|
async delete(@Query('id') id: number) {
|
||||||
|
await this.service.checkUserId(id, this.getUserId());
|
||||||
|
return await super.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/all', { summary: Constants.per.authOnly })
|
||||||
|
async all() {
|
||||||
|
const list: any = await this.service.find({
|
||||||
|
userId: this.getUserId(),
|
||||||
|
});
|
||||||
|
return this.ok(list);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('pi_pipeline_group')
|
||||||
|
export class PipelineGroupEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column({ name: 'user_id', comment: '用户id' })
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
@Column({ name: 'name', comment: '分组名称' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ name: 'icon', comment: '图标' })
|
||||||
|
icon: string;
|
||||||
|
|
||||||
|
@Column({ name: 'favorite', comment: '收藏' })
|
||||||
|
favorite: boolean;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'create_time',
|
||||||
|
comment: '创建时间',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP',
|
||||||
|
})
|
||||||
|
createTime: Date;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'update_time',
|
||||||
|
comment: '修改时间',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP',
|
||||||
|
})
|
||||||
|
updateTime: Date;
|
||||||
|
}
|
|
@ -14,13 +14,12 @@ export class PipelineEntity {
|
||||||
@Column({ comment: '配置', length: 40960 })
|
@Column({ comment: '配置', length: 40960 })
|
||||||
content: string;
|
content: string;
|
||||||
|
|
||||||
@Column({
|
@Column({ name: 'keep_history_count', comment: '历史记录保持数量', nullable: true })
|
||||||
name: 'keep_history_count',
|
|
||||||
comment: '历史记录保持数量',
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
keepHistoryCount: number;
|
keepHistoryCount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'group_id', comment: '分组id', nullable: true })
|
||||||
|
groupId: number;
|
||||||
|
|
||||||
@Column({ comment: '备注', length: 100, nullable: true })
|
@Column({ comment: '备注', length: 100, nullable: true })
|
||||||
remark: string;
|
remark: string;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
|
import { BaseService } from '@certd/lib-server';
|
||||||
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { PipelineGroupEntity } from '../entity/pipeline-group.js';
|
||||||
|
import { merge } from 'lodash-es';
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class PipelineGroupService extends BaseService<PipelineGroupEntity> {
|
||||||
|
@InjectEntityModel(PipelineGroupEntity)
|
||||||
|
repository: Repository<PipelineGroupEntity>;
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
getRepository() {
|
||||||
|
return this.repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(bean: any) {
|
||||||
|
bean = merge(
|
||||||
|
{
|
||||||
|
favorite: false,
|
||||||
|
},
|
||||||
|
bean
|
||||||
|
);
|
||||||
|
return await this.repository.save(bean);
|
||||||
|
}
|
||||||
|
}
|
|
@ -554,4 +554,21 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async batchDelete(ids: number[], userId: number) {
|
||||||
|
for (const id of ids) {
|
||||||
|
await this.checkUserId(id, userId);
|
||||||
|
await this.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async batchUpdateGroup(ids: number[], groupId: number, userId: any) {
|
||||||
|
await this.repository.update(
|
||||||
|
{
|
||||||
|
id: In(ids),
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
{ groupId }
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue