index.vue 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352
  1. <template>
  2. <view class="container" style="position: relative;">
  3. <!-- 首屏广告 -->
  4. <div style="margin-bottom: 8px;min-height: 130px">
  5. <ad-custom v-if="isShow" unit-id="adunit-f8a0fb31ae0f3569" bindload="adLoad" binderror="adError" bindclose="adClose"></ad-custom>
  6. </div>
  7. <!-- 侧边广告 -->
  8. <div style="position: absolute;top:400px;right:0px;z-index:999;opacity: 0.5">
  9. <ad-custom v-if="isShow" unit-id="adunit-2768960e9f32db36" bindload="adLoad" binderror="adError" bindclose="adClose"></ad-custom>
  10. </div>
  11. <div style="display: flex;flex-direction: column;">
  12. <div style="display: flex;justify-content: space-between;margin-bottom:8px">
  13. <button plain="true" class="primary-btn" style="padding:5px;width:70px;margin:2px" @click="viewExplam">
  14. {{ isMockData ? '清除' : '查看' }}示例
  15. </button>
  16. <button plain="true" class="primary-btn" style="padding:5px;width:70px;margin:2px" @click="switchRoles">
  17. 交换角色
  18. </button>
  19. <button plain="true" class="primary-btn" style="padding:5px;width:135px;margin:2px" @click="calculateBestMatch">
  20. 点击获取最佳搭配
  21. </button>
  22. </div>
  23. <uni-table border stripe>
  24. <uni-tr>
  25. <uni-th align="left" width="65px">
  26. <div style="display: flex;flex-direction: column;color:#989ba1">
  27. <p style="text-wrap:nowrap;text-align:center">角色</p>
  28. </div>
  29. </uni-th>
  30. <uni-th >
  31. <div style="display: flex;flex-direction: column;color:#989ba1">
  32. <p style="text-align:left">数量_性别: 势力</p>
  33. </div>
  34. </uni-th>
  35. <!-- <uni-th width="40px" ></uni-th> -->
  36. </uni-tr>
  37. <!-- 表格数据行 -->
  38. <uni-tr v-for="(item, index) in computedInputData" :key="index">
  39. <uni-td width="50px" >
  40. <div style="display: flex;flex-direction: column;">
  41. <p style="text-wrap:nowrap;text-align:center">{{ item.role }}</p>
  42. <!-- <p style="text-align:center">—</p> -->
  43. <p style="text-align:center">{{ item.role === '目标' ? totalTarget : totalBackup }}</p>
  44. </div>
  45. </uni-td>
  46. <uni-td>
  47. <div style="display: flex;flex-direction: column;">
  48. <div style="display:flex">
  49. <p style="width:50px;min-width:50px ;white-space: nowrap;text-align: right;">{{item.gender.male.length}}_公:</p>
  50. <div style="display: flex;flex-wrap:wrap;flex:1">
  51. <p
  52. :style="`margin-right: 5px;width:25px;text-align: center;color:${item.role === '备选' && picks.male.includes(index) ? '#67c23a' : ''}`"
  53. v-for="(power, index) in item.gender.male"
  54. >
  55. {{ power }}
  56. </p>
  57. </div>
  58. <div type="primary" link style="margin-left: 0px;" @click="editRow(item.role)">
  59. <!-- <uni-icons style="color: #409eff" size="14" type="compose" /> -->
  60. <image src="./edit.png" style="width:15px;height:15px" />
  61. </div>
  62. </div>
  63. <div style="display:flex">
  64. <p style="width:50px;min-width:50px ;white-space: nowrap;text-align: right;">{{item.gender.female.length}}_母:</p>
  65. <div style="display: flex;flex-wrap:wrap">
  66. <p :style="`margin-right: 5px;width:25px;text-align: center;color:${item.role === '备选' && picks.female.includes(index) ? '#67c23a' : ''}`" v-for="(power, index) in item.gender.female">{{ power }}</p>
  67. </div>
  68. </div>
  69. </div>
  70. </uni-td>
  71. <!-- <uni-td> -->
  72. <!-- </uni-td> -->
  73. </uni-tr>
  74. </uni-table>
  75. <!-- <div style="border:1px #ebeef5 solid;padding: 10px;text-align:center;color:#909399;margin-top:-1px" v-if="com">
  76. 暂无数据
  77. </div -->
  78. <view class="section" v-if="outputData.length > 0" style="flex: 1;margin-bottom: 0px">
  79. <div style="display: flex;justify-content: space-between;margin-top:10px">
  80. <text class="section-title" style="color: #303133;font-weight: 500;">最佳搭配方案</text>
  81. <text style="color:red">差额:{{ powerDifference }}</text>
  82. </div>
  83. <uni-table border stripe style="width:100%">
  84. <uni-tr class="has-bg" style="background: #f2f2f2">
  85. <uni-th align="left" width="65px">
  86. <div style="display: flex;flex-direction: column;border-bottom: 0px !important;position: absolute;top:26px;left: 2px;width:62px;background: #f5f7fa;">
  87. <p style="text-wrap:nowrap;text-align:center;color:#989ba1">角色</p>
  88. <!-- <p style="text-align:center">—</p> -->
  89. </div>
  90. </uni-th>
  91. <uni-th align="left" style="border-right:0px !important">
  92. <div style="display: flex;flex-direction: column;">
  93. <p style="text-align:left;color:#989ba1">搭配结果</p>
  94. <div style="position: absolute;height:42px;left:223px;background:#f5f7fa;top:1px;width:10px"></div>
  95. </div>
  96. </uni-th>
  97. <uni-th align="left" style="border-left:0px !important"></uni-th>
  98. </uni-tr>
  99. <div class="uni-table-tr no-padding" style="display: table-row;">
  100. <uni-th align="left" width="50px">
  101. </uni-th>
  102. <!-- <uni-th align="center" width="70px"></uni-th> -->
  103. <uni-th :width="1000 + 'px'" class="uni-table-th table--border no-padding" style="text-align: left;color:#989ba1;">
  104. {{ outputData[1].femalePowers.length }}对
  105. </uni-th>
  106. <uni-th :width="1000 + 'px'" class="uni-table-th table--border no-padding" style="text-align: left;color:#989ba1">
  107. {{ outputData[1].malePowers.length }}对
  108. </uni-th>
  109. </div>
  110. <!-- 表格数据行 -->
  111. <uni-tr v-for="(item, index) in outputData" :key="index">
  112. <uni-td width="50px" >
  113. <div style="display: flex;flex-direction: column;">
  114. <p style="text-wrap:nowrap;text-align:center">{{ item.role }}</p>
  115. <!-- <p style="text-align:center">—</p> -->
  116. <p style="text-align:center">{{ item.role === '目标' ? totalTarget : totalPick }}</p>
  117. </div>
  118. </uni-td>
  119. <uni-td >
  120. <div style="display: flex;">
  121. <!-- <p style="width: 25px;min-width:25px">{{ index === 0 ? '公' : '母' }}:</p> -->
  122. <div style="display:flex;flex-wrap:wrap">
  123. <p :style="`margin-right: 5px;width:25px;text-align: center;color: ${index === 0 ? 'rgb(103, 194, 58)' : '#e6a23c'}`" v-for="power in (index ===0 ? item.malePowers : item.femalePowers)">{{ power }}</p>
  124. </div>
  125. </div>
  126. </uni-td>
  127. <uni-td>
  128. <div style="display: flex;">
  129. <!-- <p style="width: 25px;min-width:25px">{{ index === 0 ? '母' : '公' }}:</p> -->
  130. <div style="display:flex;flex-wrap:wrap">
  131. <p :style="`margin-right: 5px;width:25px;text-align: center;color: ${index === 1 ? 'rgb(103, 194, 58)' : '#e6a23c'}`" v-for="power in (index === 0 ? item.femalePowers : item.malePowers)">{{ power }}</p>
  132. </div>
  133. </div>
  134. </uni-td>
  135. </uni-tr>
  136. </uni-table>
  137. </view>
  138. </div>
  139. <uni-popup ref="batchRef" :is-mask-click="false">
  140. <div
  141. style="display: flex;flex-direction: column;justify-content: center;padding: 10px; width: 95%;background: #fff;border-radius: 10px;padding: 10px;">
  142. <div style="display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px;">
  143. <h4 style="margin-bottom: 10px"> 编辑{{ role }}</h4>
  144. <uni-icons type="closeempty" size="18" @click="batchRef.close()"></uni-icons>
  145. </div>
  146. <div style="display: flex;justify-content: space-between; align-items: center;">
  147. <span class="dialog-title">猫咪势力(公)</span>
  148. <uni-icons type="close" size="18" @click="malePowers = ''"></uni-icons>
  149. </div>
  150. <textarea v-model="malePowers" placeholder="用逗号或顿号隔开,如:200,150,100"
  151. style="height: 70px;border: 1px solid #dfe2e5" />
  152. <div style="display: flex;justify-content: space-between; align-items: center;">
  153. <span class="dialog-title">猫咪势力(母)</span>
  154. <uni-icons type="close" size="18" @click="feMalePowers = ''"></uni-icons>
  155. </div>
  156. <textarea v-model="feMalePowers" placeholder="用逗号或顿号隔开,如:200,150,100"
  157. style="height: 70px;border: 1px solid #dfe2e5" />
  158. <div style="display: flex;justify-content: flex-end;">
  159. <button size='mini' type="primary" @click="handleConfirm">保存</button>
  160. </div>
  161. </div>
  162. </uni-popup>
  163. <uni-popup ref="messageRef" type="message">
  164. <uni-popup-message type="error" :message="messageText" :duration="5000"></uni-popup-message>
  165. </uni-popup>
  166. <uni-popup ref="helpRef" :is-mask-click="false">
  167. <div class="help-modal"
  168. style="display: flex; justify-content: center;flex-direction: column;padding: 10px; width: 90vw;background: #fff;border-radius: 10px;padding: 30px;box-sizing: border-box;">
  169. <div style="display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px;">
  170. <span></span>
  171. <h4 style="margin-bottom: 10px">使用说明</h4>
  172. <uni-icons type="closeempty" size="18" @click="helpRef.close()"></uni-icons>
  173. </div>
  174. <div style="margin-bottom: 10px;">
  175. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;“喵缘礼堂”是一款专注于为猫咪结婚提供解决方案的应用。它以所有目标角色猫咪的势力总和作为基准,通过筛选备选角色猫咪,为用户匹配出其势力总和最接近目标角色猫咪势力总和的方案。
  176. </div>
  177. <div>
  178. <p style="font-weight: bold;margin-bottom: 10px">使用方法:</p>
  179. <p style="margin-bottom: 10px">1.录入目标猫咪势力数据,总数≤20 只,依次准确填写。</p>
  180. <p style="margin-bottom: 10px">2.输入备选猫咪势力数据,不同性别数均≤20 只,详细填写。</p>
  181. <p>3.点击 “计算最佳匹配方案”,系统将据此算出最适配方案。</p>
  182. </div>
  183. </div>
  184. </uni-popup>
  185. <div class="loading" v-if="loading">
  186. <div class="loading-wrapper">
  187. <image src="./cat-load.gif" style="width:260px;height:260px" />
  188. </div>
  189. </div>
  190. </view>
  191. </template>
  192. <script setup>
  193. import { ref, computed, onMounted, nextTick } from 'vue';
  194. const helpRef = ref(null)
  195. const messageText = ref('')
  196. const messageRef = ref(null)
  197. const loading = ref(false)
  198. const isMockData = ref(false)
  199. const index = ref(-1)
  200. const time = ref("")
  201. const role = ref("")
  202. const type = ref('')
  203. const gender = ref("")
  204. const powers = ref("")
  205. const malePowers = ref('')
  206. const feMalePowers = ref('')
  207. const totalTarget = ref(0)
  208. const totalBackup = ref(0)
  209. const totalPick = ref(0)
  210. const picks = ref({
  211. male: [],
  212. female: []
  213. })
  214. const isShow = ref(true)
  215. setInterval(() => {
  216. isShow.value = false
  217. nextTick(() => isShow.value = true)
  218. }, 10000)
  219. const mockData = [
  220. {
  221. role: '目标', num: 11, gender: '公',
  222. power: '153, 91, 77, 138, 90, 80, 60, 54, 52, 52, 52'
  223. },
  224. {
  225. role: '目标', num: 9, gender: '母',
  226. power: '121, 118, 115, 86, 84, 84, 59, 53, 51'
  227. },
  228. {
  229. role: '备选', num:12, gender: '公',
  230. power: '174, 152, 147, 102, 101, 98, 98, 97, 62, 60, 58, 19'
  231. },
  232. {
  233. role: '备选', num: 20, gender: '母',
  234. power: '180, 165, 158, 154, 152, 151, 150, 150, 102, 101, 101, 93, 61, 30, 30, 29, 29, 29, 27, 10'
  235. }
  236. ]
  237. const mockOutData = [
  238. {
  239. "role": "目标",
  240. "malePowers": [
  241. "153",
  242. " 138",
  243. " 91",
  244. " 90",
  245. " 80",
  246. " 77",
  247. " 60",
  248. " 54",
  249. " 52",
  250. " 52",
  251. " 52"
  252. ],
  253. "femalePowers": [
  254. "121",
  255. " 118",
  256. " 115",
  257. " 86",
  258. " 84",
  259. " 84",
  260. " 59",
  261. " 53",
  262. " 51"
  263. ]
  264. },
  265. {
  266. "role": "备选",
  267. "malePowers": [
  268. 102,
  269. 101,
  270. 98,
  271. 98,
  272. 97,
  273. 62,
  274. 60,
  275. 58,
  276. 19
  277. ],
  278. "femalePowers": [
  279. 180,
  280. 165,
  281. 158,
  282. 154,
  283. 101,
  284. 93,
  285. 29,
  286. 29,
  287. 29,
  288. 27,
  289. 10
  290. ]
  291. }
  292. ]
  293. const inputData = ref([
  294. { role: '目标', num: 0, gender: '公', power: '' },
  295. { role: '目标', num: 0, gender: '母', power: '' },
  296. { role: '备选', num: 0, gender: '公', power: '' },
  297. { role: '备选', num: 0, gender: '母', power: '' }
  298. ]);
  299. const computedInputData = computed(() => {
  300. const target = inputData.value.slice(0, 2)
  301. const backup = inputData.value.slice(2)
  302. return [
  303. {
  304. role: '目标',
  305. gender: {
  306. male: target[0].power.split(',').filter(item => !!item).sort((a, b) => b - a),
  307. female: target[1].power.split(',').filter(item => !!item).sort((a, b) => b - a),
  308. }
  309. },
  310. {
  311. role: '备选',
  312. gender: {
  313. male: backup[0].power.split(',').filter(item => !!item).sort((a, b) => b - a),
  314. female: backup[1].power.split(',').filter(item => !!item).sort((a, b) => b - a),
  315. }
  316. }
  317. ]
  318. })
  319. const computedOutputData = computed(() => {
  320. console.log(outputData.value)
  321. })
  322. const outputData = ref([]);
  323. const batchRef = ref(null)
  324. function editRow(_role) {
  325. type.value = 'edit'
  326. role.value = _role
  327. // index.value = _index
  328. if(_role === '目标'){
  329. inputData.value[1].power = inputData.value[1].power.split(',').sort((a,b) => b - a).join(',')
  330. inputData.value[0].power = inputData.value[0].power.split(',').sort((a,b) => b - a).join(',')
  331. feMalePowers.value = inputData.value[1].power
  332. malePowers.value = inputData.value[0].power
  333. } else {
  334. inputData.value[3].power = inputData.value[3].power.split(',').sort((a,b) => b - a).join(',')
  335. inputData.value[2].power = inputData.value[2].power.split(',').sort((a,b) => b - a).join(',')
  336. feMalePowers.value = inputData.value[3].power
  337. malePowers.value = inputData.value[2].power
  338. }
  339. batchRef.value.open()
  340. }
  341. function viewExplam(){
  342. if(!isMockData.value){
  343. inputData.value = JSON.parse(JSON.stringify(mockData))
  344. outputData.value = JSON.parse(JSON.stringify(mockOutData))
  345. totalTarget.value = 1670
  346. totalBackup.value = 3070
  347. totalPick.value = 1670
  348. picks.value = {
  349. "male": [
  350. 3,
  351. 4,
  352. 5,
  353. 6,
  354. 7,
  355. 8,
  356. 9,
  357. 10,
  358. 11
  359. ],
  360. "female": [
  361. 0,
  362. 1,
  363. 2,
  364. 3,
  365. 9,
  366. 11,
  367. 15,
  368. 16,
  369. 17,
  370. 18,
  371. 19
  372. ]
  373. }
  374. } else {
  375. picks.value = { male: [], female: [] }
  376. inputData.value = [
  377. { role: '目标', num: 0, gender: '公', power: '' },
  378. { role: '目标', num: 0, gender: '母', power: '' },
  379. { role: '备选', num: 0, gender: '公', power: '' },
  380. { role: '备选', num: 0, gender: '母', power: '' }
  381. ]
  382. outputData.value = []
  383. totalTarget.value = 0
  384. totalBackup.value = 0
  385. }
  386. isMockData.value = !isMockData.value
  387. }
  388. function switchRoles(){
  389. outputData.value = []
  390. const [t1, t2, b1, b2] = inputData.value
  391. picks.value.male = []
  392. picks.value.female = []
  393. const temp = totalBackup.value
  394. totalBackup.value = totalTarget.value
  395. totalTarget.value = temp
  396. inputData.value[0] = { ...b1 }
  397. inputData.value[1] = { ...b2 }
  398. inputData.value[2] = { ...t1 }
  399. inputData.value[3] = { ...t2 }
  400. isMockData.value = false
  401. }
  402. const powerDifference = ref(0)
  403. function isPositiveInteger(str) {
  404. return /^[1-9]\d*$/.test(str);
  405. }
  406. function generatePowers(){
  407. const melePowers = malePowers.value.replace(/,|、|,/g, ',')
  408. const femalePowers = feMalePowers.value.replace(/,|、|,/g, ',')
  409. const malePowerSet = melePowers.split(',').map(item => item.trim()).filter(item => item !== '')
  410. const femalePowerSet = femalePowers.split(',').map(item => item.trim()).filter(item => item !== '')
  411. const isOk = malePowerSet.every(num => isPositiveInteger(num))
  412. const isFeOk = femalePowerSet.every(num => isPositiveInteger(num))
  413. if ((!isOk && malePowerSet.length !== 0) && (!isFeOk && femalePowerSet.length === 0)) return false
  414. const isTaget = role.value === '目标'
  415. if(isTaget){
  416. if(malePowerSet.length + femalePowerSet.length > 20){
  417. messageRef.value.close()
  418. messageRef.value.open()
  419. messageText.value = '目标角色总数不能超过20只'
  420. return 'overflow'
  421. }
  422. inputData.value[0].num = malePowerSet.length;
  423. inputData.value[0].power = malePowerSet.join(',')
  424. inputData.value[1].num = femalePowerSet.length;
  425. inputData.value[1].power = femalePowerSet.join(',')
  426. const total1 = inputData.value[0].power.split(',').reduce((prev, current) => Number(current) + prev, 0)
  427. const total2 = inputData.value[1].power.split(',').reduce((prev, current) => Number(current) + prev, 0)
  428. totalTarget.value = total1 + total2
  429. } else {
  430. if(malePowerSet.length === 0 && femalePowerSet.length === 0){
  431. messageRef.value.close()
  432. messageRef.value.open()
  433. messageText.value = '至少输入一个势力!'
  434. return 'overflow'
  435. }
  436. inputData.value[2].num = malePowerSet.length;
  437. inputData.value[2].power = malePowerSet.join(',')
  438. inputData.value[3].num = femalePowerSet.length;
  439. inputData.value[3].power = femalePowerSet.join(',')
  440. const total1 = inputData.value[2].power.split(',').reduce((prev, current) => Number(current) + prev, 0)
  441. const total2 = inputData.value[3].power.split(',').reduce((prev, current) => Number(current) + prev,0)
  442. totalBackup.value = total1 + total2
  443. }
  444. }
  445. function handleConfirm(){
  446. messageRef.value.close()
  447. const isMaleOk = generatePowers()
  448. const isFemaleOk = generatePowers()
  449. if(isMaleOk === 'overflow'){ return }
  450. if (typeof isMaleOk === 'boolean' && !isMaleOk){
  451. messageRef.value.close()
  452. messageRef.value.open()
  453. messageText.value = '势力必须为数字'
  454. return
  455. } else if (typeof isFemaleOk === 'boolean' && !isFemaleOk) {
  456. messageRef.value.close()
  457. messageRef.value.open()
  458. messageText.value = '势力必须为数字'
  459. return
  460. }
  461. outputData.value = []
  462. picks.value.male = []
  463. picks.value.female = []
  464. batchRef.value.close()
  465. isMockData.value = false
  466. }
  467. function adLoad() {
  468. console.log('原生模板广告加载成功')
  469. }
  470. function adError(err) {
  471. console.error('原生模板广告加载失败', err)
  472. }
  473. function adClose() {
  474. console.log('原生模板广告关闭')
  475. }
  476. // 生成数组的所有组合(辅助函数,用于生成指定个数元素的组合情况)
  477. function getCombinations(arr, num) {
  478. const result = [];
  479. if (num === 0) { return [[]]; }
  480. for (let i = 0; i < arr.length; i++) {
  481. const element = arr[i];
  482. const remainingCombos = getCombinations(arr.slice(i + 1), num - 1);
  483. for (const combo of remainingCombos) {
  484. result.push([element].concat(combo));
  485. }
  486. }
  487. return result;
  488. }
  489. function stop(delay) {
  490. return new Promise((resolve) => setTimeout(() => { resolve(true) }, delay))
  491. }
  492. let videoAd = null
  493. if (process.env.UNI_PLATFORM.toUpperCase() === 'MP-WEIXIN') {
  494. if (wx.createRewardedVideoAd) {
  495. videoAd = wx.createRewardedVideoAd({
  496. adUnitId: 'adunit-c90f984d29d8d175'
  497. })
  498. videoAd.onLoad(() => {
  499. loading.value = false
  500. console.log('激励视频家加载....')
  501. })
  502. videoAd.onError((err) => {
  503. loading.value = false
  504. console.error('激励视频光告加载失败', err)
  505. })
  506. }
  507. }
  508. function onShareAppMessage(res){
  509. return{
  510. title:"喵缘礼堂",
  511. path:"/pages/index/index",
  512. imageUrl:"https://example.com/share-image.png",
  513. }
  514. }
  515. function onShareTimeline(res){
  516. return{
  517. title:"喵缘礼堂",
  518. query:"path/to/page?param=value",
  519. imageUrl:"https://example.com/share-timeline-image.png"
  520. }
  521. }
  522. function calculateBestMatch() {
  523. // loading.value = true
  524. if (outputData.value.length !== 0) {
  525. loading.value = false
  526. messageRef.value.open()
  527. messageText.value = '请编辑猫咪势力后,再匹配方案'
  528. return
  529. }
  530. const result = handleDataSync()
  531. if(!result){ return }
  532. if (process.env.UNI_PLATFORM.toUpperCase() === 'MP-WEIXIN') {
  533. videoAd.onClose((res) => {
  534. if(res.isEnded){
  535. console.log('加载完成', result)
  536. outputData.value = result
  537. } else {
  538. picks.value.male = []
  539. picks.value.female = []
  540. outputData.value = []
  541. }
  542. })
  543. // 用户触发广告后,显示激励视频广告
  544. if (videoAd) {
  545. videoAd.show().catch((res) => {
  546. // 失败重试
  547. videoAd.load()
  548. .then(() => {
  549. console.log('拉取失败')
  550. loading.value = false
  551. videoAd.show()
  552. })
  553. .catch(err => {
  554. console.error('激励视频 广告显示失败', err)
  555. loading.value = false
  556. })
  557. })
  558. }
  559. } else {
  560. outputData.value = result
  561. loading.value = false
  562. }
  563. }
  564. function handleDataSync(){
  565. let startTime = new Date().getTime()
  566. let endTime = null
  567. outputData.value = []
  568. const targetMale = inputData.value[0].power.split(',').filter(item => item !== '').map(Number)
  569. const targetFemale = inputData.value[1].power.split(',').filter(item => item !== '').map(Number)
  570. const backupMale = inputData.value[2].power.split(',').filter(item => item !== '').map(Number)
  571. const backupFemale = inputData.value[3].power.split(',').filter(item => item !== '').map(Number)
  572. if (targetMale.length === 0 && targetFemale.length === 0) {
  573. loading.value = false
  574. messageRef.value.open()
  575. messageText.value = '至少输入一个势力!'
  576. return
  577. }
  578. if(targetFemale.length + targetFemale.length > 20){
  579. loading.value = false
  580. messageRef.value.open()
  581. messageText.value = '目标角色的猫咪总数不能超过20只!'
  582. return
  583. }
  584. if(backupMale.length > 20){
  585. loading.value = false
  586. messageRef.value.open()
  587. messageText.value = '备选角色的公猫咪不能超过20只!'
  588. return
  589. }
  590. if(backupFemale.length > 20){
  591. loading.value = false
  592. messageRef.value.open()
  593. messageText.value = '备选角色的母猫咪不能超过20只!'
  594. return
  595. }
  596. if (targetFemale.length > backupMale.length && backupMale.length !== 0){
  597. loading.value = false
  598. messageRef.value.open()
  599. messageText.value = '备选角色的公猫咪数量应不少于目标角色的母猫咪数量!'
  600. return
  601. }
  602. if (targetMale.length > backupFemale.length && backupFemale.length !== 0) {
  603. loading.value = false
  604. messageRef.value.open()
  605. messageText.value = '备选角色的母猫咪数量应不少于目标角色的公猫咪数量!'
  606. return
  607. }
  608. const targetMaleLen = targetMale.length;
  609. const targetFemaleLen = targetFemale.length;
  610. let targetWeight = 0
  611. if (backupMale.length === 0){
  612. targetWeight = targetMale.reduce((acc, val) => acc + val, 0);
  613. } else if(backupFemale.length === 0){
  614. targetWeight = targetFemale.reduce((acc, val) => acc + val, 0)
  615. } else if(backupMale.length !== 0 && backupFemale.length !== 0){
  616. targetWeight = targetMale.reduce((acc, val) => acc + val, 0) + targetFemale.reduce((acc, val) => acc + val, 0);
  617. }
  618. let minDifference = Infinity;
  619. let bestfemaleCombination = null;
  620. let bestMaleCombination = null;
  621. const femaleCombinations = getCombinations(backupFemale, targetMaleLen);
  622. const maleCombinations = getCombinations(backupMale, targetFemaleLen);
  623. if (femaleCombinations.length !== 0 && maleCombinations.length !== 0){
  624. for (const femaleComb of femaleCombinations) {
  625. for (const maleComb of maleCombinations) {
  626. const totalWeight = femaleComb.reduce((acc, val) => acc + val, 0) + maleComb.reduce((acc, val) => acc + val, 0);
  627. const difference = Math.abs(totalWeight - targetWeight);
  628. if (difference < minDifference) {
  629. minDifference = difference;
  630. bestfemaleCombination = femaleComb;
  631. bestMaleCombination = maleComb;
  632. if (minDifference == 0) {
  633. break;
  634. }
  635. }
  636. }
  637. }
  638. }
  639. // 备选数据的母为空
  640. if (femaleCombinations.length === 0 && maleCombinations.length !== 0) {
  641. for (const maleComb of maleCombinations) {
  642. const totalWeight = maleComb.reduce((acc, val) => acc + val , 0);
  643. const difference = Math.abs(totalWeight - targetWeight);
  644. if (difference < minDifference) {
  645. minDifference = difference;
  646. bestMaleCombination = maleComb;
  647. if (minDifference == 0) {
  648. break;
  649. }
  650. }
  651. }
  652. }
  653. // 备选数据的公为空
  654. if (femaleCombinations.length !== 0 && maleCombinations.length === 0) {
  655. for (const femaleComb of femaleCombinations) {
  656. const totalWeight = femaleComb.reduce((acc, val) => acc + val, 0)
  657. const difference = Math.abs(totalWeight - targetWeight);
  658. if (difference < minDifference) {
  659. minDifference = difference;
  660. bestfemaleCombination = femaleComb;
  661. // bestMaleCombination = maleComb;
  662. if (minDifference == 0) {
  663. break;
  664. }
  665. }
  666. }
  667. }
  668. bestMaleCombination && bestMaleCombination.sort((a, b) => b - a)
  669. bestfemaleCombination && bestfemaleCombination.sort((a, b) => b - a)
  670. targetMale.sort((a, b) => b - a)
  671. targetFemale.sort((a, b) => b - a)
  672. if (!bestMaleCombination && !bestfemaleCombination){ return }
  673. const result = [
  674. {
  675. role: '目标',
  676. malePowers: computedInputData.value[0].gender.male.sort((a, b) => b - a),
  677. femalePowers: computedInputData.value[0].gender.female.sort((a, b) => b - a),
  678. },
  679. {
  680. role: '备选',
  681. malePowers: bestMaleCombination.sort((a, b) => b - a),
  682. femalePowers: bestfemaleCombination.sort((a, b) => b - a),
  683. }
  684. ]
  685. bestMaleCombination.forEach(power => {
  686. const index = backupMale.findIndex(p => p === power)
  687. backupMale[index] = null
  688. picks.value.male.push(index)
  689. })
  690. bestfemaleCombination.forEach(power => {
  691. const index = backupFemale.findIndex(p => p === power)
  692. backupFemale[index] = null
  693. picks.value.female.push(index)
  694. })
  695. totalPick.value = bestfemaleCombination.reduce((prev, current) => Number(current) + prev, 0)
  696. totalPick.value += bestMaleCombination.reduce((prev, current) => Number(current) + prev, 0)
  697. powerDifference.value = totalTarget.value - totalPick.value
  698. endTime = new Date().getTime()
  699. time.value = endTime - startTime
  700. return result
  701. // resolve(result)
  702. }
  703. function handleData(){
  704. return new Promise(async (resolve) => {
  705. setTimeout(async () => {
  706. let startTime = new Date().getTime()
  707. let endTime = null
  708. outputData.value = []
  709. const targetMale = inputData.value[0].power.split(',').filter(item => item !== '').map(Number)
  710. const targetFemale = inputData.value[1].power.split(',').filter(item => item !== '').map(Number)
  711. const backupMale = inputData.value[2].power.split(',').filter(item => item !== '').map(Number)
  712. const backupFemale = inputData.value[3].power.split(',').filter(item => item !== '').map(Number)
  713. if (targetMale.length === 0 && targetFemale.length === 0) {
  714. loading.value = false
  715. messageRef.value.open()
  716. messageText.value = '至少输入一个势力!'
  717. return
  718. }
  719. if(targetFemale.length + targetFemale.length > 20){
  720. loading.value = false
  721. messageRef.value.open()
  722. messageText.value = '目标角色的猫咪总数不能超过20只!'
  723. return
  724. }
  725. if(backupMale.length > 20){
  726. loading.value = false
  727. messageRef.value.open()
  728. messageText.value = '备选角色的公猫咪不能超过20只!'
  729. return
  730. }
  731. if(backupFemale.length > 20){
  732. loading.value = false
  733. messageRef.value.open()
  734. messageText.value = '备选角色的母猫咪不能超过20只!'
  735. return
  736. }
  737. if (targetFemale.length > backupMale.length && backupMale.length !== 0){
  738. loading.value = false
  739. messageRef.value.open()
  740. messageText.value = '备选角色的公猫咪数量应不少于目标角色的母猫咪数量!'
  741. return
  742. }
  743. if (targetMale.length > backupFemale.length && backupFemale.length !== 0) {
  744. loading.value = false
  745. messageRef.value.open()
  746. messageText.value = '备选角色的母猫咪数量应不少于目标角色的公猫咪数量!'
  747. return
  748. }
  749. const targetMaleLen = targetMale.length;
  750. const targetFemaleLen = targetFemale.length;
  751. let targetWeight = 0
  752. if (backupMale.length === 0){
  753. targetWeight = targetMale.reduce((acc, val) => acc + val, 0);
  754. } else if(backupFemale.length === 0){
  755. targetWeight = targetFemale.reduce((acc, val) => acc + val, 0)
  756. } else if(backupMale.length !== 0 && backupFemale.length !== 0){
  757. targetWeight = targetMale.reduce((acc, val) => acc + val, 0) + targetFemale.reduce((acc, val) => acc + val, 0);
  758. }
  759. let minDifference = Infinity;
  760. let bestfemaleCombination = null;
  761. let bestMaleCombination = null;
  762. const femaleCombinations = getCombinations(backupFemale, targetMaleLen);
  763. const maleCombinations = getCombinations(backupMale, targetFemaleLen);
  764. if (femaleCombinations.length !== 0 && maleCombinations.length !== 0){
  765. for (const femaleComb of femaleCombinations) {
  766. for (const maleComb of maleCombinations) {
  767. const totalWeight = femaleComb.reduce((acc, val) => acc + val, 0) + maleComb.reduce((acc, val) => acc + val, 0);
  768. const difference = Math.abs(totalWeight - targetWeight);
  769. if (difference < minDifference) {
  770. minDifference = difference;
  771. bestfemaleCombination = femaleComb;
  772. bestMaleCombination = maleComb;
  773. if (minDifference == 0) {
  774. break;
  775. }
  776. }
  777. }
  778. }
  779. }
  780. // 备选数据的母为空
  781. if (femaleCombinations.length === 0 && maleCombinations.length !== 0) {
  782. for (const maleComb of maleCombinations) {
  783. const totalWeight = maleComb.reduce((acc, val) => acc + val , 0);
  784. const difference = Math.abs(totalWeight - targetWeight);
  785. if (difference < minDifference) {
  786. minDifference = difference;
  787. bestMaleCombination = maleComb;
  788. if (minDifference == 0) {
  789. break;
  790. }
  791. }
  792. }
  793. }
  794. // 备选数据的公为空
  795. if (femaleCombinations.length !== 0 && maleCombinations.length === 0) {
  796. for (const femaleComb of femaleCombinations) {
  797. const totalWeight = femaleComb.reduce((acc, val) => acc + val, 0)
  798. const difference = Math.abs(totalWeight - targetWeight);
  799. if (difference < minDifference) {
  800. minDifference = difference;
  801. bestfemaleCombination = femaleComb;
  802. // bestMaleCombination = maleComb;
  803. if (minDifference == 0) {
  804. break;
  805. }
  806. }
  807. }
  808. }
  809. bestMaleCombination && bestMaleCombination.sort((a, b) => b - a)
  810. bestfemaleCombination && bestfemaleCombination.sort((a, b) => b - a)
  811. targetMale.sort((a, b) => b - a)
  812. targetFemale.sort((a, b) => b - a)
  813. if (!bestMaleCombination && !bestfemaleCombination){ return }
  814. loading.value = false
  815. const result = [
  816. {
  817. role: '目标',
  818. malePowers: computedInputData.value[0].gender.male.sort((a, b) => b - a),
  819. femalePowers: computedInputData.value[0].gender.female.sort((a, b) => b - a),
  820. },
  821. {
  822. role: '备选',
  823. malePowers: bestMaleCombination.sort((a, b) => b - a),
  824. femalePowers: bestfemaleCombination.sort((a, b) => b - a),
  825. }
  826. ]
  827. bestMaleCombination.forEach(power => {
  828. const index = backupMale.findIndex(p => p === power)
  829. backupMale[index] = null
  830. picks.value.male.push(index)
  831. })
  832. bestfemaleCombination.forEach(power => {
  833. const index = backupFemale.findIndex(p => p === power)
  834. backupFemale[index] = null
  835. picks.value.female.push(index)
  836. })
  837. totalPick.value = bestfemaleCombination.reduce((prev, current) => Number(current) + prev, 0)
  838. totalPick.value += bestMaleCombination.reduce((prev, current) => Number(current) + prev, 0)
  839. powerDifference.value = totalTarget.value - totalPick.value
  840. endTime = new Date().getTime()
  841. time.value = endTime - startTime
  842. resolve(result)
  843. }, 0)
  844. })
  845. }
  846. const width = ref(0)
  847. function getInner(){
  848. uni.getSystemInfo({
  849. success: function (res) {
  850. const windowWidth = res.windowWidth; // 窗口宽度
  851. width.value = (windowWidth - 40 - 85) / 2
  852. }
  853. });
  854. }
  855. onMounted(() => {
  856. getInner()
  857. })
  858. // uni.onWindowResize(getInner)
  859. viewExplam()
  860. </script>
  861. <style>
  862. </style>
  863. <style>
  864. /* uni-button {
  865. margin: 2px !important;
  866. }
  867. */
  868. uni-button::after {
  869. /* display: none; */
  870. /* border: 0px solid rgb(224.6, 242.8, 215.6) !important; */
  871. /* border: 2px solid transparent !important; */
  872. }
  873. button {
  874. border: none !important;
  875. background: rgb(239.8, 248.9, 235.3) !important;
  876. color: #67c23a !important;
  877. outline: none !important;
  878. }
  879. .primary-btn {
  880. }
  881. .no-padding .uni-table-th {
  882. padding: 0px !important;
  883. padding-left: 10px !important;
  884. }
  885. .uni-table-th {
  886. background: #f5f7fa !important;
  887. /* margin-left: -1px !important; */
  888. /* border: 1px solid rgb(226, 226, 226) !important; */
  889. /* margin-bottom: -1px !important; */
  890. }
  891. .uni-table-td {
  892. /* border: 1px solid rgb(226, 226, 226) !important; */
  893. }
  894. .edit-button {
  895. color: #409eff !important;
  896. cursor: pointer;
  897. }
  898. textarea {
  899. font-size: 13px;
  900. padding: 8px;
  901. }
  902. .text-power {
  903. overflow-wrap: anywhere;
  904. text-wrap: wrap;
  905. }
  906. .help-modal {
  907. line-height: 24px;
  908. color: #2c3e50;
  909. }
  910. .u-table{
  911. display: flex;
  912. flex-direction: column;
  913. width: 100%;
  914. height: 80px;
  915. justify-content: flex-start;
  916. font-size: 14px !important;
  917. height: auto;
  918. border-top: 1px solid #ebeef5;
  919. border-left: 1px solid #ebeef5;
  920. /* background: red */
  921. }
  922. .u-table-tr {
  923. display: flex;
  924. color: #909399;
  925. height: 40px !important;
  926. margin-top: -1px;
  927. }
  928. .u-table-body {
  929. height: calc(100% - 80px);
  930. overflow: auto;
  931. }
  932. .u-table-th {
  933. display: flex;
  934. border: 1px solid #ebeef5;
  935. padding: 8px 10px;
  936. margin-left: -1px;
  937. font-weight: bold;
  938. }
  939. .u-table-td {
  940. display: flex;
  941. border: 1px solid #ebeef5;
  942. padding: 8px 10px;
  943. margin-left: -1px;
  944. }
  945. .u-table-th-custom {
  946. display: flex;
  947. border: 1px solid #ebeef5;
  948. margin-left: -1px;
  949. }
  950. .edit-btn {
  951. padding: 0px !important;
  952. border: 0px !important;
  953. display: block;
  954. background: #fff !important;
  955. }
  956. .edit-btn:after {
  957. border: 0px !important;
  958. }
  959. @keyframes spin {
  960. 0% { transform: rotate(0deg); }
  961. 100% { transform: rotate(360deg); }
  962. }
  963. .loader {
  964. animation: spin 2s linear infinite; /* 应用旋转动画 */
  965. }
  966. textarea {
  967. font-size: 13px;
  968. padding: 8px;
  969. }
  970. .uni-table-loading {
  971. display: none !important;
  972. }
  973. </style>
  974. <style lang="scss" scoped>
  975. button {
  976. height: 30px;
  977. display: flex;
  978. align-items: center;
  979. justify-content: center;
  980. font-size: 14px;
  981. text-wrap: nowrap !important;
  982. width: 85px;
  983. // padding: 13px 10px;
  984. }
  985. .dialog-title {
  986. line-height: 30px;
  987. padding-left: 10px;
  988. font-size: 14px;
  989. }
  990. .table-wrapper {
  991. font-size: 14px !important;
  992. }
  993. .custom-picker {
  994. border: 1px solid #e3e3e3;
  995. padding: 3px 5px;
  996. text-align: center;
  997. width: 100%;
  998. div {
  999. display: flex;
  1000. justify-content: space-between;
  1001. text {
  1002. overflow: hidden;
  1003. white-space: nowrap;
  1004. text-overflow: ellipsis;
  1005. }
  1006. }
  1007. }
  1008. .container {
  1009. padding: 10px 20px;
  1010. }
  1011. .section {
  1012. margin-bottom: 20px;
  1013. }
  1014. .section-title {
  1015. font-size: 18px;
  1016. font-weight: bold;
  1017. margin-bottom: 10px;
  1018. text-wrap: nowrap;
  1019. }
  1020. .input {
  1021. width: 100%;
  1022. text-align: center;
  1023. border: 1px solid #ddd;
  1024. padding: 5px;
  1025. }
  1026. button {
  1027. margin: 5px;
  1028. }
  1029. .add-btn,
  1030. .calculate-btn {
  1031. background-color: #007bff;
  1032. color: white;
  1033. padding: 10px;
  1034. font-size: 16px;
  1035. text-align: center;
  1036. border-radius: 5px;
  1037. }
  1038. .delete-btn {
  1039. background-color: #ff4d4f;
  1040. color: white;
  1041. padding: 5px;
  1042. font-size: 14px;
  1043. border-radius: 5px;
  1044. }
  1045. .summary {
  1046. margin-top: 20px;
  1047. font-size: 14px;
  1048. text-align: right;
  1049. font-weight: bold;
  1050. }
  1051. .loading {
  1052. position: fixed;
  1053. top: 0px;
  1054. left: 0px;
  1055. width: 100vw;
  1056. height: 100vh;
  1057. background: rgba(0, 0, 0, .6);
  1058. display: flex;
  1059. justify-content: center;
  1060. align-items: center;
  1061. .loading-wrapper {
  1062. height: 260px;
  1063. width: 260px;
  1064. border-radius: 100%;
  1065. overflow: hidden;
  1066. display: flex;
  1067. justify-content: center;
  1068. align-items: center;
  1069. img {
  1070. height: 260px;
  1071. width: 260px;
  1072. }
  1073. }
  1074. }
  1075. .uni-table-th-content {
  1076. text-wrap: nowrap;
  1077. }
  1078. .uni-table-loading {
  1079. display: block !important;
  1080. }
  1081. .uni-table-scroll {
  1082. border: 1px solid #EBEEF5;
  1083. }
  1084. </style>