zhengnaiwen_citu 3 місяців тому
батько
коміт
d92c6ea0ce

+ 117 - 82
src/views/sandbox/index.vue

@@ -1,125 +1,160 @@
 <template>
   <div class="pa-3 white">
-    <el-tabs v-model="activeName" @tab-click="handleClick">
-      <el-tab-pane label="核算规则" name="Solution">
-        <Solution
-          label="核算规则"
-          ref="Solution"
-          @history="onHistory"
-        >
-          <template #actions="{ row }">
-            <m-button text type="success" size="small" @click="onCalculate(row)">开始测算</m-button>
-            <m-button text type="success" size="small" @click="onSend(row)">发布</m-button>
-          </template>
-        </Solution>
-      </el-tab-pane>
-      <el-tab-pane label="系数导向" name="Coefficient">
-        <Coefficient label="系数导向" ref="Coefficient"></Coefficient>
-      </el-tab-pane>
-      <el-tab-pane label="历史记录" name="History">
-        <History label="历史记录" ref="History" @history="onHistory"></History>
-      </el-tab-pane>
-    </el-tabs>
-    <el-drawer
-      :title="itemData.title"
-      :visible.sync="show"
-      direction="rtl"
+    <m-search class="mb-3" :items="searchItems" v-model="searchValues"></m-search>
+    <m-table
+      card-title="测算规则"
+      :items="items"
+      :headers="headers"
+      :total="total"
+      :page-size="pageInfo.size"
+      :page-current="pageInfo.current"
+      v-bind="$attrs"
+      @page-change="onPageChange"
+      @sort-change="onSortChange"
     >
-      <div class="pa-3" v-loading="loading">
-        <SolutionDetails :item-data="itemData"></SolutionDetails>
-      </div>
-    </el-drawer>
-    <!-- <SalarySolutionEdit ref="salarySolutionEditRefs" @refresh="$refs.Solution.onInit()"></SalarySolutionEdit>
-    <SalarySandboxRules ref="salarySandboxRulesRefs" @refresh="$refs.Solution.onInit()"></SalarySandboxRules> -->
+      <template #card-tools>
+        <m-button type="orange" size="small" icon="el-icon-plus" @click="onOpen('sandboxEditRefs')" >新增</m-button>
+      </template>
+      <template #actions="{ row }">
+        <m-button text type="primary" size="small" @click="onOpen('sandboxDetailsRefs', row)">查看</m-button>
+        <m-button text type="primary" size="small" @click="onOpen('sandboxEditRefs', row)">编辑</m-button>
+        <m-button text type="primary" size="small" @click="onOpen('salarySolutionRulesRefs', row)">规则配置</m-button>
+        <m-button text type="primary" size="small" @click="onOpen('sandboxParamRefs', row)">系数导向</m-button>
+        <m-button text type="success" size="small" @click="onCalculate(row)">开始测算</m-button>
+        <m-button text type="primary" size="small" @click="onCalculateHistory(row)">测算历史</m-button>
+        <m-button text type="success" size="small" @click="onSend(row)">发布</m-button>
+        <m-button text type="danger" size="small" @click="onDel(row)">删除</m-button>
+      </template>
+    </m-table>
+    <SandboxDetails ref="sandboxDetailsRefs"></SandboxDetails>
+    <SandboxEdit ref="sandboxEditRefs" @refresh="onInit"></SandboxEdit>
+    <SalarySolutionRules ref="salarySolutionRulesRefs" @refresh="onInit"></SalarySolutionRules>
+    <SandboxParam ref="sandboxParamRefs" @refresh="onInit"></SandboxParam>
   </div>
 </template>
 
 <script>
-import Solution from '../salary/solution/salarySolution'
-// import SalarySolutionEdit from './salarySandboxEdit.vue'
-// import SalarySandboxRules from './salarySandboxRules.vue'
-import Coefficient from '../salary/solution/salaryCoefficient'
-import History from '../salary/solution/salarySolutionHistory'
-import SolutionDetails from '../salary/solution/components/solutionDetails.vue'
-
 import {
+  deleteSolution,
   sendSalaryRelease,
-  getSolutionDetails
+  getSolutionPage
 } from '@/api/salary'
