Explorar o código

✨ feat(put/book): 书籍搜索

晓晓晓晓丶vv %!s(int64=4) %!d(string=hai) anos
pai
achega
8625ae8301
Modificáronse 5 ficheiros con 173 adicións e 10 borrados
  1. 19 1
      src/api/index.ts
  2. 6 3
      src/hooks/useFormLayout.ts
  3. 59 0
      src/hooks/utils/index.ts
  4. 4 0
      src/scss/index.scss
  5. 85 6
      src/views/put/put-book.tsx

+ 19 - 1
src/api/index.ts

@@ -3,7 +3,13 @@ import axios from "./config";
 
 // NOTE: 暂时使用ts-ignore忽略module not exported的问题
 // @ts-ignore
-import { IUser, IList, IDeliveryBook, IOfficialSimple } from "@/types/api";
+import {
+  IUser,
+  IList,
+  IDeliveryBook,
+  IOfficialSimple,
+  IBookSearchResult,
+} from "@/types/api";
 
 /**
  * 登录
@@ -36,3 +42,15 @@ export const getDeliveryBookList = (
 export const getOfficialSimpleAccount = (): AxiosPromise<IOfficialSimple[]> => {
   return axios("/simpleUserOfficialAccounts");
 };
+
+/**
+ * 通过书名搜索书籍
+ * @param key_word
+ * @param page
+ */
+export const getBooksByName = (
+  key_word: string,
+  page = 1
+): AxiosPromise<IList<IBookSearchResult>> => {
+  return axios("/searchBooks", { params: { key_word, page } });
+};

+ 6 - 3
src/hooks/useFormLayout.ts

@@ -1,9 +1,12 @@
-const useFormLayout = (label: number) => {
-  const rate = 4;
+const useFormLayout = (label: number = 6) => {
+  const total = 24;
+  const offset = 1;
+
+  const wrapperSpan = total - label - offset * 4;
 
   return {
     labelCol: { span: label },
-    wrapperCol: { span: label * rate },
+    wrapperCol: { span: wrapperSpan, offset },
   };
 };
 

+ 59 - 0
src/hooks/utils/index.ts

@@ -0,0 +1,59 @@
+import { EventFilter, FunctionArgs } from "@/types/hooks";
+
+export function createFilterWrapper<T extends FunctionArgs>(
+  filter: EventFilter,
+  fn: T
+) {
+  function wrapper(this: any, ...args: any[]) {
+    filter(() => fn.apply(this, args), { fn, args, thisArgs: this });
+  }
+
+  return (wrapper as any) as T;
+}
+
+const bypassFilter: EventFilter = (invoke) => invoke();
+
+export const throttleFilter = (ms: number, trailing = true) => {
+  if (ms <= 0) return bypassFilter;
+
+  let lastExecute = 0;
+  let timer: ReturnType<typeof setTimeout> | undefined;
+
+  const clear = () => {
+    if (timer) {
+      clearTimeout(timer);
+      timer = undefined;
+    }
+  };
+
+  const filter: EventFilter = (invoke) => {
+    const elapsed = Date.now() - lastExecute;
+
+    clear();
+
+    if (elapsed > ms) {
+      lastExecute = Date.now();
+      invoke();
+    } else if (trailing) {
+      timer = setTimeout(() => {
+        clear();
+        invoke();
+      }, ms);
+    }
+  };
+
+  return filter;
+};
+
+export const debounceFilter = (ms: number) => {
+  if (ms <= 0) return bypassFilter;
+
+  let timer: ReturnType<typeof setTimeout> | undefined;
+
+  const filter: EventFilter = (invoke) => {
+    if (timer) clearTimeout(timer);
+    timer = setTimeout(invoke, ms);
+  };
+
+  return filter;
+};

+ 4 - 0
src/scss/index.scss

@@ -89,3 +89,7 @@ body {
     margin-right: 5px;
   }
 }
+
+.operator-bar {
+  margin-bottom: 15px;
+}

+ 85 - 6
src/views/put/put-book.tsx

@@ -3,9 +3,14 @@ import { defineComponent, reactive, ref } from "vue";
 import ToolBar from "@/components/tool-bar/index.vue";
 
 import usePagination from "@/hooks/usePagination";
