You've already forked agentic-coding-workflow
feat(web): WorkspaceHomeView with worktrees/main/settings tabs and CommitDialog
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<AppShell>
|
||||
<div class="space-y-4">
|
||||
<NPageHeader :title="store.current?.name ?? wsSlug">
|
||||
<template #subtitle>
|
||||
{{ store.current?.slug }}
|
||||
</template>
|
||||
</NPageHeader>
|
||||
|
||||
<SyncBar
|
||||
v-if="store.current"
|
||||
:ws="store.current"
|
||||
/>
|
||||
|
||||
<NTabs
|
||||
v-model:value="tab"
|
||||
type="line"
|
||||
>
|
||||
<NTabPane
|
||||
name="worktrees"
|
||||
tab="Worktrees"
|
||||
>
|
||||
<WorktreesTab
|
||||
v-if="store.current"
|
||||
:ws="store.current"
|
||||
/>
|
||||
</NTabPane>
|
||||
<NTabPane
|
||||
name="main"
|
||||
tab="Main"
|
||||
>
|
||||
<MainTab
|
||||
v-if="store.current"
|
||||
:ws="store.current"
|
||||
/>
|
||||
</NTabPane>
|
||||
<NTabPane
|
||||
name="settings"
|
||||
tab="Settings"
|
||||
>
|
||||
<SettingsTab
|
||||
v-if="store.current"
|
||||
:ws="store.current"
|
||||
/>
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</div>
|
||||
</AppShell>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { NTabs, NTabPane, NPageHeader, useMessage } from 'naive-ui'
|
||||
import AppShell from '@/layouts/AppShell.vue'
|
||||
import { useWorkspacesStore } from '@/stores/workspaces'
|
||||
import SyncBar from './components/SyncBar.vue'
|
||||
import WorktreesTab from './components/WorktreesTab.vue'
|
||||
import MainTab from './components/MainTab.vue'
|
||||
import SettingsTab from './components/SettingsTab.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const store = useWorkspacesStore()
|
||||
const msg = useMessage()
|
||||
const tab = ref<'worktrees' | 'main' | 'settings'>('worktrees')
|
||||
|
||||
const projectSlug = computed(() => route.params.slug as string)
|
||||
const wsSlug = computed(() => route.params.wsSlug as string)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 通过 fetchByProject 拿列表后定位 wsID
|
||||
await store.fetchByProject(projectSlug.value)
|
||||
const ws = store.list.find((w) => w.slug === wsSlug.value)
|
||||
if (!ws) {
|
||||
msg.error('找不到工作区')
|
||||
return
|
||||
}
|
||||
await store.fetchOne(ws.id)
|
||||
await store.fetchWorktrees(ws.id)
|
||||
await store.fetchCredential(ws.id)
|
||||
} catch (e: unknown) {
|
||||
msg.error('加载失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<NModal
|
||||
:show="true"
|
||||
preset="card"
|
||||
title="提交修改"
|
||||
:style="{ width: '640px' }"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<p class="text-sm text-gray-600 mb-2">
|
||||
{{ status.length }} 个改动
|
||||
</p>
|
||||
<NScrollbar
|
||||
:style="{ maxHeight: '240px' }"
|
||||
class="mb-4 border rounded p-2 bg-gray-50"
|
||||
>
|
||||
<div
|
||||
v-for="f in status"
|
||||
:key="f.path"
|
||||
class="font-mono text-sm py-1"
|
||||
>
|
||||
<NTag
|
||||
size="small"
|
||||
type="info"
|
||||
class="mr-2"
|
||||
>
|
||||
{{ f.index_x }}{{ f.worktree_y }}
|
||||
</NTag>{{ f.path }}
|
||||
</div>
|
||||
<div
|
||||
v-if="status.length === 0"
|
||||
class="text-sm text-gray-400"
|
||||
>
|
||||
无改动
|
||||
</div>
|
||||
</NScrollbar>
|
||||
|
||||
<NForm label-placement="top">
|
||||
<NFormItem
|
||||
label="Commit message"
|
||||
required
|
||||
>
|
||||
<NInput
|
||||
v-model:value="message"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem>
|
||||
<NCheckbox v-model:checked="push">
|
||||
提交后推送到远端
|
||||
</NCheckbox>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
|
||||
<template #footer>
|
||||
<NSpace justify="end">
|
||||
<NButton @click="emit('close')">
|
||||
取消
|
||||
</NButton>
|
||||
<NButton
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
:disabled="status.length === 0"
|
||||
@click="submit"
|
||||
>
|
||||
提交
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import {
|
||||
NModal,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NCheckbox,
|
||||
NButton,
|
||||
NSpace,
|
||||
NScrollbar,
|
||||
NTag,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { useWorkspacesStore } from '@/stores/workspaces'
|
||||
import type { FileStatus } from '@/api/types'
|
||||
|
||||
// target: 'main' 时用 wsID 调 commitOnMain;'worktree' 时用 wtID 调 commitOnWorktree。
|
||||
const props = defineProps<{ target: 'main' | 'worktree'; targetId: string }>()
|
||||
const emit = defineEmits<{ close: []; committed: [sha: string] }>()
|
||||
const store = useWorkspacesStore()
|
||||
const msg = useMessage()
|
||||
|
||||
const status = ref<FileStatus[]>([])
|
||||
const message = ref<string>('')
|
||||
const push = ref<boolean>(true)
|
||||
const submitting = ref<boolean>(false)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
status.value =
|
||||
props.target === 'main'
|
||||
? await store.statusOnMain(props.targetId)
|
||||
: await store.statusOnWorktree(props.targetId)
|
||||
} catch (e: unknown) {
|
||||
msg.error('读取 git status 失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
}
|
||||
})
|
||||
|
||||
async function submit() {
|
||||
if (!message.value.trim()) {
|
||||
msg.warning('请填写 commit message')
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
const body = { message: message.value, push: push.value, add_all: true }
|
||||
const resp =
|
||||
props.target === 'main'
|
||||
? await store.commitOnMain(props.targetId, body)
|
||||
: await store.commitOnWorktree(props.targetId, body)
|
||||
msg.success('已提交:' + resp.sha.slice(0, 8))
|
||||
emit('committed', resp.sha)
|
||||
} catch (e: unknown) {
|
||||
msg.error('提交失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<NSpace>
|
||||
<NButton
|
||||
type="primary"
|
||||
@click="showCommit = true"
|
||||
>
|
||||
提交修改
|
||||
</NButton>
|
||||
<NButton @click="onPush">
|
||||
Push 到远端
|
||||
</NButton>
|
||||
</NSpace>
|
||||
|
||||
<CommitDialog
|
||||
v-if="showCommit"
|
||||
target="main"
|
||||
:target-id="ws.id"
|
||||
@close="showCommit = false"
|
||||
@committed="showCommit = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { NSpace, NButton, useMessage } from 'naive-ui'
|
||||
import { useWorkspacesStore } from '@/stores/workspaces'
|
||||
import CommitDialog from './CommitDialog.vue'
|
||||
import type { Workspace } from '@/api/types'
|
||||
|
||||
const props = defineProps<{ ws: Workspace }>()
|
||||
const store = useWorkspacesStore()
|
||||
const msg = useMessage()
|
||||
const showCommit = ref<boolean>(false)
|
||||
|
||||
async function onPush() {
|
||||
try {
|
||||
await store.pushOnMain(props.ws.id)
|
||||
msg.success('已 push')
|
||||
} catch (e: unknown) {
|
||||
msg.error('push 失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<NSpace
|
||||
vertical
|
||||
size="large"
|
||||
>
|
||||
<NCard title="基本信息">
|
||||
<NForm
|
||||
label-placement="left"
|
||||
:label-width="100"
|
||||
>
|
||||
<NFormItem label="名称">
|
||||
<NInput v-model:value="form.name" />
|
||||
</NFormItem>
|
||||
<NFormItem label="描述">
|
||||
<NInput
|
||||
v-model:value="form.description"
|
||||
type="textarea"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="默认分支">
|
||||
<NInput v-model:value="form.default_branch" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<NButton
|
||||
type="primary"
|
||||
:loading="savingCore"
|
||||
@click="saveCore"
|
||||
>
|
||||
保存
|
||||
</NButton>
|
||||
</NCard>
|
||||
|
||||
<NCard title="Git 凭据">
|
||||
<p class="text-sm text-gray-500 mb-2">
|
||||
当前指纹:{{ store.credential?.fingerprint || '(未设置)' }}
|
||||
</p>
|
||||
<NForm
|
||||
label-placement="left"
|
||||
:label-width="100"
|
||||
>
|
||||
<NFormItem label="类型">
|
||||
<NSelect
|
||||
v-model:value="credForm.kind"
|
||||
:options="kindOptions"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem
|
||||
v-if="credForm.kind === 'pat'"
|
||||
label="用户名"
|
||||
>
|
||||
<NInput v-model:value="credForm.username" />
|
||||
</NFormItem>
|
||||
<NFormItem
|
||||
v-if="credForm.kind === 'pat'"
|
||||
label="Token"
|
||||
>
|
||||
<NInput
|
||||
v-model:value="credForm.secret"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem
|
||||
v-if="credForm.kind === 'ssh_key'"
|
||||
label="私钥(PEM)"
|
||||
>
|
||||
<NInput
|
||||
v-model:value="credForm.secret"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 4, maxRows: 8 }"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<NButton
|
||||
type="primary"
|
||||
:loading="savingCred"
|
||||
@click="saveCred"
|
||||
>
|
||||
替换凭据
|
||||
</NButton>
|
||||
</NCard>
|
||||
|
||||
<NCard title="危险区">
|
||||
<NButton
|
||||
type="error"
|
||||
@click="onDelete"
|
||||
>
|
||||
删除工作区
|
||||
</NButton>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NSelect,
|
||||
NButton,
|
||||
NSpace,
|
||||
NCard,
|
||||
useMessage,
|
||||
useDialog,
|
||||
} from 'naive-ui'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useWorkspacesStore } from '@/stores/workspaces'
|
||||
import type { CredentialKind, CredentialUpsertBody, Workspace } from '@/api/types'
|
||||
|
||||
interface CoreFormState {
|
||||
name: string
|
||||
description: string
|
||||
default_branch: string
|
||||
}
|
||||
|
||||
interface CredFormState {
|
||||
kind: CredentialKind
|
||||
username: string
|
||||
secret: string
|
||||
}
|
||||
|
||||
const props = defineProps<{ ws: Workspace }>()
|
||||
const store = useWorkspacesStore()
|
||||
const msg = useMessage()
|
||||
const dialog = useDialog()
|
||||
const router = useRouter()
|
||||
|
||||
const form = ref<CoreFormState>({
|
||||
name: props.ws.name,
|
||||
description: props.ws.description,
|
||||
default_branch: props.ws.default_branch,
|
||||
})
|
||||
|
||||
const credForm = ref<CredFormState>({
|
||||
kind: store.credential?.kind ?? 'none',
|
||||
username: store.credential?.username ?? '',
|
||||
secret: '',
|
||||
})
|
||||
|
||||
const savingCore = ref(false)
|
||||
const savingCred = ref(false)
|
||||
|
||||
const kindOptions: { label: string; value: CredentialKind }[] = [
|
||||
{ label: 'HTTPS PAT', value: 'pat' },
|
||||
{ label: 'SSH Key', value: 'ssh_key' },
|
||||
{ label: '公开仓库(无凭据)', value: 'none' },
|
||||
]
|
||||
|
||||
async function saveCore() {
|
||||
savingCore.value = true
|
||||
try {
|
||||
await store.update(props.ws.id, form.value)
|
||||
msg.success('已保存')
|
||||
} catch (e: unknown) {
|
||||
msg.error('保存失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
} finally {
|
||||
savingCore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveCred() {
|
||||
// kind!=='none' 时 secret 必填,避免向后端推空 token;与 WorkspaceCreateModal 校验一致。
|
||||
if (credForm.value.kind !== 'none' && !credForm.value.secret) {
|
||||
msg.error('请填写凭据 secret')
|
||||
return
|
||||
}
|
||||
// kind==='none' 时不带 username/secret,避免之前残留的 PAT 用户名混入 'none' 请求体。
|
||||
const body: CredentialUpsertBody =
|
||||
credForm.value.kind === 'none'
|
||||
? { kind: 'none' }
|
||||
: {
|
||||
kind: credForm.value.kind,
|
||||
username: credForm.value.username,
|
||||
secret: credForm.value.secret,
|
||||
}
|
||||
savingCred.value = true
|
||||
try {
|
||||
await store.upsertCredential(props.ws.id, body)
|
||||
credForm.value.secret = ''
|
||||
msg.success('凭据已替换')
|
||||
} catch (e: unknown) {
|
||||
msg.error('替换失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
} finally {
|
||||
savingCred.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onDelete() {
|
||||
dialog.error({
|
||||
title: '确认删除工作区',
|
||||
content: '将级联删除所有 worktree 并 rm -rf 磁盘文件,不可恢复。',
|
||||
positiveText: '删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await store.remove(props.ws.id)
|
||||
msg.success('已删除')
|
||||
router.push(`/p/${router.currentRoute.value.params.slug}/workspaces`)
|
||||
} catch (e: unknown) {
|
||||
msg.error('删除失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<NCard size="small">
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
>
|
||||
<NSpace align="center">
|
||||
<NTag :type="ws.sync_status === 'idle' ? 'success' : ws.sync_status === 'error' ? 'error' : 'info'">
|
||||
{{ ws.sync_status }}
|
||||
</NTag>
|
||||
<span
|
||||
v-if="ws.last_synced_at"
|
||||
class="text-sm text-gray-500"
|
||||
>
|
||||
上次同步 {{ ws.last_synced_at }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-sm text-gray-500"
|
||||
>尚未同步</span>
|
||||
<span
|
||||
v-if="ws.last_sync_error"
|
||||
class="text-sm text-red-600"
|
||||
>{{ ws.last_sync_error }}</span>
|
||||
</NSpace>
|
||||
<NButton
|
||||
:disabled="busy"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="onSync"
|
||||
>
|
||||
立即同步
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { NCard, NTag, NButton, NSpace, useMessage } from 'naive-ui'
|
||||
import { useWorkspacesStore } from '@/stores/workspaces'
|
||||
import type { Workspace } from '@/api/types'
|
||||
|
||||
const props = defineProps<{ ws: Workspace }>()
|
||||
const store = useWorkspacesStore()
|
||||
const msg = useMessage()
|
||||
|
||||
const busy = computed(() => props.ws.sync_status === 'cloning' || props.ws.sync_status === 'syncing')
|
||||
|
||||
async function onSync() {
|
||||
// store.sync() 仅触发异步 fetch;要等真正落到 idle/error,必须 pollUntilSettled。
|
||||
try {
|
||||
await store.sync(props.ws.id)
|
||||
msg.info('已开始同步')
|
||||
await store.pollUntilSettled(props.ws.id)
|
||||
msg.success('已同步')
|
||||
} catch (e: unknown) {
|
||||
msg.error('同步失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<div>
|
||||
<NSpace
|
||||
justify="end"
|
||||
class="mb-2"
|
||||
>
|
||||
<NButton
|
||||
type="primary"
|
||||
@click="showCreate = true"
|
||||
>
|
||||
新建 worktree
|
||||
</NButton>
|
||||
</NSpace>
|
||||
<NDataTable
|
||||
:columns="columns"
|
||||
:data="store.worktrees"
|
||||
/>
|
||||
|
||||
<NModal
|
||||
:show="showCreate"
|
||||
preset="card"
|
||||
title="新建 worktree"
|
||||
:style="{ width: '420px' }"
|
||||
@close="showCreate = false"
|
||||
>
|
||||
<NForm>
|
||||
<NFormItem
|
||||
label="分支"
|
||||
required
|
||||
>
|
||||
<NInput v-model:value="newBranch" />
|
||||
</NFormItem>
|
||||
<NFormItem label="基线分支(默认 default_branch)">
|
||||
<NInput v-model:value="newBase" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace justify="end">
|
||||
<NButton @click="showCreate = false">
|
||||
取消
|
||||
</NButton>
|
||||
<NButton
|
||||
type="primary"
|
||||
:loading="creating"
|
||||
@click="onCreate"
|
||||
>
|
||||
创建
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
|
||||
<CommitDialog
|
||||
v-if="commitFor"
|
||||
target="worktree"
|
||||
:target-id="commitFor.id"
|
||||
@close="commitFor = null"
|
||||
@committed="commitFor = null"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, h } from 'vue'
|
||||
import {
|
||||
NDataTable,
|
||||
NButton,
|
||||
NSpace,
|
||||
NTag,
|
||||
NModal,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
useMessage,
|
||||
useDialog,
|
||||
} from 'naive-ui'
|
||||
import type { DataTableColumns } from 'naive-ui'
|
||||
import { useWorkspacesStore } from '@/stores/workspaces'
|
||||
import CommitDialog from './CommitDialog.vue'
|
||||
import type { Workspace, Worktree, WorktreeStatus } from '@/api/types'
|
||||
|
||||
const props = defineProps<{ ws: Workspace }>()
|
||||
const store = useWorkspacesStore()
|
||||
const msg = useMessage()
|
||||
const dialog = useDialog()
|
||||
|
||||
const showCreate = ref<boolean>(false)
|
||||
const creating = ref<boolean>(false)
|
||||
const newBranch = ref<string>('')
|
||||
const newBase = ref<string>('')
|
||||
|
||||
const commitFor = ref<Worktree | null>(null)
|
||||
|
||||
const STATUS_TAG_TYPE: Record<WorktreeStatus, 'warning' | 'error' | 'default'> = {
|
||||
active: 'warning',
|
||||
pruning: 'error',
|
||||
idle: 'default',
|
||||
}
|
||||
|
||||
async function onCreate() {
|
||||
if (!newBranch.value.trim()) {
|
||||
msg.error('分支名为必填')
|
||||
return
|
||||
}
|
||||
creating.value = true
|
||||
try {
|
||||
await store.createWorktree(props.ws.id, {
|
||||
branch: newBranch.value,
|
||||
base_branch: newBase.value || undefined,
|
||||
})
|
||||
showCreate.value = false
|
||||
newBranch.value = ''
|
||||
newBase.value = ''
|
||||
} catch (e: unknown) {
|
||||
msg.error('创建失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onAcquire(wt: Worktree) {
|
||||
try {
|
||||
await store.acquire(wt.id)
|
||||
} catch (e: unknown) {
|
||||
msg.error('占用失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
}
|
||||
}
|
||||
|
||||
async function onRelease(wt: Worktree) {
|
||||
try {
|
||||
await store.release(wt.id)
|
||||
} catch (e: unknown) {
|
||||
msg.error('释放失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
}
|
||||
}
|
||||
|
||||
function onDelete(wt: Worktree) {
|
||||
dialog.warning({
|
||||
title: '确认删除',
|
||||
content: `删除 worktree "${wt.branch}" 将移除磁盘文件夹,不可恢复。`,
|
||||
positiveText: '删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await store.deleteWorktree(wt.id)
|
||||
} catch (e: unknown) {
|
||||
msg.error('删除失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function onPush(wt: Worktree) {
|
||||
try {
|
||||
await store.pushOnWorktree(wt.id)
|
||||
msg.success('已 push')
|
||||
} catch (e: unknown) {
|
||||
msg.error('push 失败:' + (e instanceof Error ? e.message : String(e)))
|
||||
}
|
||||
}
|
||||
|
||||
const columns = computed<DataTableColumns<Worktree>>(() => [
|
||||
{ title: '分支', key: 'branch' },
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row: Worktree) {
|
||||
return h(
|
||||
NTag,
|
||||
{ type: STATUS_TAG_TYPE[row.status] ?? 'default', size: 'small' },
|
||||
{ default: () => row.status },
|
||||
)
|
||||
},
|
||||
},
|
||||
{ title: '占用者', key: 'active_holder' },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'ops',
|
||||
render(row: Worktree) {
|
||||
return h(
|
||||
NSpace,
|
||||
{ size: 'small' },
|
||||
{
|
||||
default: () => [
|
||||
h(
|
||||
NButton,
|
||||
{ size: 'tiny', onClick: () => (commitFor.value = row) },
|
||||
{ default: () => '提交' },
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{ size: 'tiny', onClick: () => onPush(row) },
|
||||
{ default: () => 'Push' },
|
||||
),
|
||||
row.status === 'idle'
|
||||
? h(
|
||||
NButton,
|
||||
{ size: 'tiny', type: 'primary', onClick: () => onAcquire(row) },
|
||||
{ default: () => '占用' },
|
||||
)
|
||||
: h(
|
||||
NButton,
|
||||
{ size: 'tiny', onClick: () => onRelease(row) },
|
||||
{ default: () => '释放' },
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{ size: 'tiny', type: 'error', onClick: () => onDelete(row) },
|
||||
{ default: () => '删除' },
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
])
|
||||
</script>
|
||||
Reference in New Issue
Block a user