Crud.php 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546
  1. <?php
  2. namespace app\admin\command;
  3. use FilesystemIterator;
  4. use think\Exception;
  5. use think\facade\Db;
  6. use think\facade\Lang;
  7. use think\console\Input;
  8. use think\console\Output;
  9. use think\console\Command;
  10. use think\facade\Config;
  11. use app\common\library\Menu;
  12. use app\admin\model\MenuRule;
  13. use think\console\input\Option;
  14. use think\exception\ErrorException;
  15. use app\admin\command\Crud\library\Stub;
  16. use UnexpectedValueException;
  17. class Crud extends Command
  18. {
  19. protected $input;
  20. protected $output;
  21. /**
  22. * 命令选项列表
  23. * solveArray 是否切割数组参数,可自动以`逗号`切割输入值
  24. * setInputRule 是否设置输入框识别规则,此类选项较多,方便自动化批量设置
  25. */
  26. public $options = [
  27. 'table' => ['name' => 'table', 'shortcut' => 't', 'mode' => Option::VALUE_REQUIRED, 'description' => 'table name without prefix', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  28. 'controller' => ['name' => 'controller', 'shortcut' => 'c', 'mode' => Option::VALUE_OPTIONAL, 'description' => 'controller name', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  29. 'model' => ['name' => 'model', 'shortcut' => 'm', 'mode' => Option::VALUE_OPTIONAL, 'description' => 'model name', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  30. 'fields' => ['name' => 'fields', 'shortcut' => 'f', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'visible fields', 'default' => null, 'solveArray' => true, 'setInputRule' => false],
  31. 'force' => ['name' => 'force', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL, 'description' => 'force override or force delete,without tips', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  32. 'commonmodel' => ['name' => 'commonmodel', 'shortcut' => 'o', 'mode' => Option::VALUE_OPTIONAL, 'description' => 'common model', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  33. // 关联表
  34. 'relation' => ['name' => 'relation', 'shortcut' => 'r', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation table name without prefix', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  35. 'relationmodel' => ['name' => 'relationmodel', 'shortcut' => 'e', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation model name', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  36. 'relationforeignkey' => ['name' => 'relationforeignkey', 'shortcut' => 'k', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation foreign key', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  37. 'relationprimarykey' => ['name' => 'relationprimarykey', 'shortcut' => 'p', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation primary key', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  38. 'relationfields' => ['name' => 'relationfields', 'shortcut' => 'l', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation table fields', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  39. 'relationmode' => ['name' => 'relationmode', 'shortcut' => 'a', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation table mode,hasone or belongsto', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  40. // 关联远程下拉select显示字段
  41. 'remoteselectfield' => ['name' => 'remoteselectfield', 'shortcut' => 's', 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'relation table Remote select table field', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  42. // 是否删除模式
  43. 'delete' => ['name' => 'delete', 'shortcut' => 'd', 'mode' => Option::VALUE_OPTIONAL, 'description' => 'delete all files generated by CRUD', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  44. // 指定数据库
  45. 'db' => ['name' => 'db', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL, 'description' => 'database config name', 'default' => 'mysql', 'solveArray' => false, 'setInputRule' => false],
  46. // 快速搜索字段设置
  47. 'quicksearchfield' => ['name' => 'quicksearchfield', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'quick search field', 'default' => null, 'solveArray' => true, 'setInputRule' => false],
  48. // 排序字段设置
  49. 'sortfield' => ['name' => 'sortfield', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL, 'description' => 'sort field', 'default' => null, 'solveArray' => false, 'setInputRule' => false],
  50. // 排除字段设置
  51. 'ignorefields' => ['name' => 'ignorefields', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'ignore fields', 'default' => null, 'solveArray' => true, 'setInputRule' => false],
  52. // 设置后缀识别规则
  53. 'radiofieldsuffix' => ['name' => 'radiofieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for radio components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  54. 'checkboxfieldsuffix' => ['name' => 'checkboxfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for checkbox components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  55. 'datetimefieldsuffix' => ['name' => 'datetimefieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for datetime components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  56. 'switchfieldsuffix' => ['name' => 'switchfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for switch components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  57. 'editorfieldsuffix' => ['name' => 'editorfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for editor components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  58. 'textareafieldsuffix' => ['name' => 'textareafieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for textarea components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  59. 'cityfieldsuffix' => ['name' => 'cityfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for city components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  60. 'remoteselectfieldsuffix' => ['name' => 'remoteselectfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for remote select components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  61. 'arrayfieldsuffix' => ['name' => 'arrayfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for array components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  62. 'imagefieldsuffix' => ['name' => 'imagefieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for image components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  63. 'imagesfieldsuffix' => ['name' => 'imagesfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for images components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  64. 'filefieldsuffix' => ['name' => 'filefieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for file components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  65. 'filesfieldsuffix' => ['name' => 'filesfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for files components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  66. 'numberfieldsuffix' => ['name' => 'numberfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for number components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  67. 'selectfieldsuffix' => ['name' => 'selectfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for select components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  68. 'selectsfieldsuffix' => ['name' => 'selectsfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for selects components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  69. 'iconfieldsuffix' => ['name' => 'iconfieldsuffix', 'shortcut' => null, 'mode' => Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'description' => 'Automatically generate field suffixes for icon components', 'default' => null, 'solveArray' => true, 'setInputRule' => true],
  70. ];
  71. /**
  72. * 输入框类型的识别规则
  73. */
  74. protected $inputTypeRule = [
  75. // 开关组件
  76. [
  77. 'type' => ['tinyint', 'int', 'enum'],
  78. 'suffix' => ['switch', 'toggle'],
  79. 'value' => 'switch',
  80. ],
  81. [
  82. 'column_type' => ['tinyint(1)', 'char(1)', 'tinyint(1) unsigned'],
  83. 'suffix' => ['switch', 'toggle'],
  84. 'value' => 'switch',
  85. ],
  86. // 富文本-识别规则和textarea重合,优先识别为富文本
  87. [
  88. 'type' => ['longtext', 'text', 'mediumtext', 'smalltext', 'tinytext', 'bigtext'],
  89. 'suffix' => ['content', 'editor'],
  90. 'value' => 'editor',
  91. ],
  92. // textarea
  93. [
  94. 'type' => ['varchar'],
  95. 'suffix' => ['textarea', 'multiline', 'rows'],
  96. 'value' => 'textarea',
  97. ],
  98. // Array
  99. [
  100. 'suffix' => ['array'],
  101. 'value' => 'array',
  102. ],
  103. // 时间选择器-字段类型为int同时以['time', 'datetime']结尾
  104. [
  105. 'type' => ['int'],
  106. 'suffix' => ['time', 'datetime'],
  107. 'value' => 'datetime',
  108. ],
  109. [
  110. 'type' => ['datetime', 'timestamp'],
  111. 'value' => 'datetime',
  112. ],
  113. [
  114. 'type' => ['date'],
  115. 'value' => 'date',
  116. ],
  117. [
  118. 'type' => ['year'],
  119. 'value' => 'year',
  120. ],
  121. [
  122. 'type' => ['time'],
  123. 'value' => 'time',
  124. ],
  125. // 单选select
  126. [
  127. 'suffix' => ['select', 'list', 'data'],
  128. 'value' => 'select',
  129. ],
  130. // 多选select
  131. [
  132. 'suffix' => ['selects', 'multi', 'lists'],
  133. 'value' => 'selects',
  134. ],
  135. // 远程select
  136. [
  137. 'suffix' => ['_id'],
  138. 'value' => 'remoteSelect',
  139. ],
  140. // 远程selects
  141. [
  142. 'suffix' => ['_ids'],
  143. 'value' => 'remoteSelects',
  144. ],
  145. // 城市选择器
  146. [
  147. 'suffix' => ['city'],
  148. 'value' => 'city',
  149. ],
  150. // 单图上传
  151. [
  152. 'suffix' => ['image', 'avatar'],
  153. 'value' => 'image',
  154. ],
  155. // 多图上传
  156. [
  157. 'suffix' => ['images', 'avatars'],
  158. 'value' => 'images',
  159. ],
  160. // 文件上传
  161. [
  162. 'suffix' => ['file'],
  163. 'value' => 'file',
  164. ],
  165. // 多文件上传
  166. [
  167. 'suffix' => ['files'],
  168. 'value' => 'files',
  169. ],
  170. // icon选择器
  171. [
  172. 'suffix' => ['icon'],
  173. 'value' => 'icon',
  174. ],
  175. // 单选框
  176. [
  177. 'column_type' => ['tinyint(1)', 'char(1)', 'tinyint(1) unsigned'],
  178. 'suffix' => ['status', 'state', 'type'],
  179. 'value' => 'radio',
  180. ],
  181. // 数字输入框
  182. [
  183. 'suffix' => ['number', 'int', 'num'],
  184. 'value' => 'number',
  185. ],
  186. [
  187. 'type' => ['bigint', 'int', 'mediumint', 'smallint', 'tinyint', 'decimal', 'double', 'float'],
  188. 'value' => 'number',
  189. ],
  190. // 富文本-低权重
  191. [
  192. 'type' => ['longtext', 'text', 'mediumtext', 'smalltext', 'tinytext', 'bigtext'],
  193. 'value' => 'textarea',
  194. ],
  195. // 单选框-低权重
  196. [
  197. 'type' => ['enum'],
  198. 'value' => 'radio',
  199. ],
  200. // 多选框
  201. [
  202. 'type' => ['set'],
  203. 'value' => 'checkbox',
  204. ],
  205. ];
  206. /**
  207. * 表格字段渲染方案
  208. */
  209. protected $fieldRenderRule = [
  210. // 字段名称为 ['id'] 则字段宽度为70,且开启排序
  211. [
  212. 'name' => ['id'],
  213. 'attr' => [
  214. 'width' => 70,
  215. 'sortable' => 'custom',
  216. 'operator' => 'RANGE',
  217. ],
  218. ],
  219. // 字段名称为 ['weigh'] 则关闭字段筛选
  220. [
  221. 'name' => ['weigh'],
  222. 'attr' => [
  223. 'sortable' => 'custom',
  224. 'operator' => 'false',
  225. ],
  226. ],
  227. // 输入框被判定为 ['number'] 则通用搜索中使用范围筛选
  228. [
  229. 'type' => ['number'],
  230. 'attr' => [
  231. 'operator' => 'RANGE',
  232. ],
  233. ],
  234. // 输入框被判定为['radio', 'select'] 或者字段名后缀为 ['flag'] 则渲染为 tag
  235. [
  236. 'type' => ['radio', 'select'],
  237. 'suffix' => ['flag'],
  238. 'attr' => [
  239. 'render' => 'tag',
  240. ],
  241. ],
  242. // 输入框被判定为 ['textarea', 'editor', '...'] 则隐藏字段
  243. [
  244. 'type' => ['textarea', 'editor', 'file', 'files', 'array'],
  245. 'attr' => [
  246. 'show' => false,
  247. ],
  248. ],
  249. [
  250. 'type' => ['checkbox', 'selects'],
  251. 'suffix' => ['flags'],
  252. 'attr' => [
  253. 'render' => 'tags',
  254. ],
  255. ],
  256. [
  257. 'type' => ['datetime'],
  258. 'attr' => [
  259. 'render' => 'datetime',
  260. 'sortable' => 'custom',
  261. 'operator' => 'RANGE',
  262. 'width' => 160
  263. ],
  264. ],
  265. [
  266. 'type' => ['switch'],
  267. 'attr' => [
  268. 'render' => 'switch',
  269. ],
  270. ],
  271. [
  272. 'type' => ['image'],
  273. 'suffix' => ['avatar'],
  274. 'attr' => [
  275. 'render' => 'image',
  276. ],
  277. ],
  278. [
  279. 'type' => ['images'],
  280. 'suffix' => ['avatars'],
  281. 'attr' => [
  282. 'render' => 'images',
  283. ],
  284. ],
  285. [
  286. 'type' => ['icon'],
  287. 'attr' => [
  288. 'render' => 'icon',
  289. ],
  290. ],
  291. [
  292. 'suffix' => ['url'],
  293. 'attr' => [
  294. 'render' => 'url',
  295. ],
  296. ]
  297. ];
  298. /**
  299. * select远程搜索字段关联
  300. */
  301. protected $remoteSelectFieldMap = [
  302. 'nickname' => ['user_id', 'user_ids', 'admin_id', 'admin_ids'],
  303. ];
  304. /**
  305. * 内部保留词
  306. */
  307. protected $reservedKeywords = [
  308. 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor', 'yield'
  309. ];
  310. /**
  311. * 保留字段
  312. */
  313. protected $reservedField = [];
  314. /**
  315. * 快速搜索字段
  316. */
  317. protected $quicksearchfield = ['id'];
  318. /**
  319. * 默认排序字段,el-table只支持单字段排序
  320. */
  321. protected $defaultSortField = 'weigh,desc';
  322. /**
  323. * 排除字段
  324. */
  325. protected $ignoreFields = [];
  326. /**
  327. * 添加时间字段
  328. * @var string
  329. */
  330. protected $createTimeField = 'createtime';
  331. /**
  332. * 更新时间字段
  333. * @var string
  334. */
  335. protected $updateTimeField = 'updatetime';
  336. /**
  337. * 子级菜单数组(权限节点)
  338. * @var string
  339. */
  340. protected $menuChildren = [
  341. ['type' => 'button', 'title' => '查看', 'name' => '/index', 'status' => '1'],
  342. ['type' => 'button', 'title' => '添加', 'name' => '/add', 'status' => '1'],
  343. ['type' => 'button', 'title' => '编辑', 'name' => '/edit', 'status' => '1'],
  344. ['type' => 'button', 'title' => '删除', 'name' => '/del', 'status' => '1'],
  345. ['type' => 'button', 'title' => '快速排序', 'name' => '/sortable', 'status' => '1'],
  346. ];
  347. protected $langPrefix = '';
  348. protected $stub = null;
  349. protected function configure()
  350. {
  351. $this->setName('crud')->setDescription('Build CRUD code controller and model, and views from table');
  352. foreach ($this->options as $option) {
  353. $this->addOption($option['name'], $option['shortcut'], $option['mode'], $option['description'], $option['default']);
  354. }
  355. }
  356. protected function execute(Input $input, Output $output)
  357. {
  358. $this->stub = Stub::instance();
  359. $this->input = $input;
  360. $this->output = $output;
  361. $adminPath = dirname(__DIR__) . DIRECTORY_SEPARATOR;
  362. $webPath = root_path() . 'web' . DIRECTORY_SEPARATOR;
  363. $webViewsPath = $webPath . 'src' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'backend' . DIRECTORY_SEPARATOR;
  364. $webLangPath = $webPath . 'src' . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR;
  365. $webControllerUrls = $webPath . 'src' . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'controllerUrls.ts';
  366. // 数据库配置名
  367. $db = $this->getOption('db');
  368. // 表名
  369. $table = $this->getOption('table');
  370. if (!$table) {
  371. throw new Exception('table name can\'t empty');
  372. }
  373. // 自定义控制器
  374. $controller = $this->getOption('controller');
  375. // 自定义模型
  376. $model = $this->getOption('model');
  377. $model = $model ?: $controller;
  378. // 验证器类
  379. $validate = $model;
  380. // 自定义显示字段
  381. $fields = $this->getOption('fields');
  382. // 强制覆盖
  383. $force = $this->getOption('force');
  384. // 是否将model放在app/common/model中
  385. $commonModel = $this->getOption('commonmodel');
  386. // 关联表
  387. $relation = $this->getOption('relation');
  388. // 自定义关联表模型
  389. $relationModel = $this->getOption('relationmodel');
  390. // 关联模式
  391. $relationMode = $mode = $this->getOption('relationmode');
  392. // 外键
  393. $relationForeignKey = $this->getOption('relationforeignkey');
  394. // 主键
  395. $relationPrimaryKey = $this->getOption('relationprimarykey');
  396. // 远程下拉字段
  397. $remoteSelectField = $this->getOption('remoteselectfield');
  398. // 关联表显示字段
  399. $relationFields = $this->getOption('relationfields');
  400. // 快速搜索字段
  401. $quicksearchfield = $this->getOption('quicksearchfield');
  402. // 排序字段
  403. $sortfield = $this->getOption('sortfield');
  404. // 排除字段
  405. $ignoreFields = $this->getOption('ignorefields');
  406. // 字段识别后缀规则批量设置
  407. foreach ($this->options as $option) {
  408. if ($option['setInputRule']) {
  409. $field = explode('field', $option['name']);
  410. $this->setInputTypeRule($field[0], $this->getOption($option['name']));
  411. }
  412. }
  413. if ($quicksearchfield) $this->quicksearchfield = $quicksearchfield;
  414. if ($ignoreFields) $this->ignoreFields = $ignoreFields;
  415. if ($sortfield) $this->defaultSortField = $sortfield;
  416. $this->reservedField = array_merge($this->reservedField, [$this->createTimeField, $this->updateTimeField]);
  417. $dbconnect = Db::connect($db);
  418. $prefix = Config::get('database.connections.' . $db . '.prefix');
  419. $dbname = Config::get('database.connections.' . $db . '.database');
  420. // 模块
  421. $moduleName = 'admin';
  422. $modelModuleName = $validateModuleName = $commonModel ? 'common' : $moduleName;
  423. // 检查主表
  424. $modelName = $table = stripos($table, $prefix) === 0 ? substr($table, strlen($prefix)) : $table;
  425. $modelTableType = 'table';
  426. $modelTableTypeName = $modelTableName = $modelName;
  427. $modelTableInfo = $dbconnect->query("SHOW TABLE STATUS LIKE '{$modelTableName}'", [], true);
  428. if (!$modelTableInfo) {
  429. $modelTableType = 'name';
  430. $modelTableName = $prefix . $modelName;
  431. $modelTableInfo = $dbconnect->query("SHOW TABLE STATUS LIKE '{$modelTableName}'", [], true);
  432. if (!$modelTableInfo) {
  433. throw new Exception('table not found');
  434. }
  435. }
  436. $modelTableInfo = $modelTableInfo[0];
  437. $relations = [];
  438. // 检查关联表
  439. if ($relation) {
  440. foreach ($relation as $index => $relationTable) {
  441. $relationName = stripos($relationTable, $prefix) === 0 ? substr($relationTable, strlen($prefix)) : $relationTable;
  442. $relationTableType = 'table';
  443. $relationTableTypeName = $relationTableName = $relationName;
  444. $relationTableInfo = $dbconnect->query("SHOW TABLE STATUS LIKE '{$relationTableName}'", [], true);
  445. if (!$relationTableInfo) {
  446. $relationTableType = 'name';
  447. $relationTableName = $prefix . $relationName;
  448. $relationTableInfo = $dbconnect->query("SHOW TABLE STATUS LIKE '{$relationTableName}'", [], true);
  449. if (!$relationTableInfo) {
  450. throw new Exception('relation table not found');
  451. }
  452. }
  453. $relationTableInfo = $relationTableInfo[0];// 关联表信息
  454. $relationModelTemp = $relationModel[$index] ?? '';// 关联模型
  455. [$relationNamespace, $relationName, $relationFile] = $this->getModelData($modelModuleName, $relationModelTemp, $relationName);
  456. $relations[] = [
  457. // 关联表基础名
  458. 'relationName' => $relationName,
  459. // 关联表类命名空间
  460. 'relationNamespace' => $relationNamespace,
  461. // 关联模型名
  462. 'relationModel' => $relationModelTemp,
  463. // 关联文件
  464. 'relationFile' => $relationFile,
  465. // 关联表名称
  466. 'relationTableName' => $relationTableName,
  467. // 关联表信息
  468. 'relationTableInfo' => $relationTableInfo,
  469. // 关联模型表类型(name或table)
  470. 'relationTableType' => $relationTableType,
  471. // 关联模型表类型名称
  472. 'relationTableTypeName' => $relationTableTypeName,
  473. // 关联表字段
  474. 'relationFields' => isset($relationFields[$index]) ? explode(',', $relationFields[$index]) : [],
  475. // 关联模式
  476. 'relationMode' => $relationMode[$index] ?? 'belongsto',
  477. // 关联表外键
  478. 'relationForeignKey' => $relationForeignKey[$index] ?? parse_name($relationName) . '_id',
  479. // 关联表主键
  480. 'relationPrimaryKey' => $relationPrimaryKey[$index] ?? 'id',
  481. // 远程下拉字段
  482. 'remoteSelectField' => $remoteSelectField[$index] ?? '',
  483. ];
  484. }
  485. }
  486. // 控制器
  487. [$controllerNamespace, $controllerName, $controllerFile, $controllerArr] = $this->getControllerData($moduleName, $controller, $table);
  488. // 模型
  489. [$modelNamespace, $modelName, $modelFile, $modelArr] = $this->getModelData($modelModuleName, $model, $table);
  490. // 验证器
  491. [$validateNamespace, $validateName, $validateFile, $validateArr] = $this->getValidateData($validateModuleName, $validate, $table);
  492. // 处理基础文件名,取消所有下划线并转换为小写
  493. $baseNameArr = $controllerArr;
  494. $baseFileName = parse_name(array_pop($baseNameArr));
  495. array_push($baseNameArr, $baseFileName);
  496. $controllerBaseName = strtolower(implode(DIRECTORY_SEPARATOR, $baseNameArr));
  497. // 视图文件
  498. $viewArr = $controllerArr;
  499. foreach ($viewArr as $index => $item) {
  500. $viewArr[$index] = parse_name($item, 1, false);
  501. }
  502. $viewPath = implode(DIRECTORY_SEPARATOR, $viewArr);
  503. $viewDir = $webViewsPath . $viewPath . DIRECTORY_SEPARATOR;
  504. $this->langPrefix = implode('.', $viewArr) . '.';
  505. $controllerUrl = $originControllerUrl = implode('/', $viewArr);
  506. $controllerUrl = '/admin/' . preg_replace("/\//", '.', $controllerUrl) . '/';
  507. $controllerUrlVarName = '';
  508. foreach ($viewArr as $item) {
  509. $controllerUrlVarName .= parse_name($item, 1);
  510. }
  511. $controllerUrlVarName = lcfirst($controllerUrlVarName);
  512. // 最终将生成的文件路径
  513. $formFile = $viewDir . 'popupForm.vue';
  514. $indexFile = $viewDir . 'index.vue';
  515. // $serverLangZhCnFile = $adminPath . 'lang' . DIRECTORY_SEPARATOR . 'zh-cn' . DIRECTORY_SEPARATOR . $controllerBaseName . '.php';
  516. // $serverLangEnFile = $adminPath . 'lang' . DIRECTORY_SEPARATOR . 'en' . DIRECTORY_SEPARATOR . $controllerBaseName . '.php';
  517. $webLangZhCnFile = $webLangPath . 'zh-cn' . DIRECTORY_SEPARATOR . $viewPath . '.ts';
  518. $webLangEnFile = $webLangPath . 'en' . DIRECTORY_SEPARATOR . $viewPath . '.ts';
  519. // 是否为删除模式
  520. $delete = $this->getOption('delete');
  521. if ($delete) {
  522. $readyFiles = [$controllerFile, $modelFile, $validateFile, $formFile, $indexFile, $webLangZhCnFile, $webLangEnFile];
  523. foreach ($readyFiles as $v) {
  524. $output->warning($v);
  525. }
  526. if (!$force) {
  527. $output->info("Are you sure you want to delete all those files? Type 'yes' to continue: ");
  528. $line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
  529. if (trim($line) != 'yes') {
  530. throw new Exception('Operation is aborted!');
  531. }
  532. }
  533. foreach ($readyFiles as $v) {
  534. if (file_exists($v)) {
  535. unlink($v);
  536. }
  537. //删除空文件夹
  538. switch ($v) {
  539. case $modelFile:
  540. $this->removeEmptyBaseDir($v, $modelArr);
  541. break;
  542. case $validateFile:
  543. $this->removeEmptyBaseDir($v, $validateArr);
  544. break;
  545. case $formFile:
  546. case $indexFile:
  547. $this->removeEmptyBaseDir($v, $viewArr, 0);
  548. break;
  549. default:
  550. $this->removeEmptyBaseDir($v, $controllerArr);
  551. }
  552. }
  553. // 删除菜单
  554. Menu::delete($originControllerUrl, true);
  555. $output->info('Delete Successed');
  556. return;
  557. }
  558. // 非覆盖模式时如果存在控制器文件则报错
  559. if (is_file($controllerFile) && !$force) {
  560. throw new Exception("controller already exists!\nIf you need to rebuild again, use the parameter --force=true ");
  561. }
  562. // 非覆盖模式时如果存在模型文件则报错
  563. if (is_file($modelFile) && !$force) {
  564. throw new Exception("model already exists!\nIf you need to rebuild again, use the parameter --force=true ");
  565. }
  566. // 非覆盖模式时如果存在验证文件则报错
  567. if (is_file($validateFile) && !$force) {
  568. throw new Exception("validate already exists!\nIf you need to rebuild again, use the parameter --force=true ");
  569. }
  570. require $adminPath . 'common.php';
  571. // 从数据库中获取表字段信息
  572. $sql = 'SELECT * FROM `information_schema`.`columns` '
  573. . 'WHERE TABLE_SCHEMA = ? AND table_name = ? '
  574. . 'ORDER BY ORDINAL_POSITION';
  575. // 加载主表的列
  576. $columnList = $dbconnect->query($sql, [$dbname, $modelTableName]);
  577. $fieldArr = [];
  578. foreach ($columnList as $v) {
  579. $fieldArr[] = $v['COLUMN_NAME'];
  580. }
  581. // 加载关联表的列
  582. foreach ($relations as &$relation) {
  583. $relationColumnList = $dbconnect->query($sql, [$dbname, $relation['relationTableName']]);
  584. $relationFieldList = [];
  585. foreach ($relationColumnList as $v) {
  586. $relationFieldList[] = $v['COLUMN_NAME'];
  587. }
  588. if (!$relation['relationPrimaryKey']) {
  589. foreach ($relationColumnList as $v) {
  590. if ($v['COLUMN_KEY'] == 'PRI') {
  591. $relation['relationPrimaryKey'] = $v['COLUMN_NAME'];
  592. break;
  593. }
  594. }
  595. }
  596. // 如果主键为空
  597. if (!$relation['relationPrimaryKey']) {
  598. throw new Exception('Relation Primary key not found!');
  599. }
  600. // 如果主键不在表字段中
  601. if (!in_array($relation['relationPrimaryKey'], $relationFieldList)) {
  602. throw new Exception('Relation Primary key not found in table!');
  603. }
  604. $relation['relationColumnList'] = $relationColumnList;
  605. $relation['relationFieldList'] = $relationFieldList;
  606. }
  607. unset($relation);
  608. // 检查主键是否存在
  609. $priKey = '';// 主键
  610. foreach ($columnList as $v) {
  611. if ($v['COLUMN_KEY'] == 'PRI') {
  612. $priKey = $v['COLUMN_NAME'];
  613. break;
  614. }
  615. }
  616. if (!$priKey) {
  617. throw new Exception('Primary key not found!');
  618. }
  619. // 检查快速搜索字段是否存在
  620. foreach ($this->quicksearchfield as $key => $item) {
  621. if (!in_array($item, $fieldArr)) {
  622. unset($this->quicksearchfield[$key]);
  623. }
  624. }
  625. if (!$this->quicksearchfield) {
  626. $this->quicksearchfield = [$priKey];
  627. }
  628. // 如果是关联模型
  629. foreach ($relations as &$relation) {
  630. if ($relation['relationMode'] == 'hasone') {
  631. $relationForeignKey = $relation['relationForeignKey'] ?: $table . '_id';
  632. $relationPrimaryKey = $relation['relationPrimaryKey'] ?: $priKey;
  633. if (!in_array($relationForeignKey, $relation['relationFieldList'])) {
  634. throw new Exception('relation table [' . $relation['relationTableName'] . '] must be contain field [' . $relationForeignKey . ']');
  635. }
  636. if (!in_array($relationPrimaryKey, $fieldArr)) {
  637. throw new Exception('table [' . $modelTableName . '] must be contain field [' . $relationPrimaryKey . ']');
  638. }
  639. } else {
  640. $relationForeignKey = $relation['relationForeignKey'] ?: parse_name($relation['relationName']) . '_id';
  641. $relationPrimaryKey = $relation['relationPrimaryKey'] ?: $relation['relationPriKey'];
  642. if (!in_array($relationForeignKey, $fieldArr)) {
  643. throw new Exception('table [' . $modelTableName . '] must be contain field [' . $relationForeignKey . ']');
  644. }
  645. if (!in_array($relationPrimaryKey, $relation['relationFieldList'])) {
  646. throw new Exception('relation table [' . $relation['relationTableName'] . '] must be contain field [' . $relationPrimaryKey . ']');
  647. }
  648. }
  649. $relation['relationForeignKey'] = $relationForeignKey;
  650. $relation['relationPrimaryKey'] = $relationPrimaryKey;
  651. $relation['relationClassName'] = $modelNamespace != $relation['relationNamespace'] ? '\\' . $relation['relationNamespace'] . '\\' . $relation['relationName'] . '::class' : $relation['relationName'] . '::class';
  652. }
  653. unset($relation);
  654. try {
  655. $quickSearchFieldNames = [];// 快速搜索字段列表
  656. $formFieldList = [];// 表单字段列表
  657. $dblClickNotEditColumn = ['undefined'];// 不允许双击编辑的字段
  658. $optButtons = ['edit', 'delete'];// 表格行操作按钮
  659. $inputDefaultItems = [];// 表单默认值
  660. $defaultOrder = [];// 默认排序字段
  661. $langList = ['en' => [], 'zh-cn' => []];// web端语言项列表
  662. $controllerAttrList = [];// 控制器内要设置的变量列表
  663. $importControllerUrls = [];// 要引入的控制器url列表
  664. $importPackages = '';
  665. $formItemRules = [];// 字段验证规则
  666. $formDialogBig = false;// 存在富文本编辑器则加宽表单dialog
  667. $modelSetAttrArr = [];
  668. $modelFieldType = [];
  669. $enableDragSort = in_array('weigh', $fieldArr);
  670. $controllerData = [
  671. 'controllerNamespace' => $controllerNamespace,
  672. 'controllerName' => $controllerName,
  673. 'modelName' => $modelName,
  674. 'modelNamespace' => $modelNamespace,
  675. 'methods' => [],
  676. ];
  677. // 表格列数据
  678. $tableColumnList = [
  679. [
  680. 'type' => 'selection',
  681. 'align' => 'center',
  682. 'operator' => 'false',
  683. ],
  684. ];
  685. foreach ($columnList as $column) {
  686. $field = $column['COLUMN_NAME'];
  687. $inputType = $this->getFieldInputType($column);
  688. // 列替换数据
  689. $columnData = $this->getColumnReplaceData($column, $field, $inputType);
  690. // 字段语言包数据
  691. if ($column['COLUMN_COMMENT'] != '') {
  692. $columnLang = $this->getLangItem($field, $column['COLUMN_COMMENT']);
  693. $langList['en'] = array_merge($langList['en'], $columnLang['en']);
  694. $langList['zh-cn'] = array_merge($langList['zh-cn'], $columnLang['zh-cn']);
  695. }
  696. // 快速搜索字段
  697. if (in_array($field, $this->quicksearchfield)) {
  698. $quickSearchFieldNames[] = $column['COLUMN_COMMENT'] ?? $field;
  699. }
  700. // 不允许双击编辑的字段
  701. if ($inputType == 'switch') {
  702. $dblClickNotEditColumn[] = $field;
  703. }
  704. // 表单字段
  705. if ($column['COLUMN_KEY'] != 'PRI' && !in_array($field, $this->reservedField) && !in_array($field, $this->ignoreFields)) {
  706. // 输入框默认值
  707. if ($column['COLUMN_DEFAULT'] || ($column['COLUMN_DEFAULT'] === '0' && $inputType != 'remoteSelect' && $inputType != 'remoteSelects')) {
  708. if (in_array($inputType, ['checkbox', 'selects', 'remoteSelects', 'city'])) {
  709. $column['COLUMN_DEFAULT'] = explode(',', $column['COLUMN_DEFAULT']);
  710. }
  711. $inputDefaultItems[$field] = $column['COLUMN_DEFAULT'];
  712. }
  713. $formFieldList[$field] = [
  714. ':label' => 't(\'' . $this->langPrefix . $field . '\')',
  715. 'type' => $inputType,
  716. 'v-model' => 'baTable.form.items!.' . $field,
  717. 'prop' => $field,
  718. ];
  719. // 输入框类型的特殊属性
  720. if (in_array($inputType, ['radio', 'checkbox', 'select', 'selects'])) {
  721. $formFieldList[$field][':data'] = [
  722. 'content' => $columnData,
  723. ];
  724. } elseif ($inputType == 'textarea') {
  725. $formFieldList[$field][':input-attr']['rows'] = 3;
  726. $formFieldList[$field]['@keyup.enter.stop'] = '';
  727. $formFieldList[$field]['@keyup.ctrl.enter'] = 'baTable.onSubmit(formRef)';
  728. } elseif ($inputType == 'remoteSelect' || $inputType == 'remoteSelects') {
  729. $formFieldList[$field][':input-attr']['field'] = $this->getRemoteSelectField($field, $relations);
  730. $remoteUrl = $this->getRemoteSelectUrl($field, $relations, $webControllerUrls);
  731. $formFieldList[$field][':input-attr']['remote-url'] = $remoteUrl['url'];
  732. $formFieldList[$field][':input-attr']['pk'] = $remoteUrl['pk'];
  733. if ($remoteUrl['importControllerUrls']) {
  734. $importControllerUrls[$remoteUrl['importControllerUrls']] = '';
  735. }
  736. if ($inputType == 'remoteSelects') {
  737. $formFieldList[$field]['type'] = 'remoteSelect';
  738. $formFieldList[$field][':input-attr']['multiple'] = 'true';
  739. }
  740. } elseif ($inputType == 'number') {
  741. $formFieldList[$field]['v-model.number'] = $formFieldList[$field]['v-model'];
  742. unset($formFieldList[$field]['v-model']);
  743. $formFieldList[$field][':input-attr']['step'] = $column['NUMERIC_SCALE'] > 0 ? '0.' . str_repeat(0, $column['NUMERIC_SCALE'] - 1) . '1' : 1;
  744. } elseif ($inputType == 'icon') {
  745. $formFieldList[$field][':input-attr']['placement'] = 'top';
  746. } elseif ($inputType == 'array') {
  747. $modelFieldType[$field] = 'json';
  748. $inputDefaultItems[$field] = [];
  749. } elseif ($inputType == 'datetime' && $column['DATA_TYPE'] == 'int') {
  750. $modelFieldType[$field] = 'timestamp:Y-m-d H:i:s';
  751. } elseif ($inputType == 'editor') {
  752. $formFieldList[$field]['@keyup.enter.stop'] = '';
  753. $formFieldList[$field]['@keyup.ctrl.enter'] = 'baTable.onSubmit(formRef)';
  754. $formDialogBig = true; // form 使用较宽的 Dialog
  755. // 重写edit和add方法
  756. $controllerData['methods']['add'] = $this->stub->getReplacedStub('mixins' . DIRECTORY_SEPARATOR . 'controllerEditorAddMethod', []);
  757. $controllerData['methods']['edit'] = $this->stub->getReplacedStub('mixins' . DIRECTORY_SEPARATOR . 'controllerEditorEditMethod', []);
  758. }
  759. // 模型的属性修改器获取器
  760. $this->getModelAttrMethod($modelSetAttrArr, $field, $inputType, $column);
  761. // placeholder
  762. if (!in_array($inputType, ['image', 'images', 'file', 'files'])) {
  763. if (in_array($inputType, ['radio', 'checkbox', 'datetime', 'year', 'date', 'time', 'select', 'selects', 'remoteSelect', 'remoteSelects', 'city', 'icon'])) {
  764. $formFieldList[$field][':input-attr']['placeholder'] = "t('Please select field', { field: t('" . $this->langPrefix . $field . "') })";
  765. } else {
  766. $formFieldList[$field][':input-attr']['placeholder'] = "t('Please input field', { field: t('" . $this->langPrefix . $field . "') })";
  767. }
  768. }
  769. // 字段验证规则
  770. if ($column['IS_NULLABLE'] == 'NO' && !in_array($inputType, ['switch', 'icon'])) {
  771. if ($inputType == 'editor') {
  772. $formItemRules[$field][] = "buildValidatorData({name: 'editorRequired', message: t('Please input field', { field: t('" . $this->langPrefix . $field . "') })})";
  773. } elseif (in_array($inputType, ['radio', 'checkbox', 'datetime', 'year', 'date', 'time', 'select', 'selects', 'remoteSelect', 'city', 'image', 'images', 'file', 'files', 'icon'])) {
  774. $formItemRules[$field][] = "buildValidatorData({name: 'required', message: t('Please select field', { field: t('" . $this->langPrefix . $field . "') })})";
  775. } else {
  776. $formItemRules[$field][] = "buildValidatorData({name: 'required', title: t('" . $this->langPrefix . $field . "')})";
  777. }
  778. }
  779. if ($field == 'mobile') {
  780. $formItemRules[$field][] = "buildValidatorData({name: 'mobile', message: t('Please enter the correct field', { field: t('" . $this->langPrefix . $field . "') })})";
  781. }
  782. if ($inputType == 'datetime' || $inputType == 'date') {
  783. $formItemRules[$field][] = "buildValidatorData({name: 'date', message: t('Please enter the correct field', { field: t('" . $this->langPrefix . $field . "') })})";
  784. }
  785. }
  786. // 表格列
  787. if (!$fields || in_array($field, $fields)) {
  788. $tableColumnList[] = $this->getTableColumn($field, $inputType, $column['DATA_TYPE'], $columnData);
  789. }
  790. }
  791. // 关联表
  792. foreach ($relations as $relation) {
  793. foreach ($relation['relationColumnList'] as $v) {
  794. // 不显示的字段直接过滤掉
  795. if ($relation['relationFields'] && !in_array($v['COLUMN_NAME'], $relation['relationFields'])) {
  796. continue;
  797. }
  798. $inputType = $this->getFieldInputType($v);
  799. $relationField = strtolower($relation['relationName']) . '.' . $v['COLUMN_NAME'];
  800. $relationFieldLang = strtolower($relation['relationName']) . '__' . $v['COLUMN_NAME'];
  801. // 列替换数据
  802. $columnData = $this->getColumnReplaceData($v, $relationFieldLang, $inputType);
  803. // 不允许双击编辑的字段
  804. if ($inputType == 'switch') {
  805. $dblClickNotEditColumn[] = $relationField;
  806. }
  807. // 表格列
  808. $tableColumnList[] = $this->getTableColumn($relationField, $inputType, $v['DATA_TYPE'], $columnData, $relationFieldLang);
  809. // 字段语言包数据
  810. if ($v['COLUMN_COMMENT'] != '') {
  811. $columnLang = $this->getLangItem($relationFieldLang, $v['COLUMN_COMMENT']);
  812. $langList['en'] = array_merge($langList['en'], $columnLang['en']);
  813. $langList['zh-cn'] = array_merge($langList['zh-cn'], $columnLang['zh-cn']);
  814. }
  815. }
  816. }
  817. Stub::buildFormField($formFieldList);
  818. Stub::buildFormRules($formItemRules);
  819. // 表格列额外处理
  820. $tableColumnList[] = [
  821. 'label' => "t('operate')",
  822. 'align' => 'center',
  823. 'width' => $enableDragSort ? 140 : 100,
  824. 'render' => 'buttons',
  825. 'buttons' => 'optButtons',
  826. 'operator' => 'false',
  827. ];
  828. foreach ($tableColumnList as $key => $item) {
  829. if (isset($item['show']) && !$item['show']) {
  830. unset($tableColumnList[$key]);
  831. }
  832. }
  833. Stub::buildTableColumn($tableColumnList);
  834. // 不允许双击编辑的字段
  835. Stub::buildDblClickNotEditColumn($dblClickNotEditColumn);
  836. // 开启拖拽排序
  837. if ($enableDragSort) {
  838. array_unshift($optButtons, 'weigh-sort');
  839. }
  840. $optButtons = json_encode($optButtons);
  841. // 处理快速搜索提示信息
  842. $langList['en']['quick Search Fields'] = implode('、', $this->quicksearchfield);
  843. $langList['zh-cn']['quick Search Fields'] = implode('、', $quickSearchFieldNames);
  844. $controllerAttrList['quickSearchField'] = $this->quicksearchfield;
  845. // 输入框默认值
  846. $inputDefaultItems = json_encode($inputDefaultItems);
  847. // 默认排序
  848. if ($this->defaultSortField) {
  849. $sortItem = explode(',', $this->defaultSortField);
  850. if (isset($sortItem[0]) && isset($sortItem[1]) && in_array($sortItem[0], $fieldArr)) {
  851. $defaultOrder = $sortItem;
  852. }
  853. }
  854. if (!$defaultOrder) {
  855. $defaultOrder = [$priKey, 'desc'];
  856. }
  857. $defaultOrderStub = Stub::buildDefaultOrder($defaultOrder);
  858. $controllerAttrList['defaultSortField'] = implode(',', $defaultOrder);
  859. // 添加Url到controllerUrls.ts
  860. $appendUrl = "export const {$controllerUrlVarName} = '{$controllerUrl}'";
  861. $webControllerUrlsContents = @file_get_contents($webControllerUrls);
  862. if (strpos($webControllerUrlsContents, $appendUrl) === false) {
  863. @file_put_contents($webControllerUrls, $appendUrl . "\n", FILE_APPEND);
  864. }
  865. // 组装index.vue
  866. $indexVue = $this->stub->getReplacedStub('html' . DIRECTORY_SEPARATOR . 'index', [
  867. 'tablePk' => $priKey,
  868. 'controllerUrlVarName' => $controllerUrlVarName,
  869. 'originControllerUrl' => $originControllerUrl,
  870. 'tableColumnList' => $tableColumnList,
  871. 'dblClickNotEditColumn' => $dblClickNotEditColumn,
  872. 'optButtons' => $optButtons,
  873. 'inputDefaultItems' => $inputDefaultItems ?: '',
  874. 'defaultOrderStub' => $defaultOrderStub,
  875. 'langPrefix' => $this->langPrefix,
  876. ]);
  877. Stub::writeToFile($indexFile, $indexVue);
  878. // 组装form.vue
  879. if ($importControllerUrls) {
  880. $importPackages .= "import { " . implode(',', array_keys($importControllerUrls)) . " } from '/@/api/controllerUrls'\n";
  881. }
  882. $formDialogBig = $formDialogBig ? "\n" . Stub::tab(2) . "width='50%'" : '';
  883. $formVue = $this->stub->getReplacedStub('html' . DIRECTORY_SEPARATOR . 'form', [
  884. 'formItem' => $formFieldList ?: '',
  885. 'importPackages' => $importPackages,
  886. 'formItemRules' => $formItemRules ?: '',
  887. 'formDialogBig' => $formDialogBig,
  888. ]);
  889. Stub::writeToFile($formFile, $formVue);
  890. // 组装和写入web语言包
  891. Stub::writeWebLangFile($langList, $webLangEnFile, $webLangZhCnFile);
  892. // 表注释
  893. $tableComment = mb_substr($modelTableInfo['Comment'], -1) == '表' ? mb_substr($modelTableInfo['Comment'], 0, -1) . '管理' : $modelTableInfo['Comment'];
  894. $modelData = [
  895. 'modelNamespace' => $modelNamespace,
  896. 'modelName' => $modelName,
  897. 'controllerUrlVarName' => $controllerUrlVarName,
  898. 'modelConnection' => $db == 'mysql' ? '' : "\n" . Stub::tab() . "protected \$connection = '{$db}';",
  899. 'modelTableType' => $modelTableType,
  900. 'modelTableTypeName' => $modelTableTypeName,
  901. 'modelAutoWriteTimestamp' => in_array($this->createTimeField, $fieldArr) || in_array($this->updateTimeField, $fieldArr) ? "'int'" : 'false',
  902. 'createTime' => in_array($this->createTimeField, $fieldArr) ? "'{$this->createTimeField}'" : 'false',
  903. 'updateTime' => in_array($this->updateTimeField, $fieldArr) ? "'{$this->updateTimeField}'" : 'false',
  904. 'modeAfterInsert' => '',
  905. 'priKey' => $priKey == 'id' ? '' : "\n" . Stub::tab() . "// 表主键\n" . Stub::tab() . 'protected $pk = ' . "'{$priKey}';\n" . Stub::tab(),
  906. ];
  907. $controllerData['tableComment'] = $tableComment;
  908. if ($priKey != $defaultOrder[0]) {
  909. $modelData['modeAfterInsert'] = $this->stub->getReplacedStub('mixins' . DIRECTORY_SEPARATOR . 'modeAfterInsert', [
  910. 'order' => $defaultOrder[0]
  911. ]);
  912. }
  913. // 如果使用关联模型
  914. if ($relations) {
  915. $relationWithList = $relationMethodList = $relationVisibleFieldList = [];
  916. foreach ($relations as $relation) {
  917. // 需要构造关联的方法
  918. $relation['relationMethod'] = strtolower($relation['relationName']);
  919. // 关联的模式
  920. $relation['relationMode'] = $relation['relationMode'] == 'hasone' ? 'hasOne' : 'belongsTo';
  921. // 关联字段
  922. $relation['relationPrimaryKey'] = $relation['relationPrimaryKey'] ?: $priKey;
  923. // 预载入的方法
  924. $relationWithList[] = $relation['relationMethod'];
  925. unset($relation['relationColumnList'], $relation['relationFieldList'], $relation['relationTableInfo']);
  926. // 构造关联模型的方法
  927. $relationMethodList[] = $this->stub->getReplacedStub('mixins' . DIRECTORY_SEPARATOR . 'modelBelongsToMethod', $relation);
  928. // 显示的字段
  929. if ($relation['relationFields']) {
  930. $relationVisibleFieldList[] = "\$res->visible(['{$relation['relationMethod']}' => ['" . implode("','", $relation['relationFields']) . "']]);";
  931. }
  932. }
  933. $controllerData['relationVisibleFieldList'] = $relationVisibleFieldList ? implode("\n" . Stub::tab(2), $relationVisibleFieldList) : '';
  934. $controllerAttrList['withJoinTable'] = $relationWithList;
  935. // 需要重写index方法
  936. if ($controllerData['relationVisibleFieldList']) {
  937. $controllerData['methods']['index'] = $this->stub->getReplacedStub('mixins' . DIRECTORY_SEPARATOR . 'controllerIndex', $controllerData);
  938. }
  939. }
  940. // 排除字段
  941. $preExcludeFields = ['createtime', 'updatetime', 'salt'];
  942. $controllerExcludeFields = [];
  943. foreach ($preExcludeFields as $preExcludeField) {
  944. if (in_array($preExcludeField, $fieldArr)) {
  945. $controllerExcludeFields[] = $preExcludeField;
  946. }
  947. }
  948. $controllerAttrList['preExcludeFields'] = $controllerExcludeFields;
  949. // 控制器属性
  950. $controllerAttr = '';
  951. foreach ($controllerAttrList as $key => $item) {
  952. if (is_array($item)) {
  953. $controllerAttr .= "\n" . Stub::tab() . "protected \${$key} = ['" . implode("', '", $item) . "'];\n";
  954. } else {
  955. $controllerAttr .= "\n" . Stub::tab() . "protected \${$key} = '$item';\n";
  956. }
  957. }
  958. $controllerData['controllerAttr'] = $controllerAttr;
  959. $controllerData['methods'] = (isset($controllerData['methods']) && $controllerData['methods']) ? implode("\n", $controllerData['methods']) : '';
  960. $controllerContent = $this->stub->getReplacedStub('controller', $controllerData);
  961. // 生成控制器文件
  962. Stub::writeToFile($controllerFile, $controllerContent);
  963. // 生成模型文件
  964. $modelMethodList = array_merge($modelSetAttrArr, $relationMethodList ?? []);
  965. $modelData['modelMethodList'] = $modelMethodList ? implode("\n", $modelMethodList) : '';
  966. $modelData['modelFieldType'] = Stub::buildModelFieldType($modelFieldType);
  967. $modelContent = $this->stub->getReplacedStub('model', $modelData);
  968. Stub::writeToFile($modelFile, $modelContent);
  969. // 生成关联模型文件
  970. if ($relations) {
  971. foreach ($relations as $relation) {
  972. if (!is_file($relation['relationFile'])) {
  973. $relationFileContent = $this->stub->getReplacedStub('relationModel', $relation);
  974. Stub::writeToFile($relation['relationFile'], $relationFileContent);
  975. }
  976. }
  977. }
  978. // 生成验证器文件
  979. Stub::writeToFile($validateFile, $this->stub->getReplacedStub('validate', [
  980. 'validateNamespace' => $validateNamespace,
  981. 'validateName' => $validateName
  982. ]));
  983. // TODO 生成server端语言包文件备用
  984. } catch (ErrorException $e) {
  985. throw new Exception('Code: ' . $e->getCode() . "\nLine: " . $e->getLine() . "\nMessage: " . $e->getMessage() . "\nFile: " . $e->getFile());
  986. }
  987. // 继续生成菜单
  988. if (MenuRule::where('name', $originControllerUrl)->value('id')) {
  989. $output->warning('Menu rules are not automatically created because they are repeated!');
  990. } else {
  991. // 建立菜单目录
  992. array_pop($baseNameArr);
  993. $pid = 0;
  994. if ($baseNameArr) {
  995. foreach ($baseNameArr as $item) {
  996. $pMenu = MenuRule::where('name', $item)->value('id');
  997. if ($pMenu) {
  998. $pid = $pMenu;
  999. continue;
  1000. }
  1001. $menu = [
  1002. 'pid' => $pid,
  1003. 'type' => 'menu_dir',
  1004. 'title' => $item,
  1005. 'name' => $item,
  1006. 'path' => $item,
  1007. ];
  1008. $menu = MenuRule::create($menu);
  1009. $pid = $menu->id;
  1010. }
  1011. }
  1012. // 建立菜单
  1013. foreach ($this->menuChildren as &$item) {
  1014. $item['name'] = $originControllerUrl . $item['name'];
  1015. }
  1016. $componentPath = '/' . str_replace($webPath, '', $indexFile);
  1017. $componentPath = str_replace('\\', '/', $componentPath);
  1018. Menu::create([
  1019. [
  1020. 'type' => 'menu',
  1021. 'title' => $tableComment ?: $table,
  1022. 'name' => $originControllerUrl,
  1023. 'path' => $originControllerUrl,
  1024. 'menu_type' => 'tab',
  1025. 'component' => $componentPath,
  1026. 'children' => $this->menuChildren,
  1027. ]
  1028. ], $pid);
  1029. }
  1030. $output->info('Build Successed');
  1031. }
  1032. /**
  1033. * 获取输入选项
  1034. * @param $name string
  1035. * @return string[]|string
  1036. */
  1037. public function getOption(string $name)
  1038. {
  1039. $value = $this->input->getOption($name);
  1040. // 数组处理,兼容[php think crud -f a,b -f c -f d,e,f]
  1041. if (array_key_exists($name, $this->options) && $this->options[$name]['solveArray']) {
  1042. $valueTmp = [];
  1043. foreach ($value as $item) {
  1044. if (stripos($item, ',') !== false) {
  1045. $valueTmp = array_merge($valueTmp, explode(',', $item));
  1046. } elseif (stripos($item, ',') !== false) {
  1047. $valueTmp = array_merge($valueTmp, explode(',', $item));
  1048. } else {
  1049. $valueTmp[] = $item;
  1050. }
  1051. }
  1052. return $valueTmp;
  1053. }
  1054. return $value;
  1055. }
  1056. public function setInputTypeRule($inputType, $newSuffix = null, $newType = null)
  1057. {
  1058. foreach ($this->inputTypeRule as &$item) {
  1059. if ($item['value'] == $inputType) {
  1060. if ($newSuffix) $item['suffix'] = $newSuffix;
  1061. if ($newType) $item['suffix'] = $newType;
  1062. }
  1063. }
  1064. }
  1065. /**
  1066. * 获取模型相关信息.
  1067. *
  1068. * @param $module
  1069. * @param $model
  1070. * @param $table
  1071. * @return array
  1072. */
  1073. protected function getModelData($module, $model, $table): array
  1074. {
  1075. return $this->getParseNameData($module, $model, $table, 'model');
  1076. }
  1077. /**
  1078. * 获取控制器相关信息.
  1079. * @param $module
  1080. * @param $controller
  1081. * @param $table
  1082. * @return array
  1083. */
  1084. protected function getControllerData($module, $controller, $table): array
  1085. {
  1086. return $this->getParseNameData($module, $controller, $table, 'controller');
  1087. }
  1088. /**
  1089. * 获取验证器相关信息.
  1090. * @param $module
  1091. * @param $validate
  1092. * @param $table
  1093. * @return array
  1094. */
  1095. protected function getValidateData($module, $validate, $table): array
  1096. {
  1097. return $this->getParseNameData($module, $validate, $table, 'validate');
  1098. }
  1099. /**
  1100. * 获取已解析相关信息.
  1101. *
  1102. * @param string $module 模块名称
  1103. * @param string $name 自定义名称
  1104. * @param string $table 数据表名
  1105. * @param string $type 解析类型,本例中为controller、model、validate
  1106. * @return array
  1107. */
  1108. protected function getParseNameData($module, $name, $table, $type): array
  1109. {
  1110. $arr = [];
  1111. if (!$name) {
  1112. $parseName = parse_name($table, 1);
  1113. $parseArr = [$table];
  1114. } else {
  1115. $name = str_replace(['.', '/', '\\'], '/', $name);
  1116. $arr = explode('/', $name);
  1117. $parseName = ucfirst(array_pop($arr));
  1118. $parseArr = $arr;
  1119. array_push($parseArr, $parseName);
  1120. }
  1121. //类名不能为内部关键字
  1122. if (in_array(strtolower($parseName), $this->reservedKeywords)) {
  1123. throw new Exception('Unable to use internal variable:' . $parseName);
  1124. }
  1125. $appNamespace = 'app'; //Config::get('app_namespace');
  1126. $parseNamespace = "{$appNamespace}\\{$module}\\{$type}" . ($arr ? '\\' . implode('\\', $arr) : '');
  1127. $moduleDir = app()->getBasePath() . $module . DIRECTORY_SEPARATOR;
  1128. $parseFile = $moduleDir . $type . DIRECTORY_SEPARATOR . ($arr ? implode(DIRECTORY_SEPARATOR, $arr) . DIRECTORY_SEPARATOR : '') . $parseName . '.php';
  1129. return [$parseNamespace, $parseName, $parseFile, $parseArr];
  1130. }
  1131. /**
  1132. * 移除相对的空目录.
  1133. * @param string $parseFile
  1134. * @param array $parseArr
  1135. * @param int $level
  1136. * @return bool
  1137. */
  1138. protected function removeEmptyBaseDir(string $parseFile, array $parseArr, int $level = 1): bool
  1139. {
  1140. if (count($parseArr) > $level) {
  1141. $parentDir = dirname($parseFile);
  1142. for ($i = 0; $i < count($parseArr); $i++) {
  1143. try {
  1144. $iterator = new FilesystemIterator($parentDir);
  1145. $isDirEmpty = !$iterator->valid();
  1146. if ($isDirEmpty) {
  1147. rmdir($parentDir);
  1148. $parentDir = dirname($parentDir);
  1149. } else {
  1150. return true;
  1151. }
  1152. } catch (UnexpectedValueException $e) {
  1153. return false;
  1154. }
  1155. }
  1156. }
  1157. return true;
  1158. }
  1159. protected function getItemArray($item, $field, $comment)
  1160. {
  1161. $itemArr = [];
  1162. $comment = str_replace(',', ',', $comment);
  1163. if (stripos($comment, ':') !== false && stripos($comment, ',') && stripos($comment, '=') !== false) {
  1164. [$fieldLang, $item] = explode(':', $comment);
  1165. foreach (explode(',', $item) as $v) {
  1166. $valArr = explode('=', $v);
  1167. if (count($valArr) == 2) {
  1168. [$key, $value] = $valArr;
  1169. $itemArr[$key] = $field . ' ' . $key;
  1170. }
  1171. }
  1172. } else {
  1173. foreach ($item as $v) {
  1174. $itemArr[$v] = is_numeric($v) ? $field . ' ' . $v : $v;
  1175. }
  1176. }
  1177. return $itemArr;
  1178. }
  1179. public function getTableColumn($field, $inputType, $fieldType, $columnData, $relationFieldLang = null)
  1180. {
  1181. if (!$relationFieldLang) $relationFieldLang = $field;
  1182. $nameBool = false;
  1183. $typeBool = false;
  1184. $suffixBool = false;
  1185. $extendAttr = [];
  1186. foreach ($this->fieldRenderRule as $rule) {
  1187. if (!$rule['attr']) {
  1188. continue;
  1189. }
  1190. if (isset($rule['name']) && $rule['name'] && in_array($field, $rule['name'])) {
  1191. $nameBool = true;
  1192. }
  1193. if (isset($rule['type']) && $rule['type'] && in_array($inputType, $rule['type'])) {
  1194. $typeBool = true;
  1195. }
  1196. if (isset($item['suffix']) && $item['suffix'] && $this->isMatchSuffix($field, $item['suffix'])) {
  1197. $suffixBool = true;
  1198. }
  1199. if ($nameBool || $typeBool || $suffixBool) {
  1200. $extendAttr = $rule['attr'];
  1201. break;
  1202. }
  1203. }
  1204. $column = [
  1205. 'label' => "t('" . $this->langPrefix . $relationFieldLang . "')",
  1206. 'prop' => $field,
  1207. 'align' => 'center',
  1208. ];
  1209. $column = array_merge($column, $extendAttr);
  1210. $columnReplaceValue = [
  1211. 'tag',
  1212. 'tags',
  1213. 'switch',
  1214. ];
  1215. if (isset($column['render']) && in_array($column['render'], $columnReplaceValue)) {
  1216. $column['replaceValue'] = $columnData;
  1217. }
  1218. return $column;
  1219. }
  1220. protected function getLangItem($field, $content)
  1221. {
  1222. $en[$field] = $field;
  1223. $zhCn[$field] = $field;
  1224. if ($content || !Lang::has($field)) {
  1225. $content = str_replace(',', ',', $content);
  1226. $content = str_replace(':', ':', $content);
  1227. if (stripos($content, ':') !== false && stripos($content, ',') && stripos($content, '=') !== false) {
  1228. [$fieldLang, $item] = explode(':', $content);
  1229. $zhCn[$field] = $fieldLang;
  1230. foreach (explode(',', $item) as $v) {
  1231. $valArr = explode('=', $v);
  1232. if (count($valArr) == 2) {
  1233. [$key, $value] = $valArr;
  1234. $en[$field . ' ' . $key] = $field . ' ' . $key;
  1235. $zhCn[$field . ' ' . $key] = $value;
  1236. }
  1237. }
  1238. } else {
  1239. $zhCn[$field] = $content;
  1240. }
  1241. }
  1242. return [
  1243. 'en' => $en,
  1244. 'zh-cn' => $zhCn
  1245. ];
  1246. }
  1247. protected function getFieldInputType($v)
  1248. {
  1249. $inputType = 'string';
  1250. foreach ($this->inputTypeRule as $item) {
  1251. $typeBool = true;
  1252. $suffixBool = true;
  1253. $columnTypeBool = true;
  1254. if (isset($item['type']) && $item['type'] && !in_array($v['DATA_TYPE'], $item['type'])) {
  1255. $typeBool = false;
  1256. }
  1257. if (isset($item['suffix']) && $item['suffix']) {
  1258. $suffixBool = $this->isMatchSuffix($v['COLUMN_NAME'], $item['suffix']);
  1259. }
  1260. if (isset($item['column_type']) && $item['column_type'] && !in_array($v['COLUMN_TYPE'], $item['column_type'])) {
  1261. $columnTypeBool = false;
  1262. }
  1263. if ($typeBool && $suffixBool && $columnTypeBool) {
  1264. return $item['value'];
  1265. }
  1266. }
  1267. return $inputType;
  1268. }
  1269. /**
  1270. * 判断是否符合指定后缀
  1271. *
  1272. * @param string $field 字段名称
  1273. * @param mixed $suffixArr 后缀
  1274. * @return bool
  1275. */
  1276. protected function isMatchSuffix($field, $suffixArr)
  1277. {
  1278. $suffixArr = is_array($suffixArr) ? $suffixArr : explode(',', $suffixArr);
  1279. foreach ($suffixArr as $v) {
  1280. if (preg_match("/{$v}$/i", $field)) {
  1281. return true;
  1282. }
  1283. }
  1284. return false;
  1285. }
  1286. protected function getRemoteSelectField($fieldName, $relations = [])
  1287. {
  1288. $field = explode('_', $fieldName);
  1289. foreach ($relations as $relation) {
  1290. if ($relation['remoteSelectField'] && $relation['relationTableTypeName'] == $field[0]) {
  1291. return $relation['remoteSelectField'];
  1292. }
  1293. }
  1294. foreach ($this->remoteSelectFieldMap as $field => $item) {
  1295. if (in_array($fieldName, $item)) {
  1296. return $field;
  1297. }
  1298. }
  1299. return 'name';
  1300. }
  1301. protected function getModelAttrMethod(&$modelSetAttrArr, $field, $inputType, $column)
  1302. {
  1303. $fieldName = ucfirst($this->getCamelizeName($field));
  1304. if ($inputType == 'switch') {
  1305. $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'setSwitch', [
  1306. 'field' => $fieldName
  1307. ]);
  1308. } elseif (in_array($inputType, ['checkbox', 'selects', 'remoteSelects', 'city', 'images', 'files'])) {
  1309. $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'stringToArrayMethod', [
  1310. 'field' => $fieldName
  1311. ]);
  1312. $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'arrayToStringMethod', [
  1313. 'field' => $fieldName
  1314. ]);
  1315. } elseif ($inputType == 'array') {
  1316. $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'getArray', [
  1317. 'field' => $fieldName
  1318. ]);
  1319. } elseif (in_array($inputType, ['textarea', 'remoteSelect'])) {
  1320. $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'defaultEmptyStringMethod', [
  1321. 'field' => $fieldName
  1322. ]);
  1323. } elseif ($inputType == 'time') {
  1324. $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'setTime', [
  1325. 'field' => $fieldName
  1326. ]);
  1327. } elseif ($inputType == 'editor') {
  1328. $modelSetAttrArr[] = $this->stub->getReplacedStub('modelAttr' . DIRECTORY_SEPARATOR . 'getEditor', [
  1329. 'field' => $fieldName
  1330. ]);
  1331. }
  1332. }
  1333. protected function getCamelizeName($uncamelized_words, $separator = '_')
  1334. {
  1335. $uncamelized_words = $separator . str_replace($separator, " ", strtolower($uncamelized_words));
  1336. return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator);
  1337. }
  1338. protected function getRemoteSelectUrl($fieldName, $relations, $webControllerUrls)
  1339. {
  1340. $fieldName = explode('_', $fieldName);
  1341. $defaultValue = [
  1342. 'importControllerUrls' => false,
  1343. 'url' => $fieldName[0],
  1344. 'pk' => $fieldName[0] . '.id',
  1345. ];
  1346. $relationFile = false;
  1347. foreach ($relations as $relation) {
  1348. if ($relation['relationTableTypeName'] == $fieldName[0]) {
  1349. $relationFile = $relation['relationFile'];
  1350. $defaultValue['pk'] = $fieldName[0] . '.' . $relation['relationPrimaryKey'];
  1351. }
  1352. }
  1353. if (!$relationFile) {
  1354. return $defaultValue;
  1355. }
  1356. // 在关联模型中获取控制器url名称
  1357. $preg = "/@controllerUrl '(.+?)'/i";
  1358. $controllerUrlName = $this->getPregValueFromFile($relationFile, $preg);
  1359. if (!$controllerUrlName) {
  1360. return $defaultValue;
  1361. }
  1362. // 确定$webControllerUrls中存在该url的定义
  1363. $preg = '/(' . $controllerUrlName . ')/is';
  1364. $existWebControllerUrls = $this->getPregValueFromFile($webControllerUrls, $preg);
  1365. if (!$existWebControllerUrls) {
  1366. return $defaultValue;
  1367. }
  1368. return [
  1369. 'importControllerUrls' => $controllerUrlName,
  1370. 'url' => $controllerUrlName . " + 'index'",
  1371. 'pk' => $defaultValue['pk'],
  1372. ];
  1373. }
  1374. public function getPregValueFromFile($file, $preg, $valIdx = 1)
  1375. {
  1376. $fileContent = @file_get_contents($file);
  1377. if (!$fileContent) {
  1378. return false;
  1379. }
  1380. preg_match($preg, $fileContent, $result);
  1381. if ($result && isset($result[$valIdx])) {
  1382. return $result[$valIdx];
  1383. }
  1384. return false;
  1385. }
  1386. /**
  1387. * 列替换数据
  1388. */
  1389. public function getColumnReplaceData($column, $fieldName, $inputType)
  1390. {
  1391. $columnData = [];
  1392. if (in_array($column['DATA_TYPE'], ['enum', 'set', 'tinyint', 'char'])) {
  1393. if ($column['DATA_TYPE'] !== 'tinyint') {
  1394. $columnData = substr($column['COLUMN_TYPE'], strlen($column['DATA_TYPE']) + 1, -1);
  1395. $columnData = explode(',', str_replace("'", '', $columnData));
  1396. }
  1397. $columnData = $this->getItemArray($columnData, $fieldName, $column['COLUMN_COMMENT']);
  1398. }
  1399. if (!$columnData && in_array($inputType, ['select', 'selects'])) {
  1400. $columnData = $this->getItemArray($columnData, $fieldName, $column['COLUMN_COMMENT']);
  1401. }
  1402. if ($columnData) {
  1403. foreach ($columnData as $key => $columnDatum) {
  1404. $columnData[$key] = "t('" . $this->langPrefix . $columnDatum . "')";
  1405. }
  1406. }
  1407. return $columnData;
  1408. }
  1409. }