feat(backend): implement task creation and admin web
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
{{define "error"}}
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<title>{{.Page.Title}} - 采购任务管理</title>
|
||||
{{template "document-head" .}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "site-header" .}}
|
||||
<main id="main-content" class="page narrow-page">
|
||||
<section class="error-page" role="alert">
|
||||
<h1>{{.Heading}}</h1>
|
||||
<p>{{.Message}}</p>
|
||||
<a class="button primary" href="/tasks">返回任务列表</a>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -0,0 +1,22 @@
|
||||
{{define "document-head"}}
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light">
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="/static/admin.css">
|
||||
<script src="/static/admin.js" defer></script>
|
||||
{{end}}
|
||||
|
||||
{{define "site-header"}}
|
||||
<a class="skip-link" href="#main-content">跳到主要内容</a>
|
||||
<header class="site-header">
|
||||
<a class="brand" href="/tasks" aria-label="采购任务管理首页">
|
||||
<span class="brand-mark" aria-hidden="true">采</span>
|
||||
<span>采购任务管理</span>
|
||||
</a>
|
||||
<nav class="main-nav" aria-label="主导航">
|
||||
<a href="/tasks" {{if .Page.TasksCurrent}}aria-current="page"{{end}}>任务列表</a>
|
||||
<a href="/tasks/new" {{if .Page.NewCurrent}}aria-current="page"{{end}}>新建任务</a>
|
||||
</nav>
|
||||
</header>
|
||||
{{end}}
|
||||
@@ -0,0 +1,106 @@
|
||||
{{define "task-detail"}}
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<title>{{.Task.Title}} - 采购任务管理</title>
|
||||
{{template "document-head" .}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "site-header" .}}
|
||||
<main id="main-content" class="page">
|
||||
<a class="back-link" href="/tasks">返回任务列表</a>
|
||||
|
||||
{{if .Notice}}
|
||||
<div class="notice" role="status" tabindex="-1" data-notice>{{.Notice}}</div>
|
||||
{{end}}
|
||||
|
||||
<div class="title-row detail-title">
|
||||
<div>
|
||||
<h1>{{.Task.Title}}</h1>
|
||||
<p class="subtitle">SKU:{{.Task.SKU}} · 任务编号:{{.Task.ID}}</p>
|
||||
</div>
|
||||
<div class="title-actions">
|
||||
<span class="status {{.Task.StatusClass}}" title="状态码 {{.Task.Status}}">
|
||||
{{.Task.StatusLabel}}
|
||||
</span>
|
||||
{{if .Task.CanCancel}}
|
||||
<button class="button danger-outline" type="button" data-open-cancel>取消任务</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if eq .Task.Status "SUCCEEDED"}}
|
||||
<section class="success-banner" aria-labelledby="success-heading">
|
||||
<div aria-hidden="true" class="success-mark">✓</div>
|
||||
<div>
|
||||
<h2 id="success-heading">验证完成,未提交订单</h2>
|
||||
<p>采购人员已完成候选确认,系统只记录验证结果。</p>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<div class="detail-layout">
|
||||
<section class="content-section" aria-labelledby="original-heading">
|
||||
<h2 id="original-heading">原始采购要求</h2>
|
||||
<p class="section-note">以下内容来自管理员输入,不会被 AI 派生结果覆盖。</p>
|
||||
<div class="requirement-layout">
|
||||
<figure class="reference-figure">
|
||||
<img class="reference-image"
|
||||
src="/api/v1/assets/{{pathPart .Task.ReferenceAssetID}}/content"
|
||||
alt="商品参考图">
|
||||
<figcaption>已规范化的参考图片</figcaption>
|
||||
</figure>
|
||||
<dl class="definition-list">
|
||||
<dt>商品标题</dt><dd>{{.Task.Title}}</dd>
|
||||
<dt>SKU</dt><dd>{{.Task.SKU}}</dd>
|
||||
<dt>描述</dt><dd>{{if .Task.Description}}{{.Task.Description}}{{else}}未填写{{end}}</dd>
|
||||
<dt>数量</dt><dd>{{.Task.Quantity}}</dd>
|
||||
<dt>最高总预算</dt>
|
||||
<dd>{{if .Task.MaxBudget}}{{.Task.MaxBudget}} 元{{else}}未设置{{end}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside class="content-section summary-section" aria-labelledby="summary-heading">
|
||||
<h2 id="summary-heading">任务摘要</h2>
|
||||
<dl class="definition-list">
|
||||
<dt>状态</dt><dd>{{.Task.StatusLabel}}({{.Task.Status}})</dd>
|
||||
<dt>创建时间</dt>
|
||||
<dd><time datetime="{{machineTime .Task.CreatedAt}}">{{displayTime .Task.CreatedAt}}</time></dd>
|
||||
<dt>最后更新</dt>
|
||||
<dd><time datetime="{{machineTime .Task.UpdatedAt}}">{{displayTime .Task.UpdatedAt}}</time></dd>
|
||||
</dl>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
{{if .Task.CanCancel}}
|
||||
<noscript>
|
||||
<section class="noscript-cancel" aria-labelledby="noscript-cancel-heading">
|
||||
<h2 id="noscript-cancel-heading">确认取消任务</h2>
|
||||
<p>取消后任务不会自动恢复。</p>
|
||||
<form method="post" action="/tasks/{{pathPart .Task.ID}}/cancel">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<input type="hidden" name="cancel_key" value="{{.CancelKey}}">
|
||||
<button class="button danger" type="submit">确认取消</button>
|
||||
</form>
|
||||
</section>
|
||||
</noscript>
|
||||
|
||||
<dialog class="confirm-dialog" data-cancel-dialog aria-labelledby="cancel-title">
|
||||
<h2 id="cancel-title">确认取消任务?</h2>
|
||||
<p>任务取消后不会自动恢复,采购执行员将不能再领取。</p>
|
||||
<div class="dialog-actions">
|
||||
<button class="button" type="button" data-keep-task>保留任务</button>
|
||||
<form method="post" action="/tasks/{{pathPart .Task.ID}}/cancel" data-submit-form>
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<input type="hidden" name="cancel_key" value="{{.CancelKey}}">
|
||||
<button class="button danger" type="submit" data-submit-button
|
||||
data-loading-label="正在取消…">确认取消</button>
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
{{end}}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -0,0 +1,126 @@
|
||||
{{define "task-new"}}
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<title>{{.Page.Title}} - 采购任务管理</title>
|
||||
{{template "document-head" .}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "site-header" .}}
|
||||
<main id="main-content" class="page form-page">
|
||||
<div class="title-row">
|
||||
<div>
|
||||
<h1>新建采购任务</h1>
|
||||
<p class="subtitle">填写商品要求和不可覆盖的采购约束</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if or .Notice .Errors.Form}}
|
||||
<div class="notice notice-error" role="alert" tabindex="-1" data-error-summary>
|
||||
{{if .Notice}}<p>{{.Notice}}</p>{{end}}
|
||||
{{if .Errors.Form}}<p>{{.Errors.Form}}</p>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<form class="task-form" method="post" action="/tasks" enctype="multipart/form-data"
|
||||
novalidate data-dirty-form data-submit-form>
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<input type="hidden" name="upload_key" value="{{.UploadKey}}">
|
||||
<input type="hidden" name="create_key" value="{{.CreateKey}}">
|
||||
<input type="hidden" name="image_asset_id" value="{{.UploadedAsset.ID}}">
|
||||
|
||||
<section class="form-section" aria-labelledby="requirement-heading">
|
||||
<h2 id="requirement-heading">商品要求</h2>
|
||||
<div class="form-grid">
|
||||
<div class="field span-2">
|
||||
<label for="title">商品标题 <span class="required-text">必填</span></label>
|
||||
<input id="title" name="title" value="{{.Form.Title}}" maxlength="120" required
|
||||
autocomplete="off" aria-describedby="title-error"
|
||||
{{if .Errors.Title}}aria-invalid="true" data-error-field{{end}}>
|
||||
<p id="title-error" class="field-error">{{.Errors.Title}}</p>
|
||||
</div>
|
||||
|
||||
<div class="field span-2">
|
||||
<label for="sku">SKU <span class="required-text">必填</span></label>
|
||||
<input id="sku" name="sku" value="{{.Form.SKU}}" maxlength="512" required
|
||||
autocomplete="off" aria-describedby="sku-help sku-error"
|
||||
{{if .Errors.SKU}}aria-invalid="true" data-error-field{{end}}>
|
||||
<p id="sku-help" class="field-help">填写采购时用于核对的商品规格编号。</p>
|
||||
<p id="sku-error" class="field-error">{{.Errors.SKU}}</p>
|
||||
</div>
|
||||
|
||||
<div class="field span-2">
|
||||
<label for="description">商品描述</label>
|
||||
<textarea id="description" name="description" maxlength="8192"
|
||||
aria-describedby="description-help description-error"
|
||||
{{if .Errors.Description}}aria-invalid="true" data-error-field{{end}}>{{.Form.Description}}</textarea>
|
||||
<p id="description-help" class="field-help">只填写采购判断所需信息,不填写订单、地址或个人信息。</p>
|
||||
<p id="description-error" class="field-error">{{.Errors.Description}}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="quantity">数量 <span class="required-text">必填</span></label>
|
||||
<input id="quantity" name="quantity" type="number" value="{{.Form.Quantity}}"
|
||||
min="1" step="1" inputmode="numeric" required aria-describedby="quantity-error"
|
||||
{{if .Errors.Quantity}}aria-invalid="true" data-error-field{{end}}>
|
||||
<p id="quantity-error" class="field-error">{{.Errors.Quantity}}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="max-budget">最高总预算(元)</label>
|
||||
<input id="max-budget" name="max_budget" type="text" value="{{.Form.MaxBudget}}"
|
||||
inputmode="decimal" placeholder="不填写则不设上限"
|
||||
aria-describedby="budget-help budget-error"
|
||||
{{if .Errors.MaxBudget}}aria-invalid="true" data-error-field{{end}}>
|
||||
<p id="budget-help" class="field-help">数量合计的最高预算,不是单件价格。</p>
|
||||
<p id="budget-error" class="field-error">{{.Errors.MaxBudget}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="form-section" aria-labelledby="image-heading">
|
||||
<h2 id="image-heading">参考图片</h2>
|
||||
<div class="upload-layout">
|
||||
<div class="field">
|
||||
<label for="image">商品参考图 <span class="required-text">必填</span></label>
|
||||
{{if .UploadedAsset.ID}}
|
||||
<p class="retained-file">已保留上传成功的参考图片</p>
|
||||
<p class="field-help">再次提交会复用该图片,不会重复上传。</p>
|
||||
{{else}}
|
||||
<input id="image" name="image" type="file"
|
||||
accept="image/png,image/jpeg,image/webp" required
|
||||
aria-describedby="image-help image-error"
|
||||
{{if .Errors.Image}}aria-invalid="true" data-error-field{{end}}>
|
||||
<p id="image-help" class="field-help">支持 JPG、PNG、WebP;服务端会校验实际内容。</p>
|
||||
{{end}}
|
||||
<p id="image-error" class="field-error">{{.Errors.Image}}</p>
|
||||
</div>
|
||||
<figure class="image-preview">
|
||||
<div class="image-placeholder" data-image-placeholder>尚未选择图片</div>
|
||||
<img alt="待上传的商品参考图预览" data-image-preview hidden>
|
||||
<figcaption data-image-name>
|
||||
{{if .UploadedAsset.ID}}参考图片已保留{{else}}选择后仅在本地预览{{end}}
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="form-actions">
|
||||
<a class="button" href="/tasks">取消</a>
|
||||
<button class="button primary" type="submit" data-submit-button
|
||||
data-loading-label="正在创建…">创建任务</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<dialog class="confirm-dialog" data-leave-dialog aria-labelledby="leave-title">
|
||||
<h2 id="leave-title">放弃未提交内容?</h2>
|
||||
<p>已填写的商品要求不会保存。</p>
|
||||
<div class="dialog-actions">
|
||||
<button class="button" type="button" data-stay>继续填写</button>
|
||||
<a class="button danger" href="/tasks" data-discard>放弃并离开</a>
|
||||
</div>
|
||||
</dialog>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -0,0 +1,93 @@
|
||||
{{define "tasks"}}
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<title>{{.Page.Title}} - 采购任务管理</title>
|
||||
{{template "document-head" .}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "site-header" .}}
|
||||
<main id="main-content" class="page">
|
||||
<div class="title-row">
|
||||
<div>
|
||||
<h1>采购任务</h1>
|
||||
<p class="subtitle">按最近创建时间查看采购任务状态</p>
|
||||
</div>
|
||||
<a class="button primary" href="/tasks/new">新建任务</a>
|
||||
</div>
|
||||
|
||||
<form class="filters" method="get" action="/tasks" data-loading-form>
|
||||
<div class="field compact">
|
||||
<label for="task-search">搜索</label>
|
||||
<input id="task-search" name="q" type="search" value="{{.Query}}"
|
||||
placeholder="任务编号、标题或 SKU">
|
||||
</div>
|
||||
<div class="field compact">
|
||||
<label for="task-status">状态</label>
|
||||
<select id="task-status" name="status">
|
||||
{{range .StatusOptions}}
|
||||
<option value="{{.Value}}" {{if .Selected}}selected{{end}}>{{.Label}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<button class="button filter-button" type="submit" data-loading-label="正在加载…">应用筛选</button>
|
||||
</form>
|
||||
|
||||
<section class="table-region" aria-labelledby="task-table-title" data-loading-region>
|
||||
<h2 id="task-table-title" class="visually-hidden">任务列表结果</h2>
|
||||
{{if .Items}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">任务</th>
|
||||
<th scope="col">状态</th>
|
||||
<th scope="col">执行设备</th>
|
||||
<th scope="col">更新时间</th>
|
||||
<th scope="col"><span class="visually-hidden">操作</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Items}}
|
||||
<tr>
|
||||
<td data-label="任务">
|
||||
<strong class="task-title">{{.Title}}</strong>
|
||||
<span class="secondary">SKU:{{.SKU}}</span>
|
||||
<span class="secondary">编号:{{.ID}}</span>
|
||||
</td>
|
||||
<td data-label="状态">
|
||||
<span class="status {{.StatusClass}}" title="状态码 {{.Status}}">{{.StatusLabel}}</span>
|
||||
</td>
|
||||
<td data-label="执行设备">{{.DeviceName}}</td>
|
||||
<td data-label="更新时间">
|
||||
<time datetime="{{machineTime .UpdatedAt}}">{{displayTime .UpdatedAt}}</time>
|
||||
</td>
|
||||
<td data-label="操作">
|
||||
<a class="detail-link" href="/tasks/{{pathPart .ID}}">查看详情</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<h2>没有符合条件的任务</h2>
|
||||
{{if or .Query .Status}}
|
||||
<p>请调整搜索或状态筛选后重试。</p>
|
||||
<a class="button" href="/tasks">清除筛选</a>
|
||||
{{else}}
|
||||
<p>创建第一条采购任务后,它会显示在这里。</p>
|
||||
<a class="button primary" href="/tasks/new">新建任务</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
{{if .NextCursor}}
|
||||
<nav class="pagination" aria-label="任务分页">
|
||||
<a class="button" href="/tasks?q={{.Query}}&status={{.Status}}&cursor={{.NextCursor}}">下一页</a>
|
||||
</nav>
|
||||
{{end}}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user