index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
  4. <el-form-item label="项目来源" prop="sourceFrom">
  5. <el-select v-model="queryParams.sourceFrom" placeholder="请选择项目来源" clearable filterable @change="handleQuery">
  6. <el-option
  7. v-for="item in projectTypeList"
  8. :key="item.dictCode || item.dictValue || item.value"
  9. :label="item.dictLabel || item.label"
  10. :value="item.sourceFrom || item.dictValue || item.value"
  11. />
  12. </el-select>
  13. </el-form-item>
  14. <el-form-item label="项目编号" prop="projectPid">
  15. <el-input v-model="queryParams.projectPid" placeholder="请输入项目编号" clearable @keyup.enter.native="handleQuery" />
  16. </el-form-item>
  17. <el-form-item label="项目名称" prop="projectName">
  18. <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery" />
  19. </el-form-item>
  20. <!-- <el-form-item label="应用名称" prop="appId">
  21. <el-select v-model="queryParams.appId">
  22. <el-option
  23. v-for="item in appList"
  24. :key="item.appId"
  25. :label="item.name"
  26. :value="item.appId"
  27. />
  28. </el-select>
  29. </el-form-item> -->
  30. <el-form-item>
  31. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  32. <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  33. </el-form-item>
  34. </el-form>
  35. <el-row :gutter="10" class="mb8">
  36. <!-- <el-col :span="1.5">
  37. <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
  38. v-hasPermi="['system:project:add']">新增</el-button>
  39. </el-col> -->
  40. <!-- <el-col :span="1.5">
  41. <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"
  42. v-hasPermi="['system:project:edit']">修改</el-button>
  43. </el-col> -->
  44. <!-- <el-col :span="1.5">
  45. <el-button
  46. type="danger"
  47. plain
  48. icon="el-icon-delete"
  49. size="mini"
  50. :disabled="multiple"
  51. @click="handleDelete"
  52. v-hasPermi="['system:project:remove']"
  53. >删除</el-button>
  54. </el-col> -->
  55. <el-col :span="1.5">
  56. <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
  57. v-hasPermi="['system:project:export']">导出</el-button>
  58. </el-col>
  59. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  60. </el-row>
  61. <el-table v-loading="loading" :data="projectList" @selection-change="handleSelectionChange">
  62. <el-table-column type="selection" width="55" align="center" />
  63. <el-table-column label="项目来源" width="90" align="center" prop="sourceFromName" />
  64. <el-table-column label="项目编号" align="center" prop="projectPid" min-width="90">
  65. <template slot-scope="scope">
  66. <el-tooltip placement="top" popper-class="pid-tooltip">
  67. <div slot="content" class="pid-tooltip-content">{{ scope.row.projectPid }}</div>
  68. <span class="pid-ellipsis">{{ scope.row.projectPid }}</span>
  69. </el-tooltip>
  70. </template>
  71. </el-table-column>
  72. <el-table-column label="项目名称" align="center" prop="projectName" min-width="320">
  73. <template slot-scope="scope">
  74. <el-tooltip placement="top" popper-class="pid-tooltip">
  75. <div slot="content" class="pid-tooltip-content">{{ scope.row.projectName }}</div>
  76. <span class="name-ellipsis">{{ scope.row.projectName }}</span>
  77. </el-tooltip>
  78. </template>
  79. </el-table-column>
  80. <!-- <el-table-column label="应用名称" align="center" prop="name" /> -->
  81. <el-table-column label="应用名称" align="center" min-width="360">
  82. <template slot-scope="scope">
  83. <div class="app-tags">
  84. <el-tag
  85. v-for="item in (scope.row.name || [])"
  86. :key="item"
  87. size="mini"
  88. effect="plain"
  89. :class="['app-tag', getTagColorClass(item)]"
  90. disable-transitions
  91. >{{ item }}</el-tag>
  92. </div>
  93. </template>
  94. </el-table-column>
  95. <el-table-column label="项目创建时间" width="160" align="center" prop="createTime" />
  96. <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
  97. <template slot-scope="scope">
  98. <!-- <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
  99. v-hasPermi="['system:project:edit']">修改</el-button> -->
  100. <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
  101. v-hasPermi="['system:project:remove']">删除</el-button>
  102. </template>
  103. </el-table-column>
  104. </el-table>
  105. <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
  106. @pagination="getList" />
  107. <!-- 添加或修改项目对话框 -->
  108. <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
  109. <el-form ref="form" :model="form" :rules="rules" label-width="80px">
  110. <!-- <el-form-item label="项目编号" prop="projectPid">
  111. <el-input v-model="form.projectPid" placeholder="请输入项目编号" />
  112. </el-form-item> -->
  113. <el-form-item label="项目名称" prop="projectName">
  114. <el-input v-model="form.projectName" placeholder="请输入项目名称" />
  115. </el-form-item>
  116. <!-- <el-form-item label="类型" prop="projectType">
  117. <el-select v-model="form.projectType" clearable>
  118. <el-option v-for="item in projectTypeList" :key="item.dictCode" :label="item.dictLabel"
  119. :value="item.dictCode" />
  120. </el-select>
  121. </el-form-item> -->
  122. <el-form-item label="应用名称" prop="appId">
  123. <el-select v-model="form.appId" multiple style="width: 100%;">
  124. <el-option
  125. v-for="item in appList"
  126. :key="item.appId || item.name"
  127. :label="item.name"
  128. :value="item.appId"
  129. />
  130. </el-select>
  131. </el-form-item>
  132. </el-form>
  133. <div slot="footer" class="dialog-footer">
  134. <el-button type="primary" @click="submitForm">确 定</el-button>
  135. <el-button @click="cancel">取 消</el-button>
  136. </div>
  137. </el-dialog>
  138. </div>
  139. </template>
  140. <script>
  141. import { listProject, getProject, delProject, addProject, updateProject, appList } from "@/api/system/project";
  142. import { getDicts } from "@/api/system/dict/data";
  143. import { getInfo } from "@/api/login";
  144. export default {
  145. name: "Project",
  146. data() {
  147. return {
  148. // 遮罩层
  149. loading: true,
  150. // 选中数组
  151. ids: [],
  152. // 非单个禁用
  153. single: true,
  154. // 非多个禁用
  155. multiple: true,
  156. // 显示搜索条件
  157. showSearch: true,
  158. // 总条数
  159. total: 0,
  160. // 项目表格数据
  161. projectList: [],
  162. // 所有项目数据(用于客户端筛选)
  163. allProjectList: [],
  164. // 弹出层标题
  165. title: "",
  166. // 是否显示弹出层
  167. open: false,
  168. // 查询参数
  169. queryParams: {
  170. pageNum: 1,
  171. pageSize: 10,
  172. projectPid: null,
  173. projectName: null,
  174. appId: null,
  175. sourceFrom: null,
  176. },
  177. appList: [],
  178. projectTypeList: [],
  179. // 表单参数
  180. form: {
  181. projectId: null,
  182. projectPid: null,
  183. projectName: null,
  184. appId: [],
  185. createBy: null,
  186. createTime: null,
  187. updateBy: null,
  188. updateTime: null,
  189. name: null
  190. },
  191. // 表单校验
  192. rules: {
  193. projectName: [
  194. { required: true, message: '项目名称不能为空', trigger: 'red' }
  195. ],
  196. appId: [
  197. { required: true, message: '应用名称不能为空', trigger: 'red' }
  198. ]
  199. }
  200. };
  201. },
  202. created() {
  203. this.getList();
  204. this.getAppList();
  205. this.getProjectTypeList();
  206. },
  207. methods: {
  208. /** 查询项目列表 */
  209. getList() {
  210. this.loading = true;
  211. // 如果已有完整数据且只是筛选sourceFrom或更改分页,则使用客户端筛选
  212. if (this.allProjectList.length > 0) {
  213. this.filterProjectListBySource();
  214. this.loading = false;
  215. return;
  216. }
  217. // 构建请求参数,获取所有数据
  218. const params = {
  219. pageNum: 1,
  220. pageSize: 1000,
  221. projectPid: this.queryParams.projectPid,
  222. projectName: this.queryParams.projectName,
  223. appId: this.queryParams.appId,
  224. // 不传sourceFrom,获取所有数据
  225. };
  226. listProject(params).then(response => {
  227. // 存储所有数据
  228. this.allProjectList = response.rows || [];
  229. // 应用筛选和分页
  230. this.filterProjectListBySource();
  231. this.loading = false;
  232. }).catch(() => {
  233. this.loading = false;
  234. this.allProjectList = [];
  235. this.projectList = [];
  236. this.total = 0;
  237. });
  238. },
  239. /** 根据sourceFrom筛选项目列表 */
  240. filterProjectListBySource() {
  241. if (!this.queryParams.sourceFrom) {
  242. // 直接使用所有数据,但要处理分页
  243. this.applyPaginationToData(this.allProjectList);
  244. this.total = this.allProjectList.length;
  245. return;
  246. }
  247. // 在客户端根据sourceFrom筛选
  248. const filteredData = this.allProjectList.filter(item => {
  249. return item.sourceFrom === this.queryParams.sourceFrom;
  250. });
  251. // 应用分页
  252. this.applyPaginationToData(filteredData);
  253. this.total = filteredData.length;
  254. },
  255. /** 对数据应用分页 */
  256. applyPaginationToData(data) {
  257. const start = (this.queryParams.pageNum - 1) * this.queryParams.pageSize;
  258. const end = start + this.queryParams.pageSize;
  259. this.projectList = data.slice(start, end);
  260. },
  261. getProjectTypeList() {
  262. getDicts('project_type').then(response => {
  263. this.projectTypeList = (response.data || []).map(item => {
  264. // 为特定项目类型添加sourceFrom属性
  265. if (item.dictLabel === "慧监理") {
  266. return { ...item, sourceFrom: "a34851c530ee0d07d1154beb47bb93dd" };
  267. } else if (item.dictLabel === "慧项管") {
  268. return { ...item, sourceFrom: "f47ac10b58cc4372a5670e06f141669d" };
  269. }
  270. return item;
  271. });
  272. }).catch(() => {
  273. this.projectTypeList = [];
  274. });
  275. },
  276. // 取消按钮
  277. cancel() {
  278. this.open = false;
  279. this.reset();
  280. },
  281. // 表单重置
  282. reset() {
  283. this.form = {
  284. projectId: null,
  285. projectPid: null,
  286. projectName: null,
  287. appId: [],
  288. createBy: null,
  289. createTime: null,
  290. updateBy: null,
  291. updateTime: null,
  292. name: null
  293. };
  294. this.resetForm("form");
  295. },
  296. /** 搜索按钮操作 */
  297. handleQuery() {
  298. this.queryParams.pageNum = 1;
  299. // 搜索时清空缓存数据,确保重新加载
  300. this.allProjectList = [];
  301. this.getList();
  302. },
  303. /** 重置按钮操作 */
  304. resetQuery() {
  305. this.resetForm("queryForm");
  306. // 重置时清空所有数据缓存,确保重新加载
  307. this.allProjectList = [];
  308. this.handleQuery();
  309. },
  310. // 多选框选中数据
  311. handleSelectionChange(selection) {
  312. this.ids = selection.map(item => item.projectId)
  313. this.single = selection.length !== 1
  314. this.multiple = !selection.length
  315. },
  316. /** 新增按钮操作 */
  317. handleAdd() {
  318. this.reset();
  319. this.getAppList();
  320. this.open = true;
  321. this.title = "添加项目";
  322. },
  323. /** 修改按钮操作 */
  324. handleUpdate(row) {
  325. this.reset();
  326. const projectId = row.projectId || this.ids
  327. getProject(projectId).then(response => {
  328. this.form = response.data || {};
  329. this.open = true;
  330. this.title = "修改项目";
  331. });
  332. },
  333. /** 提交按钮 */
  334. submitForm() {
  335. this.$refs["form"].validate(valid => {
  336. if (valid) {
  337. if (this.form.projectId != null) {
  338. updateProject(this.form).then(response => {
  339. this.$modal.msgSuccess("修改成功");
  340. this.open = false;
  341. this.getList();
  342. });
  343. } else {
  344. addProject(this.form).then(response => {
  345. this.$modal.msgSuccess("新增成功");
  346. this.open = false;
  347. this.getList();
  348. });
  349. }
  350. }
  351. });
  352. },
  353. /** 删除按钮操作 */
  354. handleDelete(row) {
  355. const projectIds = row.projectId || this.ids;
  356. this.$modal.confirm('是否确认删除项目编号为"' + projectIds + '"的数据项?').then(function () {
  357. return delProject(projectIds);
  358. }).then(() => {
  359. this.getList();
  360. this.$modal.msgSuccess("删除成功");
  361. }).catch(() => { });
  362. },
  363. /** 导出按钮操作 */
  364. handleExport() {
  365. this.download('system/project/export', {
  366. ...this.queryParams
  367. }, `project_${new Date().getTime()}.xlsx`)
  368. },
  369. /** 获取应用列表 */
  370. getAppList() {
  371. getInfo().then(res => {
  372. // 获取登录用户信息
  373. const user = res.user
  374. if (user && user.userId) {
  375. appList(user.userId).then(response => {
  376. this.appList = response.data || [];
  377. }).catch(() => {
  378. this.appList = [];
  379. });
  380. }
  381. }).catch(error => {
  382. console.error('获取用户信息失败:', error);
  383. this.appList = [];
  384. })
  385. },
  386. // 根据名称生成稳定的颜色 class
  387. getTagColorClass(name) {
  388. if (!name) return 'tag-color-0'
  389. let hash = 0
  390. for (let i = 0; i < name.length; i++) {
  391. hash = ((hash << 5) - hash) + name.charCodeAt(i)
  392. hash |= 0
  393. }
  394. const idx = Math.abs(hash) % 6
  395. return `tag-color-${idx}`
  396. },
  397. }
  398. };
  399. </script>
  400. <style>
  401. .pid-ellipsis {
  402. display: inline-block;
  403. max-width: 100%;
  404. overflow: hidden;
  405. text-overflow: ellipsis;
  406. white-space: nowrap;
  407. vertical-align: middle;
  408. }
  409. /* Project name ellipsis behavior consistent with pid */
  410. .name-ellipsis {
  411. display: inline-block;
  412. max-width: 100%;
  413. overflow: hidden;
  414. text-overflow: ellipsis;
  415. white-space: nowrap;
  416. vertical-align: middle;
  417. }
  418. /* App names single-line ellipsis inside flexible column */
  419. .app-ellipsis {
  420. display: inline;
  421. max-width: 100%;
  422. white-space: normal;
  423. word-break: break-all;
  424. overflow: visible;
  425. text-overflow: clip;
  426. vertical-align: middle;
  427. }
  428. /* App name tags container: wrap to next line automatically */
  429. .app-tags {
  430. display: flex;
  431. flex-wrap: wrap;
  432. gap: 4px 6px;
  433. justify-content: center;
  434. }
  435. .app-tag {
  436. margin: 0; /* use gap instead */
  437. }
  438. /* Tag color variants */
  439. .app-tag.tag-color-0 {
  440. color: #409EFF;
  441. background-color: rgba(64, 158, 255, 0.08);
  442. border-color: rgba(64, 158, 255, 0.35);
  443. }
  444. .app-tag.tag-color-1 {
  445. color: #67C23A;
  446. background-color: rgba(103, 194, 58, 0.08);
  447. border-color: rgba(103, 194, 58, 0.35);
  448. }
  449. .app-tag.tag-color-2 {
  450. color: #E6A23C;
  451. background-color: rgba(230, 162, 60, 0.08);
  452. border-color: rgba(230, 162, 60, 0.35);
  453. }
  454. .app-tag.tag-color-3 {
  455. color: #F56C6C;
  456. background-color: rgba(245, 108, 108, 0.08);
  457. border-color: rgba(245, 108, 108, 0.35);
  458. }
  459. .app-tag.tag-color-4 {
  460. color: #8A5CF6;
  461. background-color: rgba(138, 92, 246, 0.08);
  462. border-color: rgba(138, 92, 246, 0.35);
  463. }
  464. .app-tag.tag-color-5 {
  465. color: #13C2C2;
  466. background-color: rgba(19, 194, 194, 0.08);
  467. border-color: rgba(19, 194, 194, 0.35);
  468. }
  469. /* Tooltip content selectable and readable */
  470. .pid-tooltip {
  471. max-width: 640px;
  472. user-select: text;
  473. word-break: break-all;
  474. }
  475. .pid-tooltip .pid-tooltip-content {
  476. font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
  477. font-size: 12px;
  478. line-height: 1.5;
  479. }
  480. </style>