You've already forked agentic-coding-workflow
style(web): lint --fix sweep + disable no-undef for ts/vue (DOM globals)
This commit is contained in:
@@ -35,6 +35,9 @@ export default [
|
|||||||
files: ['**/*.{ts,tsx,vue}'],
|
files: ['**/*.{ts,tsx,vue}'],
|
||||||
rules: {
|
rules: {
|
||||||
'vue/multi-word-component-names': 'off',
|
'vue/multi-word-component-names': 'off',
|
||||||
|
// TypeScript/vue-tsc 已经做名字解析;no-undef 在 .vue 内对 DOM 全局
|
||||||
|
// (DragEvent / HTMLElement 等) 误报,关掉走 TS 校验。
|
||||||
|
'no-undef': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
'error',
|
'error',
|
||||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppShell>
|
<AppShell>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h2 class="text-xl font-semibold">欢迎,{{ auth.user?.display_name }}</h2>
|
<h2 class="text-xl font-semibold">
|
||||||
|
欢迎,{{ auth.user?.display_name }}
|
||||||
|
</h2>
|
||||||
<p>这是占位 dashboard。后续 plan 会接入项目/工作区/Issue。</p>
|
<p>这是占位 dashboard。后续 plan 会接入项目/工作区/Issue。</p>
|
||||||
</div>
|
</div>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
|
|||||||
@@ -1,19 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen flex items-center justify-center p-4">
|
<div class="min-h-screen flex items-center justify-center p-4">
|
||||||
<div class="w-full max-w-sm space-y-4">
|
<div class="w-full max-w-sm space-y-4">
|
||||||
<h1 class="text-2xl font-semibold">登录</h1>
|
<h1 class="text-2xl font-semibold">
|
||||||
|
登录
|
||||||
|
</h1>
|
||||||
<NForm @submit.prevent="onSubmit">
|
<NForm @submit.prevent="onSubmit">
|
||||||
<NFormItem label="邮箱">
|
<NFormItem label="邮箱">
|
||||||
<NInput v-model:value="email" placeholder="admin@local" />
|
<NInput
|
||||||
|
v-model:value="email"
|
||||||
|
placeholder="admin@local"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="密码">
|
<NFormItem label="密码">
|
||||||
<NInput v-model:value="password" type="password" show-password-on="click" />
|
<NInput
|
||||||
|
v-model:value="password"
|
||||||
|
type="password"
|
||||||
|
show-password-on="click"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NButton type="primary" attr-type="submit" :loading="loading" block>
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
attr-type="submit"
|
||||||
|
:loading="loading"
|
||||||
|
block
|
||||||
|
>
|
||||||
登录
|
登录
|
||||||
</NButton>
|
</NButton>
|
||||||
</NForm>
|
</NForm>
|
||||||
<p v-if="error" class="text-red-500 text-sm">{{ error }}</p>
|
<p
|
||||||
|
v-if="error"
|
||||||
|
class="text-red-500 text-sm"
|
||||||
|
>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppShell>
|
<AppShell>
|
||||||
<NSpin :show="loading">
|
<NSpin :show="loading">
|
||||||
<div v-if="issue" class="space-y-6">
|
<div
|
||||||
|
v-if="issue"
|
||||||
|
class="space-y-6"
|
||||||
|
>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="text-gray-400 font-mono text-sm">#{{ issue.number }}</span>
|
<span class="text-gray-400 font-mono text-sm">#{{ issue.number }}</span>
|
||||||
<h1 class="text-2xl font-semibold">{{ issue.title }}</h1>
|
<h1 class="text-2xl font-semibold">
|
||||||
<NTag :type="issue.status === 'open' ? 'success' : 'default'" size="small">
|
{{ issue.title }}
|
||||||
|
</h1>
|
||||||
|
<NTag
|
||||||
|
:type="issue.status === 'open' ? 'success' : 'default'"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
{{ issue.status === 'open' ? '开放' : '关闭' }}
|
{{ issue.status === 'open' ? '开放' : '关闭' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,7 +38,12 @@
|
|||||||
>
|
>
|
||||||
重新开放
|
重新开放
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton size="small" @click="router.back()">返回</NButton>
|
<NButton
|
||||||
|
size="small"
|
||||||
|
@click="router.back()"
|
||||||
|
>
|
||||||
|
返回
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -52,7 +65,13 @@
|
|||||||
{{ issue.assignee_id ? `已指派:${issue.assignee_id}` : '暂未指派' }}
|
{{ issue.assignee_id ? `已指派:${issue.assignee_id}` : '暂未指派' }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<NButton size="small" :loading="assigning" @click="onAssignToMe">指派给我</NButton>
|
<NButton
|
||||||
|
size="small"
|
||||||
|
:loading="assigning"
|
||||||
|
@click="onAssignToMe"
|
||||||
|
>
|
||||||
|
指派给我
|
||||||
|
</NButton>
|
||||||
<NButton
|
<NButton
|
||||||
v-if="issue.assignee_id"
|
v-if="issue.assignee_id"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -67,14 +86,25 @@
|
|||||||
|
|
||||||
<!-- Description card -->
|
<!-- Description card -->
|
||||||
<NCard title="描述">
|
<NCard title="描述">
|
||||||
<p v-if="issue.description" class="text-gray-700 whitespace-pre-wrap">
|
<p
|
||||||
|
v-if="issue.description"
|
||||||
|
class="text-gray-700 whitespace-pre-wrap"
|
||||||
|
>
|
||||||
{{ issue.description }}
|
{{ issue.description }}
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="text-gray-400 text-sm">暂无描述</p>
|
<p
|
||||||
|
v-else
|
||||||
|
class="text-gray-400 text-sm"
|
||||||
|
>
|
||||||
|
暂无描述
|
||||||
|
</p>
|
||||||
</NCard>
|
</NCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="!loading" class="text-gray-400 text-sm py-8 text-center">
|
<div
|
||||||
|
v-else-if="!loading"
|
||||||
|
class="text-gray-400 text-sm py-8 text-center"
|
||||||
|
>
|
||||||
Issue 不存在或无权访问。
|
Issue 不存在或无权访问。
|
||||||
</div>
|
</div>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
|
|||||||
@@ -4,8 +4,15 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h1 class="text-2xl font-semibold">Issue 列表</h1>
|
<h1 class="text-2xl font-semibold">
|
||||||
<NButton type="primary" @click="showCreate = true">新建 Issue</NButton>
|
Issue 列表
|
||||||
|
</h1>
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
@click="showCreate = true"
|
||||||
|
>
|
||||||
|
新建 Issue
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filter chips -->
|
<!-- Filter chips -->
|
||||||
@@ -44,20 +51,36 @@
|
|||||||
striped
|
striped
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-if="!loading && items.length === 0" class="text-gray-400 text-sm py-8 text-center">
|
<div
|
||||||
|
v-if="!loading && items.length === 0"
|
||||||
|
class="text-gray-400 text-sm py-8 text-center"
|
||||||
|
>
|
||||||
暂无 Issue。
|
暂无 Issue。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
|
|
||||||
<!-- Create modal -->
|
<!-- Create modal -->
|
||||||
<NModal v-model:show="showCreate" preset="card" title="新建 Issue" class="w-full max-w-md">
|
<NModal
|
||||||
|
v-model:show="showCreate"
|
||||||
|
preset="card"
|
||||||
|
title="新建 Issue"
|
||||||
|
class="w-full max-w-md"
|
||||||
|
>
|
||||||
<NForm @submit.prevent="onSubmit">
|
<NForm @submit.prevent="onSubmit">
|
||||||
<NFormItem label="标题">
|
<NFormItem label="标题">
|
||||||
<NInput v-model:value="form.title" placeholder="Issue 标题" />
|
<NInput
|
||||||
|
v-model:value="form.title"
|
||||||
|
placeholder="Issue 标题"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="描述">
|
<NFormItem label="描述">
|
||||||
<NInput v-model:value="form.description" type="textarea" :rows="4" placeholder="(可选)" />
|
<NInput
|
||||||
|
v-model:value="form.description"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
placeholder="(可选)"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="挂载到 Requirement(可选)">
|
<NFormItem label="挂载到 Requirement(可选)">
|
||||||
<NSelect
|
<NSelect
|
||||||
@@ -68,11 +91,24 @@
|
|||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<div class="flex justify-end gap-2 mt-4">
|
<div class="flex justify-end gap-2 mt-4">
|
||||||
<NButton @click="showCreate = false">取消</NButton>
|
<NButton @click="showCreate = false">
|
||||||
<NButton type="primary" attr-type="submit" :loading="creating">创建</NButton>
|
取消
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
attr-type="submit"
|
||||||
|
:loading="creating"
|
||||||
|
>
|
||||||
|
创建
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</NForm>
|
</NForm>
|
||||||
<p v-if="createError" class="text-red-500 text-sm mt-2">{{ createError }}</p>
|
<p
|
||||||
|
v-if="createError"
|
||||||
|
class="text-red-500 text-sm mt-2"
|
||||||
|
>
|
||||||
|
{{ createError }}
|
||||||
|
</p>
|
||||||
</NModal>
|
</NModal>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,32 +1,64 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppShell>
|
<AppShell>
|
||||||
<NSpin :show="store.loading">
|
<NSpin :show="store.loading">
|
||||||
<div v-if="store.current" class="space-y-6">
|
<div
|
||||||
|
v-if="store.current"
|
||||||
|
class="space-y-6"
|
||||||
|
>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<h1 class="text-2xl font-semibold">{{ store.current.name }}</h1>
|
<h1 class="text-2xl font-semibold">
|
||||||
<NTag :type="store.current.visibility === 'private' ? 'warning' : 'info'" size="small">
|
{{ store.current.name }}
|
||||||
|
</h1>
|
||||||
|
<NTag
|
||||||
|
:type="store.current.visibility === 'private' ? 'warning' : 'info'"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
{{ store.current.visibility === 'private' ? '私有' : '内部' }}
|
{{ store.current.visibility === 'private' ? '私有' : '内部' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
<NTag v-if="store.current.archived_at" size="small">已归档</NTag>
|
<NTag
|
||||||
|
v-if="store.current.archived_at"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
已归档
|
||||||
|
</NTag>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-500 text-sm font-mono">{{ store.current.slug }}</p>
|
<p class="text-gray-500 text-sm font-mono">
|
||||||
|
{{ store.current.slug }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<p v-if="store.current.description" class="text-gray-600">{{ store.current.description }}</p>
|
<p
|
||||||
|
v-if="store.current.description"
|
||||||
|
class="text-gray-600"
|
||||||
|
>
|
||||||
|
{{ store.current.description }}
|
||||||
|
</p>
|
||||||
|
|
||||||
<!-- Nav buttons -->
|
<!-- Nav buttons -->
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<NButton type="primary" @click="goRequirements">需求列表</NButton>
|
<NButton
|
||||||
<NButton @click="goIssues">Issue 列表</NButton>
|
type="primary"
|
||||||
|
@click="goRequirements"
|
||||||
|
>
|
||||||
|
需求列表
|
||||||
|
</NButton>
|
||||||
|
<NButton @click="goIssues">
|
||||||
|
Issue 列表
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="!store.loading" class="text-gray-400 text-sm">项目不存在或无权访问。</div>
|
<div
|
||||||
|
v-else-if="!store.loading"
|
||||||
|
class="text-gray-400 text-sm"
|
||||||
|
>
|
||||||
|
项目不存在或无权访问。
|
||||||
|
</div>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,18 +4,31 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h1 class="text-2xl font-semibold">项目</h1>
|
<h1 class="text-2xl font-semibold">
|
||||||
|
项目
|
||||||
|
</h1>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-sm text-gray-500">显示已归档</span>
|
<span class="text-sm text-gray-500">显示已归档</span>
|
||||||
<NSwitch v-model:value="store.includeArchived" @update:value="store.refresh()" />
|
<NSwitch
|
||||||
|
v-model:value="store.includeArchived"
|
||||||
|
@update:value="store.refresh()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<NButton type="primary" @click="showCreate = true">新建项目</NButton>
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
@click="showCreate = true"
|
||||||
|
>
|
||||||
|
新建项目
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Active projects grid -->
|
<!-- Active projects grid -->
|
||||||
<div v-if="store.activeItems.length > 0" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div
|
||||||
|
v-if="store.activeItems.length > 0"
|
||||||
|
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||||
|
>
|
||||||
<NCard
|
<NCard
|
||||||
v-for="p in store.activeItems"
|
v-for="p in store.activeItems"
|
||||||
:key="p.id"
|
:key="p.id"
|
||||||
@@ -25,20 +38,34 @@
|
|||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="font-medium">{{ p.name }}</span>
|
<span class="font-medium">{{ p.name }}</span>
|
||||||
<NTag :type="p.visibility === 'private' ? 'warning' : 'info'" size="small">
|
<NTag
|
||||||
|
:type="p.visibility === 'private' ? 'warning' : 'info'"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
{{ p.visibility === 'private' ? '私有' : '内部' }}
|
{{ p.visibility === 'private' ? '私有' : '内部' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-500 line-clamp-2">{{ p.description || '暂无描述' }}</p>
|
<p class="text-sm text-gray-500 line-clamp-2">
|
||||||
<p class="text-xs text-gray-400 font-mono">{{ p.slug }}</p>
|
{{ p.description || '暂无描述' }}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-400 font-mono">
|
||||||
|
{{ p.slug }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="!store.loading" class="text-gray-400 text-sm">暂无活跃项目。</div>
|
<div
|
||||||
|
v-else-if="!store.loading"
|
||||||
|
class="text-gray-400 text-sm"
|
||||||
|
>
|
||||||
|
暂无活跃项目。
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Archived projects -->
|
<!-- Archived projects -->
|
||||||
<template v-if="store.includeArchived && store.archivedItems.length > 0">
|
<template v-if="store.includeArchived && store.archivedItems.length > 0">
|
||||||
<h2 class="text-base font-medium text-gray-500">已归档</h2>
|
<h2 class="text-base font-medium text-gray-500">
|
||||||
|
已归档
|
||||||
|
</h2>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
<NCard
|
<NCard
|
||||||
v-for="p in store.archivedItems"
|
v-for="p in store.archivedItems"
|
||||||
@@ -49,10 +76,16 @@
|
|||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="font-medium">{{ p.name }}</span>
|
<span class="font-medium">{{ p.name }}</span>
|
||||||
<NTag size="small">已归档</NTag>
|
<NTag size="small">
|
||||||
|
已归档
|
||||||
|
</NTag>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-500 line-clamp-2">{{ p.description || '暂无描述' }}</p>
|
<p class="text-sm text-gray-500 line-clamp-2">
|
||||||
<p class="text-xs text-gray-400 font-mono">{{ p.slug }}</p>
|
{{ p.description || '暂无描述' }}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-400 font-mono">
|
||||||
|
{{ p.slug }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,29 +94,62 @@
|
|||||||
</NSpin>
|
</NSpin>
|
||||||
|
|
||||||
<!-- Create project modal -->
|
<!-- Create project modal -->
|
||||||
<NModal v-model:show="showCreate" preset="card" title="新建项目" class="w-full max-w-md">
|
<NModal
|
||||||
|
v-model:show="showCreate"
|
||||||
|
preset="card"
|
||||||
|
title="新建项目"
|
||||||
|
class="w-full max-w-md"
|
||||||
|
>
|
||||||
<NForm @submit.prevent="onSubmit">
|
<NForm @submit.prevent="onSubmit">
|
||||||
<NFormItem label="Slug(URL 标识)">
|
<NFormItem label="Slug(URL 标识)">
|
||||||
<NInput v-model:value="form.slug" placeholder="my-project" />
|
<NInput
|
||||||
|
v-model:value="form.slug"
|
||||||
|
placeholder="my-project"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="名称">
|
<NFormItem label="名称">
|
||||||
<NInput v-model:value="form.name" placeholder="My Project" />
|
<NInput
|
||||||
|
v-model:value="form.name"
|
||||||
|
placeholder="My Project"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="描述">
|
<NFormItem label="描述">
|
||||||
<NInput v-model:value="form.description" type="textarea" :rows="3" placeholder="(可选)" />
|
<NInput
|
||||||
|
v-model:value="form.description"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="(可选)"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="可见性">
|
<NFormItem label="可见性">
|
||||||
<NRadioGroup v-model:value="form.visibility">
|
<NRadioGroup v-model:value="form.visibility">
|
||||||
<NRadio value="private">私有</NRadio>
|
<NRadio value="private">
|
||||||
<NRadio value="internal">内部</NRadio>
|
私有
|
||||||
|
</NRadio>
|
||||||
|
<NRadio value="internal">
|
||||||
|
内部
|
||||||
|
</NRadio>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<div class="flex justify-end gap-2 mt-4">
|
<div class="flex justify-end gap-2 mt-4">
|
||||||
<NButton @click="showCreate = false">取消</NButton>
|
<NButton @click="showCreate = false">
|
||||||
<NButton type="primary" attr-type="submit" :loading="creating">创建</NButton>
|
取消
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
attr-type="submit"
|
||||||
|
:loading="creating"
|
||||||
|
>
|
||||||
|
创建
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</NForm>
|
</NForm>
|
||||||
<p v-if="createError" class="text-red-500 text-sm mt-2">{{ createError }}</p>
|
<p
|
||||||
|
v-if="createError"
|
||||||
|
class="text-red-500 text-sm mt-2"
|
||||||
|
>
|
||||||
|
{{ createError }}
|
||||||
|
</p>
|
||||||
</NModal>
|
</NModal>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppShell>
|
<AppShell>
|
||||||
<NSpin :show="loading">
|
<NSpin :show="loading">
|
||||||
<div v-if="data" class="space-y-6">
|
<div
|
||||||
|
v-if="data"
|
||||||
|
class="space-y-6"
|
||||||
|
>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="text-gray-400 font-mono text-sm">#{{ data.requirement.number }}</span>
|
<span class="text-gray-400 font-mono text-sm">#{{ data.requirement.number }}</span>
|
||||||
<h1 class="text-2xl font-semibold">{{ data.requirement.title }}</h1>
|
<h1 class="text-2xl font-semibold">
|
||||||
<NTag :type="data.requirement.status === 'open' ? 'success' : 'default'" size="small">
|
{{ data.requirement.title }}
|
||||||
|
</h1>
|
||||||
|
<NTag
|
||||||
|
:type="data.requirement.status === 'open' ? 'success' : 'default'"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
{{ data.requirement.status === 'open' ? '开放' : '关闭' }}
|
{{ data.requirement.status === 'open' ? '开放' : '关闭' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,13 +38,21 @@
|
|||||||
>
|
>
|
||||||
重新开放
|
重新开放
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton size="small" @click="router.back()">返回</NButton>
|
<NButton
|
||||||
|
size="small"
|
||||||
|
@click="router.back()"
|
||||||
|
>
|
||||||
|
返回
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Phase flow bar -->
|
<!-- Phase flow bar -->
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<template v-for="(phase, idx) in PHASES" :key="phase">
|
<template
|
||||||
|
v-for="(phase, idx) in PHASES"
|
||||||
|
:key="phase"
|
||||||
|
>
|
||||||
<NButton
|
<NButton
|
||||||
:type="phase === data.requirement.phase ? 'primary' : 'default'"
|
:type="phase === data.requirement.phase ? 'primary' : 'default'"
|
||||||
:secondary="phase !== data.requirement.phase"
|
:secondary="phase !== data.requirement.phase"
|
||||||
@@ -46,22 +62,39 @@
|
|||||||
>
|
>
|
||||||
{{ PHASE_LABELS[phase] }}
|
{{ PHASE_LABELS[phase] }}
|
||||||
</NButton>
|
</NButton>
|
||||||
<span v-if="idx < PHASES.length - 1" class="text-gray-300 text-xs">›</span>
|
<span
|
||||||
|
v-if="idx < PHASES.length - 1"
|
||||||
|
class="text-gray-300 text-xs"
|
||||||
|
>›</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<NCard title="描述">
|
<NCard title="描述">
|
||||||
<p v-if="data.requirement.description" class="text-gray-700 whitespace-pre-wrap">
|
<p
|
||||||
|
v-if="data.requirement.description"
|
||||||
|
class="text-gray-700 whitespace-pre-wrap"
|
||||||
|
>
|
||||||
{{ data.requirement.description }}
|
{{ data.requirement.description }}
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="text-gray-400 text-sm">暂无描述</p>
|
<p
|
||||||
|
v-else
|
||||||
|
class="text-gray-400 text-sm"
|
||||||
|
>
|
||||||
|
暂无描述
|
||||||
|
</p>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
|
||||||
<!-- Linked Issues -->
|
<!-- Linked Issues -->
|
||||||
<NCard title="关联 Issue">
|
<NCard title="关联 Issue">
|
||||||
<NList v-if="data.issues.length > 0" :show-divider="false">
|
<NList
|
||||||
<NListItem v-for="issue in data.issues" :key="issue.id">
|
v-if="data.issues.length > 0"
|
||||||
|
:show-divider="false"
|
||||||
|
>
|
||||||
|
<NListItem
|
||||||
|
v-for="issue in data.issues"
|
||||||
|
:key="issue.id"
|
||||||
|
>
|
||||||
<NThing>
|
<NThing>
|
||||||
<template #header>
|
<template #header>
|
||||||
<span class="font-mono text-xs text-gray-400 mr-2">#{{ issue.number }}</span>
|
<span class="font-mono text-xs text-gray-400 mr-2">#{{ issue.number }}</span>
|
||||||
@@ -78,11 +111,17 @@
|
|||||||
</NThing>
|
</NThing>
|
||||||
</NListItem>
|
</NListItem>
|
||||||
</NList>
|
</NList>
|
||||||
<NEmpty v-else description="暂无关联 Issue" />
|
<NEmpty
|
||||||
|
v-else
|
||||||
|
description="暂无关联 Issue"
|
||||||
|
/>
|
||||||
</NCard>
|
</NCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="!loading" class="text-gray-400 text-sm py-8 text-center">
|
<div
|
||||||
|
v-else-if="!loading"
|
||||||
|
class="text-gray-400 text-sm py-8 text-center"
|
||||||
|
>
|
||||||
需求不存在或无权访问。
|
需求不存在或无权访问。
|
||||||
</div>
|
</div>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
|
|||||||
@@ -4,18 +4,35 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h1 class="text-2xl font-semibold">需求列表</h1>
|
<h1 class="text-2xl font-semibold">
|
||||||
|
需求列表
|
||||||
|
</h1>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<NRadioGroup v-model:value="viewMode" size="small">
|
<NRadioGroup
|
||||||
<NRadioButton value="kanban">看板</NRadioButton>
|
v-model:value="viewMode"
|
||||||
<NRadioButton value="table">表格</NRadioButton>
|
size="small"
|
||||||
|
>
|
||||||
|
<NRadioButton value="kanban">
|
||||||
|
看板
|
||||||
|
</NRadioButton>
|
||||||
|
<NRadioButton value="table">
|
||||||
|
表格
|
||||||
|
</NRadioButton>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
<NButton type="primary" @click="showCreate = true">新建需求</NButton>
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
@click="showCreate = true"
|
||||||
|
>
|
||||||
|
新建需求
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Kanban view -->
|
<!-- Kanban view -->
|
||||||
<div v-if="viewMode === 'kanban'" class="flex gap-3 overflow-x-auto pb-2">
|
<div
|
||||||
|
v-if="viewMode === 'kanban'"
|
||||||
|
class="flex gap-3 overflow-x-auto pb-2"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="phase in PHASES"
|
v-for="phase in PHASES"
|
||||||
:key="phase"
|
:key="phase"
|
||||||
@@ -39,14 +56,22 @@
|
|||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="flex items-center justify-between gap-1">
|
<div class="flex items-center justify-between gap-1">
|
||||||
<span class="text-xs text-gray-400 font-mono">#{{ r.number }}</span>
|
<span class="text-xs text-gray-400 font-mono">#{{ r.number }}</span>
|
||||||
<NTag :type="r.status === 'open' ? 'success' : 'default'" size="tiny">
|
<NTag
|
||||||
|
:type="r.status === 'open' ? 'success' : 'default'"
|
||||||
|
size="tiny"
|
||||||
|
>
|
||||||
{{ r.status === 'open' ? '开放' : '关闭' }}
|
{{ r.status === 'open' ? '开放' : '关闭' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm font-medium leading-snug">{{ r.title }}</p>
|
<p class="text-sm font-medium leading-snug">
|
||||||
|
{{ r.title }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
<div v-if="columnItems(phase).length === 0" class="text-xs text-gray-300 text-center py-4">
|
<div
|
||||||
|
v-if="columnItems(phase).length === 0"
|
||||||
|
class="text-xs text-gray-300 text-center py-4"
|
||||||
|
>
|
||||||
拖放到此处
|
拖放到此处
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,27 +87,56 @@
|
|||||||
striped
|
striped
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-if="!store.loading && store.items.length === 0" class="text-gray-400 text-sm py-8 text-center">
|
<div
|
||||||
|
v-if="!store.loading && store.items.length === 0"
|
||||||
|
class="text-gray-400 text-sm py-8 text-center"
|
||||||
|
>
|
||||||
暂无需求,点击「新建需求」创建第一条。
|
暂无需求,点击「新建需求」创建第一条。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
|
|
||||||
<!-- Create modal -->
|
<!-- Create modal -->
|
||||||
<NModal v-model:show="showCreate" preset="card" title="新建需求" class="w-full max-w-md">
|
<NModal
|
||||||
|
v-model:show="showCreate"
|
||||||
|
preset="card"
|
||||||
|
title="新建需求"
|
||||||
|
class="w-full max-w-md"
|
||||||
|
>
|
||||||
<NForm @submit.prevent="onSubmit">
|
<NForm @submit.prevent="onSubmit">
|
||||||
<NFormItem label="标题">
|
<NFormItem label="标题">
|
||||||
<NInput v-model:value="form.title" placeholder="需求标题" />
|
<NInput
|
||||||
|
v-model:value="form.title"
|
||||||
|
placeholder="需求标题"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="描述">
|
<NFormItem label="描述">
|
||||||
<NInput v-model:value="form.description" type="textarea" :rows="4" placeholder="(可选)" />
|
<NInput
|
||||||
|
v-model:value="form.description"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
placeholder="(可选)"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<div class="flex justify-end gap-2 mt-4">
|
<div class="flex justify-end gap-2 mt-4">
|
||||||
<NButton @click="showCreate = false">取消</NButton>
|
<NButton @click="showCreate = false">
|
||||||
<NButton type="primary" attr-type="submit" :loading="creating">创建</NButton>
|
取消
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
attr-type="submit"
|
||||||
|
:loading="creating"
|
||||||
|
>
|
||||||
|
创建
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</NForm>
|
</NForm>
|
||||||
<p v-if="createError" class="text-red-500 text-sm mt-2">{{ createError }}</p>
|
<p
|
||||||
|
v-if="createError"
|
||||||
|
class="text-red-500 text-sm mt-2"
|
||||||
|
>
|
||||||
|
{{ createError }}
|
||||||
|
</p>
|
||||||
</NModal>
|
</NModal>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user