From e487bbd39f18573ec5d31dd360e0d32a002acc05 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:23:46 +0000 Subject: [PATCH] Add pin to top feature for environment variables Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/api/env.ts | 34 +++++++++++++++++ back/data/env.ts | 3 ++ back/services/env.ts | 9 +++++ src/pages/env/index.tsx | 84 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 129 insertions(+), 1 deletion(-) diff --git a/back/api/env.ts b/back/api/env.ts index 845b5df5..77eae38c 100644 --- a/back/api/env.ts +++ b/back/api/env.ts @@ -196,6 +196,40 @@ export default (app: Router) => { }, ); + route.put( + '/pin', + celebrate({ + body: Joi.array().items(Joi.number().required()), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const envService = Container.get(EnvService); + const data = await envService.pin(req.body); + return res.send({ code: 200, data }); + } catch (e) { + return next(e); + } + }, + ); + + route.put( + '/unpin', + celebrate({ + body: Joi.array().items(Joi.number().required()), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const envService = Container.get(EnvService); + const data = await envService.unPin(req.body); + return res.send({ code: 200, data }); + } catch (e) { + return next(e); + } + }, + ); + route.post( '/upload', upload.single('env'), diff --git a/back/data/env.ts b/back/data/env.ts index f6a9094e..a3670b41 100644 --- a/back/data/env.ts +++ b/back/data/env.ts @@ -9,6 +9,7 @@ export class Env { position?: number; name?: string; remarks?: string; + isPinned?: 1 | 0; constructor(options: Env) { this.value = options.value; @@ -21,6 +22,7 @@ export class Env { this.position = options.position; this.name = options.name; this.remarks = options.remarks || ''; + this.isPinned = options.isPinned || 0; } } @@ -42,4 +44,5 @@ export const EnvModel = sequelize.define('Env', { position: DataTypes.NUMBER, name: { type: DataTypes.STRING, unique: 'compositeIndex' }, remarks: DataTypes.STRING, + isPinned: DataTypes.NUMBER, }); diff --git a/back/services/env.ts b/back/services/env.ts index be89cc86..7ea53674 100644 --- a/back/services/env.ts +++ b/back/services/env.ts @@ -147,6 +147,7 @@ export default class EnvService { } try { const result = await this.find(condition, [ + ['isPinned', 'DESC'], ['position', 'DESC'], ['createdAt', 'ASC'], ]); @@ -190,6 +191,14 @@ export default class EnvService { await this.set_envs(); } + public async pin(ids: number[]) { + await EnvModel.update({ isPinned: 1 }, { where: { id: ids } }); + } + + public async unPin(ids: number[]) { + await EnvModel.update({ isPinned: 0 }, { where: { id: ids } }); + } + public async set_envs() { const envs = await this.envs('', { name: { [Op.not]: null }, diff --git a/src/pages/env/index.tsx b/src/pages/env/index.tsx index 3600e0a8..a4c53107 100644 --- a/src/pages/env/index.tsx +++ b/src/pages/env/index.tsx @@ -26,6 +26,8 @@ import { CheckCircleOutlined, StopOutlined, UploadOutlined, + PushpinOutlined, + PushpinFilled, } from '@ant-design/icons'; import config from '@/utils/config'; import { PageContainer } from '@ant-design/pro-layout'; @@ -59,11 +61,15 @@ enum StatusColor { enum OperationName { '启用', '禁用', + '置顶', + '取消置顶', } enum OperationPath { 'enable', 'disable', + 'pin', + 'unpin', } const type = 'DragableBodyRow'; @@ -181,7 +187,7 @@ const Env = () => { { title: intl.get('操作'), key: 'action', - width: 120, + width: 160, render: (text: string, record: any, index: number) => { const isPc = !isPhone; return ( @@ -208,6 +214,23 @@ const Env = () => { )} + + pinOrUnpinEnv(record, index)}> + {record.isPinned === 1 ? ( + + ) : ( + + )} + + deleteEnv(record, index)}> @@ -305,6 +328,51 @@ const Env = () => { setIsModalVisible(true); }; + const pinOrUnpinEnv = (record: any, index: number) => { + Modal.confirm({ + title: `确认${ + record.isPinned === 1 ? intl.get('取消置顶') : intl.get('置顶') + }`, + content: ( + <> + {intl.get('确认')} + {record.isPinned === 1 ? intl.get('取消置顶') : intl.get('置顶')} + Env{' '} + + {record.name}: {record.value} + {' '} + {intl.get('吗')} + + ), + onOk() { + request + .put( + `${config.apiPrefix}envs/${ + record.isPinned === 1 ? 'unpin' : 'pin' + }`, + [record.id], + ) + .then(({ code, data }) => { + if (code === 200) { + message.success( + `${ + record.isPinned === 1 + ? intl.get('取消置顶') + : intl.get('置顶') + }${intl.get('成功')}`, + ); + getEnvs(); + } + }); + }, + }); + }; + const deleteEnv = (record: any, index: number) => { Modal.confirm({ title: intl.get('确认删除'), @@ -589,6 +657,20 @@ const Env = () => { > {intl.get('批量禁用')} + + {intl.get('已选择')} {selectedRowIds?.length}