feat: 增加蝦皮店铺名称搜索 (#182)
This commit is contained in:
@@ -43,11 +43,11 @@ func assertRedirectQuery(t *testing.T, recorder *httptest.ResponseRecorder, want
|
|||||||
func Test列表写操作跳转保留筛选和页码(t *testing.T) {
|
func Test列表写操作跳转保留筛选和页码(t *testing.T) {
|
||||||
t.Run("蝦皮", func(t *testing.T) {
|
t.Run("蝦皮", func(t *testing.T) {
|
||||||
context, recorder := listPostContext(t, "/shopee/save", url.Values{
|
context, recorder := listPostContext(t, "/shopee/save", url.Values{
|
||||||
"list_goods_id": {"1001"}, "status": {"no_link"}, "shop": {"has"}, "image": {"missing"}, "page": {"2"},
|
"list_goods_id": {"1001"}, "list_shop_name": {"店铺 A"}, "status": {"no_link"}, "shop": {"has"}, "image": {"missing"}, "page": {"2"},
|
||||||
})
|
})
|
||||||
(&Handler{}).shopeeRedirect(context, "完成")
|
(&Handler{}).shopeeRedirect(context, "完成")
|
||||||
assertRedirectQuery(t, recorder, "/shopee", map[string]string{
|
assertRedirectQuery(t, recorder, "/shopee", map[string]string{
|
||||||
"goods_id": "1001", "status": "no_link", "shop": "has", "image": "missing", "page": "2", "msg": "完成",
|
"goods_id": "1001", "shop_name": "店铺 A", "status": "no_link", "shop": "has", "image": "missing", "page": "2", "msg": "完成",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
t.Run("顺运宝", func(t *testing.T) {
|
t.Run("顺运宝", func(t *testing.T) {
|
||||||
@@ -101,6 +101,7 @@ func Test列表写操作跳转保留筛选和页码(t *testing.T) {
|
|||||||
func Test蝦皮分页详情和状态条保留资料筛选(t *testing.T) {
|
func Test蝦皮分页详情和状态条保留资料筛选(t *testing.T) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"goods_id": {"1001"},
|
"goods_id": {"1001"},
|
||||||
|
"shop_name": {"店铺 A"},
|
||||||
"status": {"has_link"},
|
"status": {"has_link"},
|
||||||
"shop": {"has"},
|
"shop": {"has"},
|
||||||
"image": {"missing"},
|
"image": {"missing"},
|
||||||
@@ -110,7 +111,7 @@ func Test蝦皮分页详情和状态条保留资料筛选(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for key, want := range map[string]string{
|
for key, want := range map[string]string{
|
||||||
"goods_id": "1001", "status": "has_link", "shop": "has", "image": "missing", "page": "3",
|
"goods_id": "1001", "shop_name": "店铺 A", "status": "has_link", "shop": "has", "image": "missing", "page": "3",
|
||||||
} {
|
} {
|
||||||
if got := detail.Query().Get(key); got != want {
|
if got := detail.Query().Get(key); got != want {
|
||||||
t.Errorf("详情参数 %s=%q,期望 %q", key, got, want)
|
t.Errorf("详情参数 %s=%q,期望 %q", key, got, want)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
// **商品级一行**(不是 SKU 级),数据来自 shopee_products 和 shopee_skus
|
// **商品级一行**(不是 SKU 级),数据来自 shopee_products 和 shopee_skus
|
||||||
// 聚合联查。列定义见 docs/admin/05-ui-specification.md §4.2、工单 #41。
|
// 聚合联查。列定义见 docs/admin/05-ui-specification.md §4.2、工单 #41。
|
||||||
func (h *Handler) ShopeeList(c *gin.Context) {
|
func (h *Handler) ShopeeList(c *gin.Context) {
|
||||||
h.renderShopeeList(c, c.Query("goods_id"), c.Query("status"), c.Query("shop"), c.Query("image"), c.Query("page"), c.Query("msg"))
|
h.renderShopeeList(c, c.Query("goods_id"), c.Query("shop_name"), c.Query("status"), c.Query("shop"), c.Query("image"), c.Query("page"), c.Query("msg"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShopeeDetail 渲染双击行弹出的那个弹窗的**内容**(不是整页)。
|
// ShopeeDetail 渲染双击行弹出的那个弹窗的**内容**(不是整页)。
|
||||||
@@ -46,6 +46,7 @@ func (h *Handler) ShopeeDetail(c *gin.Context) {
|
|||||||
c.HTML(http.StatusOK, "shopee/detail_modal", gin.H{
|
c.HTML(http.StatusOK, "shopee/detail_modal", gin.H{
|
||||||
"D": detail, "CSRFToken": csrfToken(c),
|
"D": detail, "CSRFToken": csrfToken(c),
|
||||||
"Keyword": c.Query("goods_id"), "StatusFilter": c.Query("status"),
|
"Keyword": c.Query("goods_id"), "StatusFilter": c.Query("status"),
|
||||||
|
"ShopNameKeyword": strings.TrimSpace(c.Query("shop_name")),
|
||||||
"ShopFilter": service.ParseShopeePresence(c.Query("shop")),
|
"ShopFilter": service.ParseShopeePresence(c.Query("shop")),
|
||||||
"ImageFilter": service.ParseShopeePresence(c.Query("image")),
|
"ImageFilter": service.ParseShopeePresence(c.Query("image")),
|
||||||
"CurrentPage": service.ParsePage(c.Query("page")),
|
"CurrentPage": service.ParsePage(c.Query("page")),
|
||||||
@@ -56,9 +57,10 @@ func (h *Handler) ShopeeDetail(c *gin.Context) {
|
|||||||
//
|
//
|
||||||
// `[必须]` 分页控件用 <a href> 纯 GET 导航,翻页要保留关键词和三个下拉筛选,
|
// `[必须]` 分页控件用 <a href> 纯 GET 导航,翻页要保留关键词和三个下拉筛选,
|
||||||
// 所以这里把归一后的有效值透传回模板去拼下一页的链接(工单 #43、#150)。
|
// 所以这里把归一后的有效值透传回模板去拼下一页的链接(工单 #43、#150)。
|
||||||
func (h *Handler) renderShopeeList(c *gin.Context, keyword, statusRaw, shopRaw, imageRaw, pageRaw, msg string) {
|
func (h *Handler) renderShopeeList(c *gin.Context, keyword, shopName, statusRaw, shopRaw, imageRaw, pageRaw, msg string) {
|
||||||
filter := repository.ShopeeFilter{
|
filter := repository.ShopeeFilter{
|
||||||
Keyword: keyword,
|
Keyword: keyword,
|
||||||
|
ShopName: strings.TrimSpace(shopName),
|
||||||
Status: service.ParseShopeeStatus(statusRaw),
|
Status: service.ParseShopeeStatus(statusRaw),
|
||||||
Shop: service.ParseShopeePresence(shopRaw),
|
Shop: service.ParseShopeePresence(shopRaw),
|
||||||
Image: service.ParseShopeePresence(imageRaw),
|
Image: service.ParseShopeePresence(imageRaw),
|
||||||
@@ -83,6 +85,9 @@ func (h *Handler) renderShopeeList(c *gin.Context, keyword, statusRaw, shopRaw,
|
|||||||
if keyword != "" {
|
if keyword != "" {
|
||||||
values.Set("goods_id", keyword)
|
values.Set("goods_id", keyword)
|
||||||
}
|
}
|
||||||
|
if filter.ShopName != "" {
|
||||||
|
values.Set("shop_name", filter.ShopName)
|
||||||
|
}
|
||||||
if filter.Status != "" {
|
if filter.Status != "" {
|
||||||
values.Set("status", filter.Status)
|
values.Set("status", filter.Status)
|
||||||
}
|
}
|
||||||
@@ -95,6 +100,7 @@ func (h *Handler) renderShopeeList(c *gin.Context, keyword, statusRaw, shopRaw,
|
|||||||
|
|
||||||
c.HTML(http.StatusOK, "shopee/list", page(c, "shopee", "蝦皮数据", gin.H{
|
c.HTML(http.StatusOK, "shopee/list", page(c, "shopee", "蝦皮数据", gin.H{
|
||||||
"Keyword": keyword,
|
"Keyword": keyword,
|
||||||
|
"ShopNameKeyword": filter.ShopName,
|
||||||
"StatusFilter": filter.Status,
|
"StatusFilter": filter.Status,
|
||||||
"StatusOptions": service.ShopeeStatusOptions(),
|
"StatusOptions": service.ShopeeStatusOptions(),
|
||||||
"ShopFilter": filter.Shop,
|
"ShopFilter": filter.Shop,
|
||||||
@@ -179,6 +185,9 @@ func (h *Handler) shopeeRedirect(c *gin.Context, msg string) {
|
|||||||
if value := strings.TrimSpace(c.PostForm("list_goods_id")); value != "" {
|
if value := strings.TrimSpace(c.PostForm("list_goods_id")); value != "" {
|
||||||
params.Set("goods_id", value)
|
params.Set("goods_id", value)
|
||||||
}
|
}
|
||||||
|
if value := strings.TrimSpace(c.PostForm("list_shop_name")); value != "" {
|
||||||
|
params.Set("shop_name", value)
|
||||||
|
}
|
||||||
if value := strings.TrimSpace(c.PostForm("status")); value != "" {
|
if value := strings.TrimSpace(c.PostForm("status")); value != "" {
|
||||||
params.Set("status", service.ParseShopeeStatus(value))
|
params.Set("status", service.ParseShopeeStatus(value))
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@ func TestModuleNavigation_按账号保存稳定列表状态且安全回退(t *te
|
|||||||
jsSource := string(js)
|
jsSource := string(js)
|
||||||
for _, want := range []string{
|
for _, want := range []string{
|
||||||
"var MODULE_QUERY_KEYS = {",
|
"var MODULE_QUERY_KEYS = {",
|
||||||
"\"/shopee\": [\"goods_id\", \"status\", \"shop\", \"image\", \"page\"]",
|
"\"/shopee\": [\"goods_id\", \"shop_name\", \"status\", \"shop\", \"image\", \"page\"]",
|
||||||
"\"/syb\": [\"order_no\", \"shop\", \"stage\", \"page\", \"date_from\", \"date_to\"]",
|
"\"/syb\": [\"order_no\", \"shop\", \"stage\", \"page\", \"date_from\", \"date_to\"]",
|
||||||
"var MODULE_STATE_PREFIX = \"cmautobuy:module-state:\"",
|
"var MODULE_STATE_PREFIX = \"cmautobuy:module-state:\"",
|
||||||
"parsed.origin !== window.location.origin || parsed.pathname !== moduleRoot",
|
"parsed.origin !== window.location.origin || parsed.pathname !== moduleRoot",
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ type ShopeeProductRow struct {
|
|||||||
// 这里不做校验——repository 只管拼 SQL。
|
// 这里不做校验——repository 只管拼 SQL。
|
||||||
type ShopeeFilter struct {
|
type ShopeeFilter struct {
|
||||||
Keyword string
|
Keyword string
|
||||||
|
ShopName string
|
||||||
Status string
|
Status string
|
||||||
Shop string // has / missing / 空(全部)
|
Shop string // has / missing / 空(全部)
|
||||||
Image string // has / missing / 空(全部)
|
Image string // has / missing / 空(全部)
|
||||||
@@ -153,6 +154,10 @@ func shopeeFilterClause(filter ShopeeFilter) (string, []any) {
|
|||||||
clauses = append(clauses, `(sp.goods_id LIKE ? ESCAPE '!' OR sp.title LIKE ? ESCAPE '!')`)
|
clauses = append(clauses, `(sp.goods_id LIKE ? ESCAPE '!' OR sp.title LIKE ? ESCAPE '!')`)
|
||||||
args = append(args, like, like)
|
args = append(args, like, like)
|
||||||
}
|
}
|
||||||
|
if shopName := strings.TrimSpace(filter.ShopName); shopName != "" {
|
||||||
|
clauses = append(clauses, `sp.shopee_shop_name LIKE ? ESCAPE '!'`)
|
||||||
|
args = append(args, "%"+escapeLike(shopName)+"%")
|
||||||
|
}
|
||||||
|
|
||||||
switch filter.Status {
|
switch filter.Status {
|
||||||
case "pending_spec":
|
case "pending_spec":
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ func ListShopeeProducts(db *sql.DB, filter repository.ShopeeFilter, page int) (*
|
|||||||
Rows: make([]ShopeeProductView, 0, len(rows)),
|
Rows: make([]ShopeeProductView, 0, len(rows)),
|
||||||
Total: total,
|
Total: total,
|
||||||
HasAnyProducts: hasAny > 0,
|
HasAnyProducts: hasAny > 0,
|
||||||
IsFiltered: strings.TrimSpace(filter.Keyword) != "" || filter.Status != "" || filter.Shop != "" || filter.Image != "",
|
IsFiltered: strings.TrimSpace(filter.Keyword) != "" || strings.TrimSpace(filter.ShopName) != "" || filter.Status != "" || filter.Shop != "" || filter.Image != "",
|
||||||
Page: page,
|
Page: page,
|
||||||
PageSize: PageSize,
|
PageSize: PageSize,
|
||||||
TotalPages: totalPages,
|
TotalPages: totalPages,
|
||||||
|
|||||||
@@ -503,6 +503,8 @@ func TestListShopeeProducts_店铺图片三态及组合筛选(t *testing.T) {
|
|||||||
{"有店铺且有图片", repository.ShopeeFilter{Shop: "has", Image: "has"}, []string{"A"}},
|
{"有店铺且有图片", repository.ShopeeFilter{Shop: "has", Image: "has"}, []string{"A"}},
|
||||||
{"无店铺且无图片", repository.ShopeeFilter{Shop: "missing", Image: "missing"}, []string{"D"}},
|
{"无店铺且无图片", repository.ShopeeFilter{Shop: "missing", Image: "missing"}, []string{"D"}},
|
||||||
{"组合关键词", repository.ShopeeFilter{Keyword: "店铺有图", Shop: "has", Image: "has"}, []string{"A"}},
|
{"组合关键词", repository.ShopeeFilter{Keyword: "店铺有图", Shop: "has", Image: "has"}, []string{"A"}},
|
||||||
|
{"店铺名称", repository.ShopeeFilter{ShopName: "店铺 A"}, []string{"A"}},
|
||||||
|
{"店铺名称组合", repository.ShopeeFilter{ShopName: "店铺", Shop: "has", Image: "missing"}, []string{"B"}},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@@ -525,6 +527,32 @@ func TestListShopeeProducts_店铺图片三态及组合筛选(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListShopeeProducts_店铺名称LIKE特殊字符按原文匹配(t *testing.T) {
|
||||||
|
db := newTestDB(t)
|
||||||
|
for _, product := range []struct{ id, shop string }{
|
||||||
|
{"SPECIAL", "测试%店_铺!"},
|
||||||
|
{"PLAIN", "测试普通店铺"},
|
||||||
|
} {
|
||||||
|
seedShopeeProduct(t, db, product.id, product.id)
|
||||||
|
if _, err := db.Exec(`UPDATE shopee_products SET shopee_shop_name=? WHERE goods_id=?`, product.shop, product.id); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, keyword := range []string{"%", "_", "!", "%店_"} {
|
||||||
|
result, err := ListShopeeProducts(db, repository.ShopeeFilter{ShopName: keyword}, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if result.Total != 1 || len(result.Rows) != 1 || result.Rows[0].GoodsID != "SPECIAL" {
|
||||||
|
t.Fatalf("店铺搜索 %q 得到 %+v,总数 %d,期望只匹配 SPECIAL", keyword, result.Rows, result.Total)
|
||||||
|
}
|
||||||
|
if !result.IsFiltered {
|
||||||
|
t.Fatal("只使用店铺名称时 IsFiltered 应为 true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseShopeePresence_三态参数(t *testing.T) {
|
func TestParseShopeePresence_三态参数(t *testing.T) {
|
||||||
for _, value := range []string{"has", "missing"} {
|
for _, value := range []string{"has", "missing"} {
|
||||||
if got := ParseShopeePresence(value); got != value {
|
if got := ParseShopeePresence(value); got != value {
|
||||||
|
|||||||
@@ -112,6 +112,9 @@ body {
|
|||||||
用 flex 而不是 width:工具条是 flex 布局,width 会被 flex-grow 盖掉。
|
用 flex 而不是 width:工具条是 flex 布局,width 会被 flex-grow 盖掉。
|
||||||
min-width 由下面那条继承,窄屏时不会缩到没法输入。 */
|
min-width 由下面那条继承,窄屏时不会缩到没法输入。 */
|
||||||
.toolbar input[type="text"].search-narrow { flex: 0 0 30%; }
|
.toolbar input[type="text"].search-narrow { flex: 0 0 30%; }
|
||||||
|
/* 蝦皮工具条同时放商品和店铺两个搜索框,各占剩余工具条的约 20%。
|
||||||
|
min-width 继续由通用输入框规则提供,窄窗口时交给工具条自然换行。 */
|
||||||
|
.toolbar input[type="text"].shopee-search-field { flex: 0 1 20%; }
|
||||||
|
|
||||||
.toolbar input[type="text"] {
|
.toolbar input[type="text"] {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
都不进入 sessionStorage。链接原始 href 保持模块根地址,因此脚本失效时
|
都不进入 sessionStorage。链接原始 href 保持模块根地址,因此脚本失效时
|
||||||
仍可正常导航。 */
|
仍可正常导航。 */
|
||||||
var MODULE_QUERY_KEYS = {
|
var MODULE_QUERY_KEYS = {
|
||||||
"/shopee": ["goods_id", "status", "shop", "image", "page"],
|
"/shopee": ["goods_id", "shop_name", "status", "shop", "image", "page"],
|
||||||
"/pdd": ["q", "status", "page"],
|
"/pdd": ["q", "status", "page"],
|
||||||
"/syb": ["order_no", "shop", "stage", "page", "date_from", "date_to"],
|
"/syb": ["order_no", "shop", "stage", "page", "date_from", "date_to"],
|
||||||
"/tasks": ["type", "status", "creator", "q", "page"],
|
"/tasks": ["type", "status", "creator", "q", "page"],
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||||
<input type="hidden" name="shopee_goods_id" value="{{.GoodsID}}">
|
<input type="hidden" name="shopee_goods_id" value="{{.GoodsID}}">
|
||||||
<input type="hidden" name="list_goods_id" value="{{$.Keyword}}">
|
<input type="hidden" name="list_goods_id" value="{{$.Keyword}}">
|
||||||
|
<input type="hidden" name="list_shop_name" value="{{$.ShopNameKeyword}}">
|
||||||
<input type="hidden" name="status" value="{{$.StatusFilter}}">
|
<input type="hidden" name="status" value="{{$.StatusFilter}}">
|
||||||
<input type="hidden" name="shop" value="{{$.ShopFilter}}">
|
<input type="hidden" name="shop" value="{{$.ShopFilter}}">
|
||||||
<input type="hidden" name="image" value="{{$.ImageFilter}}">
|
<input type="hidden" name="image" value="{{$.ImageFilter}}">
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||||
<input type="hidden" name="shopee_goods_id" value="{{.GoodsID}}">
|
<input type="hidden" name="shopee_goods_id" value="{{.GoodsID}}">
|
||||||
<input type="hidden" name="list_goods_id" value="{{$.Keyword}}">
|
<input type="hidden" name="list_goods_id" value="{{$.Keyword}}">
|
||||||
|
<input type="hidden" name="list_shop_name" value="{{$.ShopNameKeyword}}">
|
||||||
<input type="hidden" name="status" value="{{$.StatusFilter}}">
|
<input type="hidden" name="status" value="{{$.StatusFilter}}">
|
||||||
<input type="hidden" name="shop" value="{{$.ShopFilter}}">
|
<input type="hidden" name="shop" value="{{$.ShopFilter}}">
|
||||||
<input type="hidden" name="image" value="{{$.ImageFilter}}">
|
<input type="hidden" name="image" value="{{$.ImageFilter}}">
|
||||||
|
|||||||
@@ -26,8 +26,10 @@
|
|||||||
<option value="{{.Value}}" {{if eq .Value $.ImageFilter}}selected{{end}}>{{.Text}}</option>
|
<option value="{{.Value}}" {{if eq .Value $.ImageFilter}}selected{{end}}>{{.Text}}</option>
|
||||||
{{end}}
|
{{end}}
|
||||||
</select>
|
</select>
|
||||||
<label for="q">商品 ID</label>
|
<label for="q">商品 ID / 名称</label>
|
||||||
<input id="q" type="text" name="goods_id" value="{{.Keyword}}" placeholder="商品 ID">
|
<input id="q" class="shopee-search-field" type="text" name="goods_id" value="{{.Keyword}}" placeholder="商品 ID / 名称">
|
||||||
|
<label for="shop-name">店铺名称</label>
|
||||||
|
<input id="shop-name" class="shopee-search-field" type="text" name="shop_name" value="{{.ShopNameKeyword}}" placeholder="店铺名称">
|
||||||
<button type="submit">搜索</button>
|
<button type="submit">搜索</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -35,6 +37,7 @@
|
|||||||
data-confirm-delete>
|
data-confirm-delete>
|
||||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||||
<input type="hidden" name="list_goods_id" value="{{.Keyword}}">
|
<input type="hidden" name="list_goods_id" value="{{.Keyword}}">
|
||||||
|
<input type="hidden" name="list_shop_name" value="{{.ShopNameKeyword}}">
|
||||||
<input type="hidden" name="status" value="{{.StatusFilter}}">
|
<input type="hidden" name="status" value="{{.StatusFilter}}">
|
||||||
<input type="hidden" name="shop" value="{{.ShopFilter}}">
|
<input type="hidden" name="shop" value="{{.ShopFilter}}">
|
||||||
<input type="hidden" name="image" value="{{.ImageFilter}}">
|
<input type="hidden" name="image" value="{{.ImageFilter}}">
|
||||||
|
|||||||
@@ -136,7 +136,7 @@
|
|||||||
### 4.1 工具条
|
### 4.1 工具条
|
||||||
|
|
||||||
```text
|
```text
|
||||||
[导入记录] 状态[全部▾] 店铺[全部▾] 图片[全部▾] 商品ID [________] [搜索] [删除]
|
[导入记录] 状态[全部▾] 店铺[全部▾] 图片[全部▾] 商品 ID / 名称 [____] 店铺名称 [____] [搜索] [删除]
|
||||||
```
|
```
|
||||||
|
|
||||||
- **导入记录**:管理员进入统一商品目录导入记录页;采购员无系统级审计入口。
|
- **导入记录**:管理员进入统一商品目录导入记录页;采购员无系统级审计入口。
|
||||||
@@ -158,6 +158,9 @@
|
|||||||
- `[必须]` 筛选与关键词搜索可以叠加,翻页时都要保留,见 §3.2。
|
- `[必须]` 筛选与关键词搜索可以叠加,翻页时都要保留,见 §3.2。
|
||||||
- **店铺筛选**(工单 #150):全部 / 有店铺 / 无店铺;按
|
- **店铺筛选**(工单 #150):全部 / 有店铺 / 无店铺;按
|
||||||
`shopee_shop_name` 去除首尾空白后的非空/空判断。
|
`shopee_shop_name` 去除首尾空白后的非空/空判断。
|
||||||
|
- **店铺名称搜索**(工单 #182):独立的 `shop_name` 包含匹配,和店铺三态筛选
|
||||||
|
同时保留;商品 ID / 名称与店铺名称两个输入框各使用 20% 弹性基础宽度,最小宽度
|
||||||
|
160px,空间不足时随工具条换行,不影响其他模块的搜索框宽度。
|
||||||
- **图片筛选**(工单 #150):全部 / 有图片 / 无图片;按 `image_url`
|
- **图片筛选**(工单 #150):全部 / 有图片 / 无图片;按 `image_url`
|
||||||
去除首尾空白后的非空/空判断。
|
去除首尾空白后的非空/空判断。
|
||||||
- `[必须]` 店铺、图片使用独立三态下拉框,不使用含义不清的复选框;两个条件可互相
|
- `[必须]` 店铺、图片使用独立三态下拉框,不使用含义不清的复选框;两个条件可互相
|
||||||
|
|||||||
Reference in New Issue
Block a user