-import { getDeliveryBookList } from "@/api";
+import useFormLayout from "@/hooks/useFormLayout";
+import useDebounceFn from "@/hooks/useDebounceFn";
+
+import { getDeliveryBookList, getBooksByName } from "@/api";
 import { TableColumnOfPutBooks } from "../_pageOptions/table-put";
-import { IDeliveryBook } from "@/types/api";
+import { IBookSearchResult, IDeliveryBook } from "@/types/api";
+
+// TODO 结构待优化
 
 const PutBooks = defineComponent({
   components: {
@@ -14,6 +19,8 @@ const PutBooks = defineComponent({
   setup() {
     let { loading, meta, tablePageOptions } = usePagination();
 
+    const { labelCol, wrapperCol } = useFormLayout();
+
     const state = reactive({
       inSearching: false,
       open: false,
@@ -21,6 +28,13 @@ const PutBooks = defineComponent({
       columns: TableColumnOfPutBooks,
     });
 
+    const addFormState = reactive({
+      official_id: 0,
+      book: ref<Partial<IBookSearchResult>>({}),
+      platforms: 0,
+      books: ref<IBookSearchResult[]>([]),
+    });
+
     const onSearch = async (fields: Record<string, string>) => {
       try {
         const { official_name, book_name } = fields;
@@ -38,11 +52,70 @@ const PutBooks = defineComponent({
       }
     };
 
-    // 弹窗
-    const onOpenModal = () => {
+    // 添加书籍弹窗
+    const renderAddForm = () => {
+      const onBookSearch = useDebounceFn(async (keywords: string) => {
+        if (!keywords) return;
+        const { data } = await getBooksByName(keywords);
+        addFormState.books.push(...data.list);
+      }, 500);
+
+      const onBookCheck = (value: any, options: any) => {
+        addFormState.book = options.key;
+      };
+
+      const ok = () => {
+        console.log("ok");
+      };
+
+      const renderSearchBooksList = () => {
+        return addFormState.books.map((book) => (
+          <a-select-option value={book.bid} key={book}>
+            {book.name}
+          </a-select-option>
+        ));
+      };
+
       return (
-        <a-modal v-model={[state.open, "visible"]}>
-          <p>123</p>
+        <a-modal
+          title="投放书籍添加"
+          width="400px"
+          v-model={[state.open, "visible"]}
+          onOk={ok}
+        >
+          <a-form
+            model={addFormState}
+            labelCol={labelCol}
+            wrapperCol={wrapperCol}
+          >
+            <a-form-item label="公众号名称">
+              <a-select v-model={[addFormState.official_id, "value"]}>
+                <a-select-option value="1">1</a-select-option>
+                <a-select-option value="2">2</a-select-option>
+              </a-select>
+            </a-form-item>
+            <a-form-item label="书籍">
+              <a-select
+                show-search
+                placeholder="请输入要搜索的书名"
+                default-active-first-option={false}
+                filter-option={false}
+                show-arrow={false}
+                not-found-content="暂无数据"
+                value={addFormState.book.bid}
+                onSearch={onBookSearch}
+                onChange={onBookCheck}
+              >
+                {renderSearchBooksList()}
+              </a-select>
+            </a-form-item>
+            <a-form-item label="流量平台">
+              <a-select v-model={[addFormState.platforms, "value"]}>
+                <a-select-option value="1">1</a-select-option>
+                <a-select-option value="2">2</a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-form>
         </a-modal>
       );
     };
@@ -55,6 +128,11 @@ const PutBooks = defineComponent({
           v-model={[state.inSearching, "loading"]}
           onConfirm={onSearch}
         />
+        <div class="operator-bar">
+          <a-button type="primary" onClick={() => (state.open = true)}>
+            添加
+          </a-button>
+        </div>
         <a-table
           row-key="id"
           pagination={tablePageOptions}
@@ -62,6 +140,7 @@ const PutBooks = defineComponent({
           columns={state.columns}
           data-source={state.list}
         ></a-table>
+        {renderAddForm()}
       </div>
     );
   },