35 lines
15 KiB
HTML
35 lines
15 KiB
HTML
{{define "tasks.html"}}
|
|
<!doctype html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>采购任务 · 采购服务</title>
|
|
<style>
|
|
:root{--bg:#f4f7fb;--surface:#fff;--text:#172033;--muted:#526079;--border:#cfd8e6;--primary:#155eef;--danger:#b42318;--success:#067647;--focus:#ffbf47;font-family:"Segoe UI","Microsoft YaHei UI",system-ui,sans-serif}*{box-sizing:border-box}html{min-width:320px;background:var(--bg)}body{min-height:100dvh;margin:0;color:var(--text);background:var(--bg);font-size:16px;line-height:1.55}button,input,select{font:inherit}:focus-visible{outline:3px solid var(--focus);outline-offset:3px}.skip{position:fixed;z-index:100;top:8px;left:8px;padding:10px;color:#fff;background:#172033;transform:translateY(-160%)}.skip:focus{transform:translateY(0)}header{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:64px;padding:10px clamp(16px,4vw,40px);border-bottom:1px solid var(--border);background:var(--surface)}.brand{font-weight:700}.brand b{display:inline-grid;place-items:center;width:32px;height:32px;margin-right:8px;border-radius:8px;background:var(--primary);color:#fff;font-size:.82rem}.logout,.button{display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:9px 14px;border:1px solid var(--border);border-radius:8px;color:var(--text);background:#fff;font-weight:700;text-decoration:none;cursor:pointer}.button.primary{border-color:var(--primary);background:var(--primary);color:#fff}.button:disabled,.filters input:disabled,.filters select:disabled{opacity:.5;cursor:not-allowed}main{width:min(100% - 32px,1200px);margin:32px auto}.toolbar{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:16px}.toolbar-actions,.filters,.actions,.batch-actions{display:flex;flex-wrap:wrap;gap:10px}.muted,.placeholder,.not-selectable{color:var(--muted)}.filters{align-items:end;margin:0 0 16px}.filter-field{display:grid;gap:4px}.filter-field label{font-weight:700}.filters input,.filters select{min-height:44px;min-width:180px;padding:8px 10px;border:1px solid var(--border);border-radius:8px;background:#fff}.filters [aria-invalid=true]{border-color:var(--danger)}.batch-bar{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:0 0 16px;padding:14px 16px;border:1px solid var(--border);border-radius:12px;background:var(--surface)}.batch-bar p{margin:2px 0}.batch-summary{font-weight:700}.batch-message{min-height:1.55em;color:var(--muted)}.table-wrap{overflow-x:auto;border:1px solid var(--border);border-radius:12px;background:var(--surface)}table{width:100%;min-width:880px;border-collapse:collapse}th,td{padding:12px 14px;border-bottom:1px solid var(--border);text-align:left;vertical-align:top}th{background:#f8fafc;font-size:.88rem}td a{color:#124cc5;font-weight:700;text-underline-offset:3px}.select-cell{width:64px;text-align:center}.checkbox-target{display:inline-grid;place-items:center;min-width:44px;min-height:44px;margin:-10px;cursor:pointer}.checkbox-target input{width:18px;height:18px}.status{display:inline-block;padding:3px 8px;border-radius:999px;background:#eaf1ff;color:#173d8f;font-size:.85rem;font-weight:700}.empty,.success{padding:20px;border:1px solid var(--border);border-radius:12px;background:var(--surface)}.success{margin:0 0 16px;border-color:#9dd9b8;background:#ecfdf3;color:var(--success)}.modal-scrim{position:fixed;z-index:20;inset:0;background:rgba(23,32,51,.52)}dialog[open]{position:fixed;z-index:30;top:50%;left:50%;width:min(calc(100% - 24px),640px);max-height:calc(100dvh - 24px);margin:0;padding:28px;overflow-y:auto;border:1px solid var(--border);border-radius:14px;box-shadow:0 18px 48px rgba(23,32,51,.24);transform:translate(-50%,-50%);background:var(--surface)}.form-page{width:min(100% - 32px,640px);margin:32px auto;padding:28px;border:1px solid var(--border);border-radius:14px;background:var(--surface)}.form-grid{display:grid;gap:16px}.field label{display:block;margin-bottom:6px;font-weight:700}.required{color:var(--danger)}.field input{width:100%;min-height:44px;padding:10px 12px;border:1px solid #9ba9bc;border-radius:8px}.field input[aria-invalid=true]{border-color:var(--danger)}.error{margin:5px 0 0;color:var(--danger);font-size:.9rem}.summary{margin:0 0 16px;padding:12px;border-left:4px solid var(--danger);background:#fef3f2;color:var(--danger)}.summary p{margin:0}.summary ul{margin:8px 0 0;padding-left:20px}.summary a{color:inherit}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(max-width:420px){main,.form-page{width:calc(100% - 24px);margin:24px auto}.toolbar,.batch-bar{align-items:stretch;flex-direction:column}.toolbar-actions,.toolbar .button,.batch-actions,.batch-actions .button{width:100%}.toolbar-actions .button,.batch-actions .button{flex:1}.filters{align-items:stretch;flex-direction:column}.filters input,.filters select,.filters .button{width:100%}}@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition-duration:.01ms!important;animation-duration:.01ms!important}}</style>
|
|
</head>
|
|
<body>
|
|
<a class="skip" href="#main">跳到主要内容</a>
|
|
<header><div class="brand"><b aria-hidden="true">采</b>采购服务</div><form method="post" action="/logout"><input type="hidden" name="csrf_token" value="{{.CSRFToken}}"><button class="logout" type="submit">退出登录</button></form></header>
|
|
{{if .FullPage}}<main class="form-page" id="main">{{template "form" .}}</main>{{else}}<main id="main">
|
|
<div class="toolbar"><div><h1>采购任务</h1><p class="muted">查询任务并统一授权待开始任务。</p></div><div class="toolbar-actions"><button class="button" type="button" disabled>导入</button><a class="button primary" href="/tasks?create=1">创建任务</a></div></div>
|
|
{{if .FilterErrors}}<div class="summary" role="alert" aria-live="assertive"><p>请修正筛选条件后重新查询。</p><ul>{{with index .FilterErrors "status"}}<li><a href="#filter-status">状态:{{.}}</a></li>{{end}}{{with index .FilterErrors "created_from"}}<li><a href="#filter-created-from">开始日期:{{.}}</a></li>{{end}}{{with index .FilterErrors "created_to"}}<li><a href="#filter-created-to">结束日期:{{.}}</a></li>{{end}}</ul></div>{{end}}
|
|
<form class="filters" method="get" action="/tasks" aria-label="任务筛选">
|
|
<div class="filter-field"><label for="filter-keyword">关键词</label><input id="filter-keyword" name="keyword" type="search" value="{{.Filter.Keyword}}" placeholder="标题或商品编号"></div>
|
|
<div class="filter-field"><label for="filter-status">状态</label><select id="filter-status" name="status" aria-invalid="{{if index .FilterErrors "status"}}true{{else}}false{{end}}"{{with index .FilterErrors "status"}} aria-describedby="filter-status-error"{{end}}>{{if index .FilterErrors "status"}}<option value="{{.Filter.Status}}" selected>无效状态:{{.Filter.Status}}</option>{{end}}<option value=""{{if eq .Filter.Status ""}} selected{{end}}>全部状态</option><option value="DRAFT"{{if eq .Filter.Status "DRAFT"}} selected{{end}}>待开始</option><option value="PENDING"{{if eq .Filter.Status "PENDING"}} selected{{end}}>已授权待领取</option><option value="CLAIMED"{{if eq .Filter.Status "CLAIMED"}} selected{{end}}>已领取</option><option value="ORDERING"{{if eq .Filter.Status "ORDERING"}} selected{{end}}>执行中</option><option value="NEEDS_MANUAL"{{if eq .Filter.Status "NEEDS_MANUAL"}} selected{{end}}>待人工处理</option><option value="WAITING_PAYMENT"{{if eq .Filter.Status "WAITING_PAYMENT"}} selected{{end}}>待付款</option><option value="RECONCILIATION_REQUIRED"{{if eq .Filter.Status "RECONCILIATION_REQUIRED"}} selected{{end}}>围栏后待调和</option><option value="SUCCEEDED"{{if eq .Filter.Status "SUCCEEDED"}} selected{{end}}>已完成</option><option value="FAILED"{{if eq .Filter.Status "FAILED"}} selected{{end}}>失败</option><option value="CANCELED"{{if eq .Filter.Status "CANCELED"}} selected{{end}}>已取消</option></select>{{with index .FilterErrors "status"}}<p class="error" id="filter-status-error">{{.}}</p>{{end}}</div>
|
|
<div class="filter-field"><label for="filter-created-from">开始日期</label><input id="filter-created-from" name="created_from" type="date" value="{{.Filter.CreatedFrom}}" aria-invalid="{{if index .FilterErrors "created_from"}}true{{else}}false{{end}}"{{with index .FilterErrors "created_from"}} aria-describedby="filter-created-from-error"{{end}}>{{with index .FilterErrors "created_from"}}<p class="error" id="filter-created-from-error">{{.}}</p>{{end}}</div>
|
|
<div class="filter-field"><label for="filter-created-to">结束日期</label><input id="filter-created-to" name="created_to" type="date" value="{{.Filter.CreatedTo}}" aria-invalid="{{if index .FilterErrors "created_to"}}true{{else}}false{{end}}"{{with index .FilterErrors "created_to"}} aria-describedby="filter-created-to-error"{{end}}>{{with index .FilterErrors "created_to"}}<p class="error" id="filter-created-to-error">{{.}}</p>{{end}}</div>
|
|
<div class="actions"><button class="button primary" type="submit">筛选</button><a class="button" href="/tasks">清除筛选</a></div>
|
|
</form>
|
|
{{if .Success}}<p class="success" role="status">任务已创建,已显示在列表首行。</p>{{end}}
|
|
<form data-start-purchases data-start-key="{{.StartKey}}" data-csrf="{{.CSRFToken}}">
|
|
<section class="batch-bar" aria-label="批量开始采购"><div><p class="batch-summary" data-selection-summary aria-live="polite">已选 0 条,最高总额 ¥0.00</p><p class="muted" id="payment-note">采购工具会逐条创建待付款订单,系统不会付款。</p><p class="batch-message" id="start-feedback" data-start-feedback role="status" aria-live="polite"></p></div><div class="batch-actions"><button class="button primary" type="submit" data-start-button aria-describedby="payment-note start-feedback" disabled>开始采购(只创建待付款订单)</button></div></section>
|
|
<div class="table-wrap"><table><thead><tr><th class="select-cell" scope="col"><label class="checkbox-target"><span class="sr-only">选择全部当前筛选结果中的待开始任务</span><input type="checkbox" data-select-all aria-label="选择全部任务"{{if not .Tasks}} disabled{{end}}></label></th><th scope="col">标题</th><th scope="col">颜色分类</th><th scope="col">尺码</th><th scope="col">价格上限</th><th scope="col">数量</th><th scope="col">采购结果</th><th scope="col">状态</th><th scope="col">创建时间(上海)</th></tr></thead><tbody>{{if .Tasks}}{{range .Tasks}}<tr><td class="select-cell">{{if eq .Status "DRAFT"}}<label class="checkbox-target"><span class="sr-only">选择任务 {{.Title}}</span><input type="checkbox" name="task_ids" value="{{.ID}}" data-task-id="{{.ID}}" data-task-version="{{.Version}}" data-price="{{.MaxTotalPrice}}" aria-label="选择任务 {{.Title}}"></label>{{else}}<span class="not-selectable">—<span class="sr-only">{{statusLabel .Status}}任务不可选择</span></span>{{end}}</td><td><a href="https://mobile.yangkeduo.com/goods.html?goods_id={{.GoodsID}}" target="_blank" rel="noopener noreferrer">{{.Title}}</a></td><td>{{.SKUColor}}</td><td>{{.SKUSize}}</td><td>¥{{.MaxTotalPrice}}</td><td>{{.Quantity}}</td><td>—</td><td><span class="status">{{statusLabel .Status}}</span></td><td><time datetime="{{shanghaiDateTime .CreatedAt}}">{{shanghaiTime .CreatedAt}}</time></td></tr>{{end}}{{else}}<tr><td colspan="9">{{if .FilterErrors}}<section class="empty"><h2>筛选条件有误</h2><p>请修正上方标出的字段后重新查询。</p></section>{{else if .HasFilter}}<section class="empty"><h2>没有符合筛选条件的任务</h2><p><a class="button" href="/tasks">清除筛选</a></p></section>{{else}}<section class="empty"><h2>还没有采购任务</h2><p>创建一条手工任务后会显示在这里。</p></section>{{end}}</td></tr>{{end}}</tbody></table></div>
|
|
</form>
|
|
</main>{{if .OpenForm}}<div class="modal-scrim" aria-hidden="true"></div><dialog open aria-modal="true" aria-labelledby="form-title">{{template "form" .}}</dialog>{{end}}<script src="/static/tasks.js" defer></script>{{end}}
|
|
</body>
|
|
</html>
|
|
{{end}}
|
|
{{define "form"}}<h1 id="form-title">创建采购任务</h1><p class="muted">保存后仅生成待开始任务,不会执行其他动作。</p>{{if .Errors}}<div class="summary" role="alert" aria-live="assertive"><p>请修正下列字段后再保存。</p><ul>{{with index .Errors "title"}}<li><a href="#title">任务名称:{{.}}</a></li>{{end}}{{with index .Errors "product_url"}}<li><a href="#product_url">商品链接:{{.}}</a></li>{{end}}{{with index .Errors "sku_color"}}<li><a href="#sku_color">颜色分类:{{.}}</a></li>{{end}}{{with index .Errors "sku_size"}}<li><a href="#sku_size">尺码:{{.}}</a></li>{{end}}{{with index .Errors "quantity"}}<li><a href="#quantity">数量:{{.}}</a></li>{{end}}{{with index .Errors "max_total_price"}}<li><a href="#max_total_price">价格上限:{{.}}</a></li>{{end}}{{with index .Errors "create_key"}}<li>{{.}}</li>{{end}}</ul></div>{{end}}<form method="post" action="/tasks" class="form-grid"><input type="hidden" name="csrf_token" value="{{.CSRFToken}}"><input type="hidden" name="create_key" value="{{.Form.CreateKey}}"><input type="hidden" name="form_mode" value="{{if .FullPage}}full{{else}}dialog{{end}}">{{template "field" (list "title" "任务名称" .Form.Title .Errors .FocusField)}}{{template "field" (list "product_url" "商品链接" .Form.ProductURL .Errors .FocusField)}}{{template "field" (list "sku_color" "颜色分类" .Form.SKUColor .Errors .FocusField)}}{{template "field" (list "sku_size" "尺码" .Form.SKUSize .Errors .FocusField)}}{{template "field" (list "quantity" "数量" .Form.Quantity .Errors .FocusField)}}{{template "field" (list "max_total_price" "价格上限" .Form.MaxTotalPrice .Errors .FocusField)}}<div class="actions"><button class="button primary" type="submit">保存任务</button><a class="button" href="/tasks">取消</a></div></form>{{end}}
|
|
{{define "field"}}{{$name:=index . 0}}{{$label:=index . 1}}{{$value:=index . 2}}{{$errors:=index . 3}}{{$focus:=index . 4}}<div class="field"><label for="{{$name}}">{{$label}} <span class="required" aria-hidden="true">*</span><span class="sr-only">(必填)</span></label><input id="{{$name}}" name="{{$name}}" value="{{$value}}" required {{if eq $focus $name}}autofocus{{end}} aria-invalid="{{if index $errors $name}}true{{else}}false{{end}}"{{with index $errors $name}} aria-describedby="{{$name}}-error"{{end}} {{if eq $name "product_url"}}type="url" inputmode="url" maxlength="2048"{{else if eq $name "quantity"}}type="number" inputmode="numeric" min="1" step="1"{{else if eq $name "max_total_price"}}type="text" inputmode="decimal" pattern="[0-9]+(\.[0-9]{1,2})?" maxlength="64"{{else if eq $name "title"}}type="text" maxlength="120"{{else}}type="text" maxlength="80"{{end}}>{{with index $errors $name}}<p class="error" id="{{$name}}-error">{{.}}</p>{{end}}</div>{{end}}
|