ABAP 网页端 通用配置表维护程序

实现效果:

源码实现:

复制代码
CLASS zcl_ztest01_web_maint DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES if_http_extension.

  PRIVATE SECTION.
    TYPES:
      BEGIN OF ty_field,
        name      TYPE fieldname,
        json_name TYPE string,
        label     TYPE string,
        is_key    TYPE abap_bool,
        readonly  TYPE abap_bool,
      END OF ty_field.

    TYPES ty_field_list TYPE STANDARD TABLE OF ty_field WITH EMPTY KEY.

*    CONSTANTS maintained_table TYPE tabname VALUE 'ZTEST01'.
    CONSTANTS maintained_table TYPE tabname VALUE 'ZTEST01'.
    CONSTANTS content_type_html TYPE string VALUE 'text/html; charset=utf-8'.
    CONSTANTS content_type_json TYPE string VALUE 'application/json; charset=utf-8'.
    CONSTANTS max_rows TYPE i VALUE 1000.
    CONSTANTS max_response_rows TYPE i VALUE 200.

    METHODS handle_page
      IMPORTING
        server TYPE REF TO if_http_server.

    METHODS handle_list
      IMPORTING
        server TYPE REF TO if_http_server.

    METHODS handle_save
      IMPORTING
        server TYPE REF TO if_http_server.

    METHODS handle_delete
      IMPORTING
        server TYPE REF TO if_http_server.

    METHODS get_fields
      RETURNING
        VALUE(result) TYPE ty_field_list.

    METHODS get_request_values
      IMPORTING
        server        TYPE REF TO if_http_server
        fields        TYPE ty_field_list
      RETURNING
        VALUE(result) TYPE string_table.

    METHODS append_json_fields
      IMPORTING
        fields TYPE ty_field_list
      CHANGING
        json   TYPE string.

    METHODS append_json_rows
      IMPORTING
        fields  TYPE ty_field_list
        keyword TYPE string
      CHANGING
        json    TYPE string.

    METHODS append_json_row
      IMPORTING
        fields TYPE ty_field_list
        row    TYPE any
      CHANGING
        json   TYPE string.

    METHODS row_matches_keyword
      IMPORTING
        fields        TYPE ty_field_list
        row           TYPE any
        keyword       TYPE string
      RETURNING
        VALUE(result) TYPE abap_bool.

    METHODS write_json
      IMPORTING
        server TYPE REF TO if_http_server
        json   TYPE string.

    METHODS write_error
      IMPORTING
        server      TYPE REF TO if_http_server
        status_code TYPE i
        reason      TYPE string
        message     TYPE string.

    METHODS escape_json_value
      IMPORTING
        value         TYPE string
      RETURNING
        VALUE(result) TYPE string.
ENDCLASS.



