addPermanceTemp.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <template>
  2. <!--新增履约模板-->
  3. <Drawer :title="`${isUpdate() ? '修改' : '新增'}履约模板`" :placement="'right'" :visible="visible" @cancel="cancel" :class="[position === 'top' ? 'top600' : 'delistingBottom']">
  4. <a-spin :spinning="loading">
  5. <div class="listed">
  6. <a-form class="inlineForm dialogForm" ref="formRef" :model="formState" :rules="rules">
  7. <div class="formBar">
  8. <a-row :gutter="24">
  9. <a-col :span="24">
  10. <a-form-item label="模版名称" name="name">
  11. <a-input class="commonInput" v-model:value="formState.name" placeholder="请输入模版名称" style="width: 260px" />
  12. </a-form-item>
  13. </a-col>
  14. </a-row>
  15. <a-row class="tableTitle">
  16. <a-col :span="7">步骤类型</a-col>
  17. <a-col :span="6">步骤值(%)</a-col>
  18. <a-col :span="7">距离上一步天数</a-col>
  19. <a-col :span="4">操作</a-col>
  20. </a-row>
  21. <a-row class="tableContent" v-for="(parent, index) in formState.domains" :key="parent">
  22. <a-col :span="7">
  23. <a-form-item :name="['domains', index, 'steptypeid']" :rules="rules.domains.steptypeid">
  24. <a-select class="inlineFormSelect dialogTableSelect" style="width: 158px" placeholder="请选择" @change="stepTypeChange(parent)" v-model:value="parent.steptypeid">
  25. <a-select-option v-for="item in list" :key="item.steptypeid" :value="item.steptypeid">{{ item.steptypename }} </a-select-option>
  26. </a-select>
  27. </a-form-item>
  28. </a-col>
  29. <a-col :span="6">
  30. <a-form-item :name="['domains', index, 'stepvalue']" :rules="rules.domains.stepvalue">
  31. <a-input-number class="commonInput dialogTableInput" :disabled="isSummary(parent)" :max="100" style="width: 135px" type="number" v-model:value="parent.stepvalue"></a-input-number>
  32. </a-form-item>
  33. </a-col>
  34. <a-col :span="7">
  35. <a-form-item :name="['domains', index, 'stepdays']" :rules="rules.domains.stepdays">
  36. <a-input-number class="commonInput dialogTableInput" style="width: 157px" type="number" v-model:value="parent.stepdays"></a-input-number>
  37. </a-form-item>
  38. </a-col>
  39. <a-col :span="4">
  40. <svg class="icon svg-icon" @click="deleteTemp(index)" v-if="showDeleteTemp(index)" aria-hidden="true">
  41. <use xlink:href="#icon-shanchu" />
  42. </svg>
  43. <PlusCircleOutlined v-if="showAddTempBtn(index)" @click="addTemp()" />
  44. </a-col>
  45. </a-row>
  46. <div class="noticeTip">
  47. <div>注意事项:</div>
  48. <div>1、买方支付汇总值必须为100%;</div>
  49. <div>2、卖方收款汇总值必须为100%;</div>
  50. <div>3、配置卖方收款之前要有买方支付,且其值不能多于买方支付的值。</div>
  51. </div>
  52. </div>
  53. <a-row :gutter="24">
  54. <a-col :span="24" class="fixedBtns">
  55. <a-form-item class="btnCenter">
  56. <a-button class="listedBtn" @click="submit">确定</a-button>
  57. </a-form-item>
  58. </a-col>
  59. </a-row>
  60. </a-form>
  61. </div>
  62. </a-spin>
  63. </Drawer>
  64. </template>
  65. <script lang="ts">
  66. import { Des } from '@/common/components/commonDes';
  67. import Drawer from '@/common/components/drawer/index.vue';
  68. import { requestResultLoadingAndInfo } from '@/common/methods/request/resultInfo';
  69. import { validateAction } from '@/common/setup/form';
  70. import { _closeModal } from '@/common/setup/modal/modal';
  71. import { useQueryData } from '@/common/setup/request';
  72. import { getUserId } from '@/services/bus/user';
  73. import { geLoginID_number } from '@/services/bus/login';
  74. import { queryWrPerformanceStepType } from '@/services/go/wrtrade';
  75. import { QueryPermancePlanTmpRsp, QueryWrPerformanceStepTypeRsp } from '@/services/go/wrtrade/interface';
  76. import { addPerformanceTemp } from '@/services/proto/performance';
  77. import { AddPerformanceTemp, PerfomanceStempTempInfo } from '@/services/proto/performance/interface';
  78. import { MinusOutlined, PlusCircleOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons-vue';
  79. import { message } from 'ant-design-vue';
  80. import { defineComponent, PropType, reactive, Ref, ref, UnwrapRef } from 'vue';
  81. import { PermanceTemp, PermanceTempForm } from './interface';
  82. // 初始化 模板
  83. const initTemp = (): PermanceTemp => {
  84. return { steptypeid: undefined, stepindex: 1, stepvalue: null, isauto: 0, remark: '', stepdays: null, steptypename: '' };
  85. };
  86. // 新增 模板
  87. const useAddTemp = (formState: UnwrapRef<PermanceTempForm>) => {
  88. function addTemp() {
  89. const temp = initTemp();
  90. formState.domains.push(temp);
  91. }
  92. function showAddTempBtn(index: number) {
  93. return index === formState.domains.length - 1;
  94. }
  95. return { addTemp, showAddTempBtn };
  96. };
  97. // 删除 模板
  98. const useDeleteTemp = (formState: UnwrapRef<PermanceTempForm>) => {
  99. function deleteTemp(index: number) {
  100. formState.domains.splice(index, 1);
  101. }
  102. function showDeleteTemp(index: number) {
  103. return index !== 0;
  104. }
  105. return { deleteTemp, showDeleteTemp };
  106. };
  107. // 编辑 模板
  108. const useEditeTemp = (typeList: Ref<QueryWrPerformanceStepTypeRsp[]>) => {
  109. function findTemp(item: PermanceTemp) {
  110. return typeList.value.find((el) => el.steptypeid === item.steptypeid);
  111. }
  112. // 汇总
  113. function isSummary({ steptypename }: PermanceTemp) {
  114. return steptypename === '买方支付' || steptypename === '卖方收款' ? false : true;
  115. }
  116. // 切换步骤类型
  117. function stepTypeChange(item: PermanceTemp) {
  118. const stepType = findTemp(item)!;
  119. item.steptypename = stepType.steptypename;
  120. item.stepvalue = null;
  121. // 卖方收款汇总值必须为100%;
  122. // if (!isSummary(item)) {
  123. // item.stepvalue = 100;
  124. // }
  125. }
  126. // function isDisable(temp: PermanceTemp) {
  127. // const { steptypename } = findTemp(temp)!;
  128. // return steptypename === '买方支付' || steptypename === '卖方收款';
  129. // }
  130. return { isSummary, stepTypeChange };
  131. };
  132. // 表单
  133. const userForm = (selectedRow: QueryPermancePlanTmpRsp) => {
  134. const formRef = ref();
  135. const formState: UnwrapRef<PermanceTempForm> = reactive({ domains: [initTemp()], name: '' });
  136. function isUpdate() {
  137. return !!selectedRow.autoid;
  138. }
  139. if (isUpdate()) {
  140. // 有值代表 修改,无值是新增
  141. formState.name = selectedRow.templatename;
  142. formState.domains = selectedRow.LstStep.map((el) => {
  143. const stepvalue = el.stepvalue ? +(el.stepvalue * 100).toFixed(0) : el.stepvalue;
  144. return { ...el, stepvalue };
  145. });
  146. }
  147. const findIndex = (name: string): number => formState.domains.findIndex((temp) => temp.steptypename === name);
  148. const total = (name: string): number =>
  149. formState.domains
  150. .filter((temp) => temp.steptypename === name)
  151. .reduce((acc, cur) => {
  152. const { stepvalue } = cur;
  153. return stepvalue ? acc + stepvalue : acc;
  154. }, 0);
  155. // 配置卖方收款之前要有买方支付
  156. function validateSepeTypeId(value: any) {
  157. const sellIndex = findIndex('卖方收款');
  158. if (sellIndex !== -1) {
  159. const buyIndex = findIndex('买方支付');
  160. if (buyIndex > sellIndex) {
  161. return Promise.reject('配置卖方收款之前要有买方支付');
  162. }
  163. }
  164. return Promise.resolve();
  165. }
  166. function validateStepValue(rule: any, value: number) {
  167. if (rule.field) {
  168. const arr = rule.fullField.split('.');
  169. if (Array.isArray(arr) && arr.length === 3) {
  170. const index = +arr[1];
  171. const item = formState.domains[index];
  172. const steptypename = item.steptypename;
  173. if (steptypename === '买方支付' || steptypename === '卖方收款') {
  174. if (!value) {
  175. return Promise.reject('请输入步骤值');
  176. }
  177. const buyTotal = total('买方支付');
  178. const sellTotal = total('卖方收款');
  179. if (findIndex('买方支付') !== -1) {
  180. if (buyTotal !== 100) {
  181. return Promise.reject('买方支付汇总值必须为100%');
  182. }
  183. }
  184. if (findIndex('卖方收款') !== -1) {
  185. if (sellTotal !== 100) {
  186. return Promise.reject('卖方收款汇总值必须为100%');
  187. }
  188. }
  189. if (sellTotal > buyTotal) {
  190. return Promise.reject('卖方收款不能多于买方付款');
  191. }
  192. }
  193. }
  194. }
  195. return Promise.resolve();
  196. }
  197. const rules = {
  198. name: { required: true, message: '请输入模版名称', trigger: 'blur' },
  199. domains: {
  200. steptypeid: [
  201. { required: true, message: '请选择步骤类型' },
  202. { required: true, validator: validateSepeTypeId },
  203. ],
  204. stepvalue: [
  205. // { required: true, message: '请输入步骤值', trigger: 'blur', type: 'number' },
  206. { required: true, validator: validateStepValue, type: 'number' },
  207. ],
  208. stepdays: { required: true, message: '请输入距离上一步天数', trigger: 'blur', type: 'number' },
  209. },
  210. };
  211. return { formRef, formState, rules, isUpdate, findIndex };
  212. };
  213. export default defineComponent({
  214. emits: ['cancel', 'update'],
  215. name: 'warehouse_receipt_trade_blocs_delisting',
  216. props: {
  217. selectedRow: {
  218. type: Object as PropType<QueryPermancePlanTmpRsp>,
  219. default: {},
  220. },
  221. position: {
  222. type: String,
  223. default: 'top',
  224. },
  225. },
  226. components: { Des, Drawer, PlusOutlined, MinusOutlined, SearchOutlined, PlusCircleOutlined },
  227. setup(props, context) {
  228. const { visible, cancel } = _closeModal(context);
  229. const loading = ref<boolean>(false);
  230. // 步骤类型
  231. const { list } = useQueryData<QueryWrPerformanceStepTypeRsp>(queryWrPerformanceStepType);
  232. const { formRef, formState, rules, isUpdate, findIndex } = userForm(props.selectedRow);
  233. function submit() {
  234. validateAction<PermanceTempForm>(formRef, formState).then((res) => {
  235. if (findIndex('仓单转移') == -1) {
  236. message.error('未配置仓单转移步骤');
  237. return;
  238. }
  239. const performancesteps: PerfomanceStempTempInfo[] = res.domains.map((el, i) => {
  240. const { steptypeid, stepvalue, stepdays, remark } = el;
  241. return {
  242. steptypeid: steptypeid as number,
  243. stepvalue: stepvalue as number,
  244. stepdays: stepdays as number,
  245. isauto: 1,
  246. remark,
  247. stepindex: i + 1,
  248. };
  249. });
  250. const param: AddPerformanceTemp = {
  251. autoid: isUpdate() ? props.selectedRow.autoid : 0,
  252. templatename: res.name,
  253. userid: getUserId(),
  254. performancesteps,
  255. creatorid: geLoginID_number()!,
  256. };
  257. const msg: [string, string] = isUpdate() ? ['修改履约模板成功', '修改履约模板失败:'] : ['新增履约模板成功', '新增履约模板失败:'];
  258. requestResultLoadingAndInfo(addPerformanceTemp, param, loading, msg).then(() => {
  259. cancel(true);
  260. });
  261. });
  262. }
  263. return {
  264. loading,
  265. list,
  266. formRef,
  267. formState,
  268. rules,
  269. submit,
  270. ...useAddTemp(formState),
  271. ...useDeleteTemp(formState),
  272. ...useEditeTemp(list),
  273. cancel,
  274. visible,
  275. isUpdate,
  276. };
  277. },
  278. });
  279. </script>
  280. <style lang="less" scoped>
  281. .listed {
  282. padding: 18px 10px 0;
  283. .formBar {
  284. height: calc(100% - 70px);
  285. padding: 0;
  286. .ant-row.tableTitle {
  287. .ant-col {
  288. height: 40px;
  289. line-height: 40px;
  290. white-space: nowrap;
  291. text-align: center;
  292. color: @m-grey1;
  293. font-size: 14px;
  294. border-right: 1px solid @m-black6;
  295. border-top: 1px solid @m-black6;
  296. border-bottom: 1px solid @m-black6;
  297. }
  298. .ant-col:first-child {
  299. border-left: 1px solid @m-black6;
  300. }
  301. }
  302. .ant-row.tableContent {
  303. .ant-col {
  304. // height: 40px;
  305. line-height: 40px;
  306. text-align: center;
  307. border-right: 1px solid @m-black6;
  308. border-bottom: 1px solid @m-black6;
  309. .icon {
  310. color: @m-yellow8;
  311. font-size: 20px;
  312. cursor: pointer;
  313. }
  314. .anticon {
  315. font-size: 20px;
  316. color: @m-blue0;
  317. margin-left: 14px;
  318. cursor: pointer;
  319. }
  320. }
  321. .ant-col:first-child {
  322. border-left: 1px solid @m-black6;
  323. }
  324. }
  325. .noticeTip {
  326. width: 100%;
  327. margin-top: 15px;
  328. .flex;
  329. flex-direction: column;
  330. > div {
  331. height: 30px;
  332. line-height: 30px;
  333. font-size: 14px;
  334. color: @m-grey1;
  335. }
  336. }
  337. }
  338. .ant-form.inlineForm {
  339. .ant-row.ant-form-item {
  340. margin-bottom: 5px;
  341. }
  342. .ant-row.ant-form-item.ant-form-item-with-help {
  343. white-space: nowrap;
  344. margin-left: 2px;
  345. }
  346. }
  347. .dialogTableSelect,
  348. .dialogTableInput {
  349. margin-left: 2px;
  350. }
  351. }
  352. </style>