ensure-shba-perm.mjs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /**
  2. * 确保生产 DMS 有 shba_perm 栏目(职务菜单/按钮权限)。
  3. * 凭据默认与 src/api/dmsAuth.ts 相同。栏目已存在则灌默认四职务(若为空)。
  4. * addModel 必须从 shba_org_model 整表克隆并重排 index,不能自造字段结构。
  5. */
  6. import { DMS_CLIENT_ID, DMS_GATEWAY, DMS_PASSWORD, DMS_USER } from './dms-cred.mjs'
  7. const gateway = DMS_GATEWAY
  8. let token = ''
  9. async function call(urlPath, fields = {}, method = 'POST') {
  10. const body = new URLSearchParams()
  11. for (const [k, v] of Object.entries(fields)) body.set(k, String(v))
  12. const headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
  13. if (token) headers.Token = token
  14. const res = await fetch(urlPath.startsWith('http') ? urlPath : `${gateway}${urlPath}`, { method, headers, body })
  15. return res.json()
  16. }
  17. function walk(nodes, map = {}) {
  18. for (const n of nodes || []) {
  19. if (n.tag) map[n.tag] = n
  20. walk(n.columnList, map)
  21. }
  22. return map
  23. }
  24. function extra(name, alias, seq, describe = alias) {
  25. return {
  26. alias, customType: '', defaultValue: '', describe,
  27. frontType: seq === 1 ? 'varchar' : 'text',
  28. must: false, name, searchType: '2', sequence: seq, index: seq,
  29. showParam: 'name,alias,desc,type,front_type,must,default_value', type: 'text',
  30. }
  31. }
  32. function parseFieldList(raw) {
  33. const obj = typeof raw === 'string' ? JSON.parse(raw) : (raw || {})
  34. const out = {}
  35. for (const [k, v] of Object.entries(obj)) out[k] = typeof v === 'string' ? JSON.parse(v) : v
  36. return out
  37. }
  38. function cloneFieldList(parsed) {
  39. const cloned = {}
  40. Object.keys(parsed).forEach((key, i) => {
  41. const src = parsed[key] || {}
  42. cloned[key] = { ...src, name: src.name || key, index: i }
  43. })
  44. return cloned
  45. }
  46. function asModels(raw) {
  47. if (Array.isArray(raw)) return raw
  48. if (raw?.data && Array.isArray(raw.data)) return raw.data
  49. return []
  50. }
  51. const login = await call(`${gateway}/proxy_oauth/user/login`, {
  52. userName: DMS_USER,
  53. password: DMS_PASSWORD,
  54. clientId: DMS_CLIENT_ID,
  55. })
  56. if (login.code !== 200 || !login.message) {
  57. console.error('登录失败', login.message || login)
  58. process.exit(1)
  59. }
  60. token = login.message
  61. const author = login.content || {}
  62. const cols = walk((await call('/proxy_dms/column/getColumnList', {})).content)
  63. let col = cols.shba_perm
  64. if (!col?.id) {
  65. const models = asModels((await call('/proxy_dms/model/getModelList', { type: 1 })).content)
  66. let model = models.find((m) => m.modelName === 'shba_perm_model')
  67. if (!model) {
  68. const org = models.find((m) => m.modelName === 'shba_org_model')
  69. if (!org?.id) {
  70. console.error('找不到 shba_org_model,无法克隆')
  71. process.exit(1)
  72. }
  73. const detail = await call('/proxy_dms/model/getModelById', { modelId: org.id })
  74. const cloned = cloneFieldList(parseFieldList(detail.content.fieldList))
  75. const created = await call('/proxy_dms/model/addModel', {
  76. modelName: 'shba_perm_model',
  77. modelAlias: '职务权限',
  78. pageSize: 20,
  79. sortField: '[]',
  80. searchField: '[]',
  81. fieldList: JSON.stringify(cloned),
  82. type: 1,
  83. authorName: author.username || 'demo',
  84. authorId: author.id || 98,
  85. }, 'PUT')
  86. console.log('addModel', created.code, created.message, created.content && created.content.id)
  87. if (created.code !== 200 || !created.content?.id) {
  88. console.error('addModel 失败', JSON.stringify(created).slice(0, 500))
  89. process.exit(1)
  90. }
  91. model = { id: created.content.id }
  92. } else {
  93. console.log('reuse model', model.id)
  94. }
  95. const parent = cols.shba_master || cols.shba
  96. const added = await call('/proxy_dms/column/addColumn', {
  97. tag: 'shba_perm',
  98. title: '职务权限',
  99. content: '按职务配置后台菜单和按钮。',
  100. parentId: parent.id,
  101. type: 1,
  102. state: 0,
  103. level: 2,
  104. modelId: model.id,
  105. authorName: author.username || 'demo',
  106. authorId: author.id || 98,
  107. }, 'PUT')
  108. console.log('column', added.code, added.message)
  109. if (added.code !== 200) {
  110. console.error('addColumn 失败', JSON.stringify(added).slice(0, 500))
  111. process.exit(1)
  112. }
  113. const again = walk((await call('/proxy_dms/column/getColumnList', {})).content)
  114. col = again.shba_perm
  115. }
  116. if (!col?.id || !col.modelId) {
  117. console.error('栏目未就绪', col)
  118. process.exit(1)
  119. }
  120. console.log('shba_perm', col.id, col.modelName, 'model', col.modelId)
  121. const detail = await call('/proxy_dms/model/getModelById', { modelId: col.modelId })
  122. const fieldList = parseFieldList(detail.content.fieldList)
  123. const addfieldList = {}
  124. const specs = [extra('role', '职务', 1, 'hq/branch/site/guard'), extra('menus', '菜单权限', 2, '菜单 key JSON'), extra('actions', '按钮权限', 3, '操作 key JSON')]
  125. for (const spec of specs) {
  126. const colName = `c_${spec.name}`
  127. if (fieldList[colName]) continue
  128. fieldList[colName] = extra(colName, spec.alias, spec.sequence, spec.describe)
  129. addfieldList[spec.name] = spec
  130. }
  131. if (Object.keys(addfieldList).length) {
  132. const upd = await call('/proxy_dms/model/updateFieldList', {
  133. modelId: col.modelId,
  134. fieldList: JSON.stringify(fieldList),
  135. addfieldList: JSON.stringify(addfieldList),
  136. delfieldList: '{}',
  137. updatefieldList: '{}',
  138. })
  139. console.log('fields', upd.code, upd.message, Object.keys(addfieldList).join(','))
  140. } else {
  141. console.log('fields already', Object.keys(fieldList).filter((k) => k.startsWith('c_')).join(','))
  142. }
  143. const listed = await call('/proxy_dms/content/selectContentList', {
  144. columnId: col.id, search: '[]', orderBy: '[{"field":"update_time","orderByType":2}]', page: 0, pageSize: 50,
  145. })
  146. const rows = listed.content?.data || []
  147. console.log('existing perms', rows.length, rows.map((r) => `${r.content}:${r.c_role}`).join(' | '))
  148. const seeds = [
  149. { content: 'perm-hq', title: '人防管理员', c_role: 'hq', c_menus: '["*"]', c_actions: '["*"]' },
  150. { content: 'perm-branch', title: '分公司经理', c_role: 'branch', c_menus: JSON.stringify(['workbench','master.people','master.sites','master.contracts','master.train','duty.schedule','duty.attend','duty.ot','duty.split','duty.ident','field','collab.notice','collab.event','collab.alarm','collab.perform','collab.alert','finance.ledger','finance.board','finance.staffing','finance.supplier']), c_actions: JSON.stringify(['person.create','person.edit','person.delete','person.import','site.create','site.edit','site.delete','contract.create','contract.edit','contract.delete','train.create','train.edit','train.delete','schedule.edit','attend.edit','attend.delete','attend.export','split.export','ident.export','field.punch','notice.send','notice.read','notice.delete','notice.call','event.create','event.flow','event.delete','alarm.trigger','invert.create','invert.delete','alert.toggle','finance.edit','finance.lock','staffing.edit','supplier.pay']) },
  151. { content: 'perm-site', title: '驻点督导', c_role: 'site', c_menus: JSON.stringify(['workbench','master.people','master.sites','master.contracts','master.train','duty.schedule','duty.attend','duty.ot','duty.split','duty.ident','field','collab.notice','collab.event','collab.alarm','collab.perform','collab.alert']), c_actions: JSON.stringify(['person.create','person.edit','schedule.edit','attend.edit','attend.export','notice.send','notice.read','notice.call','event.create','event.flow','alarm.trigger','alert.toggle','field.punch']) },
  152. { content: 'perm-guard', title: '保安队员', c_role: 'guard', c_menus: '[]', c_actions: '[]' },
  153. ]
  154. const seen = new Set()
  155. for (const row of rows) {
  156. const key = String(row.c_role || row.content || '')
  157. if (!key) continue
  158. if (seen.has(key)) {
  159. const gone = await call('/proxy_dms/content/updateAudit', {
  160. columnId: col.id, id: row.id, state: 4, auditorName: 'demo',
  161. })
  162. console.log('dedupe', row.content, gone.code, gone.message)
  163. } else {
  164. seen.add(key)
  165. }
  166. }
  167. const have = seen
  168. for (const seed of seeds) {
  169. if (have.has(seed.content) || have.has(seed.c_role)) {
  170. console.log('skip', seed.content)
  171. continue
  172. }
  173. const added = await call('/proxy_dms/content/addContent', {
  174. columnId: col.id,
  175. modelId: col.modelId,
  176. content: JSON.stringify(seed),
  177. })
  178. console.log('add', seed.content, added.code, added.message)
  179. }