CLASS ZCL_ZTEST01_WEB_MAINT IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ZTEST01_WEB_MAINT->IF_HTTP_EXTENSION~HANDLE_REQUEST
* +-------------------------------------------------------------------------------------------------+
* | [--->] SERVER                         TYPE REF TO IF_HTTP_SERVER
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD if_http_extension~handle_request.
    DATA(action) = server->request->get_form_field( `action` ).

    CASE action.
      WHEN ``.
        handle_page( server ).
      WHEN `list`.
        handle_list( server ).
      WHEN `save`.
        handle_save( server ).
      WHEN `delete`.
        handle_delete( server ).
      WHEN OTHERS.
        write_error(
          server      = server
          status_code = 404
          reason      = `Not Found`
          message     = `Unsupported action` ).
    ENDCASE.
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->HANDLE_PAGE
* +-------------------------------------------------------------------------------------------------+
* | [--->] SERVER                         TYPE REF TO IF_HTTP_SERVER
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD handle_page.
    DATA html_lines TYPE STANDARD TABLE OF string WITH EMPTY KEY.

    INSERT `<!doctype html>` INTO TABLE html_lines.
    INSERT `<html lang="zh-CN">` INTO TABLE html_lines.
    INSERT `<head>` INTO TABLE html_lines.
    INSERT `  <meta charset="utf-8">` INTO TABLE html_lines.
    INSERT `  <meta name="viewport" content="width=device-width, initial-scale=1">` INTO TABLE html_lines.
    INSERT `  <title>通用表维护</title>` INTO TABLE html_lines.
    INSERT `  <style>` INTO TABLE html_lines.
    INSERT `    :root { --color_primary: #0066b3; --color_text: #1f2937; --color_muted: #667085; --color_border: #d7deea; --color_bg: #f4f7fb; --color_panel: #ffffff; --color_danger: #c9352b; --color_success: #18794e; }` INTO TABLE html_lines.
    INSERT `    * { box-sizing: border-box; }` INTO TABLE html_lines.
    INSERT `    body { margin: 0; font-family: "Segoe UI", "Microsoft YaHei", sans-serif; color: var(--color_text); background: linear-gradient(180deg, #eef4fb 0, var(--color_bg) 220px); }` INTO TABLE html_lines.
    INSERT `    .app { min-height: 100vh; padding: 24px; }` INTO TABLE html_lines.
    INSERT `    .shell { max-width: 1180px; margin: 0 auto; }` INTO TABLE html_lines.
    INSERT `    .header { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 18px; }` INTO TABLE html_lines.
    INSERT `    .title { margin: 0; font-size: 24px; font-weight: 650; letter-spacing: 0; }` INTO TABLE html_lines.
    INSERT `    .subtitle { margin: 6px 0 0; color: var(--color_muted); font-size: 13px; }` INTO TABLE html_lines.
    INSERT `    .panel { background: var(--color_panel); border: 1px solid var(--color_border); border-radius: 6px; box-shadow: 0 8px 24px rgba(16, 24, 40, .08); }` INTO TABLE html_lines.
    INSERT `    .toolbar { display: flex; flex-wrap: wrap; gap: 8px; padding: 14px; border-bottom: 1px solid var(--color_border); }` INTO TABLE html_lines.
    INSERT `    .input { height: 34px; min-width: 220px; padding: 0 10px; border: 1px solid var(--color_border); border-radius: 4px; background: #fff; color: var(--color_text); font-size: 14px; outline: none; }` INTO TABLE html_lines.
    INSERT `    .input:focus { border-color: var(--color_primary); box-shadow: 0 0 0 3px rgba(0, 102, 179, .12); }` INTO TABLE html_lines.
    INSERT `    .button { height: 34px; padding: 0 14px; border: 1px solid var(--color_primary); border-radius: 4px; color: #fff; background: var(--color_primary); font-size: 14px; cursor: pointer; }` INTO TABLE html_lines.
    INSERT `    .button:disabled { cursor: not-allowed; opacity: .55; }` INTO TABLE html_lines.
    INSERT `    .button_secondary { color: var(--color_primary); background: #fff; }` INTO TABLE html_lines.
    INSERT `    .button_danger { border-color: var(--color_danger); color: var(--color_danger); background: #fff; }` INTO TABLE html_lines.
    INSERT `    .message { min-height: 32px; padding: 8px 14px; color: var(--color_muted); font-size: 13px; }` INTO TABLE html_lines.
    INSERT `    .message_error { color: var(--color_danger); }` INTO TABLE html_lines.
    INSERT `    .message_success { color: var(--color_success); }` INTO TABLE html_lines.
    INSERT `    .table_wrap { overflow-x: auto; }` INTO TABLE html_lines.
    INSERT `    table { width: 100%; border-collapse: collapse; }` INTO TABLE html_lines.
    INSERT `    th, td { padding: 11px 12px; border-top: 1px solid var(--color_border); text-align: left; font-size: 14px; white-space: nowrap; }` INTO TABLE html_lines.
    INSERT `    th { background: #f0f4f9; font-weight: 600; color: #344054; }` INTO TABLE html_lines.
    INSERT `    td.empty { padding: 32px 12px; text-align: center; color: var(--color_muted); }` INTO TABLE html_lines.
    INSERT `    .actions { display: flex; gap: 6px; }` INTO TABLE html_lines.
    INSERT `    .dialog_mask { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; padding: 16px; background: rgba(15, 23, 42, .38); }` INTO TABLE html_lines.
    INSERT `    .dialog { width: min(560px, 100%); max-height: min(720px, 92vh); overflow: auto; padding: 18px; background: #fff; border-radius: 6px; box-shadow: 0 18px 45px rgba(15, 23, 42, .22); }` INTO TABLE html_lines.
    INSERT `    .dialog h2 { margin: 0 0 14px; font-size: 18px; }` INTO TABLE html_lines.
    INSERT `    .form_item { margin-bottom: 12px; }` INTO TABLE html_lines.
    INSERT `    .form_item label { display: block; margin-bottom: 5px; color: #344054; font-size: 13px; }` INTO TABLE html_lines.
    INSERT `    .form_item .input { width: 100%; }` INTO TABLE html_lines.
    INSERT `    .dialog_footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }` INTO TABLE html_lines.
    INSERT `    @media (max-width: 720px) { .app { padding: 14px; } .header { align-items: flex-start; flex-direction: column; } .input { width: 100%; } .button { flex: 1; } }` INTO TABLE html_lines.
    INSERT `  </style>` INTO TABLE html_lines.
    INSERT `</head>` INTO TABLE html_lines.
    INSERT `<body>` INTO TABLE html_lines.
    INSERT `  <main class="app">` INTO TABLE html_lines.
    INSERT `    <section class="shell">` INTO TABLE html_lines.
    INSERT `      <div class="header"><div><h1 id="pageTitle" class="title">通用表维护</h1><p id="pageSubtitle" class="subtitle"></p></div></div>` INTO TABLE html_lines.
    INSERT `      <section class="panel">` INTO TABLE html_lines.
    INSERT `        <div class="toolbar">` INTO TABLE html_lines.
    INSERT `          <input id="keyword" class="input" placeholder="输入关键字查询">` INTO TABLE html_lines.
    INSERT `          <button id="queryButton" class="button button_secondary" type="button">查询</button>` INTO TABLE html_lines.
    INSERT `          <button id="newButton" class="button" type="button">新增</button>` INTO TABLE html_lines.
    INSERT `        </div>` INTO TABLE html_lines.
    INSERT `        <div id="message" class="message"></div>` INTO TABLE html_lines.
    INSERT `        <div class="table_wrap"><table><thead><tr id="tableHead"></tr></thead><tbody id="tableBody"></tbody></table></div>` INTO TABLE html_lines.
    INSERT `      </section>` INTO TABLE html_lines.
    INSERT `    </section>` INTO TABLE html_lines.
    INSERT `  </main>` INTO TABLE html_lines.
    INSERT `  <div id="dialogMask" class="dialog_mask" role="dialog" aria-modal="true">` INTO TABLE html_lines.
    INSERT `    <div class="dialog">` INTO TABLE html_lines.
    INSERT `      <h2>维护记录</h2>` INTO TABLE html_lines.
    INSERT `      <div id="formBody"></div>` INTO TABLE html_lines.
    INSERT `      <div class="dialog_footer">` INTO TABLE html_lines.
    INSERT `        <button id="cancelButton" class="button button_secondary" type="button">取消</button>` INTO TABLE html_lines.
    INSERT `        <button id="saveButton" class="button" type="button">保存</button>` INTO TABLE html_lines.
    INSERT `      </div>` INTO TABLE html_lines.
    INSERT `    </div>` INTO TABLE html_lines.
    INSERT `  </div>` INTO TABLE html_lines.
    INSERT `  <script>` INTO TABLE html_lines.
    INSERT `    const buildApiUrl = (action, params) => {` INTO TABLE html_lines.
    INSERT `      const url = new URL(window.location.href);` INTO TABLE html_lines.
    INSERT `      url.searchParams.set("action", action);` INTO TABLE html_lines.
    INSERT `      Object.entries(params || {}).forEach(([key, value]) => url.searchParams.set(key, value));` INTO TABLE html_lines.
    INSERT `      return url.toString();` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const state = { fields: [], rows: [], current: null };` INTO TABLE html_lines.
    INSERT `    const getElement = (id) => document.getElementById(id);` INTO TABLE html_lines.
    INSERT `    const setMessage = (text, type) => {` INTO TABLE html_lines.
    INSERT `      const element = getElement("message");` INTO TABLE html_lines.
    INSERT `      element.textContent = text || "";` INTO TABLE html_lines.
    INSERT `      element.className = "message" + (type ? " message_" + type : "");` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const setBusy = (busy) => {` INTO TABLE html_lines.
    INSERT `      getElement("queryButton").disabled = busy;` INTO TABLE html_lines.
    INSERT `      getElement("newButton").disabled = busy;` INTO TABLE html_lines.
    INSERT `      getElement("saveButton").disabled = busy;` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const createCell = (text) => {` INTO TABLE html_lines.
    INSERT `      const cell = document.createElement("td");` INTO TABLE html_lines.
    INSERT `      cell.textContent = text || "";` INTO TABLE html_lines.
    INSERT `      return cell;` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const requestJson = async (url, options) => {` INTO TABLE html_lines.
    INSERT `      const response = await fetch(url, options || {});` INTO TABLE html_lines.
    INSERT `      const text = await response.text();` INTO TABLE html_lines.
    INSERT `      let data = {};` INTO TABLE html_lines.
    INSERT `      if (text) { try { data = JSON.parse(text); } catch (error) { data = { message: text }; } }` INTO TABLE html_lines.
    INSERT `      if (!response.ok) { throw new Error(data.message || "请求失败"); }` INTO TABLE html_lines.
    INSERT `      return data;` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const renderHeader = () => {` INTO TABLE html_lines.
    INSERT `      const head = getElement("tableHead");` INTO TABLE html_lines.
    INSERT `      head.innerHTML = "";` INTO TABLE html_lines.
    INSERT `      state.fields.forEach((field) => {` INTO TABLE html_lines.
    INSERT `        const cell = document.createElement("th");` INTO TABLE html_lines.
    INSERT `        cell.textContent = field.label || field.name;` INTO TABLE html_lines.
    INSERT `        head.appendChild(cell);` INTO TABLE html_lines.
    INSERT `      });` INTO TABLE html_lines.
    INSERT `      const actionCell = document.createElement("th");` INTO TABLE html_lines.
    INSERT `      actionCell.textContent = "操作";` INTO TABLE html_lines.
    INSERT `      head.appendChild(actionCell);` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const renderRows = () => {` INTO TABLE html_lines.
    INSERT `      const body = getElement("tableBody");` INTO TABLE html_lines.
    INSERT `      body.innerHTML = "";` INTO TABLE html_lines.
    INSERT `      if (!state.rows.length) {` INTO TABLE html_lines.
    INSERT `        const row = document.createElement("tr");` INTO TABLE html_lines.
    INSERT `        const cell = document.createElement("td");` INTO TABLE html_lines.
    INSERT `        cell.className = "empty";` INTO TABLE html_lines.
    INSERT `        cell.colSpan = state.fields.length + 1;` INTO TABLE html_lines.
    INSERT `        cell.textContent = "暂无数据";` INTO TABLE html_lines.
    INSERT `        row.appendChild(cell);` INTO TABLE html_lines.
    INSERT `        body.appendChild(row);` INTO TABLE html_lines.
    INSERT `        return;` INTO TABLE html_lines.
    INSERT `      }` INTO TABLE html_lines.
    INSERT `      state.rows.forEach((item) => {` INTO TABLE html_lines.
    INSERT `        const row = document.createElement("tr");` INTO TABLE html_lines.
    INSERT `        state.fields.forEach((field) => row.appendChild(createCell(item[field.json_name])));` INTO TABLE html_lines.
    INSERT `        const actionCell = document.createElement("td");` INTO TABLE html_lines.
    INSERT `        const actions = document.createElement("div");` INTO TABLE html_lines.
    INSERT `        actions.className = "actions";` INTO TABLE html_lines.
    INSERT `        const editButton = document.createElement("button");` INTO TABLE html_lines.
    INSERT `        editButton.className = "button button_secondary";` INTO TABLE html_lines.
    INSERT `        editButton.type = "button";` INTO TABLE html_lines.
    INSERT `        editButton.textContent = "编辑";` INTO TABLE html_lines.
    INSERT `        editButton.addEventListener("click", () => openDialog(item));` INTO TABLE html_lines.
    INSERT `        const deleteButton = document.createElement("button");` INTO TABLE html_lines.
    INSERT `        deleteButton.className = "button button_danger";` INTO TABLE html_lines.
    INSERT `        deleteButton.type = "button";` INTO TABLE html_lines.
    INSERT `        deleteButton.textContent = "删除";` INTO TABLE html_lines.
    INSERT `        deleteButton.addEventListener("click", () => deleteRow(item));` INTO TABLE html_lines.
    INSERT `        actions.appendChild(editButton);` INTO TABLE html_lines.
    INSERT `        actions.appendChild(deleteButton);` INTO TABLE html_lines.
    INSERT `        actionCell.appendChild(actions);` INTO TABLE html_lines.
    INSERT `        row.appendChild(actionCell);` INTO TABLE html_lines.
    INSERT `        body.appendChild(row);` INTO TABLE html_lines.
    INSERT `      });` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const renderForm = (row) => {` INTO TABLE html_lines.
    INSERT `      const formBody = getElement("formBody");` INTO TABLE html_lines.
    INSERT `      formBody.innerHTML = "";` INTO TABLE html_lines.
    INSERT `      state.fields.forEach((field) => {` INTO TABLE html_lines.
    INSERT `        const wrapper = document.createElement("div");` INTO TABLE html_lines.
    INSERT `        wrapper.className = "form_item";` INTO TABLE html_lines.
    INSERT `        const label = document.createElement("label");` INTO TABLE html_lines.
    INSERT `        label.textContent = field.label || field.name;` INTO TABLE html_lines.
    INSERT `        label.setAttribute("for", "field_" + field.name);` INTO TABLE html_lines.
    INSERT `        const input = document.createElement("input");` INTO TABLE html_lines.
    INSERT `        input.id = "field_" + field.name;` INTO TABLE html_lines.
    INSERT `        input.className = "input";` INTO TABLE html_lines.
    INSERT `        input.dataset.field = field.name;` INTO TABLE html_lines.
    INSERT `        input.value = row && row[field.json_name] ? row[field.json_name] : "";` INTO TABLE html_lines.
    INSERT `        if (field.readonly && row) { input.readOnly = true; }` INTO TABLE html_lines.
    INSERT `        wrapper.appendChild(label);` INTO TABLE html_lines.
    INSERT `        wrapper.appendChild(input);` INTO TABLE html_lines.
    INSERT `        formBody.appendChild(wrapper);` INTO TABLE html_lines.
    INSERT `      });` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const openDialog = (row) => {` INTO TABLE html_lines.
    INSERT `      state.current = row || null;` INTO TABLE html_lines.
    INSERT `      renderForm(row);` INTO TABLE html_lines.
    INSERT `      getElement("dialogMask").style.display = "flex";` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const closeDialog = () => { getElement("dialogMask").style.display = "none"; };` INTO TABLE html_lines.
    INSERT `    const collectPayload = () => {` INTO TABLE html_lines.
    INSERT `      const payload = {};` INTO TABLE html_lines.
    INSERT `      document.querySelectorAll("[data-field]").forEach((input) => { payload[input.dataset.field] = input.value.trim(); });` INTO TABLE html_lines.
    INSERT `      return payload;` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const loadRows = async () => {` INTO TABLE html_lines.
    INSERT `      setBusy(true);` INTO TABLE html_lines.
    INSERT `      setMessage("正在查询...");` INTO TABLE html_lines.
    INSERT `      try {` INTO TABLE html_lines.
    INSERT `        const keyword = getElement("keyword").value.trim();` INTO TABLE html_lines.
    INSERT `        const data = await requestJson(buildApiUrl("list", { keyword }));` INTO TABLE html_lines.
    INSERT `        state.fields = Array.isArray(data.fields) ? data.fields : [];` INTO TABLE html_lines.
    INSERT `        state.rows = Array.isArray(data.rows) ? data.rows : [];` INTO TABLE html_lines.
    INSERT `        getElement("pageTitle").textContent = data.table + " 表维护";` INTO TABLE html_lines.
    INSERT `        getElement("pageSubtitle").textContent = "表名由 ABAP 常量 maintained_table 控制";` INTO TABLE html_lines.
    INSERT `        renderHeader();` INTO TABLE html_lines.
    INSERT `        renderRows();` INTO TABLE html_lines.
    INSERT `        setMessage("查询完成,共 " + state.rows.length + " 条", "success");` INTO TABLE html_lines.
    INSERT `      } catch (error) { setMessage(error.message, "error"); } finally { setBusy(false); }` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const saveRow = async () => {` INTO TABLE html_lines.
    INSERT `      const payload = collectPayload();` INTO TABLE html_lines.
    INSERT `      setBusy(true);` INTO TABLE html_lines.
    INSERT `      try {` INTO TABLE html_lines.
    INSERT `        await requestJson(buildApiUrl("save"), {` INTO TABLE html_lines.
    INSERT `          method: "POST",` INTO TABLE html_lines.
    INSERT `          headers: { "Content-Type": "application/json" },` INTO TABLE html_lines.
    INSERT `          body: JSON.stringify(payload)` INTO TABLE html_lines.
    INSERT `        });` INTO TABLE html_lines.
    INSERT `        closeDialog();` INTO TABLE html_lines.
    INSERT `        await loadRows();` INTO TABLE html_lines.
    INSERT `      } catch (error) { setMessage(error.message, "error"); } finally { setBusy(false); }` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    const deleteRow = async (row) => {` INTO TABLE html_lines.
    INSERT `      const payload = {};` INTO TABLE html_lines.
    INSERT `      state.fields.filter((field) => field.is_key).forEach((field) => { payload[field.name] = row[field.json_name]; });` INTO TABLE html_lines.
    INSERT `      if (!confirm("确认删除当前记录?")) { return; }` INTO TABLE html_lines.
    INSERT `      setBusy(true);` INTO TABLE html_lines.
    INSERT `      try {` INTO TABLE html_lines.
    INSERT `        await requestJson(buildApiUrl("delete"), {` INTO TABLE html_lines.
    INSERT `          method: "POST",` INTO TABLE html_lines.
    INSERT `          headers: { "Content-Type": "application/json" },` INTO TABLE html_lines.
    INSERT `          body: JSON.stringify(payload)` INTO TABLE html_lines.
    INSERT `        });` INTO TABLE html_lines.
    INSERT `        await loadRows();` INTO TABLE html_lines.
    INSERT `      } catch (error) { setMessage(error.message, "error"); } finally { setBusy(false); }` INTO TABLE html_lines.
    INSERT `    };` INTO TABLE html_lines.
    INSERT `    getElement("queryButton").addEventListener("click", loadRows);` INTO TABLE html_lines.
    INSERT `    getElement("newButton").addEventListener("click", () => openDialog(null));` INTO TABLE html_lines.
    INSERT `    getElement("cancelButton").addEventListener("click", closeDialog);` INTO TABLE html_lines.
    INSERT `    getElement("saveButton").addEventListener("click", saveRow);` INTO TABLE html_lines.
    INSERT `    getElement("keyword").addEventListener("keydown", (event) => { if (event.key === "Enter") { loadRows(); } });` INTO TABLE html_lines.
    INSERT `    loadRows();` INTO TABLE html_lines.
    INSERT `  </script>` INTO TABLE html_lines.
    INSERT `</body>` INTO TABLE html_lines.
    INSERT `</html>` INTO TABLE html_lines.

    DATA(html) = concat_lines_of(
      table = html_lines
      sep   = cl_abap_char_utilities=>newline ).

    server->response->set_content_type( content_type_html ).
    server->response->set_cdata( html ).
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->HANDLE_LIST
* +-------------------------------------------------------------------------------------------------+
* | [--->] SERVER                         TYPE REF TO IF_HTTP_SERVER
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD handle_list.
    DATA fields TYPE ty_field_list.
    DATA keyword TYPE string.
    DATA json TYPE string.

    fields = get_fields( ).
    keyword = server->request->get_form_field( `keyword` ).

    json = `{"table":"` && me->escape_json_value( CONV string( maintained_table ) ) && `","fields":`.
    me->append_json_fields(
      EXPORTING
        fields = fields
      CHANGING
        json   = json ).
    json = json && `,"rows":`.
    me->append_json_rows(
      EXPORTING
        fields  = fields
        keyword = keyword
      CHANGING
        json    = json ).
    json = json && `}`.

    write_json(
      server = server
      json   = json ).
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->HANDLE_SAVE
* +-------------------------------------------------------------------------------------------------+
* | [--->] SERVER                         TYPE REF TO IF_HTTP_SERVER
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD handle_save.
    DATA method TYPE string.
    DATA fields TYPE ty_field_list.
    DATA values TYPE string_table.
    DATA row_ref TYPE REF TO data.

    FIELD-SYMBOLS <row> TYPE any.
    FIELD-SYMBOLS <component> TYPE any.

    method = server->request->get_method( ).
    IF method <> `POST`.
      me->write_error( server = server status_code = 405 reason = `Method Not Allowed` message = `Only POST is supported` ).
      RETURN.
    ENDIF.

    fields = get_fields( ).
    values = get_request_values( server = server fields = fields ).

    CREATE DATA row_ref TYPE (maintained_table).
    ASSIGN row_ref->* TO <row>.

    LOOP AT fields INTO DATA(field).
      READ TABLE values INTO DATA(value) INDEX sy-tabix.
      ASSIGN COMPONENT field-name OF STRUCTURE <row> TO <component>.
      IF sy-subrc = 0.
        <component> = value.
      ENDIF.
    ENDLOOP.

    MODIFY (maintained_table) FROM <row>.
    IF sy-subrc <> 0.
      ROLLBACK WORK.
      me->write_error( server = server status_code = 500 reason = `Save Failed` message = `Unable to save row` ).
      RETURN.
    ENDIF.

    COMMIT WORK.
    me->write_json( server = server json = `{"status":"ok"}` ).
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->HANDLE_DELETE
* +-------------------------------------------------------------------------------------------------+
* | [--->] SERVER                         TYPE REF TO IF_HTTP_SERVER
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD handle_delete.
    DATA method TYPE string.
    DATA fields TYPE ty_field_list.
    DATA values TYPE string_table.
    DATA row_ref TYPE REF TO data.
    DATA has_key_value TYPE abap_bool.

    FIELD-SYMBOLS <row> TYPE any.
    FIELD-SYMBOLS <component> TYPE any.

    method = server->request->get_method( ).
    IF method <> `POST`.
      me->write_error( server = server status_code = 405 reason = `Method Not Allowed` message = `Only POST is supported` ).
      RETURN.
    ENDIF.

    fields = get_fields( ).
    values = get_request_values( server = server fields = fields ).

    CREATE DATA row_ref TYPE (maintained_table).
    ASSIGN row_ref->* TO <row>.

    LOOP AT fields INTO DATA(field) WHERE is_key = abap_true.
      READ TABLE values INTO DATA(value) INDEX sy-tabix.
      IF value IS INITIAL.
        me->write_error( server = server status_code = 400 reason = `Bad Request` message = `Key field is required` ).
        RETURN.
      ENDIF.

      ASSIGN COMPONENT field-name OF STRUCTURE <row> TO <component>.
      IF sy-subrc = 0.
        <component> = value.
        has_key_value = abap_true.
      ENDIF.
    ENDLOOP.

    IF has_key_value = abap_false.
      me->write_error( server = server status_code = 400 reason = `Bad Request` message = `No key field found` ).
      RETURN.
    ENDIF.

    DELETE (maintained_table) FROM <row>.
    IF sy-subrc <> 0.
      ROLLBACK WORK.
      me->write_error( server = server status_code = 404 reason = `Not Found` message = `Row was not found` ).
      RETURN.
    ENDIF.

    COMMIT WORK.
    me->write_json( server = server json = `{"status":"ok"}` ).
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->GET_FIELDS
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RESULT                         TYPE        TY_FIELD_LIST
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD get_fields.
    SELECT d~fieldname,
           d~keyflag,
           d~position,
           coalesce( t_lang~ddtext, t_en~ddtext, d~fieldname ) AS label
      FROM dd03l AS d
      LEFT OUTER JOIN dd04t AS t_lang
        ON t_lang~rollname = d~rollname
       AND t_lang~ddlanguage = @sy-langu
       AND t_lang~as4local = 'A'
      LEFT OUTER JOIN dd04t AS t_en
        ON t_en~rollname = d~rollname
       AND t_en~ddlanguage = 'E'
       AND t_en~as4local = 'A'
      WHERE d~tabname = @maintained_table
        AND d~as4local = 'A'
        AND d~fieldname <> 'MANDT'
        AND d~fieldname <> '.INCLUDE'
      ORDER BY d~position
      INTO TABLE @DATA(ddic_fields).

    LOOP AT ddic_fields INTO DATA(ddic_field).
      INSERT VALUE ty_field(
        name     = ddic_field-fieldname
        json_name = to_lower( ddic_field-fieldname )
        label    = ddic_field-label
        is_key   = xsdbool( ddic_field-keyflag = 'X' )
        readonly = xsdbool( ddic_field-keyflag = 'X' ) ) INTO TABLE result.
    ENDLOOP.
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->GET_REQUEST_VALUES
* +-------------------------------------------------------------------------------------------------+
* | [--->] SERVER                         TYPE REF TO IF_HTTP_SERVER
* | [--->] FIELDS                         TYPE        TY_FIELD_LIST
* | [<-()] RESULT                         TYPE        STRING_TABLE
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD get_request_values.
    DATA body TYPE string.
    DATA name_with_quotes TYPE string.
    DATA search_name TYPE string.
    DATA offset TYPE i.
    DATA value_start TYPE i.
    DATA value_end TYPE i.
    DATA value TYPE string.

    body = server->request->get_cdata( ).

    LOOP AT fields INTO DATA(field).
      name_with_quotes = `"` && field-name && `"`.
      FIND FIRST OCCURRENCE OF name_with_quotes IN body MATCH OFFSET offset.
      IF sy-subrc <> 0.
        INSERT `` INTO TABLE result.
        CONTINUE.
      ENDIF.

      search_name = body+offset.
      FIND FIRST OCCURRENCE OF `:` IN search_name MATCH OFFSET offset.
      IF sy-subrc <> 0.
        INSERT `` INTO TABLE result.
        CONTINUE.
      ENDIF.

      value_start = offset + 1.
      search_name = search_name+value_start.
      CONDENSE search_name NO-GAPS.
      IF search_name(1) = `"`.
        search_name = search_name+1.
        FIND FIRST OCCURRENCE OF `"` IN search_name MATCH OFFSET value_end.
        IF sy-subrc = 0.
          value = search_name(value_end).
        ELSE.
          value = ``.
        ENDIF.
      ELSE.
        FIND FIRST OCCURRENCE OF `,` IN search_name MATCH OFFSET value_end.
        IF sy-subrc <> 0.
          FIND FIRST OCCURRENCE OF `}` IN search_name MATCH OFFSET value_end.
        ENDIF.
        IF sy-subrc = 0.
          value = search_name(value_end).
        ELSE.
          value = search_name.
        ENDIF.
      ENDIF.

      INSERT value INTO TABLE result.
      CLEAR value.
    ENDLOOP.
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->APPEND_JSON_FIELDS
* +-------------------------------------------------------------------------------------------------+
* | [--->] FIELDS                         TYPE        TY_FIELD_LIST
* | [<-->] JSON                           TYPE        STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD append_json_fields.
    json = json && `[`.
    LOOP AT fields INTO DATA(field).
      IF sy-tabix > 1.
        json = json && `,`.
      ENDIF.
      json = json && `{"name":"` && me->escape_json_value( CONV string( field-name ) ) && `"`.
      json = json && `,"json_name":"` && me->escape_json_value( field-json_name ) && `"`.
      json = json && `,"label":"` && me->escape_json_value( field-label ) && `"`.
      json = json && `,"is_key":` && COND string( WHEN field-is_key = abap_true THEN `true` ELSE `false` ).
      json = json && `,"readonly":` && COND string( WHEN field-readonly = abap_true THEN `true` ELSE `false` ).
      json = json && `}`.
    ENDLOOP.
    json = json && `]`.
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->APPEND_JSON_ROWS
* +-------------------------------------------------------------------------------------------------+
* | [--->] FIELDS                         TYPE        TY_FIELD_LIST
* | [--->] KEYWORD                        TYPE        STRING
* | [<-->] JSON                           TYPE        STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD append_json_rows.
    DATA table_ref TYPE REF TO data.
    DATA appended_rows TYPE i.

    FIELD-SYMBOLS <rows> TYPE STANDARD TABLE.
    FIELD-SYMBOLS <row> TYPE any.

    CREATE DATA table_ref TYPE STANDARD TABLE OF (maintained_table).
    ASSIGN table_ref->* TO <rows>.

    SELECT *
      FROM (maintained_table)
      ORDER BY PRIMARY KEY
      INTO TABLE @<rows>
      UP TO @max_rows ROWS.

    json = json && `[`.
    LOOP AT <rows> ASSIGNING <row>.
      IF keyword IS NOT INITIAL AND me->row_matches_keyword( fields = fields row = <row> keyword = keyword ) = abap_false.
        CONTINUE.
      ENDIF.

      IF appended_rows > 0.
        json = json && `,`.
      ENDIF.
      me->append_json_row(
        EXPORTING
          fields = fields
          row    = <row>
        CHANGING
          json   = json ).
      appended_rows += 1.
      IF appended_rows >= max_response_rows.
        EXIT.
      ENDIF.
    ENDLOOP.
    json = json && `]`.
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->APPEND_JSON_ROW
* +-------------------------------------------------------------------------------------------------+
* | [--->] FIELDS                         TYPE        TY_FIELD_LIST
* | [--->] ROW                            TYPE        ANY
* | [<-->] JSON                           TYPE        STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD append_json_row.
    FIELD-SYMBOLS <component> TYPE any.
    DATA value TYPE string.

    json = json && `{`.
    LOOP AT fields INTO DATA(field).
      IF sy-tabix > 1.
        json = json && `,`.
      ENDIF.
      ASSIGN COMPONENT field-name OF STRUCTURE row TO <component>.
      IF sy-subrc = 0.
        value = <component>.
      ELSE.
        value = ``.
      ENDIF.
      json = json && `"` && me->escape_json_value( field-json_name ) && `":"` && me->escape_json_value( value ) && `"`.
    ENDLOOP.
    json = json && `}`.
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->ROW_MATCHES_KEYWORD
* +-------------------------------------------------------------------------------------------------+
* | [--->] FIELDS                         TYPE        TY_FIELD_LIST
* | [--->] ROW                            TYPE        ANY
* | [--->] KEYWORD                        TYPE        STRING
* | [<-()] RESULT                         TYPE        ABAP_BOOL
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD row_matches_keyword.
    FIELD-SYMBOLS <component> TYPE any.
    DATA value TYPE string.

    LOOP AT fields INTO DATA(field).
      ASSIGN COMPONENT field-name OF STRUCTURE row TO <component>.
      IF sy-subrc = 0.
        value = <component>.
        IF value CS keyword.
          result = abap_true.
          RETURN.
        ENDIF.
      ENDIF.
    ENDLOOP.
    result = abap_false.
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->WRITE_JSON
* +-------------------------------------------------------------------------------------------------+
* | [--->] SERVER                         TYPE REF TO IF_HTTP_SERVER
* | [--->] JSON                           TYPE        STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD write_json.
    server->response->set_content_type( content_type_json ).
    server->response->set_cdata( json ).
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->WRITE_ERROR
* +-------------------------------------------------------------------------------------------------+
* | [--->] SERVER                         TYPE REF TO IF_HTTP_SERVER
* | [--->] STATUS_CODE                    TYPE        I
* | [--->] REASON                         TYPE        STRING
* | [--->] MESSAGE                        TYPE        STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD write_error.
    DATA json TYPE string.

    server->response->set_status(
      code   = status_code
      reason = reason ).
    json = `{"status":"error","message":"` && me->escape_json_value( message ) && `"}`.
    me->write_json(
      server = server
      json   = json ).
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZTEST01_WEB_MAINT->ESCAPE_JSON_VALUE
* +-------------------------------------------------------------------------------------------------+
* | [--->] VALUE                          TYPE        STRING
* | [<-()] RESULT                         TYPE        STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD escape_json_value.
    result = value.
    REPLACE ALL OCCURRENCES OF `\` IN result WITH `\\`.
    REPLACE ALL OCCURRENCES OF `"` IN result WITH `\"`.
    REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf IN result WITH `\n`.
    REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>newline IN result WITH `\n`.
  ENDMETHOD.
ENDCLASS.

最后在 SAP 里创建服务:

  1. 进入 SICF
  2. 建服务节点,例如:/sap/bc/ztest01_maint
  3. Handler List 里填:ZCL_ZTEST01_WEB_MAINT
  4. 激活服务
  5. 如需免 SAP 用户登录,在该 SICF 节点配置匿名技术用户,并只给它 ZTEST01 所需的最小权限

访问地址大概是:https://<xxxx>:<port>/sap/bc/ztest01_maint

相关推荐
xlxxy_16 小时前
sap获取批次特性报表
java·linux·开发语言·前端·数据库·abap·mm
爱喝水的鱼丶18 小时前
SAP-ABAP:ALV字段布局定制全攻略——快速适配业务端个性化展示需求
运维·性能优化·sap·abap·经验交流·alv报表
爱喝水的鱼丶10 天前
SAP-ABAP:接口 vs 抽象类:ABAP OOP两类扩展方式的差异与选型原则
运维·性能优化·sap·abap·erp·经验交流
修电脑的猫1 个月前
LSMW target field DATAM mapped as CHAR1, length 1 in table /SAPDMC/LSGBDC
sap·abap
duangww1 个月前
ALV通过GOS服务实现上传PDF附件
abap·附件
爱喝水的鱼丶1 个月前
SAP-ABAP:SAP表与视图权限管控方案:表维护权限、视图访问权限配置实操
运维·数据库·性能优化·sap·abap·权限·表和视图
爱喝水的鱼丶1 个月前
SAP-ABAP:SAP视图开发入门:四类标准视图的适用场景与创建步骤详解
服务器·数据库·性能优化·sap·abap
爱喝水的鱼丶1 个月前
SAP-ABAP:SAP多表连接视图实战:内连接/外连接配置逻辑与性能优化技巧
运维·开发语言·学习·性能优化·sap·abap
爱喝水的鱼丶1 个月前
SAP-ABAP:SAP基础数据校验工具开发系列博客(共5篇)第五篇:性能优化与上线运维:保障高并发场景下的工具稳定运行
运维·学习·性能优化·sap·abap·erp·经验交流