You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampark.apache.org by be...@apache.org on 2022/10/16 06:55:41 UTC

[incubator-streampark] branch dev updated: [Feature] Add basic functionality of variable modules such as create,… (#1831)

This is an automated email from the ASF dual-hosted git repository.

benjobs pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-streampark.git


The following commit(s) were added to refs/heads/dev by this push:
     new a2fc89ae7 [Feature] Add basic functionality of variable modules such as create,… (#1831)
a2fc89ae7 is described below

commit a2fc89ae7a28fec676165188227f4da1fc01cee1
Author: macksonmu <30...@qq.com>
AuthorDate: Sun Oct 16 14:55:36 2022 +0800

    [Feature] Add basic functionality of variable modules such as create,… (#1831)
    
    * [Feature] Add basic functionality of variable modules such as create, modify, delete, query and search #1779
    
    Co-authored-by: macksonmu <ma...@gmail.com>
---
 .../src/assembly/script/data/mysql-data.sql        |   5 +-
 .../src/assembly/script/data/pgsql-data.sql        |   5 +-
 .../src/assembly/script/schema/mysql-schema.sql    |  16 +
 .../src/assembly/script/schema/pgsql-schema.sql    |  34 +++
 .../src/assembly/script/upgrade/mysql/1.2.4.sql    |  18 ++
 .../system/controller/VariableController.java      |  98 ++++++
 .../streampark/console/system/entity/Variable.java |  71 +++++
 .../console/system/mapper/VariableMapper.java      |  35 +++
 .../console/system/service/VariableService.java    |  54 ++++
 .../system/service/impl/VariableServiceImpl.java   |  82 +++++
 .../src/main/resources/db/data-h2.sql              |   5 +-
 .../src/main/resources/db/schema-h2.sql            |  16 +
 .../resources/mapper/system/VariableMapper.xml     |  48 +++
 .../streampark-console-webapp/src/api/index.js     |   8 +
 .../streampark-console-webapp/src/api/variable.js  |  47 +++
 .../src/components/tools/UserMenu.vue              |   1 +
 .../src/views/system/variable/Add.vue              | 154 ++++++++++
 .../src/views/system/variable/Detail.vue           | 102 +++++++
 .../src/views/system/variable/Edit.vue             | 137 +++++++++
 .../src/views/system/variable/View.vue             | 338 +++++++++++++++++++++
 20 files changed, 1271 insertions(+), 3 deletions(-)

diff --git a/streampark-console/streampark-console-service/src/assembly/script/data/mysql-data.sql b/streampark-console/streampark-console-service/src/assembly/script/data/mysql-data.sql
index e44962f46..6b596a3a9 100644
--- a/streampark-console/streampark-console-service/src/assembly/script/data/mysql-data.sql
+++ b/streampark-console/streampark-console-service/src/assembly/script/data/mysql-data.sql
@@ -104,7 +104,10 @@ insert into `t_menu` values (100050, 100048, 'update', null, null, 'member:updat
 insert into `t_menu` values (100051, 100048, 'delete', null, null, 'member:delete', null, '1', 1, null, now(), now());
 insert into `t_menu` values (100052, 100048, 'role view', null, null, 'role:view', null, '1', 1, null, now(), now());
 insert into `t_menu` values (100053, 100001, 'types', null, null, 'user:types', null, '1', 1, null, now(), now());
-
+insert into `t_menu` VALUES (100054, 100000, 'Variable Management', '/system/variable', 'system/variable/View', 'variable:view', 'code', '0', 1, 3, now(), now());
+insert into `t_menu` VALUES (100055, 100054, 'add', NULL, NULL, 'variable:add', NULL, '1', 1, NULL, now(), now());
+insert into `t_menu` VALUES (100056, 100054, 'update', NULL, NULL, 'variable:update', NULL, '1', 1, NULL, now(), now());
+insert into `t_menu` VALUES (100057, 100054, 'delete', NULL, NULL, 'variable:delete', NULL, '1', 1, NULL, now(), now());
 
 -- ----------------------------
 -- Records of t_role
diff --git a/streampark-console/streampark-console-service/src/assembly/script/data/pgsql-data.sql b/streampark-console/streampark-console-service/src/assembly/script/data/pgsql-data.sql
index 348ad8885..93588e648 100644
--- a/streampark-console/streampark-console-service/src/assembly/script/data/pgsql-data.sql
+++ b/streampark-console/streampark-console-service/src/assembly/script/data/pgsql-data.sql
@@ -100,7 +100,10 @@ insert into "public"."t_menu" values (100050, 100048, 'update', null, null, 'mem
 insert into "public"."t_menu" values (100051, 100048, 'delete', null, null, 'member:delete', null, '1', 1, null, now(), now());
 insert into "public"."t_menu" values (100052, 100048, 'role view', null, null, 'role:view', null, '1', 1, null, now(), now());
 insert into "public"."t_menu" values (100053, 100001, 'types', null, null, 'user:types', null, '1', 1, null, now(), now());
-
+insert into "public"."t_menu" VALUES (100054, 100000, 'Variable Management', '/system/variable', 'system/variable/View', 'variable:view', 'code', '0', 1, 3, now(), now());
+insert into "public"."t_menu" VALUES (100055, 100054, 'add', NULL, NULL, 'variable:add', NULL, '1', 1, NULL, now(), now());
+insert into "public"."t_menu" VALUES (100056, 100054, 'update', NULL, NULL, 'variable:update', NULL, '1', 1, NULL, now(), now());
+insert into "public"."t_menu" VALUES (100057, 100054, 'delete', NULL, NULL, 'variable:delete', NULL, '1', 1, NULL, now(), now());
 
 -- ----------------------------
 -- Records of t_role
diff --git a/streampark-console/streampark-console-service/src/assembly/script/schema/mysql-schema.sql b/streampark-console/streampark-console-service/src/assembly/script/schema/mysql-schema.sql
index 7b9f134b2..0ccac576c 100644
--- a/streampark-console/streampark-console-service/src/assembly/script/schema/mysql-schema.sql
+++ b/streampark-console/streampark-console-service/src/assembly/script/schema/mysql-schema.sql
@@ -319,6 +319,22 @@ create table `t_team` (
   unique key `team_name_idx` (`team_name`) using btree
 ) engine = innodb default charset = utf8mb4 collate = utf8mb4_general_ci;
 
+-- ----------------------------
+-- Table of t_variable
+-- ----------------------------
+drop table if exists `t_variable`;
+create table `t_variable` (
+  `id` bigint not null auto_increment,
+  `variable_code` varchar(100) collate utf8mb4_general_ci not null comment 'Variable code is used for parameter names passed to the program or as placeholders',
+  `variable_value` text collate utf8mb4_general_ci not null comment 'The specific value corresponding to the variable',
+  `description` text collate utf8mb4_general_ci default null comment 'More detailed description of variables',
+  `creator_id` bigint collate utf8mb4_general_ci not null comment 'user id of creator',
+  `team_id` bigint collate utf8mb4_general_ci not null comment 'team id',
+  `create_time` datetime not null default current_timestamp comment 'create time',
+  `modify_time` datetime not null default current_timestamp on update current_timestamp comment 'modify time',
+  primary key (`id`) using btree,
+  unique key `un_team_vcode_inx` (`team_id`,`variable_code`) using btree
+) engine=innodb auto_increment=100000 default charset=utf8mb4 collate=utf8mb4_general_ci;
 
 -- ----------------------------
 -- Table structure for t_role
diff --git a/streampark-console/streampark-console-service/src/assembly/script/schema/pgsql-schema.sql b/streampark-console/streampark-console-service/src/assembly/script/schema/pgsql-schema.sql
index e121df21e..635cc854e 100644
--- a/streampark-console/streampark-console-service/src/assembly/script/schema/pgsql-schema.sql
+++ b/streampark-console/streampark-console-service/src/assembly/script/schema/pgsql-schema.sql
@@ -42,6 +42,7 @@ drop table if exists "public"."t_alert_config";
 drop table if exists "public"."t_access_token";
 drop table if exists "public"."t_flink_log";
 drop table if exists "public"."t_team";
+drop table if exists "public"."t_variable";
 
 -- ----------------------------
 -- drop sequence if exists
@@ -66,6 +67,7 @@ drop sequence if exists "public"."streampark_t_alert_config_id_seq";
 drop sequence if exists "public"."streampark_t_access_token_id_seq";
 drop sequence if exists "public"."streampark_t_flink_log_id_seq";
 drop sequence if exists "public"."streampark_t_team_id_seq";
+drop sequence if exists "public"."streampark_t_variable_id_seq";
 
 -- ----------------------------
 -- drop trigger if exists
@@ -602,6 +604,38 @@ create index "un_team_name" on "public"."t_team" using btree (
   "team_name" collate "pg_catalog"."default" "pg_catalog"."text_ops" asc nulls last
 );
 
+-- ----------------------------
+-- Table of t_variable
+-- ----------------------------
+create sequence "public"."streampark_t_variable_id_seq"
+    increment 1 start 10000 cache 1 minvalue 10000 maxvalue 9223372036854775807;
+
+create table "public"."t_variable" (
+  "id" int8 not null default nextval('streampark_t_variable_id_seq'::regclass),
+  "variable_code" varchar(100) collate "pg_catalog"."default" not null,
+  "variable_value" text collate "pg_catalog"."default" not null,
+  "description" text collate "pg_catalog"."default" default null,
+  "creator_id" int8 collate "pg_catalog"."default" not null,
+  "team_id" int8 collate "pg_catalog"."default" not null,
+  "create_time" timestamp(6) not null default timezone('UTC-8'::text, (now())::timestamp(0) without time zone),
+  "modify_time" timestamp(6) not null default timezone('UTC-8'::text, (now())::timestamp(0) without time zone)
+)
+;
+comment on column "public"."t_variable"."id" is 'variable id';
+comment on column "public"."t_variable"."variable_code" is 'Variable code is used for parameter names passed to the program or as placeholders';
+comment on column "public"."t_variable"."variable_value" is 'The specific value corresponding to the variable';
+comment on column "public"."t_variable"."description" is 'More detailed description of variables';
+comment on column "public"."t_variable"."creator_id" is 'user id of creator';
+comment on column "public"."t_variable"."team_id" is 'team id';
+comment on column "public"."t_variable"."create_time" is 'creation time';
+comment on column "public"."t_variable"."modify_time" is 'modify time';
+
+alter table "public"."t_variable" add constraint "t_variable_pkey" primary key ("id");
+create index "un_team_vcode_inx" on "public"."t_variable" using btree (
+  "team_id" "pg_catalog"."int8_ops" asc nulls last,
+  "variable_code" collate "pg_catalog"."default" "pg_catalog"."text_ops" asc nulls last
+);
+
 
 -- ----------------------------
 -- table structure for t_role
diff --git a/streampark-console/streampark-console-service/src/assembly/script/upgrade/mysql/1.2.4.sql b/streampark-console/streampark-console-service/src/assembly/script/upgrade/mysql/1.2.4.sql
index 39a991762..6f2db7de6 100644
--- a/streampark-console/streampark-console-service/src/assembly/script/upgrade/mysql/1.2.4.sql
+++ b/streampark-console/streampark-console-service/src/assembly/script/upgrade/mysql/1.2.4.sql
@@ -127,6 +127,10 @@ insert into `t_menu` values (100050, 100048, 'update', null, null, 'member:updat
 insert into `t_menu` values (100051, 100048, 'delete', null, null, 'member:delete', null, '1', 1, null, now(), now());
 insert into `t_menu` values (100052, 100048, 'role view', null, null, 'role:view', null, '1', 1, null, now(), now());
 insert into `t_menu` values (100053, 100001, 'types', null, null, 'user:types', null, '1', 1, null, now(), now());
+insert into `t_menu` VALUES (100054, 100000, 'Variable Management', '/system/variable', 'system/variable/View', 'variable:view', 'code', '0', 1, 3, now(), now());
+insert into `t_menu` VALUES (100055, 100054, 'add', NULL, NULL, 'variable:add', NULL, '1', 1, NULL, now(), now());
+insert into `t_menu` VALUES (100056, 100054, 'update', NULL, NULL, 'variable:update', NULL, '1', 1, NULL, now(), now());
+insert into `t_menu` VALUES (100057, 100054, 'delete', NULL, NULL, 'variable:delete', NULL, '1', 1, NULL, now(), now());
 
 -- Add team related sql
 create table `t_team` (
@@ -232,5 +236,19 @@ alter table `t_user`
 modify `username` varchar(255) collate utf8mb4_general_ci not null comment 'user name',
 add unique key `un_username` (`username`) using btree;
 
+drop table if exists `t_variable`;
+create table `t_variable` (
+  `id` bigint not null auto_increment,
+  `variable_code` varchar(100) collate utf8mb4_general_ci not null comment 'Variable code is used for parameter names passed to the program or as placeholders',
+  `variable_value` text collate utf8mb4_general_ci not null comment 'The specific value corresponding to the variable',
+  `description` text collate utf8mb4_general_ci default null comment 'More detailed description of variables',
+  `creator_id` bigint collate utf8mb4_general_ci not null comment 'user id of creator',
+  `team_id` bigint collate utf8mb4_general_ci not null comment 'team id',
+  `create_time` datetime not null default current_timestamp comment 'create time',
+  `modify_time` datetime not null default current_timestamp on update current_timestamp comment 'modify time',
+  primary key (`id`) using btree,
+  unique key `un_team_vcode_inx` (`team_id`,`variable_code`) using btree
+) engine=innodb auto_increment=100000 default charset=utf8mb4 collate=utf8mb4_general_ci;
+
 set foreign_key_checks = 1;
 
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/controller/VariableController.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/controller/VariableController.java
new file mode 100644
index 000000000..85c7d9bd5
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/controller/VariableController.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.system.controller;
+
+import org.apache.streampark.console.base.domain.RestRequest;
+import org.apache.streampark.console.base.domain.RestResponse;
+import org.apache.streampark.console.base.exception.ApiAlertException;
+import org.apache.streampark.console.system.entity.Variable;
+import org.apache.streampark.console.system.service.VariableService;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+
+@Slf4j
+@Validated
+@RestController
+@RequestMapping("variable")
+public class VariableController {
+
+    @Autowired
+    private VariableService variableService;
+
+    @PostMapping("list")
+    @RequiresPermissions("variable:view")
+    public RestResponse variableList(RestRequest restRequest, Variable variable) {
+        IPage<Variable> variableList = variableService.page(variable, restRequest);
+        return RestResponse.success(variableList);
+    }
+
+    @PostMapping("post")
+    @RequiresPermissions("variable:add")
+    public RestResponse addVariable(@Valid Variable variable) throws Exception {
+        this.variableService.createVariable(variable);
+        return RestResponse.success();
+    }
+
+    @PutMapping("update")
+    @RequiresPermissions("variable:update")
+    public RestResponse updateVariable(@Valid Variable variable) throws Exception {
+        if (variable.getId() == null) {
+            throw new ApiAlertException("Sorry, the variable id cannot be null.");
+        }
+        Variable findVariable = this.variableService.getById(variable.getId());
+        if (findVariable == null) {
+            throw new ApiAlertException("Sorry, the variable does not exist.");
+        }
+        if (!findVariable.getVariableCode().equals(variable.getVariableCode())) {
+            throw new ApiAlertException("Sorry, the variable code cannot be updated.");
+        }
+        this.variableService.updateById(variable);
+        return RestResponse.success();
+    }
+
+    @DeleteMapping("delete")
+    @RequiresPermissions("variable:delete")
+    public RestResponse deleteVariables(@Valid Variable variable) throws Exception {
+        this.variableService.removeById(variable);
+        return RestResponse.success();
+    }
+
+    @PostMapping("check/code")
+    public RestResponse checkVariableCode(@RequestParam Long teamId, @NotBlank(message = "{required}") String variableCode) {
+        boolean result = this.variableService.findByVariableCode(teamId, variableCode) == null;
+        return RestResponse.success(result);
+    }
+
+    @PostMapping("select")
+    public RestResponse selectVariables(@RequestParam Long teamId) {
+        return RestResponse.success().data(this.variableService.findByTeamId(teamId));
+    }
+}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/entity/Variable.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/entity/Variable.java
new file mode 100644
index 000000000..8082f8ce6
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/entity/Variable.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("t_variable")
+public class Variable implements Serializable {
+
+    private static final long serialVersionUID = -7720746591258904369L;
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    @NotBlank(message = "{required}")
+    private String variableCode;
+
+    @NotBlank(message = "{required}")
+    @Size(max = 50, message = "{noMoreThan}")
+    private String variableValue;
+
+    @Size(max = 100, message = "{noMoreThan}")
+    private String description;
+
+    /**
+     * user id of creator
+     */
+    private Long creatorId;
+
+    /**
+     * user name of creator
+     */
+    private transient String creatorName;
+
+    @NotNull(message = "{required}")
+    private Long teamId;
+
+    private transient Date createTime;
+
+    private transient Date modifyTime;
+
+    private transient String sortField;
+
+    private transient String sortOrder;
+}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/mapper/VariableMapper.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/mapper/VariableMapper.java
new file mode 100644
index 000000000..f7a8c9e57
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/mapper/VariableMapper.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.system.mapper;
+
+import org.apache.streampark.console.system.entity.Variable;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+public interface VariableMapper extends BaseMapper<Variable> {
+    IPage<Variable> page(Page<Variable> page, @Param("variable") Variable variable);
+
+    @Select("select * from t_variable where team_id = #{teamId}")
+    List<Variable> selectByTeamId(@Param("teamId") Long teamId);
+}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/service/VariableService.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/service/VariableService.java
new file mode 100644
index 000000000..3fcfa3dcc
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/service/VariableService.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.system.service;
+
+import org.apache.streampark.console.base.domain.RestRequest;
+import org.apache.streampark.console.system.entity.Variable;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+public interface VariableService extends IService<Variable> {
+
+    /**
+     * find variable
+     *
+     * @param variable        variable
+     * @param restRequest queryRequest
+     * @return IPage
+     */
+    IPage<Variable> page(Variable variable, RestRequest restRequest);
+
+    /**
+     * get variables through team
+     * @param teamId
+     * @return
+     */
+    List<Variable> findByTeamId(Long teamId);
+
+    /**
+     * create variable
+     *
+     * @param variable variable
+     */
+    void createVariable(Variable variable) throws Exception;
+
+    Variable findByVariableCode(Long teamId, String variableCode);
+}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/service/impl/VariableServiceImpl.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/service/impl/VariableServiceImpl.java
new file mode 100644
index 000000000..491fcf3b2
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/service/impl/VariableServiceImpl.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.system.service.impl;
+
+import org.apache.streampark.console.base.domain.RestRequest;
+import org.apache.streampark.console.base.exception.ApiAlertException;
+import org.apache.streampark.console.base.mybatis.pager.MybatisPager;
+import org.apache.streampark.console.core.service.ApplicationService;
+import org.apache.streampark.console.core.service.CommonService;
+import org.apache.streampark.console.system.entity.Variable;
+import org.apache.streampark.console.system.mapper.VariableMapper;
+import org.apache.streampark.console.system.service.VariableService;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Slf4j
+@Service
+@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
+public class VariableServiceImpl extends ServiceImpl<VariableMapper, Variable> implements VariableService {
+
+    @Autowired
+    private ApplicationService applicationService;
+
+    @Autowired
+    private CommonService commonService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void createVariable(Variable variable) throws Exception {
+        if (this.findByVariableCode(variable.getTeamId(), variable.getVariableCode()) != null) {
+            throw new ApiAlertException("Sorry, the variable code already exists.");
+        }
+        variable.setCreatorId(commonService.getUserId());
+        this.save(variable);
+    }
+
+    @Override
+    public IPage<Variable> page(Variable variable, RestRequest request) {
+        if (variable.getTeamId() == null) {
+            return null;
+        }
+        Page<Variable> page = new MybatisPager<Variable>().getDefaultPage(request);
+        return this.baseMapper.page(page, variable);
+    }
+
+    @Override
+    public Variable findByVariableCode(Long teamId, String variableCode) {
+        return baseMapper.selectOne(new LambdaQueryWrapper<Variable>()
+            .eq(Variable::getVariableCode, variableCode)
+            .eq(Variable::getTeamId, teamId));
+    }
+
+    @Override
+    public List<Variable> findByTeamId(Long teamId) {
+        return baseMapper.selectByTeamId(teamId);
+    }
+}
diff --git a/streampark-console/streampark-console-service/src/main/resources/db/data-h2.sql b/streampark-console/streampark-console-service/src/main/resources/db/data-h2.sql
index 31d55ad20..612705200 100644
--- a/streampark-console/streampark-console-service/src/main/resources/db/data-h2.sql
+++ b/streampark-console/streampark-console-service/src/main/resources/db/data-h2.sql
@@ -99,7 +99,10 @@ insert into `t_menu` values (100050, 100048, 'update', null, null, 'member:updat
 insert into `t_menu` values (100051, 100048, 'delete', null, null, 'member:delete', null, '1', 1, null, now(), now());
 insert into `t_menu` values (100052, 100048, 'role view', null, null, 'role:view', null, '1', 1, null, now(), now());
 insert into `t_menu` values (100053, 100001, 'types', null, null, 'user:types', null, '1', 1, null, now(), now());
-
+insert into `t_menu` VALUES (100054, 100000, 'Variable Management', '/system/variable', 'system/variable/View', 'variable:view', 'code', '0', 1, 3, now(), now());
+insert into `t_menu` VALUES (100055, 100054, 'add', NULL, NULL, 'variable:add', NULL, '1', 1, NULL, now(), now());
+insert into `t_menu` VALUES (100056, 100054, 'update', NULL, NULL, 'variable:update', NULL, '1', 1, NULL, now(), now());
+insert into `t_menu` VALUES (100057, 100054, 'delete', NULL, NULL, 'variable:delete', NULL, '1', 1, NULL, now(), now());
 
 -- ----------------------------
 -- Records of t_role
diff --git a/streampark-console/streampark-console-service/src/main/resources/db/schema-h2.sql b/streampark-console/streampark-console-service/src/main/resources/db/schema-h2.sql
index 47da039a7..d7be56dc2 100644
--- a/streampark-console/streampark-console-service/src/main/resources/db/schema-h2.sql
+++ b/streampark-console/streampark-console-service/src/main/resources/db/schema-h2.sql
@@ -282,6 +282,22 @@ create table if not exists `t_team` (
   unique (`team_name`)
 );
 
+-- ----------------------------
+-- Table of t_variable
+-- ----------------------------
+create table if not exists `t_variable` (
+  `id` bigint generated by default as identity not null comment 'variable id',
+  `variable_code` varchar(100) not null comment 'Variable code is used for parameter names passed to the program or as placeholders',
+  `variable_value` text not null comment 'The specific value corresponding to the variable',
+  `description` text default null comment 'More detailed description of variables, only for display, not involved in program logic',
+  `creator_id` bigint not null comment 'user id of creator',
+  `team_id` bigint not null comment 'team id',
+  `create_time` datetime not null default current_timestamp comment 'create time',
+  `modify_time` datetime not null default current_timestamp on update current_timestamp comment 'modify time',
+  primary key (`id`),
+  unique (`team_id`, `variable_code`)
+);
+
 -- ----------------------------
 -- Table structure for t_role
 -- ----------------------------
diff --git a/streampark-console/streampark-console-service/src/main/resources/mapper/system/VariableMapper.xml b/streampark-console/streampark-console-service/src/main/resources/mapper/system/VariableMapper.xml
new file mode 100644
index 000000000..f6aae7363
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/resources/mapper/system/VariableMapper.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       https://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.apache.streampark.console.system.mapper.VariableMapper">
+    <resultMap id="VariableMap" type="org.apache.streampark.console.system.entity.Variable">
+        <result column="id" jdbcType="BIGINT" property="id"/>
+        <result column="variable_code" jdbcType="VARCHAR" property="variableCode"/>
+        <result column="variable_value" jdbcType="VARCHAR" property="variableValue"/>
+        <result column="description" jdbcType="VARCHAR" property="description"/>
+        <result column="creator_id" jdbcType="BIGINT" property="creatorId"/>
+        <result column="team_id" jdbcType="BIGINT" property="teamId"/>
+        <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
+        <result column="modify_time" jdbcType="TIMESTAMP" property="modifyTime"/>
+    </resultMap>
+
+    <select id="page" resultType="variable" parameterType="variable">
+        select v.*, u.username as creatorName
+        from t_variable v, t_user u
+        <where>
+            v.creator_id = u.user_id
+            and v.team_id = ${variable.teamId}
+            <if test="variable.description != null and variable.description != ''">
+                and v.description like '%${variable.description}%'
+            </if>
+            <if test="variable.variableCode != null and variable.variableCode != ''">
+                and v.variable_code like '%${variable.variableCode}%'
+            </if>
+        </where>
+    </select>
+
+</mapper>
diff --git a/streampark-console/streampark-console-webapp/src/api/index.js b/streampark-console/streampark-console-webapp/src/api/index.js
index c7b48e938..cb6f087fb 100644
--- a/streampark-console/streampark-console-webapp/src/api/index.js
+++ b/streampark-console/streampark-console-webapp/src/api/index.js
@@ -210,6 +210,14 @@ export default {
     UPDATE: '/menu/update',
     ROUTER: '/menu/router'
   },
+  Variable: {
+    LIST: '/variable/list',
+    UPDATE: '/variable/update',
+    POST: '/variable/post',
+    DELETE: '/variable/delete',
+    SELECT: '/variable/select',
+    CHECK_CODE: '/variable/check/code'
+  },
   Log: {
     LIST: '/log/list',
     DELETE: '/log/delete',
diff --git a/streampark-console/streampark-console-webapp/src/api/variable.js b/streampark-console/streampark-console-webapp/src/api/variable.js
new file mode 100644
index 000000000..443ad12fa
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/api/variable.js
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import api from './index'
+import http from '@/utils/request'
+
+export function list (queryParam) {
+  return http.post(api.Variable.LIST, queryParam)
+}
+
+export function update (queryParam) {
+  return http.put(api.Variable.UPDATE, queryParam)
+}
+
+export function checkVariableCode (queryParam) {
+  return http.post(api.Variable.CHECK_CODE, queryParam)
+}
+
+export function post (queryParam) {
+  return http.post(api.Variable.POST, queryParam)
+}
+
+export function getRouter (queryParam) {
+  return http.post(api.Menu.ROUTER, queryParam)
+}
+
+export function deleteVariable (queryParam) {
+  return http.delete(api.Variable.DELETE, queryParam)
+}
+
+export function selectVariable (params) {
+  return http.post(api.Variable.SELECT, params)
+}
diff --git a/streampark-console/streampark-console-webapp/src/components/tools/UserMenu.vue b/streampark-console/streampark-console-webapp/src/components/tools/UserMenu.vue
index 5d304df9c..6fec5e9b8 100644
--- a/streampark-console/streampark-console-webapp/src/components/tools/UserMenu.vue
+++ b/streampark-console/streampark-console-webapp/src/components/tools/UserMenu.vue
@@ -343,6 +343,7 @@ export default {
         '/system/token',
         '/system/team',
         '/system/member',
+        '/system/variable',
         '/flink/project',
         '/flink/app'
       ]
diff --git a/streampark-console/streampark-console-webapp/src/views/system/variable/Add.vue b/streampark-console/streampark-console-webapp/src/views/system/variable/Add.vue
new file mode 100644
index 000000000..da9f081da
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/views/system/variable/Add.vue
@@ -0,0 +1,154 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       https://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+
+<template>
+  <a-drawer
+    :mask-closable="false"
+    width="700"
+    placement="right"
+    :closable="true"
+    @close="onClose"
+    :visible="visible"
+    style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
+    <template slot="title">
+      <a-icon type="code" />
+      Add Variable
+    </template>
+    <a-form
+      :form="form">
+      <a-form-item
+        label="Variable Code"
+        v-bind="formItemLayout"
+        :validate-status="validateStatus"
+        :help="help">
+        <a-input
+          @blur="handleVariableCodeBlur"
+          v-decorator="['variableCode', {rules: [{ required: true, max: 50, message: 'please enter Variable Code'}]}]" />
+      </a-form-item>
+      <a-form-item
+        label="Variable Value"
+        v-bind="formItemLayout">
+        <a-textarea
+          rows="4"
+          placeholder="Please enter Variable Value"
+          v-decorator="['variableValue', {rules: [{ required: true, max: 100, message: 'please enter Variable Value' }]}]" />
+      </a-form-item>
+      <a-form-item
+        label="Description"
+        v-bind="formItemLayout">
+        <a-textarea
+          rows="4"
+          placeholder="Please enter description for this variable"
+          v-decorator="['description', {rules: [{ max: 100, message: 'exceeds maximum length limit of 100 characters'}]}]" />
+      </a-form-item>
+    </a-form>
+    <div
+      class="drawer-bootom-button">
+      <a-button
+        @click="onClose">
+        Cancel
+      </a-button>
+      <a-button
+        @click="handleSubmit"
+        type="primary"
+        :loading="loading">
+        Submit
+      </a-button>
+    </div>
+  </a-drawer>
+</template>
+<script>
+import { post, checkVariableCode } from '@/api/variable'
+
+const formItemLayout = {
+  labelCol: { span: 4 },
+  wrapperCol: { span: 18 }
+}
+export default {
+  name: 'VariableAdd',
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      loading: false,
+      formItemLayout,
+      form: this.$form.createForm(this),
+      validateStatus: '',
+      help: ''
+    }
+  },
+  methods: {
+    reset () {
+      this.validateStatus = ''
+      this.help = ''
+      this.loading = false
+      this.form.resetFields()
+    },
+    onClose () {
+      this.reset()
+      this.$emit('close')
+    },
+    handleSubmit() {
+      this.form.validateFields((err, values) => {
+        if (!err && this.validateStatus === 'success') {
+          post({
+            ...values
+          }).then((r) => {
+            if (r.status === 'success') {
+              this.reset()
+              this.$emit('success')
+            }
+          }).catch(() => {
+            this.loading = false
+          })
+        }
+      })
+    },
+    handleVariableCodeBlur (e) {
+      const variableCode = (e && e.target.value) || ''
+      if (variableCode.length) {
+        if (variableCode.length > 100) {
+          this.validateStatus = 'error'
+          this.help = 'Variable Code should not be longer than 100 characters'
+        } else {
+          this.validateStatus = 'validating'
+          checkVariableCode({
+            variableCode: variableCode
+          }).then((resp) => {
+            if (resp.data) {
+              this.validateStatus = 'success'
+              this.help = ''
+            } else {
+              this.validateStatus = 'error'
+              this.help = 'Sorry, the Variable Code already exists'
+            }
+          })
+        }
+      } else {
+        this.validateStatus = 'error'
+        this.help = 'Variable Code cannot be empty'
+      }
+    }
+  }
+}
+</script>
diff --git a/streampark-console/streampark-console-webapp/src/views/system/variable/Detail.vue b/streampark-console/streampark-console-webapp/src/views/system/variable/Detail.vue
new file mode 100644
index 000000000..e95cf8f63
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/views/system/variable/Detail.vue
@@ -0,0 +1,102 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       https://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+
+<template>
+  <a-modal
+    v-model="show"
+    :centered="true"
+    :keyboard="false"
+    :footer="null"
+    :width="600"
+    @cancel="handleCancelClick">
+    <template slot="title">
+      <a-icon type="code" />
+      Variable Info
+    </template>
+    <a-layout class="variable-info">
+      <a-layout-content class="variable-content">
+        <p>
+          <a-icon type="code" />Variable Code:{{ data.variableCode }}
+        </p>
+        <p>
+          <a-icon type="down-circle" />Variable Value:{{ data.variableValue }}
+        </p>
+        <p>
+          <a-icon type="user" />Create User:{{ data.creatorName }}
+        </p>
+        <p>
+          <a-icon type="clock-circle" />Create Time:{{ data.createTime }}
+        </p>
+        <p>
+          <a-icon type="clock-circle" />Modify Time:{{ data.modifyTime }}
+        </p>
+        <p>
+          <a-icon type="message" />Description:{{ data.description }}
+        </p>
+      </a-layout-content>
+    </a-layout>
+  </a-modal>
+</template>
+<script>
+export default {
+  name: 'VariableInfo',
+  props: {
+    visible: {
+      type: Boolean,
+      require: true,
+      default: false
+    },
+    data: {
+      type: Object,
+      default: () => ({}),
+      require: true
+    }
+  },
+  computed: {
+    show: {
+      get: function () {
+        return this.visible
+      },
+      set: function () {
+      }
+    }
+  },
+  methods: {
+    handleCancelClick () {
+      this.$emit('close')
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+  .variable-info {
+    background: @body-background;
+    padding: 10px;
+  }
+  .variable-content{
+    margin-right: 1.2rem;
+    float: left;
+  }
+  p {
+    margin-bottom: 1rem;
+  }
+  i {
+    margin-right: .8rem;
+  }
+</style>
diff --git a/streampark-console/streampark-console-webapp/src/views/system/variable/Edit.vue b/streampark-console/streampark-console-webapp/src/views/system/variable/Edit.vue
new file mode 100644
index 000000000..47c3abbb5
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/views/system/variable/Edit.vue
@@ -0,0 +1,137 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       https://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+
+<template>
+  <a-drawer
+    :mask-closable="false"
+    width="700"
+    placement="right"
+    :closable="true"
+    @close="onClose"
+    :visible="visible"
+    style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
+    <template slot="title">
+      <a-icon type="code" />
+      Modify Variable
+    </template>
+    <a-form
+      :form="form">
+      <a-form-item
+        label="Variable Code"
+        v-bind="formItemLayout">
+        <a-input
+          read-only
+          v-decorator="['variableCode', {rules: [{ required: true }]}]" />
+      </a-form-item>
+      <a-form-item
+        label="Variable Value"
+        v-bind="formItemLayout">
+        <a-textarea
+          v-decorator="['variableValue', {rules: [{ required: true }]}]" />
+      </a-form-item>
+      <a-form-item
+        label="Description"
+        v-bind="formItemLayout">
+        <a-textarea
+          v-decorator="['description']" />
+      </a-form-item>
+
+    </a-form>
+    <div
+      class="drawer-bootom-button">
+      <a-button
+        @click="onClose">
+        Cancel
+      </a-button>
+      <a-button
+        @click="handleSubmit"
+        type="primary"
+        :loading="loading">
+        Submit
+      </a-button>
+    </div>
+  </a-drawer>
+</template>
+<script>
+import { update } from '@/api/variable'
+
+const formItemLayout = {
+  labelCol: { span: 4 },
+  wrapperCol: { span: 18 }
+}
+export default {
+  name: 'VariableEdit',
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      formItemLayout,
+      form: this.$form.createForm(this),
+      loading: false,
+      validateStatus: 'success',
+      help: ''
+    }
+  },
+
+  methods: {
+    onClose () {
+      this.loading = false
+      this.form.resetFields()
+      this.$emit('close')
+    },
+
+    setFormValues ({ ...variable }) {
+      this.id = variable.id
+      const fields = ['variableCode', 'variableValue', 'description']
+      Object.keys(variable).forEach((key) => {
+        if (fields.indexOf(key) !== -1) {
+          this.form.getFieldDecorator(key)
+          const obj = {}
+          obj[key] = variable[key]
+          this.form.setFieldsValue(obj)
+        }
+      })
+    },
+
+    handleSubmit () {
+      this.form.validateFields((err, values) => {
+        if (!err && this.validateStatus === 'success') {
+          this.loading = true
+          const variable = this.form.getFieldsValue()
+          variable.id = this.id
+          update(variable).then((r) => {
+            if (r.status === 'success') {
+              this.loading = false
+              this.$emit('success')
+            } else {
+              this.loading = false
+            }
+          }).catch(() => {
+            this.loading = false
+          })
+        }
+      })
+    }
+  }
+}
+</script>
diff --git a/streampark-console/streampark-console-webapp/src/views/system/variable/View.vue b/streampark-console/streampark-console-webapp/src/views/system/variable/View.vue
new file mode 100644
index 000000000..a34a157b9
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/views/system/variable/View.vue
@@ -0,0 +1,338 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       https://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+
+<template>
+  <a-card :bordered="false">
+    <div
+      class="table-page-search-wrapper">
+      <a-form
+        layout="inline">
+        <a-row
+          :gutter="48">
+          <div
+            class="fold">
+            <a-col
+              :md="8"
+              :sm="24">
+              <a-form-item
+                label="Variable Code"
+                :label-col="{span: 4}"
+                :wrapper-col="{span: 18, offset: 2}">
+                <a-input
+                  v-model="queryParams.variableCode" />
+              </a-form-item>
+            </a-col>
+            <a-col
+              :md="8"
+              :sm="24">
+              <a-form-item
+                label="Description"
+                :label-col="{span: 4}"
+                :wrapper-col="{span: 18, offset: 2}">
+                <a-input
+                  v-model="queryParams.description" />
+              </a-form-item>
+            </a-col>
+          </div>
+          <a-col
+            :md="8"
+            :sm="24">
+            <span
+              class="table-page-search-bar">
+              <a-button
+                type="primary"
+                shape="circle"
+                icon="search"
+                @click="search" />
+              <a-button
+                type="primary"
+                shape="circle"
+                icon="rest"
+                @click="reset" />
+              <a-button
+                type="primary"
+                shape="circle"
+                icon="plus"
+                v-permit="'variable:add'"
+                @click="handleAdd" />
+            </span>
+          </a-col>
+        </a-row>
+      </a-form>
+    </div>
+
+    <!-- 表格区域 -->
+    <a-table
+      ref="TableInfo"
+      :columns="columns"
+      :data-source="dataSource"
+      :pagination="pagination"
+      :loading="loading"
+      :scroll="{ x: 900 }"
+      @change="handleTableChange">
+      <template
+        slot="email"
+        slot-scope="text">
+        <a-popover
+          placement="topLeft">
+          <template
+            slot="content">
+            <div>
+              {{ text }}
+            </div>
+          </template>
+          <p
+            style="width: 150px;margin-bottom: 0">
+            {{ text }}
+          </p>
+        </a-popover>
+      </template>
+      <template
+        slot="operation"
+        slot-scope="text, record">
+        <svg-icon
+          v-permit="'variable:update'"
+          name="edit"
+          border
+          @click.native="handleEdit(record)"
+          title="modify" />
+        <svg-icon
+          name="see"
+          border
+          @click.native="handleView(record)"
+          title="view" />
+        <a-popconfirm
+          v-permit="'variable:delete'"
+          title="Are you sure delete this variable ?"
+          cancel-text="No"
+          ok-text="Yes"
+          @confirm="handleDelete(record)">
+          <svg-icon name="remove" border/>
+        </a-popconfirm>
+      </template>
+    </a-table>
+
+    <!-- view variable -->
+    <variable-info
+      :data="variableInfo.data"
+      :visible="variableInfo.visible"
+      @close="handleVariableInfoClose" />
+    <!-- add variable -->
+    <variable-add
+      @close="handleVariableAddClose"
+      @success="handleVariableAddSuccess"
+      :visible="variableAdd.visible" />
+    <!-- edit variable -->
+    <variable-edit
+      ref="variableEdit"
+      @close="handleVariableEditClose"
+      @success="handleVariableEditSuccess"
+      :visible="variableEdit.visible" />
+  </a-card>
+</template>
+
+<script>
+import VariableInfo from './Detail'
+import VariableAdd from './Add'
+import VariableEdit from './Edit'
+import SvgIcon from '@/components/SvgIcon'
+import { list, deleteVariable} from '@/api/variable'
+
+export default {
+  name: 'Variable',
+  components: { VariableInfo, VariableAdd, VariableEdit, SvgIcon },
+  data () {
+    return {
+      variableInfo: {
+        visible: false,
+        data: {}
+      },
+      variableAdd: {
+        visible: false
+      },
+      variableEdit: {
+        visible: false
+      },
+      queryParams: {},
+      filteredInfo: null,
+      sortedInfo: null,
+      paginationInfo: null,
+      dataSource: [],
+      loading: false,
+      pagination: {
+        pageSizeOptions: ['10', '20', '30', '40', '100'],
+        defaultCurrent: 1,
+        defaultPageSize: 10,
+        showQuickJumper: true,
+        showSizeChanger: true,
+        showTotal: (total, range) => `display ${range[0]} ~ ${range[1]} records,total ${total}`
+      }
+    }
+  },
+  computed: {
+    columns () {
+      let { sortedInfo, filteredInfo } = this  // eslint-disable-line no-unused-vars
+      sortedInfo = sortedInfo || {}
+      filteredInfo = filteredInfo || {}
+      return [{
+        title: 'Variable Code',
+        dataIndex: 'variableCode'
+      }, {
+        title: 'Variable Value',
+        dataIndex: 'variableValue'
+      }, {
+        title: 'Description',
+        dataIndex: 'description'
+      }, {
+        title: 'Create Time',
+        dataIndex: 'createTime',
+        sorter: true,
+        sortOrder: sortedInfo.columnKey === 'createTime' && sortedInfo.order
+      }, {
+        title: 'Modify Time',
+        dataIndex: 'modifyTime',
+        sorter: true,
+        sortOrder: sortedInfo.columnKey === 'modifyTime' && sortedInfo.order
+      }, {
+          title: 'Operation',
+          dataIndex: 'operation',
+          scopedSlots: { customRender: 'operation' }
+      }]
+    }
+  },
+
+  mounted () {
+    this.fetch()
+  },
+
+  methods: {
+    handleView (record) {
+      this.variableInfo.data = record
+      this.variableInfo.visible = true
+    },
+    handleAdd () {
+      this.variableAdd.visible = true
+    },
+    handleVariableAddClose () {
+      this.variableAdd.visible = false
+    },
+    handleVariableAddSuccess () {
+      this.variableAdd.visible = false
+      this.$message.success('add variable successfully')
+      this.search()
+    },
+    handleEdit (record) {
+      this.$refs.variableEdit.setFormValues(record)
+      this.variableEdit.visible = true
+    },
+    handleVariableEditClose () {
+      this.variableEdit.visible = false
+    },
+    handleVariableEditSuccess () {
+      this.variableEdit.visible = false
+      this.$message.success('modify variable successfully')
+      this.search()
+    },
+    handleVariableInfoClose () {
+      this.variableInfo.visible = false
+    },
+    handleDelete (record) {
+      deleteVariable({
+        id: record.id,
+        teamId : record.teamId,
+        variableCode : record.variableCode,
+        variableValue : record.variableValue
+      }).then((resp) => {
+        if (resp.status === 'success') {
+          this.$message.success('delete successful')
+          this.search()
+        } else {
+          this.$swal.fire(
+            'Failed',
+            resp.message,
+            'error'
+          )
+        }
+      })
+    },
+    search () {
+      const { sortedInfo, filteredInfo } = this
+      let sortField, sortOrder
+      if (sortedInfo) {
+        sortField = sortedInfo.field
+        sortOrder = sortedInfo.order
+      }
+      this.fetch({
+        sortField: sortField,
+        sortOrder: sortOrder,
+        ...this.queryParams,
+        ...filteredInfo
+      })
+    },
+    reset () {
+      this.$refs.TableInfo.pagination.current = this.pagination.defaultCurrent
+      if (this.paginationInfo) {
+        this.paginationInfo.current = this.pagination.defaultCurrent
+        this.paginationInfo.pageSize = this.pagination.defaultPageSize
+      }
+      this.filteredInfo = null
+      this.sortedInfo = null
+      this.queryParams = {}
+      this.$refs.createTime.reset()
+      this.fetch()
+    },
+    handleTableChange (pagination, filters, sorter) {
+      this.paginationInfo = pagination
+      this.filteredInfo = filters
+      this.sortedInfo = sorter
+      this.variableInfo.visible = false
+      this.fetch({
+        sortField: sorter.field,
+        sortOrder: sorter.order,
+        ...this.queryParams,
+        ...filters
+      })
+    },
+    fetch (params = {}) {
+      this.loading = true
+      if (this.paginationInfo) {
+        this.$refs.TableInfo.pagination.current = this.paginationInfo.current
+        this.$refs.TableInfo.pagination.pageSize = this.paginationInfo.pageSize
+        params.pageSize = this.paginationInfo.pageSize
+        params.pageNum = this.paginationInfo.current
+      } else {
+        params.pageSize = this.pagination.defaultPageSize
+        params.pageNum = this.pagination.defaultCurrent
+      }
+      if(params.status != null && params.status.length>0) {
+        params.status = params.status[0]
+      } else {
+        delete params.status
+      }
+      list({ ...params }).then((resp) => {
+        const pagination = { ...this.pagination }
+        pagination.total = parseInt(resp.data.total)
+        this.dataSource = resp.data.records
+        this.pagination = pagination
+        this.loading = false
+      })
+    }
+  }
+}
+</script>