Compare commits

...

4 Commits

Author SHA1 Message Date
xiaojunnuo bc326489ab fix: 修复复制流水线保存后丢失分组和排序号的问题 2026-02-28 19:29:13 +08:00
xiaojunnuo ea5e7d9563 chore: project setting 2026-02-28 18:49:46 +08:00
xiaojunnuo 5b5b48fc06 chore: admin mode setting 2026-02-28 18:30:04 +08:00
xiaojunnuo 1548ba0b8d chore: project manager 2026-02-28 18:17:53 +08:00
16 changed files with 175 additions and 27 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

@@ -35,7 +35,7 @@ defineOptions({
const projectStore = useProjectStore();
onMounted(async () => {
await projectStore.reload();
await projectStore.init();
console.log(projectStore.myProjects);
});
@@ -219,6 +219,7 @@ export default {
myProjectManager: "My Projects",
myProjectDetail: "Project Detail",
projectJoin: "Join Project",
currentProject: "Current Project",
},
certificateRepo: {
title: "Certificate Repository",
@@ -225,6 +225,7 @@ export default {
myProjectManager: "我的项目",
myProjectDetail: "项目详情",
projectJoin: "加入项目",
currentProject: "当前项目",
},
certificateRepo: {
title: "证书仓库",
@@ -832,8 +833,8 @@ export default {
projectDetailDescription: "管理项目成员",
projectPermission: "权限",
permission: {
read: "读取",
write: "写入",
read: "查看",
write: "修改",
admin: "管理员",
},
projectMemberStatus: "成员状态",
@@ -82,7 +82,6 @@ export function useCrudPermission({ permission }: UseCrudPermissionProps) {
if (isProjectPermission) {
removePermission = per.projectPermission || "write";
}
debugger;
return LodashMerge(
{
actionbar: {
@@ -1,7 +1,5 @@
import { useSettingStore } from "/@/store/settings";
import aboutResource from "/@/router/source/modules/about";
import i18n from "/@/locales/i18n";
import { useProjectStore } from "/@/store/project";
import { useSettingStore } from "/@/store/settings";
export const certdResources = [
{
@@ -28,11 +26,12 @@ export const certdResources = [
isMenu: false,
icon: "ion:apps",
keepAlive: true,
auth: true,
},
},
{
title: "certd.sysResources.myProjectDetail",
name: "MyProjectDetail",
title: "certd.sysResources.currentProject",
name: "CurrentProject",
path: "/certd/project/detail",
component: "/certd/project/detail/index.vue",
meta: {
@@ -42,6 +41,7 @@ export const certdResources = [
},
isMenu: true,
icon: "ion:apps",
auth: true,
},
},
{
@@ -53,6 +53,7 @@ export const certdResources = [
isMenu: false,
show: true,
icon: "ion:apps",
auth: true,
},
},
{
@@ -63,6 +64,7 @@ export const certdResources = [
meta: {
icon: "ion:analytics-sharp",
keepAlive: true,
auth: true,
},
},
{
@@ -72,6 +74,7 @@ export const certdResources = [
component: "/certd/pipeline/detail.vue",
meta: {
isMenu: false,
auth: true,
},
},
{
@@ -82,6 +85,7 @@ export const certdResources = [
meta: {
icon: "ion:timer-outline",
keepAlive: true,
auth: true,
},
},
{
@@ -92,6 +96,7 @@ export const certdResources = [
meta: {
isMenu: true,
icon: "ion:duplicate-outline",
auth: true,
},
},
{
@@ -101,6 +106,7 @@ export const certdResources = [
component: "/certd/pipeline/template/edit.vue",
meta: {
isMenu: false,
auth: true,
},
},
{
@@ -110,6 +116,7 @@ export const certdResources = [
component: "/certd/pipeline/template/import/index.vue",
meta: {
isMenu: false,
auth: true,
},
},
{
@@ -1,7 +1,4 @@
import LayoutPass from "/@/layout/layout-pass.vue";
import { useSettingStore } from "/@/store/settings";
import aboutResource from "/@/router/source/modules/about";
import i18n from "/@/locales/i18n";
export const sysResources = [
{
@@ -13,6 +10,7 @@ export const sysResources = [
icon: "ion:settings-outline",
permission: "sys:settings:view",
order: 10,
auth: true,
},
children: [
{
@@ -27,6 +25,7 @@ export const sysResources = [
},
icon: "ion:speedometer-outline",
permission: "sys:auth:user:view",
auth: true,
},
},
@@ -38,6 +37,7 @@ export const sysResources = [
meta: {
icon: "ion:settings-outline",
permission: "sys:settings:view",
auth: true,
},
},
{
@@ -47,6 +47,7 @@ export const sysResources = [
component: "/sys/enterprise/project/index.vue",
meta: {
show: true,
auth: true,
icon: "ion:apps",
permission: "sys:settings:edit",
keepAlive: true,
@@ -60,6 +61,7 @@ export const sysResources = [
meta: {
isMenu: false,
show: true,
auth: true,
icon: "ion:apps",
permission: "sys:settings:edit",
},
@@ -73,6 +75,7 @@ export const sysResources = [
icon: "ion:earth-outline",
permission: "sys:settings:view",
keepAlive: true,
auth: true,
},
},
{
@@ -96,6 +99,7 @@ export const sysResources = [
const settingStore = useSettingStore();
return settingStore.isComm;
},
auth: true,
icon: "ion:document-text-outline",
permission: "sys:settings:view",
},
@@ -110,6 +114,7 @@ export const sysResources = [
const settingStore = useSettingStore();
return settingStore.isComm;
},
auth: true,
icon: "ion:menu",
permission: "sys:settings:view",
keepAlive: true,
@@ -125,6 +130,7 @@ export const sysResources = [
const settingStore = useSettingStore();
return settingStore.isComm;
},
auth: true,
icon: "ion:disc-outline",
permission: "sys:settings:view",
keepAlive: true,
@@ -139,6 +145,7 @@ export const sysResources = [
icon: "ion:extension-puzzle-outline",
permission: "sys:settings:view",
keepAlive: true,
auth: true,
},
},
{
@@ -151,6 +158,7 @@ export const sysResources = [
icon: "ion:extension-puzzle",
permission: "sys:settings:view",
keepAlive: true,
auth: true,
},
},
{
@@ -165,6 +173,7 @@ export const sysResources = [
},
icon: "ion:extension-puzzle",
permission: "sys:settings:view",
auth: true,
},
},
{
@@ -176,6 +185,7 @@ export const sysResources = [
icon: "ion:golf-outline",
permission: "sys:settings:view",
keepAlive: true,
auth: true,
},
},
{
@@ -187,6 +197,7 @@ export const sysResources = [
icon: "ion:list-outline",
permission: "sys:auth:per:view",
keepAlive: true,
auth: true,
},
},
{
@@ -198,6 +209,7 @@ export const sysResources = [
icon: "ion:people-outline",
permission: "sys:auth:role:view",
keepAlive: true,
auth: true,
},
},
{
@@ -209,6 +221,7 @@ export const sysResources = [
icon: "ion:person-outline",
permission: "sys:auth:user:view",
keepAlive: true,
auth: true,
},
},
{
@@ -224,6 +237,7 @@ export const sysResources = [
return settingStore.isComm;
},
keepAlive: true,
auth: true,
},
children: [
{
@@ -238,6 +252,7 @@ export const sysResources = [
},
icon: "ion:cart",
permission: "sys:settings:edit",
auth: true,
},
},
{
@@ -253,6 +268,7 @@ export const sysResources = [
icon: "ion:bag-check",
permission: "sys:settings:edit",
keepAlive: true,
auth: true,
},
},
{
@@ -17,7 +17,8 @@ export const useProjectStore = defineStore("app.project", () => {
const inited = ref(false);
const userStore = useUserStore();
const userId = userStore.getUserInfo?.id;
const lastProjectId = LocalStorage.get("currentProjectId:" + userId);
const lastProjectIdCacheKey = "currentProjectId:" + userId;
const lastProjectId = LocalStorage.get(lastProjectIdCacheKey);
const currentProjectId = ref(lastProjectId); // 直接调用
const projects = computed(() => {
@@ -63,7 +64,7 @@ export const useProjectStore = defineStore("app.project", () => {
function changeCurrentProject(id: string, silent?: boolean) {
currentProjectId.value = id;
LocalStorage.set("currentProjectId", id);
LocalStorage.set(lastProjectIdCacheKey, id);
if (!silent) {
message.success("切换项目成功");
}
@@ -6,13 +6,13 @@ const projectPermissionDict = dict({
data: [
{
value: "read",
label: "只读",
label: "查看",
color: "cyan",
icon: "material-symbols:folder-eye-outline-sharp",
},
{
value: "write",
label: "读写",
label: "修改",
color: "green",
icon: "material-symbols:edit-square-outline-rounded",
},
@@ -50,6 +50,7 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
delete form.lastVars;
delete form.createTime;
delete form.id;
delete form.webhook;
let pipeline = form.content;
if (typeof pipeline === "string" && pipeline.startsWith("{")) {
pipeline = JSON.parse(form.content);
@@ -75,8 +76,8 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
function onDialogOpen(opt: any) {
const searchForm = crudExpose.getSearchValidatedFormData();
opt.initialForm = {
...opt.initialForm,
groupId: searchForm.groupId,
...opt.initialForm,
};
}
@@ -219,7 +220,7 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
row = info.pipeline;
row.content = JSON.parse(row.content);
row.title = row.title + "_copy";
await crudExpose.openCopy({
await crudExpose.openAdd({
row: row,
index: context.index,
});
@@ -2,10 +2,8 @@
<fs-page class="page-project-detail">
<template #header>
<div class="title">
{{ t("certd.ent.projectDetailManager") }}
当前项目 {{ project?.name }}
<span class="sub flex-inline items-center">
项目名称 <a-tag color="green">{{ project?.name }}</a-tag>
<a-divider type="vertical"></a-divider>
管理员<fs-values-format :model-value="project.adminId" :dict="userDict" color="green"></fs-values-format>
<!-- <a-divider type="vertical"></a-divider>
<fs-values-format :model-value="project.permission" :dict="projectPermissionDict"></fs-values-format>
@@ -7,6 +7,7 @@ import { useSettingStore } from "/@/store/settings";
import { useUserStore } from "/@/store/user";
import { useI18n } from "/src/locales";
import { userDict } from "../../dicts";
import { useDicts } from "/@/views/certd/dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
@@ -35,6 +36,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
const { projectMemberStatusDict } = useDicts();
return {
crudOptions: {
settings: {
@@ -133,6 +136,27 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
width: 200,
},
},
status: {
title: t("certd.ent.projectMemberStatus"),
type: "dict-select",
dict: projectMemberStatusDict,
search: {
show: true,
},
form: {
show: true,
},
column: {
width: 200,
cellRender: ({ row }) => {
return (
<div class="flex items-center">
<fs-values-format model-value={row.status} dict={projectMemberStatusDict}></fs-values-format>
</div>
);
},
},
},
createTime: {
title: t("certd.createTime"),
type: "datetime",
@@ -12,6 +12,7 @@
</a-tooltip>
</template>
</fs-crud>
<AdminModeIntro v-if="!projectStore.isEnterprise" title="当前为SaaS管理模式,项目管理需要切换到企业模式" :open="true"></AdminModeIntro>
</fs-page>
</template>
@@ -22,7 +23,8 @@ import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
import AdminModeIntro from "./intro.vue";
const { t } = useI18n();
defineOptions({
@@ -30,6 +32,7 @@ defineOptions({
});
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
const projectStore = useProjectStore();
const selectedRowKeys = context.selectedRowKeys;
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
@@ -0,0 +1,86 @@
<template>
<div v-if="open" class="admin-mode-intro" :style="fixed ? 'position: fixed;' : 'position: absolute;'" @click="close()">
<div class="mask">
<div class="intro-content">
<h2 class="intro-title text-xl font-bold">{{ title || "管理模式介绍" }}</h2>
<div class="mt-8 image-block">
<div class="flex gap-8">
<div class="intro-desc flex-1">SaaS模式每个用户管理自己的流水线和授权资源独立使用</div>
<div class="intro-desc flex-1">企业模式企业内部员工使用通过项目合作管理流水线证书和授权资源</div>
</div>
<div class="image-intro">
<img :src="src" alt="" />
</div>
</div>
<div class="action">
<a-button type="primary" html-type="button" @click="goSwitchMode">立即前往切换模式</a-button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="tsx">
import { ref } from "vue";
import { useRouter } from "vue-router";
defineOptions({
name: "AdminModeIntro",
});
const props = defineProps<{
title?: string;
open?: boolean;
fixed?: boolean;
}>();
const emit = defineEmits(["update:open"]);
function close() {
emit("update:open", false);
}
const src = ref("static/images/ent/admin_mode.png");
const router = useRouter();
function goSwitchMode() {
router.push("/sys/settings?tab=mode");
}
</script>
<style lang="less">
.admin-mode-intro {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
.mask {
padding: 20px;
border-radius: 10px;
}
.intro-content {
background-color: #fff;
padding: 20px;
border-radius: 10px;
text-align: center;
}
.image-block {
text-align: center;
.image-intro {
width: 100%;
img {
margin: 0 auto;
max-width: 100%;
}
}
}
}
</style>
@@ -2,13 +2,20 @@
<div class="sys-settings-form sys-settings-mode">
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish">
<a-form-item :label="t('certd.sys.setting.adminMode')" :name="['public', 'adminMode']">
<fs-dict-radio v-model:value="formState.public.adminMode" :dict="adminModeDict" />
<div class="w-full flex items-center">
<fs-dict-radio v-model:value="formState.public.adminMode" :disabled="!settingsStore.isPlus" :dict="adminModeDict" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">建议在开始使用时选择合适的模式之后就不要随意切换了</div>
<div><a @click="adminModeIntroOpen = true"> 管理模式介绍</a></div>
</a-form-item>
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 8 }">
<a-button :loading="saveLoading" type="primary" html-type="submit">{{ t("certd.saveButton") }}</a-button>
</a-form-item>
</a-form>
<AdminModeIntro v-model:open="adminModeIntroOpen" fixed></AdminModeIntro>
</div>
</template>
@@ -22,22 +29,25 @@ import { notification } from "ant-design-vue";
import { useI18n } from "/src/locales";
import { dict } from "@fast-crud/fast-crud";
import { useProjectStore } from "/@/store/project";
import AdminModeIntro from "/@/views/sys/enterprise/project/intro.vue";
const { t } = useI18n();
defineOptions({
name: "SettingMode",
});
const adminModeIntroOpen = ref(false);
const adminModeDict = dict({
data: [
{
label: t("certd.sys.setting.enterpriseMode"),
value: "enterprise",
},
{
label: t("certd.sys.setting.saasMode"),
value: "saas",
},
{
label: t("certd.sys.setting.enterpriseMode"),
value: "enterprise",
},
],
});
@@ -251,6 +251,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (bean.id > 0) {
//修改
old = await this.info(bean.id);
bean.order = old.order;
}
if (!old || !old.webhookKey) {
bean.webhookKey = await this.genWebhookKey();