VueServer.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. <?php
  2. namespace app\common\model\base\models;
  3. /**
  4. * @title : Vue代码生成服务
  5. * @desc :
  6. * @Author : Rock
  7. * @Date : 2023-04-19 18:56:57
  8. */
  9. use app\common\model\base\menu\Menu;
  10. use app\common\model\base\menu\Menurequest;
  11. use think\facade\Db;
  12. class VueServer
  13. {
  14. /**
  15. * @title: 根据数据表名生成name属性
  16. * @desc: 描述
  17. * @param {string} {name} {} {数据表名}
  18. * @return {*}
  19. * @author: Rock
  20. * @method: POST
  21. * @Date: 2023-04-18 17:09:13
  22. */
  23. static protected function getCamel(string $name)
  24. {
  25. $name = str_replace(['/','\\'],'_',$name);
  26. $arr = explode('_',$name);
  27. $arr = array_filter($arr);
  28. $classname = "";
  29. foreach($arr as $item){
  30. $classname .= ucfirst($item);
  31. }
  32. return $classname;
  33. }
  34. /**
  35. * @title: 根据数据表名生成短横线链接的类名,组件名
  36. * @desc: 描述
  37. * @param {string} $name
  38. * @return {*}
  39. * @author: Rock
  40. * @method: POST
  41. * @Date: 2023-05-04 15:22:21
  42. */
  43. static protected function getLine(string $name)
  44. {
  45. return str_replace(['/','\\','_'],'-',$name);
  46. }
  47. /**
  48. * @title: 格式化dir路径
  49. * @desc: 描述
  50. * @param {string} $dirname
  51. * @return {*}
  52. * @author: Rock
  53. * @method: POST
  54. * @Date: 2023-04-24 14:46:37
  55. */
  56. static protected function parseDirname(string $dirname)
  57. {
  58. return !empty($dirname)?explode(DS,str_replace(['/','\\'],DS,$dirname))[0]:'';
  59. }
  60. /**
  61. * @title: 生成接口文件
  62. * @desc: 描述
  63. * @return {*}
  64. * @author: Rock
  65. * @method: POST
  66. * @Date: 2023-04-20 10:38:37
  67. */
  68. static public function createApiJS(int $model_id)
  69. {
  70. $info = ModelManage::where('id',$model_id)->find();
  71. $classname = self::getCamel($info->name);
  72. $dirname = self::parseDirname($info->dirname);
  73. $url = !empty($dirname)?str_replace('/','.',$dirname).'.'.$classname:$classname;
  74. $path = !empty($dirname)?root_path()."public".DS."vue".DS."src".DS."api".DS.$dirname.DS:root_path()."public".DS."vue".DS."src".DS."api".DS;
  75. if(!is_dir($path)){
  76. mkdir($path,0777,true);
  77. }
  78. $filename = $path.$classname.'.js';
  79. $content = <<<APIJS
  80. import request from '@/utils/request'
  81. export function getList(data) {
  82. return request({
  83. url: '/$url/getList',
  84. method: 'post',
  85. data,
  86. })
  87. }
  88. export function getTree(data) {
  89. return request({
  90. url: '/$url/getTree',
  91. method: 'post',
  92. data,
  93. })
  94. }
  95. export function getInfo(data) {
  96. return request({
  97. url: '/$url/getInfo',
  98. method: 'post',
  99. data,
  100. })
  101. }
  102. export function doEdit(data) {
  103. return request({
  104. url: '/$url/doEdit',
  105. method: 'post',
  106. data,
  107. })
  108. }
  109. export function doDelete(data) {
  110. return request({
  111. url: '/$url/doDelete',
  112. method: 'post',
  113. data,
  114. })
  115. }
  116. export function doExport(data) {
  117. return request({
  118. url: '/$url/doExport',
  119. method: 'post',
  120. data,
  121. })
  122. }
  123. export function getOptions(data) {
  124. return request({
  125. url: '/$url/getOptions',
  126. method: 'post',
  127. data,
  128. })
  129. }
  130. export function changeStatus(data) {
  131. return request({
  132. url: '/$url/changeStatus',
  133. method: 'post',
  134. data,
  135. })
  136. }
  137. APIJS;
  138. FileServer::writeLine($filename,$content);
  139. }
  140. /**
  141. * @title: 创建菜单和菜单请求
  142. * @desc: 描述
  143. * @param {int} {model_id} {} {模型ID}
  144. * @return {*}
  145. * @author: Rock
  146. * @method: POST
  147. * @Date: 2023-04-21 15:41:21
  148. */
  149. static public function createMenu(int $model_id)
  150. {
  151. $info = ModelManage::where('id',$model_id)->find();
  152. $menuModel = new Menu;
  153. $requestModel = new Menurequest;
  154. $methods = ['getList','getInfo','doEdit','doDelete','changeStatus','doExport'];
  155. $classname = self::getCamel($info->name);
  156. $dirname = self::parseDirname($info->dirname);
  157. $hasParent = $menuModel->where('path','/'.$dirname)->find();
  158. $url = !empty($dirname)?str_replace('/','.',$dirname).'.'.$classname:$classname;
  159. $component = "@/views/".$dirname.'/'.$classname;//根据vue页面文件判断是否已经有这个菜单了,当模型的存放路径dirname或数据表名name有变动时,原有的菜单需要在模型编辑时删除
  160. $has = $menuModel->where('component',$component)->find();
  161. $menuData = [
  162. 'pid' => $hasParent?$hasParent->menu_id:0,
  163. 'parentName' => $hasParent?$hasParent->title:'顶级菜单',
  164. 'name' => $classname,
  165. 'path' => '/'.$dirname.'/'.$classname,
  166. 'component' => '@/views/'.$dirname.'/'.$classname,
  167. 'redirect' => '',
  168. 'title' => $info->title,
  169. 'icon' => '',
  170. 'badge' => '',
  171. 'sort' => 0,
  172. 'is_root' => 2,
  173. 'hidden' => 2,
  174. 'alwaysShow' => 1,
  175. 'isCustomSvgIcon' => 2,
  176. 'affix' => 2,
  177. 'noKeepAlive' => 1,
  178. 'tabHidden' => 2,
  179. 'parent_path' => $hasParent?$hasParent->parent_path:'',
  180. ];
  181. if($has){
  182. $menuData['menu_id'] = $has->menu_id;
  183. }
  184. $parentData = [];
  185. if(!$hasParent){
  186. $parentData = [
  187. 'pid' => 0,
  188. 'parentName' => '顶级菜单',
  189. 'name' => $classname.'Manage',
  190. 'path' => '/'.$dirname,
  191. 'component' => 'Layout',
  192. 'redirect' => $dirname.'/'.$classname,
  193. 'title' => self::getCamel(str_replace(DS,'',$dirname)),
  194. 'icon' => '',
  195. 'badge' => '',
  196. 'sort' => 0,
  197. 'is_root' => 1,
  198. 'hidden' => 2,
  199. 'alwaysShow' => 1,
  200. 'isCustomSvgIcon' => 2,
  201. 'affix' => 2,
  202. 'noKeepAlive' => 1,
  203. 'tabHidden' => 2,
  204. 'parent_path' => '',
  205. ];
  206. }
  207. try{
  208. Db::startTrans();
  209. $pres = null;
  210. if(!empty($parentData)){
  211. $res = Menu::create($parentData);
  212. $pid = $res->menu_id;
  213. $menuData['pid'] = $pid;
  214. Menu::where('menu_id',$pid)->update(['parent_path'=>$pid]);
  215. $menuData['parent_path'] = $pid;
  216. }
  217. $menuModel->replace()->save($menuData);
  218. $menu_id = $menuModel->menu_id;
  219. $info = Menu::find($menu_id);
  220. $parent_path = array_filter(explode(',',$info->parent_path));
  221. $parent_path[] = $menu_id;
  222. $info->parent_path = implode(',',$parent_path);
  223. $info->save();
  224. $oldRequest = $requestModel->where('menu_id',$menu_id)->column('*','operate');
  225. $requestData = [];
  226. foreach($methods as $method){
  227. $requestItem = [
  228. 'menu_id' => $menu_id,
  229. 'name' => '',
  230. 'path' => '',
  231. 'model' => 1,
  232. 'operate' => '',
  233. 'status' => 1
  234. ];
  235. switch($method){
  236. case 'getList':
  237. if(isset($oldRequest['READ'])){
  238. $requestItem['menu_request_id'] = $oldRequest['READ']['menu_request_id'];
  239. }
  240. $requestItem['name'] = '查看';
  241. $requestItem['operate'] = 'READ';
  242. $requestItem['path'] = '/'.$url.'/'.$method;
  243. $requestData[] = $requestItem;
  244. break;
  245. case 'getInfo':
  246. if(isset($oldRequest['INFO'])){
  247. $requestItem['menu_request_id'] = $oldRequest['INFO']['menu_request_id'];
  248. }
  249. $requestItem['name'] = '信息';
  250. $requestItem['operate'] = 'INFO';
  251. $requestItem['path'] = '/'.$url.'/'.$method;
  252. $requestData[] = $requestItem;
  253. break;
  254. case 'doEdit':
  255. if(isset($oldRequest['ADD'])){
  256. $requestItem['menu_request_id'] = $oldRequest['ADD']['menu_request_id'];
  257. }
  258. $requestItem['name'] = '添加';
  259. $requestItem['operate'] = 'ADD';
  260. $requestItem['path'] = '/'.$url.'/'.$method;
  261. $requestData[] = $requestItem;
  262. if(isset($oldRequest['EDIT'])){
  263. $requestItem['menu_request_id'] = $oldRequest['EDIT']['menu_request_id'];
  264. }
  265. $requestItem['name'] = '编辑';
  266. $requestItem['operate'] = 'EDIT';
  267. $requestItem['path'] = '/'.$url.'/'.$method;
  268. $requestData[] = $requestItem;
  269. break;
  270. case 'doDelete':
  271. if(isset($oldRequest['DELETE'])){
  272. $requestItem['menu_request_id'] = $oldRequest['DELETE']['menu_request_id'];
  273. }
  274. $requestItem['name'] = '删除';
  275. $requestItem['operate'] = 'DELETE';
  276. $requestItem['path'] = '/'.$url.'/'.$method;
  277. $requestData[] = $requestItem;
  278. break;
  279. case 'changeStatus':
  280. if(isset($oldRequest['STATUS'])){
  281. $requestItem['menu_request_id'] = $oldRequest['STATUS']['menu_request_id'];
  282. }
  283. $requestItem['name'] = '启用禁用';
  284. $requestItem['operate'] = 'STATUS';
  285. $requestItem['path'] = '/'.$url.'/'.$method;
  286. $requestData[] = $requestItem;
  287. break;
  288. case 'doExport':
  289. if(isset($oldRequest['EXPORT'])){
  290. $requestItem['menu_request_id'] = $oldRequest['EXPORT']['menu_request_id'];
  291. }
  292. $requestItem['name'] = '导出';
  293. $requestItem['operate'] = 'EXPORT';
  294. $requestItem['path'] = '/'.$url.'/'.$method;
  295. $requestData[] = $requestItem;
  296. break;
  297. }
  298. }
  299. $res = $requestModel->replace()->saveAll($requestData);
  300. Db::commit();
  301. return $res;
  302. }catch(\Exception $e){
  303. Db::rollback();
  304. abort(2,$e->getMessage());
  305. return false;
  306. }
  307. }
  308. /**
  309. * @title: 删除模型的apijs文件
  310. * @desc: 描述
  311. * @param {int} $model_id
  312. * @return {*}
  313. * @author: Rock
  314. * @method: POST
  315. * @Date: 2023-04-23 17:29:56
  316. */
  317. static public function deleteAPI(int $model_id)
  318. {
  319. $info = ModelManage::where('id',$model_id)->find();
  320. $classname = self::getCamel($info->name);
  321. $dirname = self::parseDirname($info->dirname);
  322. $path = !empty($dirname)?root_path()."public".DS."vue".DS."src".DS."api".DS.$dirname.DS:root_path()."public".DS."vue".DS."src".DS."api".DS;
  323. $filename = $path.$classname.'.js';
  324. if(file_exists($filename)){
  325. @unlink($filename);
  326. }
  327. }
  328. /**
  329. * @title: 删除菜单及菜单请求
  330. * @desc: 描述
  331. * @param {int} $model_id
  332. * @return {*}
  333. * @author: Rock
  334. * @method: POST
  335. * @Date: 2023-04-23 17:30:31
  336. */
  337. static public function deleteMenu(int $model_id)
  338. {
  339. $info = ModelManage::where('id',$model_id)->find();
  340. $menuModel = new Menu;
  341. $requestModel = new Menurequest;
  342. $classname = self::getCamel($info->name);
  343. $dirname =self::parseDirname( $info->dirname);
  344. $component = "@/views/".$dirname.'/'.$classname;//根据vue页面文件判断是否已经有这个菜单了,当模型的存放路径dirname或数据表名name有变动时,原有的菜单需要在模型编辑时删除
  345. $has = $menuModel->where('component',$component)->find();
  346. if($has){
  347. $requestModel::destroy(function($query)use($has){
  348. $query->where('menu_id',$has->menu_id);
  349. });
  350. $has->delete();
  351. }
  352. }
  353. /**
  354. * @title: 删除选择器代码
  355. * @desc: 描述
  356. * @param {int} $model_id
  357. * @return {*}
  358. * @author: Rock
  359. * @method: POST
  360. * @Date: 2023-07-25 17:02:36
  361. */
  362. static public function deleteSelector(int $model_id)
  363. {
  364. $info = ModelManage::where('id',$model_id)->find();
  365. $name = $info->name;
  366. $dirname = self::parseDirname($info->dirname);// 文件保存位置
  367. $classname = self::getCamel($info->name);// 获取类名
  368. if(!empty($dirname)){
  369. $path = root_path()."public".DS."vue".DS."src".DS."views".DS.$dirname.DS.$classname.DS.'components'.DS;
  370. }else{
  371. $path = root_path()."public".DS."vue".DS."src".DS."views".DS.$classname.DS.'components'.DS;
  372. }
  373. $filename = $path.'Selector.vue';
  374. if(file_exists($filename)){
  375. @unlink($filename);
  376. }
  377. }
  378. /**
  379. * @title: 创建选择器代码
  380. * @desc: 描述
  381. * @return {*}
  382. * @author: Rock
  383. * @method: POST
  384. * @Date: 2023-04-27 11:23:32
  385. */
  386. static public function createSelector(int $model_id)
  387. {
  388. $info = ModelManage::where('id',$model_id)->find();
  389. $name = self::getLine($info->name);
  390. $pk = ModelManage::pk($info->name);
  391. $dirname = self::parseDirname($info->dirname);// 文件保存位置
  392. $classname = self::getCamel($info->name);// 获取类名
  393. $apiJS = "@/api/$dirname/$classname";
  394. if(!empty($dirname)){
  395. $path = root_path()."public".DS."vue".DS."src".DS."views".DS.$dirname.DS.$classname.DS.'components'.DS;
  396. }else{
  397. $path = root_path()."public".DS."vue".DS."src".DS."views".DS.$classname.DS.'components'.DS;
  398. }
  399. if(!is_dir($path)){
  400. mkdir($path,0777,true);
  401. }
  402. $filename = $path.'Selector.vue';
  403. $content = <<<SELECTOR
  404. <template>
  405. <el-select
  406. ref="$name-select"
  407. v-model="title"
  408. :clearable="true"
  409. :collapse-tags="false"
  410. filterable
  411. :multiple="multiple"
  412. placeholder="请选择"
  413. style="width: 100%"
  414. @clear="clear"
  415. >
  416. <el-option label="请选择" style="height: auto; padding: 0" :value="0">
  417. <el-tree
  418. ref="$name-tree"
  419. :accordion="true"
  420. :data="list"
  421. :highlight-current="true"
  422. node-key="$pk"
  423. :props="{
  424. children: 'children',
  425. label: labelField,
  426. isLeaf: 'hasChildren',
  427. }"
  428. :show-checkbox="multiple"
  429. @check-change="handleNodeCheck"
  430. @node-click="handleNodeClick"
  431. />
  432. </el-option>
  433. </el-select>
  434. </template>
  435. <script>
  436. import { getTree } from '$apiJS'
  437. import { isArray } from '@/utils/validate'
  438. export default {
  439. name:'{$classname}Selector',
  440. components:{},
  441. props: {
  442. value: {
  443. type: [Number,String,Array],
  444. default: 0,
  445. require: false,
  446. },
  447. labelField:{
  448. type:String,
  449. default:'name',
  450. require:true,
  451. },
  452. multiple:{
  453. type:Boolean,
  454. default:false,
  455. require:false,
  456. },
  457. },
  458. data(){
  459. return {
  460. loading:false,
  461. val:0,
  462. title:'请选择',
  463. list:[],
  464. }
  465. },
  466. watch:{
  467. value:{
  468. handler(n){
  469. this.val = n
  470. this.setDefault(n, this.list)
  471. },
  472. },
  473. },
  474. created() {
  475. this.val = this.multiple ? [] : ''
  476. this.title = this.multiple ? [] : ''
  477. },
  478. mounted(){
  479. this.\$nextTick(()=>{
  480. this.fetchTree()
  481. })
  482. },
  483. methods:{
  484. // 设置默认显示
  485. setDefault(defVal, list) {
  486. list.forEach((element) => {
  487. if (this.multiple && isArray(defVal)) {
  488. defVal.forEach((val) => {
  489. if (element.$pk == val) {
  490. this.title.push(element[this.labelField])
  491. this.title = [...new Set(this.title)]
  492. }
  493. })
  494. } else if (parseInt(element.$pk) == parseInt(defVal)) {
  495. this.title = element[this.labelField]
  496. }
  497. if (element.children) {
  498. this.setDefault(defVal, element.children)
  499. }
  500. })
  501. },
  502. async fetchTree(){
  503. this.loading = true
  504. const {data} = await getTree({labelField:this.labelField})
  505. this.list = data
  506. this.loading = false
  507. if (this.multiple && isArray(this.value)) {
  508. this.\$refs['$name-tree'].setCheckedKeys(this.value)
  509. }
  510. this.setDefault(this.value,this.list)
  511. },
  512. handleNodeClick(node){
  513. this.val = node.$pk
  514. this.title = node[this.labelField]
  515. this.\$emit('input', this.val)
  516. this.\$refs['$name-select'].blur()
  517. },
  518. handleNodeCheck() {
  519. this.val = this.\$refs['$name-tree'].getCheckedKeys()
  520. let nodes = this.\$refs['$name-tree'].getCheckedNodes()
  521. let titles = []
  522. nodes.forEach((node) => {
  523. titles.push(node[this.labelField])
  524. })
  525. this.title = titles
  526. this.\$emit('input', this.val)
  527. },
  528. clear(){
  529. this.val = 0
  530. this.\$emit('input',this.val)
  531. },
  532. }
  533. }
  534. </script>
  535. SELECTOR;
  536. FileServer::writeLine($filename,$content);
  537. return true;
  538. }
  539. }