Nothing like a good ol' game of ModTen
Python 3, 114 110 bytes

lambda m,n:p(*n)-p(*m)
R=b&quot;T 2J45UNK9RL&lt;3SLM;QAK:O&gt;=/678&quot;
v=R.find
p=lambda i,s,j,t:R[s==t::2][v(j)*v(i)%10+3]

Try it online!
@Arnauld proposed the idea of merging the card value and rank table strings. After some attempts I managed to craft a merged string R=&quot;T 2J45UNK9RL&lt;3SLM;QAK:O&gt;=/678&quot;, which has the same length as the original card value string. The substring R[6:25]=&quot;UNK9RL&lt;3SLM;QAK:O&gt;=/&quot; serves as the rank table as well as a card value lookup table for 3, 9, A, K, and Q. The ASCII-value decoding of the new rank table has the same ranking effect as the previous rank table.
Using byte strings as input saves 4 bytes.
Using cmp in Python 2 can reduce the solution to 102 bytes, as shown by @xnor's solution.

Python 3, 165 142 130 129 bytes

lambda m,n:p(*n)-p(*m)
v=&quot;T 23456789   J    QA        K&quot;.find
p=lambda i,s,j,t:ord(&quot;HC92FA51GAB4E893D760&quot;[s==t::2][v(j)*v(i)%10])

Try it online!
-23 bytes thanks to @Jonathan Allan
-2 bytes thanks to @ovs
-1 byte thanks to @mypetlion
Ungolfed:
f = lambda hand1, hand2: get_rank(*hand2) - get_rank(*hand1)
def get_rank(v1, suit1, v2, suit2):
    get_card_value = &quot;T 23456789   J    QA        K&quot;.find
    # rank_table = [[17,9,15,5,16,11,14,9,13,6],[12,2,10,1,10,4,8,3,7,0]]
    # rank_table = (&quot;H9F5GBE9D6&quot;,&quot;C2A1A48370&quot;) # Base-18 encoding of ranks
    rank_table = &quot;HC92FA51GAB4E893D760&quot; # Interleaved base-18 encoding

    # ASCII-value decoding has the same ranking effect as base-18 decoding
    return ord(rank_table[suit1 == suit2::2][get_card_value(v2) * get_card_value(v1) % 10])

The function f takes two arguments representing the hand of player 1 and player 2. It returns a positive, negative, or zero value in the case of a player 1 win, a player 2 win, or a draw, correspondingly. Each hand is encoded as a single string, e.g. &quot;7cQh&quot;.