+import SandboxEdit from './sandboxEdit.vue'
+import SalarySolutionRules from './salarySolutionRules.vue'
+import SandboxDetails from './sandboxDetails.vue'
+import SandboxParam from './sandboxParam.vue'
 export default {
-  name: 'Salary-Sandbox',
-  provide () {
-    return {
-      env: 1
+  name: 'salary-solution-list',
+  props: {
+    permission: {
+      type: Array,
+      default: () => []
     }
   },
   components: {
-    // SalarySolutionEdit,
-    // SalarySandboxRules,
-    Solution,
-    Coefficient,
-    History,
-    SolutionDetails
+    SandboxEdit,
+    SalarySolutionRules,
+    SandboxDetails,
+    SandboxParam
   },
   data () {
     return {
-      itemData: {},
       loading: false,
-      show: false,
-      activeName: 'Solution'
+      searchItems: [
+        {
+          label: '名称',
+          prop: 'title',
+          type: 'input',
+          options: {
+            placeholder: '请输入方案名称'
+          }
+        }
+      ],
+      searchValues: {},
+      items: [],
+      headers: [
+        { label: '名称', prop: 'title' },
+        { label: '描述', prop: 'tag' },
+        { label: '模型名称', prop: 'modelName' },
+        { label: '创建日期', prop: 'createDate' },
+        { label: '操作', prop: 'actions', width: 500, fixed: 'right' }
+      ],
+      total: 0,
+      orders: [{
+        asc: false,
+        column: 'performance_solution_id'
+      }],
+      pageInfo: {
+        current: 1,
+        size: 10
+      }
     }
   },
   mounted () {
-    this.$refs[this.activeName].onInit()
+    this.onInit()
   },
   methods: {
-    async onSend (row) {
+    async onInit () {
       this.loading = true
       try {
-        await sendSalaryRelease({ performanceSolutionId: row.performanceSolutionId })
-        this.$message.success('发布成功')
-        this.$refs.Solution.onInit()
+        const { data } = await getSolutionPage({
+          page: {
+            ...this.pageInfo,
+            orders: this.orders
+          },
+          entity: {
+            ...this.searchQuery,
+            history: 0,
+            env: 1
+          }
+        })
+        this.items = data.records.map(e => e.entity)
+        this.total = data.total
       } catch (error) {
         this.$message.error(error)
       } finally {
         this.loading = false
       }
     },
-    onCalculate () {},
-    onOpen (ref, item) {
-      this.$refs[ref]?.open && this.$refs[ref].open(item)
+    onSearch () {
+      this.pageInfo.current = 1
+      this.onInit()
+    },
+    onPageChange (page) {
+      this.pageInfo.current = page
+      this.onInit()
     },
-    handleClick (tab, event) {
-      this.$refs[this.activeName].onInit()
+    onSortChange (v) {
+      this.orders = v
+      this.onInit()
     },
-    async onHistory ({ performanceSolutionId }) {
-      this.show = true
+    async onSend (row) {
       this.loading = true
       try {
-        const { data } = await getSolutionDetails({ performanceSolutionId })
-        const { performanceSolutionDetailRespCategoryVos, ...obj } = data
-        const resolveData = {
-          ...obj,
-          performanceSolutionDetailRespCategoryVos: performanceSolutionDetailRespCategoryVos.map(e => {
-            e.calculateConfigurations = e.calculateConfigurations.map(item => {
-              if (item.valueCategory !== 0) {
-                item.value = JSON.parse(item.value)
-                return item
-              }
-              return item
-            })
-            return e
-          })
-        }
-        this.itemData = {
-          ...resolveData.entity,
-          ...resolveData
-        }
+        await sendSalaryRelease({ performanceSolutionId: row.performanceSolutionId })
+        this.$message.success('发布成功')
+        this.$refs.Solution.onInit()
       } catch (error) {
         this.$message.error(error)
       } finally {
         this.loading = false
       }
+    },
+    onCalculate () {},
+    onCalculateHistory () {},
+    onOpen (ref, item) {
+      this.$refs[ref]?.open && this.$refs[ref].open(item)
+    },
+    onDel (item) {
+      this.$confirm(`确定删除${item.title}吗?`, '提示').then(async () => {
+        try {
+          await deleteSolution({ performanceSolutionId: item.performanceSolutionId })
+          this.$message.success('删除成功')
+          this.onInit()
+        } catch (error) {
+          this.$message.error(error)
+        }
+      }).catch(_ => {})
     }
   }
 }

+ 152 - 0
src/views/sandbox/salarySolutionRules.vue

@@ -0,0 +1,152 @@
+<template>
+  <m-dialog ref="dialog" title="规则配置" width="1000px" @sure="onSure">
+    <el-form v-loading="loading" label-width="100px">
+      <el-form-item label="名称">
+        <el-tag color="primary">{{ itemData.title }}</el-tag>
+      </el-form-item>
+      <el-form-item label="类型">
+        <template v-slot:default>
+          <el-tabs type="card" addable v-model="editableTabsValue" @edit="onTabsEdit">
+            <el-tab-pane
+              v-for="tab in editableTabs"
+              :key="tab.name"
+              :name="tab.name"
+              :closable="editableTabs.length > 1"
+            >
+              <template v-slot:label>
+                {{ tab.title }}
+                <el-popover
+                  placement="top-start"
+                  width="200"
+                  trigger="click"
+                >
+                  <el-input v-model="tab.title" size="small"></el-input>
+                  <span class="ml-3" slot="reference" @click.stop="">
+                    <i class="el-icon-edit"></i>
+                  </span>
+                </el-popover>
+              </template>
+              <template v-slot:default>
+                <SalarySolutionRulesEdit :item="tab" ref="salarySolutionRulesEditRefs"></SalarySolutionRulesEdit>
+              </template>
+            </el-tab-pane>
+          </el-tabs>
+        </template>
+      </el-form-item>
+    </el-form>
+  </m-dialog>
+</template>
+
+<script>
+import SalarySolutionRulesEdit from './salarySolutionRulesEdit.vue'
+import {
+  saveSolution,
+  getSolutionDetails
+} from '@/api/salary'
+
+export default {
+  name: 'salarySolutionRules',
+  components: {
+    SalarySolutionRulesEdit
+  },
+
+  inject: ['env'],
+  data () {
+    return {
+      editableTabsValue: '1',
+      editableTabs: [],
+      ids: 1,
+      itemData: {},
+      loading: false
+    }
+  },
+  methods: {
+    async open (item) {
+      this.itemData = item
+      this.$refs.dialog.open()
+      this.loading = true
+      this.ids = 1
+      this.editableTabsValue = '1'
+      try {
+        const { data } = await getSolutionDetails({
+          performanceSolutionId: item.performanceSolutionId
+        })
+        if (!data.performanceSolutionDetailRespCategoryVos.length) {
+          this.editableTabs = [{
+            title: '类型1',
+            name: '1'
+          }]
+          return
+        }
+        this.editableTabs = data.performanceSolutionDetailRespCategoryVos.map((e, index) => {
+          const key = String(index + 1)
+          return {
+            title: e.category,
+            name: key
+          }
+        })
+        this.ids = this.editableTabs.length
+        this.$nextTick(() => {
+          this.$refs.salarySolutionRulesEditRefs.forEach((e, index) => {
+            e.setValue(data.performanceSolutionDetailRespCategoryVos[index])
+          })
+        })
+      } catch (error) {
+        this.$message.error(error)
+      } finally {
+        this.loading = false
+      }
+    },
+    onTabsEdit (targetName, action) {
+      if (action === 'add') {
+        this.ids++
+        this.editableTabs.push(
+          { title: '类型' + this.ids, name: String(this.ids) }
+        )
+      }
+      if (action === 'remove') {
+        if (this.editableTabs.length === 1) {
+          this.$message.error('至少保留一个分类')
+          return
+        }
+        const index = this.editableTabs.findIndex(tab => tab.name === targetName)
+        this.editableTabs.splice(index, 1)
+        if (index === 0) {
+          this.editableTabsValue = this.editableTabs[0].name
+          return
+        }
+        if (targetName === this.editableTabsValue) {
+          this.editableTabsValue = this.editableTabs[index - 1].name
+        }
+      }
+    },
+    async onSure () {
+      // 验证
+      Promise.all(this.$refs.salarySolutionRulesEditRefs.map(e => e.valid())).then(async data => {
+        try {
+          await saveSolution({
+            entity: {
+              performanceSolutionId: this.itemData.performanceSolutionId,
+              env: this.env
+            },
+            performanceSolutionDetailRespCategoryVos: data
+          })
+          this.$refs.dialog.close()
+          this.$emit('refresh')
+          this.$message.success('保存成功')
+        } catch (error) {
+          this.$message.error(error)
+        }
+      }).catch(error => {
+        console.log(error)
+        this.editableTabsValue = error.name
+        this.$message.error('请填充完整')
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 193 - 0
src/views/sandbox/salarySolutionRulesEdit.vue

@@ -0,0 +1,193 @@
+<template>
+  <m-card shadow="never">
+    <div>
+      <div class="d-flex mb-3">
+        <div class="boxLabel">参数配置</div>
+        <div class="boxContent">
+          <m-table
+            ref="tableRefs"
+            shadow="naver"
+            clearHeader
+            :items="items"
+            row-key="index"
+            :headers="[
+              { type: 'expand', prop: 'expandProp' },
+              { label: '参数', prop: 'name' },
+              { label: '参数类型', prop: 'valueCategory' },
+              { label: '操作', prop: 'actions' }
+            ]"
+            @expand-change="onExpandChange"
+          >
+            <template #expandProp="{ row, $index }">
+              <SalarySolutionRulesEditParam :ref="`salarySolutionRulesEditParamRefs${$index}`" @assign="onAssign(row, $event)"></SalarySolutionRulesEditParam>
+            </template>
+            <div class="text-center mt-3">
+              <m-button icon="el-icon-plus" type="orange" size="small" @click="onAdd">添加参数</m-button>
+            </div>
+            <template #valueCategory="{ row }">
+              {{ DICT_CATEGORY.find(item => item.value === row.valueCategory)?.label }}
+            </template>
+            <template #actions="scope">
+              <m-button size="small" text type="danger" @click="onDelete(scope)">移除</m-button>
+            </template>
+          </m-table>
+        </div>
+      </div>
+      <div class="d-flex">
+        <div class="boxLabel">计算公式</div>
+        <div class="boxContent">
+          <m-card shadow="never">
+            <Toolbar
+              style="border-bottom: 1px solid #eee"
+              :editor="editor"
+              :defaultConfig="toolbarConfig"
+              :mode="mode"
+            />
+            <Editor
+                style="height: 250px; overflow-y: hidden;"
+                v-model="formulaData"
+                :defaultConfig="editorConfig"
+                :mode="mode"
+                @onCreated="onCreated"
+            />
+          </m-card>
+        </div>
+      </div>
+    </div>
+  </m-card>
+</template>
+
+<script>
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
+import '@wangeditor/editor/dist/css/style.css'
+import SalarySolutionRulesEditParam from './salarySolutionRulesEditParam.vue'
+import {
+  DICT_CATEGORY
+} from '../salary/solution/utils/Dict'
+export default {
+  name: 'salarySolutionRulesRulesEdit',
+  components: { Editor, Toolbar, SalarySolutionRulesEditParam },
+  props: {
+    item: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data () {
+    return {
+      DICT_CATEGORY,
+      editor: null,
+      formulaData: '',
+      toolbarConfig: {
+        toolbarKeys: [
+          'headerSelect',
+          // 分割线
+          '|',
+          'bold',
+          'italic'
+        ]
+      },
+      editorConfig: { placeholder: '请输入内容...' },
+      mode: 'default', // or 'simple'
+      items: []
+    }
+  },
+  beforeDestroy () {
+    const editor = this.editor
+    if (editor == null) return
+    editor.destroy() // 组件销毁时,及时销毁编辑器
+  },
+  methods: {
+    onAssign (row, obj) {
+      Object.assign(row, obj)
+    },
+    onCreated (editor) {
+      this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
+    },
+    onAdd () {
+      const item = {
+        index: this.items.length,
+        name: null,
+        valueCategory: 0
+      }
+      this.items.push(item)
+      const ref = this.$refs.tableRefs.getRefs()
+      ref.toggleRowExpansion(item)
+    },
+    onDelete (scope) {
+      this.$confirm('确定要移除改组参数吗?', '提示').then(e => {
+        this.items.splice(scope.$index, 1)
+      }).catch(_ => {})
+    },
+    valid () {
+      return new Promise((resolve, reject) => {
+        const check = this.items.every(e => {
+          if (!this.$refs[`salarySolutionRulesEditParamRefs${e.index}`]) {
+            return true
+          }
+          return this.$refs[`salarySolutionRulesEditParamRefs${e.index}`].valid()
+        })
+        if (!check) {
+          reject(this.item)
+        } else {
+          resolve(this.getValue())
+        }
+      })
+    },
+    onExpandChange (row, expandedRows) {
+      const open = expandedRows.find(e => e.index === row.index)
+      if (!open) {
+        return
+      }
+      this.$nextTick(() => {
+        this.$refs[`salarySolutionRulesEditParamRefs${row.index}`].setValue(row)
+      })
+    },
+    setValue (data) {
+      this.items = data.calculateConfigurations.map((e, index) => {
+        return {
+          index,
+          ...e
+        }
+      })
+      this.$nextTick(() => {
+        this.formulaData = data.calculateFormulas.length ? data.calculateFormulas[0].content : ''
+      })
+    },
+    getValue () {
+      const calculateConfigurations = this.items.map(e => {
+        if (!this.$refs[`salarySolutionRulesEditParamRefs${e.index}`]) {
+          const { index, ...obj } = e
+          return obj
+        }
+        return this.$refs[`salarySolutionRulesEditParamRefs${e.index}`].getValue()
+      })
+      return {
+        category: this.item.title,
+        calculateConfigurations,
+        calculateFormulas: [
+          {
+            category: this.item.title,
+            content: this.formulaData
+          }
+        ]
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep .el-tree-node__content {
+  height: 40px;
+}
+.boxLabel {
+  width: 80px;
+  text-align: right;
+  padding-right: 20px;
+  box-sizing: border-box;
+}
+.boxContent {
+  flex: 1;
+}
+</style>

+ 240 - 0
src/views/sandbox/salarySolutionRulesEditParam.vue

@@ -0,0 +1,240 @@
+<template>
+  <m-form ref="form" label-width="100px" :items="formItems" v-model="formValues">
+    <template #content>
+      <m-table
+        clearHeader
+        shadow="never"
+        :headers="headers"
+        :items="items"
+      >
+        <template #name="{ row }">
+          <el-input v-model="row.name" placeholder="请输入参数名称" size="small"></el-input>
+        </template>
+        <template #value="{ row }">
+          <el-input v-model="row.value" placeholder="请输入参数值" size="small"></el-input>
+        </template>
+        <template #actions="{ $index }">
+
+          <m-button circle type="danger" icon="el-icon-delete" size="mini" @click="onRemove($index)"></m-button>
+        </template>
+        <div class="text-center">
+          <m-button type="orange" size="mini" @click="onAdd">添加一组数据</m-button>
+        </div>
+      </m-table>
+    </template>
+  </m-form>
+</template>
+
+<script>
+import {
+  DICT_CATEGORY
+} from '../salary/solution/utils/Dict'
+export default {
+  name: 'salarySolutionRulesEditParam',
+  data () {
+    return {
+      headers: [
+        { label: '参数名称', prop: 'name' },
+        { label: '参数值', prop: 'value' },
+        { label: '操作', prop: 'actions' }
+      ],
+      items: [],
+      id: 0,
+      formValues: {
+        name: null,
+        valueCategory: 0,
+        miniValue: null,
+        maxValue: null,
+        value: null
+      }
+    }
+  },
+  computed: {
+    formItems () {
+      const style = 'margin-bottom: 12px'
+      const common = [
+        {
+          label: '参数名称',
+          prop: 'name',
+          type: 'input',
+          options: {
+            placeholder: '请输入参数名称',
+            size: 'small'
+          },
+          rules: [
+            { required: true, message: '请输入参数名称', trigger: 'blur' }
+          ],
+          style
+        },
+        {
+          label: '参数类型',
+          prop: 'valueCategory',
+          type: 'select',
+          options: {
+            placeholder: '请选择参数类型',
+            items: DICT_CATEGORY,
+            size: 'small'
+          },
+          rules: [
+            { required: true, message: '请选择参数类型', trigger: 'change' }
+          ],
+          style
+        }
+      ]
+      const maxAndMin = [
+        {
+          label: '最小值',
+          prop: 'miniValue',
+          type: 'number',
+          options: {
+            size: 'small',
+            step: 0.1
+          },
+          style
+        },
+        {
+          label: '最大值',
+          prop: 'maxValue',
+          type: 'number',
+          options: {
+            size: 'small',
+            step: 0.1
+          },
+          style
+        }
+      ]
+      if (this.formValues.valueCategory === 0) {
+        return [
+          ...common,
+          ...maxAndMin,
+          {
+            label: '初始值',
+            prop: 'value',
+            type: 'number',
+            options: {
+              size: 'small',
+              step: 0.1
+            },
+            style
+          }
+        ]
+      }
+      if (this.formValues.valueCategory === 2) {
+        return [
+          ...common,
+          ...maxAndMin,
+          {
+            label: '数据',
+            prop: 'content',
+            style
+          }
+        ]
+      }
+      return [
+        ...common,
+        {
+          label: '数据',
+          prop: 'content',
+          style
+        }
+      ]
+    }
+  },
+  methods: {
+    setValue (item) {
+      console.log(item)
+      const {
+        valueCategory,
+        value,
+        maxValue,
+        miniValue,
+        ...obj
+      } = item
+      this.formValues = {
+        ...obj,
+        maxValue: !maxValue ? undefined : Number(maxValue),
+        miniValue: !miniValue ? undefined : Number(miniValue),
+        valueCategory,
+        value: valueCategory === 0 ? value : JSON.parse(value)
+      }
+
+      if (valueCategory !== 0) {
+        this.id = 0
+        this.items = JSON.parse(value).map(e => {
+          return {
+            name: e.name,
+            value: e.value,
+            index: this.id++
+          }
+        })
+      }
+    },
+    onAdd () {
+      this.items.push({
+        name: null,
+        valueCategory: 0,
+        miniValue: null,
+        maxValue: null,
+        value: null,
+        index: this.id++
+      })
+    },
+    onRemove (index) {
+      this.items.splice(index, 1)
+    },
+    valid () {
+      if (!this.formValues.name) {
+        return
+      }
+      if (this.formValues.valueCategory !== 0) {
+        return this.items.every(e => {
+          return (e.name === null && e.value === null) || (e.name !== null && e.value !== null)
+        })
+      }
+      return true
+    },
+    getValue () {
+      const {
+        name,
+        valueCategory,
+        miniValue,
+        maxValue,
+        value
+      } = this.formValues
+      if (valueCategory === 0) {
+        return {
+          name,
+          valueCategory,
+          miniValue: miniValue ?? null,
+          maxValue: maxValue ?? null,
+          value
+        }
+      }
+      const items = this.items.map(e => {
+        return {
+          name: e.name,
+          value: e.value
+        }
+      })
+      if (valueCategory === 1) {
+        return {
+          name,
+          valueCategory,
+          value: JSON.stringify(items)
+        }
+      }
+      return {
+        name,
+        valueCategory,
+        miniValue: miniValue ?? null,
+        maxValue: maxValue ?? null,
+        value: JSON.stringify(items)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 57 - 0
src/views/sandbox/sandboxDetails.vue

@@ -0,0 +1,57 @@
+<template>
+  <m-dialog title="规则详情" ref="dialog">
+    <SolutionDetails :item-data="itemData"></SolutionDetails>
+  </m-dialog>
+</template>
+
+<script>
+import SolutionDetails from '../salary/solution/components/solutionDetails.vue'
+import {
+  getSolutionDetails
+} from '@/api/salary'
+export default {
+  name: 'salarySolutionDetails',
+  components: {
+    SolutionDetails
+  },
+  data () {
+    return {
+      loading: false,
+      itemData: {}
+    }
+  },
+  methods: {
+    async open (item) {
+      this.$refs.dialog.open()
+      this.loading = true
+      try {
+        const { data } = await getSolutionDetails({
+          performanceSolutionId: item.performanceSolutionId
+        })
+        data.performanceSolutionDetailRespCategoryVos = data.performanceSolutionDetailRespCategoryVos.map(e => {
+          e.calculateConfigurations = e.calculateConfigurations.map(item => {
+            if (item.valueCategory !== 0) {
+              item.value = JSON.parse(item.value)
+              return item
+            }
+            return item
+          })
+          return e
+        })
+        this.itemData = {
+          ...data.entity,
+          ...data
+        }
+      } catch (error) {
+        this.$message.error(error)
+      } finally {
+        this.loading = false
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 195 - 0
src/views/sandbox/sandboxEdit.vue

@@ -0,0 +1,195 @@
+<template>
+  <m-dialog ref="dialog" :title="itemData.entity ? '编辑' : '新增'" @sure="onSure">
+    <m-form ref="form" v-model="formQuery" :items="items" v-loading="loading"></m-form>
+  </m-dialog>
+</template>
+
+<script>
+import {
+  saveSolution,
+  getSolutionDetails
+} from '@/api/salary'
+import {
+  getPostNameByOrganizationNo
+// getOrganizationAtlasPostName
+} from '@/api/system'
+import { mapGetters } from 'vuex'
+export default {
+  name: 'sandbox-edit',
+  data () {
+    return {
+      loading: false,
+      formQuery: {
+        title: null,
+        tag: null,
+        organizationNo: null,
+        organizationName: null,
+        postNames: [],
+        rewriteType: 0
+      },
+      postNamesItems: [],
+      itemData: {}
+    }
+  },
+  computed: {
+    ...mapGetters(['organizationTree']),
+    items () {
+      return [
+        {
+          label: '方案名称',
+          prop: 'title',
+          type: 'input',
+          rules: [
+            { required: true, message: '请输入方案名称', trigger: 'blur' }
+          ],
+          options: {
+            placeholder: '请输入方案名称'
+          }
+        },
+        {
+          label: '方案描述',
+          prop: 'tag',
+          type: 'input',
+          options: {
+            placeholder: '请输入方案描述'
+          }
+        },
+        {
+          label: '模型名称',
+          prop: 'modelName',
+          type: 'input',
+          options: {
+            placeholder: '请输入模型名称'
+          },
+          rules: [
+            { required: true, message: '请输入模型名称', trigger: 'blur' }
+          ]
+        },
+        {
+          label: '绩效机构',
+          prop: 'organizationNo',
+          type: 'cascader',
+          options: {
+            ref: 'organizationNo',
+            filterable: true,
+            clearable: true,
+            placeholder: '请选择机构',
+            options: this.organizationTree,
+            showAllLevels: false,
+            props: {
+              emitPath: false,
+              checkStrictly: true,
+              value: 'organizationNo',
+              label: 'organizationName',
+              children: 'child'
+            }
+          },
+          handles: {
+            change: this.onchange
+          },
+          rules: [
+            { required: true, message: '请选择绩效机构', trigger: 'change' }
+          ]
+        },
+        {
+          label: '绩效职务',
+          prop: 'postNames',
+          type: 'select',
+          options: {
+            placeholder: '请选择绩效职务',
+            filterable: true,
+            collapseTags: true,
+            multiple: true,
+            items: this.postNamesItems
+          },
+          rules: [
+            { required: true, message: '请选择绩效职务', trigger: 'change' }
+          ]
+        }
+      ]
+    }
+  },
+
+  inject: ['env'],
+  methods: {
+    async open (item) {
+      this.$refs.dialog.open()
+      this.formQuery.title = item?.title ?? null
+      this.formQuery.tag = item?.tag ?? null
+      this.formQuery.organizationNo = item?.organizationNo ?? null
+      this.formQuery.organizationName = item?.organizationName ?? null
+      this.formQuery.postNames = []
+      this.itemData = {}
+      this.$nextTick(() => {
+        this.$refs.form.clearValidate()
+      })
+      if (!item) {
+        return
+      }
+      this.loading = true
+      try {
+        const { data } = await getSolutionDetails({
+          performanceSolutionId: item.performanceSolutionId
+        })
+        this.itemData = data
+        this.onchange(this.formQuery.organizationNo)
+        this.formQuery.postNames = item.postNames
+      } catch (error) {
+        this.$message.error(error)
+      } finally {
+        this.loading = false
+      }
+    },
+    async onchange (organizationNo) {
+      const nodes = this.$refs.form.$refs.organizationNo.getCheckedNodes()
+      this.formQuery.organizationName = nodes[0].data.organizationName
+      this.formQuery.postNames = []
+      if (!organizationNo) {
+        return
+      }
+      try {
+        const { data } = await getPostNameByOrganizationNo({
+          organizationNo
+        })
+        this.postNamesItems = (data && data.map(e => {
+          return {
+            label: e,
+            value: e
+          }
+        })) || []
+      } catch (error) {
+        this.$message.error(error)
+      }
+    },
+    onSure () {
+      this.$refs.form.validate(async valid => {
+        if (valid) {
+          const query = {
+            entity: {
+              performanceSolutionId: this.itemData.entity?.performanceSolutionId,
+              env: this.env,
+              ...this.formQuery
+            },
+            performanceSolutionDetailRespCategoryVos: this.itemData.performanceSolutionDetailRespCategoryVos
+          }
+          this.loading = true
+          try {
+            await saveSolution(query)
+            this.$refs.dialog.close()
+            this.$emit('refresh')
+            this.$message.success('保存成功')
+          } catch (error) {
+            this.$message.error(error)
+          } finally {
+            this.loading = false
+          }
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 136 - 0
src/views/sandbox/sandboxParam.vue

@@ -0,0 +1,136 @@
+<template>
+  <m-dialog ref="dialog" title="系数管理" @sure="onSure">
+    <el-form v-loading="loading" label-width="100px">
+      <el-form-item label="名称">
+        <el-tag color="primary">{{ itemData.title }}</el-tag>
+      </el-form-item>
+      <el-form-item label="系数管理">
+        <m-card shadow="never">
+          <template v-if="items.length" v-slot:default>
+            <el-tabs v-model="activeNames" >
+              <el-tab-pane
+                v-for="item in items"
+                :key="item.category"
+                :title="item.category"
+                :name="item.category"
+                :label="item.category"
+              >
+                <el-form-item
+                  label-width="150px"
+                  v-for="(calculateConfiguration, index) in item.calculateConfigurations"
+                  :key="index"
+                  :label="calculateConfiguration.name"
+                  class="mt-3"
+                >
+                  <template v-if="calculateConfiguration.valueCategory === 0">
+                    <el-slider
+                      input-size="small"
+                      v-model="calculateConfiguration.value"
+                      :min="calculateConfiguration.miniValue"
+                      :max="calculateConfiguration.maxValue"
+                      :step="0.01"
+                      show-input
+                    >
+                    </el-slider>
+                  </template>
+                  <template v-else>
+                    <m-table
+                      clearHeader
+                      shadow="never"
+                      :headers="[
+                        { label: '名称', prop: 'name' },
+                        { label: '值', prop: 'value' }
+                      ]"
+                      :items="calculateConfiguration.value"
+                    >
+                      <template #value="{ row }">
+                        <el-input v-model="row.value" size="small" placeholder="请输入对应的值"></el-input>
+                      </template>
+                    </m-table>
+                  </template>
+                </el-form-item>
+              </el-tab-pane>
+            </el-tabs>
+          </template>
+          <template v-if="!items.length">
+            <m-empty description="尚未配置系数"></m-empty>
+          </template>
+        </m-card>
+      </el-form-item>
+    </el-form>
+  </m-dialog>
+</template>
+
+<script>
+import {
+  getSolutionDetails,
+  saveSolution
+} from '@/api/salary'
+export default {
+  name: 'salarySolutionParam',
+  data () {
+    return {
+      activeNames: null,
+      loading: false,
+      itemData: {},
+      items: []
+    }
+  },
+  methods: {
+    async open (item) {
+      this.itemData = item
+      this.$refs.dialog.open()
+      this.loading = true
+      try {
+        const { data } = await getSolutionDetails({
+          performanceSolutionId: item.performanceSolutionId
+        })
+
+        this.items = data.performanceSolutionDetailRespCategoryVos.map(e => {
+          e.calculateConfigurations = e.calculateConfigurations.map(item => {
+            if (item.valueCategory !== 0) {
+              item.value = JSON.parse(item.value)
+              return item
+            }
+
+            item.maxValue = !item.maxValue ? Infinity : Number(item.maxValue)
+            item.miniValue = !item.miniValue ? -Infinity : Number(item.miniValue)
+
+            if (item.value) {
+              item.value = Number(item.value)
+            }
+            return item
+          })
+          return e
+        })
+        this.activeNames = this.items[0]?.category
+      } catch (error) {
+        this.$message.error(error)
+      } finally {
+        this.loading = false
+      }
+    },
+    async onSure () {
+      const query = {
+        entity: {
+          performanceSolutionId: this.itemData.performanceSolutionId,
+          env: 1
+        },
+        performanceSolutionDetailRespCategoryVos: this.items
+      }
+      try {
+        await saveSolution(query)
+        this.$refs.dialog.close()
+        this.$emit('refresh')
+        this.$message.success('保存成功')
+      } catch (error) {
+        this.$message.error(error)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>